@noy-db/hub 0.3.0-pre.12 → 0.3.0-pre.13

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 (373) hide show
  1. package/dist/aggregate/index.js +4 -4
  2. package/dist/api-ZGS6LCOF.js +20 -0
  3. package/dist/as/index.js +11 -11
  4. package/dist/attestation/index.js +18 -18
  5. package/dist/{backup-2WVBIVXW.js → backup-BJ57IKAO.js} +28 -11
  6. package/dist/backup-BJ57IKAO.js.map +1 -0
  7. package/dist/blobs/index.js +12 -10
  8. package/dist/blobs/index.js.map +1 -1
  9. package/dist/broker/index.js +12 -12
  10. package/dist/bundle/index.js +25 -22
  11. package/dist/bundle/index.js.map +1 -1
  12. package/dist/by/index.js +2 -2
  13. package/dist/cargo/index.js +22 -22
  14. package/dist/{chunk-ZL6PRULX.js → chunk-2NVOWNI3.js} +4 -4
  15. package/dist/{chunk-WEHIQYQH.js → chunk-3B5VVCNY.js} +2 -2
  16. package/dist/{chunk-FF7S3XDW.js → chunk-3LQ2YKO4.js} +2 -2
  17. package/dist/{chunk-NAR6IDKH.js → chunk-3QCRBHCY.js} +2 -2
  18. package/dist/{chunk-SXPCZ6LL.js → chunk-454JXE57.js} +3 -3
  19. package/dist/{chunk-R3NHNVS5.js → chunk-4CTHCN4J.js} +12 -10
  20. package/dist/{chunk-R3NHNVS5.js.map → chunk-4CTHCN4J.js.map} +1 -1
  21. package/dist/{chunk-JGUZMC3I.js → chunk-4JBYV75E.js} +5 -5
  22. package/dist/{chunk-RXTMLYJS.js → chunk-4NXKCLLQ.js} +2 -2
  23. package/dist/{chunk-GXLZXD2W.js → chunk-4VNQOLML.js} +2 -2
  24. package/dist/{chunk-45QPICLE.js → chunk-5M5DVGV7.js} +2 -2
  25. package/dist/{chunk-EHW5KTTA.js → chunk-5O3XF4DV.js} +4 -4
  26. package/dist/chunk-5O3XF4DV.js.map +1 -0
  27. package/dist/{chunk-S2OWPVSO.js → chunk-5YOLBEQ6.js} +2 -2
  28. package/dist/{chunk-BDEMZD4F.js → chunk-6CRIV7DE.js} +2 -2
  29. package/dist/{chunk-VCUQR6ZL.js → chunk-6JTIAGHD.js} +3 -3
  30. package/dist/{chunk-K2DLLK34.js → chunk-6QTNZJBS.js} +3 -3
  31. package/dist/{chunk-UMC5QZTX.js → chunk-6T2SCQRU.js} +4 -4
  32. package/dist/{chunk-J3JIYFYL.js → chunk-7DSTRQIJ.js} +2 -2
  33. package/dist/{chunk-3QSOFZQB.js → chunk-AUSTCW2X.js} +10 -10
  34. package/dist/{chunk-PN5332SU.js → chunk-B3HJYUOF.js} +2 -2
  35. package/dist/{chunk-DIB2BOIH.js → chunk-BDVFPG6Y.js} +5 -5
  36. package/dist/{chunk-FZCGIJEJ.js → chunk-BIOUI4HL.js} +50 -1
  37. package/dist/chunk-BIOUI4HL.js.map +1 -0
  38. package/dist/{chunk-FYNE4WLC.js → chunk-CAF6WOK7.js} +2 -2
  39. package/dist/{chunk-AKEOHKMT.js → chunk-CDUQTA3Z.js} +110 -11
  40. package/dist/chunk-CDUQTA3Z.js.map +1 -0
  41. package/dist/{chunk-37WOJQWY.js → chunk-CIE2BASH.js} +2 -2
  42. package/dist/{chunk-BLUT46UP.js → chunk-CRI4OUV4.js} +2 -2
  43. package/dist/{chunk-Y5TWPBNW.js → chunk-DLG74CNB.js} +4 -4
  44. package/dist/{chunk-HIOLCOK3.js → chunk-E5ZQT5KX.js} +2 -2
  45. package/dist/{chunk-IQIVD7TP.js → chunk-EQO2UVP4.js} +6 -6
  46. package/dist/{chunk-US34HTOI.js → chunk-ERJ7FU55.js} +3 -3
  47. package/dist/{chunk-S7TAPIRS.js → chunk-EVVGJDRQ.js} +3 -3
  48. package/dist/{chunk-EXGDL2UQ.js → chunk-EVX47VBM.js} +2 -2
  49. package/dist/{chunk-NT2ZO4KI.js → chunk-EXK7QRWF.js} +5 -5
  50. package/dist/{chunk-7QLY64AD.js → chunk-EYZ6IQ6X.js} +5 -5
  51. package/dist/{chunk-QF5LGMSV.js → chunk-F64IRYG3.js} +2 -2
  52. package/dist/{chunk-PH7G7ZI7.js → chunk-FHS2HQSH.js} +5 -5
  53. package/dist/{chunk-HG76BZ6G.js → chunk-FM3G3NOY.js} +27 -9
  54. package/dist/chunk-FM3G3NOY.js.map +1 -0
  55. package/dist/{chunk-ZHOJOT6S.js → chunk-FUG4SFN3.js} +2 -2
  56. package/dist/{chunk-JRKE5MAZ.js → chunk-GKEWXIV5.js} +2 -2
  57. package/dist/{chunk-6MA575R5.js → chunk-GTGPO5KW.js} +5 -5
  58. package/dist/{chunk-XS6DLN67.js → chunk-HB3RFQSC.js} +3 -3
  59. package/dist/{chunk-VXZNSPJM.js → chunk-HDMEGAQE.js} +5 -5
  60. package/dist/chunk-HDQIIUPH.js +135 -0
  61. package/dist/chunk-HDQIIUPH.js.map +1 -0
  62. package/dist/{chunk-DJ7MRXNA.js → chunk-HTJQYHZP.js} +2 -2
  63. package/dist/{chunk-NRYNRSGV.js → chunk-I6GALP6E.js} +6 -6
  64. package/dist/{chunk-GL3JV5EV.js → chunk-IL6POWCW.js} +6 -6
  65. package/dist/{chunk-NEYKOESM.js → chunk-IU6SNYX6.js} +5 -5
  66. package/dist/{chunk-CCIR2OJH.js → chunk-J2ZX2XUV.js} +5 -5
  67. package/dist/{chunk-F3LGBRSV.js → chunk-JIF24JPM.js} +57 -15
  68. package/dist/chunk-JIF24JPM.js.map +1 -0
  69. package/dist/{chunk-NM3IFKBL.js → chunk-JKJOGPZN.js} +2 -2
  70. package/dist/{chunk-JC75UBMT.js → chunk-JNAI2HN2.js} +1 -1
  71. package/dist/{chunk-JC75UBMT.js.map → chunk-JNAI2HN2.js.map} +1 -1
  72. package/dist/{chunk-TVS6BUUR.js → chunk-JZUV47FZ.js} +2 -2
  73. package/dist/{chunk-5HNX4OD4.js → chunk-K5IMOGHB.js} +2 -2
  74. package/dist/{chunk-XJ47GS53.js → chunk-KSKBRKGJ.js} +3 -3
  75. package/dist/{chunk-INBQ43DI.js → chunk-LASZKVMO.js} +3 -3
  76. package/dist/{chunk-EVHBK7BN.js → chunk-LXEXBTKL.js} +4 -4
  77. package/dist/{chunk-6S7ENMRE.js → chunk-M4CV6BRT.js} +2 -2
  78. package/dist/{chunk-AAIIDAUC.js → chunk-MEFJPTZ4.js} +3 -3
  79. package/dist/{chunk-WHQHTNT7.js → chunk-MV43JSJF.js} +4 -4
  80. package/dist/{chunk-F44XWXOA.js → chunk-NJ4EOKMB.js} +4 -4
  81. package/dist/{chunk-ILF5GARW.js → chunk-NR5LT5RY.js} +7 -7
  82. package/dist/{chunk-TBNF5DV5.js → chunk-NWSGI5C7.js} +3 -3
  83. package/dist/{chunk-BDPAVYGJ.js → chunk-OSDB5WW3.js} +92 -15
  84. package/dist/chunk-OSDB5WW3.js.map +1 -0
  85. package/dist/chunk-PN2XMATY.js +37 -0
  86. package/dist/chunk-PN2XMATY.js.map +1 -0
  87. package/dist/{chunk-EIZ4YOG2.js → chunk-Q5VNKNE5.js} +3 -3
  88. package/dist/{chunk-TVOHSPVK.js → chunk-QLWMEXCT.js} +2 -2
  89. package/dist/{chunk-7GAXFU2O.js → chunk-QRB7NAOJ.js} +2 -2
  90. package/dist/{chunk-X6Y5MXTH.js → chunk-R4ROFEYC.js} +2 -2
  91. package/dist/{chunk-4BQXMCE5.js → chunk-RCVJZMFE.js} +4 -4
  92. package/dist/{chunk-QG5RQQBO.js → chunk-RIX4V2TB.js} +3 -3
  93. package/dist/{chunk-SAL5X5OK.js → chunk-RYCTDFUX.js} +2 -2
  94. package/dist/{chunk-CXC3JLAQ.js → chunk-RZ5SVOYR.js} +2 -2
  95. package/dist/{chunk-44IL5G3W.js → chunk-SLFHQTTW.js} +5 -5
  96. package/dist/chunk-SNZ6URZZ.js +2782 -0
  97. package/dist/chunk-SNZ6URZZ.js.map +1 -0
  98. package/dist/{chunk-2MTRJCBI.js → chunk-T2FFWNPK.js} +3 -3
  99. package/dist/{chunk-QYPDYN23.js → chunk-UNFPPVNI.js} +2 -2
  100. package/dist/{chunk-RNXJ2VO6.js → chunk-VFUPHI2Z.js} +63 -10
  101. package/dist/chunk-VFUPHI2Z.js.map +1 -0
  102. package/dist/{chunk-7ZOSHEVS.js → chunk-VLFCJYTL.js} +9 -9
  103. package/dist/{chunk-APHLCXVC.js → chunk-VN3DBQAU.js} +2 -2
  104. package/dist/{chunk-HK4XTXO4.js → chunk-VZRODIM3.js} +4 -4
  105. package/dist/{chunk-P2LJI6JE.js → chunk-W22IKJE2.js} +2 -2
  106. package/dist/{chunk-FJTWLQV3.js → chunk-X3YYQWOQ.js} +3 -3
  107. package/dist/{chunk-YCXKFB7J.js → chunk-X7QBD67R.js} +498 -274
  108. package/dist/chunk-X7QBD67R.js.map +1 -0
  109. package/dist/{chunk-HA6ZQWA2.js → chunk-XDYREZWE.js} +82 -10
  110. package/dist/chunk-XDYREZWE.js.map +1 -0
  111. package/dist/{chunk-HBBZWEC5.js → chunk-XWZVFISC.js} +40 -6
  112. package/dist/{chunk-HBBZWEC5.js.map → chunk-XWZVFISC.js.map} +1 -1
  113. package/dist/{chunk-SGJGWBSC.js → chunk-YFXZEAYK.js} +6 -6
  114. package/dist/{chunk-SIAIYP6G.js → chunk-ZAJAVEXY.js} +2 -2
  115. package/dist/{chunk-KKYEZCQG.js → chunk-ZICD77GM.js} +2 -2
  116. package/dist/{chunk-HGAB325C.js → chunk-ZJ2AK4V7.js} +2 -2
  117. package/dist/{chunk-HYG32HCM.js → chunk-ZUODRVHY.js} +2 -2
  118. package/dist/classified/index.js +5 -5
  119. package/dist/{classified-marker-M7K3OJPM.js → classified-marker-CZKA6ITE.js} +3 -3
  120. package/dist/collection-facade-TLWIPI76.js +44 -0
  121. package/dist/{computed-ORCGM5JE.js → computed-ZIQTYTDI.js} +3 -3
  122. package/dist/consent/index.js +9 -9
  123. package/dist/{dead-filter-OV5FGKOG.js → dead-filter-ESOBZZP5.js} +2 -2
  124. package/dist/delegation-W7ZV2322.js +25 -0
  125. package/dist/derivations/index.js +12 -12
  126. package/dist/derive-H2BJTLUI.js +21 -0
  127. package/dist/{enclave-XXFUKJC6.js → enclave-AP3I5W7O.js} +15 -9
  128. package/dist/executor-KGWTNEQD.js +9 -0
  129. package/dist/executor-OVEXR6RT.js +9 -0
  130. package/dist/executor-UNEH5ZYH.js +25 -0
  131. package/dist/export-accessible-PCMXSUZS.js +23 -0
  132. package/dist/extract-partition-SWHFSYJO.js +38 -0
  133. package/dist/{fanout-sidecar-IO2KDUWV.js → fanout-sidecar-A765THMX.js} +9 -9
  134. package/dist/find-N6345CNT.js +11 -0
  135. package/dist/forget/index.js +9 -9
  136. package/dist/guards/index.js +3 -3
  137. package/dist/history/index.js +14 -11
  138. package/dist/history/index.js.map +1 -1
  139. package/dist/i18n/index.js +14 -14
  140. package/dist/index.d.ts +5 -1
  141. package/dist/index.js +108 -89
  142. package/dist/index.js.map +1 -1
  143. package/dist/indexing/index.js +2 -2
  144. package/dist/issue-5OKB6I2R.js +19 -0
  145. package/dist/kernel/collection-config.d.ts +20 -0
  146. package/dist/kernel/collection.d.ts +31 -28
  147. package/dist/kernel/enclave/classify/verify.d.ts +3 -0
  148. package/dist/kernel/enclave/index.d.ts +2 -1
  149. package/dist/kernel/enclave/record-keys/deterministic.d.ts +1 -0
  150. package/dist/kernel/enclave/record-keys/lifecycle.d.ts +50 -0
  151. package/dist/kernel/enclave/record-keys/record-codec.d.ts +27 -0
  152. package/dist/kernel/errors.d.ts +74 -0
  153. package/dist/kernel/lazy-count.d.ts +13 -0
  154. package/dist/kernel/tier-visibility.d.ts +54 -0
  155. package/dist/kernel/types.d.ts +73 -0
  156. package/dist/kernel/vault.d.ts +8 -5
  157. package/dist/kernel/via/dispatch.d.ts +10 -0
  158. package/dist/{ledger-UAQKPWNM.js → ledger-GFBKVPV4.js} +11 -11
  159. package/dist/legacy/bundle.d.ts +1 -1
  160. package/dist/liberate-3VDOT4KO.js +24 -0
  161. package/dist/{link-set-DGYUOFVD.js → link-set-Z7Q2ASFX.js} +11 -11
  162. package/dist/materialized-views/index.js +18 -18
  163. package/dist/noydb-ZCJOYBDI.js +68 -0
  164. package/dist/on/index.js +9 -9
  165. package/dist/overlay-views/index.js +4 -4
  166. package/dist/periods/index.js +12 -12
  167. package/dist/pod/index.js +11 -11
  168. package/dist/{policy-5POYLI26.js → policy-Z3RUAIY2.js} +5 -5
  169. package/dist/port/with/blob-strategy.d.ts +17 -0
  170. package/dist/portability/index.js +8 -8
  171. package/dist/{post-register-LB3P4XQU.js → post-register-JTH76BLY.js} +6 -6
  172. package/dist/{public-envelope-GOG4LUM7.js → public-envelope-B4KNWVUA.js} +4 -4
  173. package/dist/query/index.js +5 -5
  174. package/dist/register-VAHONCID.js +23 -0
  175. package/dist/registry-7MLVD3VO.js +19 -0
  176. package/dist/registry-EPKPD5SM.js +18 -0
  177. package/dist/registry-P5T7PS2U.js +9 -0
  178. package/dist/request-withdrawal-TI3A5WPS.js +31 -0
  179. package/dist/{reveal-NPVROV4D.js → reveal-DIBHLUZX.js} +7 -7
  180. package/dist/reveal-DIBHLUZX.js.map +1 -0
  181. package/dist/revoke-FDRD6GRW.js +24 -0
  182. package/dist/satellites/index.js +1 -1
  183. package/dist/sealed-record/index.js +12 -12
  184. package/dist/{seed-VI4FU7UH.js → seed-Q6M2IZFL.js} +11 -11
  185. package/dist/session/index.js +9 -9
  186. package/dist/shadow/index.js +2 -2
  187. package/dist/signer-MUWEIUI6.js +25 -0
  188. package/dist/snapshots/index.js +10 -10
  189. package/dist/stale-OSQ7J3AA.js +29 -0
  190. package/dist/storage-MVFCGEQE.js +23 -0
  191. package/dist/{store-coordination-provider-5KDS2RRP.js → store-coordination-provider-64OEZFGS.js} +3 -3
  192. package/dist/sync/index.js +9 -9
  193. package/dist/team/index.js +15 -15
  194. package/dist/tiers/index.js +14 -10
  195. package/dist/to/index.js +1 -1
  196. package/dist/tx/index.js +3 -3
  197. package/dist/util/index.js +1 -1
  198. package/dist/{verify-QK76NJ4G.js → verify-ZASTRNPR.js} +9 -8
  199. package/dist/verify-ZASTRNPR.js.map +1 -0
  200. package/dist/{walk-XQCBKSBW.js → walk-SVNDIZ62.js} +11 -11
  201. package/dist/with/index.js +11 -11
  202. package/dist/with-audit/forget/strategy.d.ts +22 -0
  203. package/dist/with-audit/tiers/index.d.ts +275 -3
  204. package/dist/with-audit/tiers/strategy.d.ts +5 -4
  205. package/dist/with-cargo/describe-extraction.d.ts +9 -1
  206. package/dist/with-cargo/extract-partition.d.ts +9 -1
  207. package/dist/with-cargo/walk-closure.d.ts +32 -3
  208. package/dist/with-commit/history/history.d.ts +46 -0
  209. package/dist/with-commit/history/ledger/store.d.ts +21 -0
  210. package/dist/with-commit/history/strategy.d.ts +10 -0
  211. package/dist/with-commit/history/time-machine.d.ts +23 -0
  212. package/dist/with-commit/tx/elevated-handle.d.ts +2 -1
  213. package/dist/with-formula/materialized-views/executor.d.ts +9 -0
  214. package/dist/with-formula/materialized-views/stale.d.ts +52 -1
  215. package/dist/with-lookup/embeddings/index.d.ts +1 -0
  216. package/dist/with-lookup/embeddings/vec-id.d.ts +47 -0
  217. package/dist/with-lookup/indexing/collection-facade.d.ts +38 -1
  218. package/dist/with-lookup/search/collection-facade.d.ts +40 -0
  219. package/dist/with-lookup/search/persisted-index-store.d.ts +82 -2
  220. package/dist/with-lookup/search/strategy.d.ts +4 -0
  221. package/dist/with-shape/blobs/blob-intent.d.ts +153 -0
  222. package/dist/with-shape/blobs/blob-set.d.ts +949 -5
  223. package/dist/withdraw-accessible-WZIEVWSR.js +28 -0
  224. package/package.json +3 -3
  225. package/dist/api-3TH2GOFQ.js +0 -20
  226. package/dist/backup-2WVBIVXW.js.map +0 -1
  227. package/dist/chunk-65VLRG3I.js +0 -54
  228. package/dist/chunk-65VLRG3I.js.map +0 -1
  229. package/dist/chunk-AKEOHKMT.js.map +0 -1
  230. package/dist/chunk-BDPAVYGJ.js.map +0 -1
  231. package/dist/chunk-CZMCOFEA.js +0 -1221
  232. package/dist/chunk-CZMCOFEA.js.map +0 -1
  233. package/dist/chunk-EHW5KTTA.js.map +0 -1
  234. package/dist/chunk-F3LGBRSV.js.map +0 -1
  235. package/dist/chunk-FZCGIJEJ.js.map +0 -1
  236. package/dist/chunk-HA6ZQWA2.js.map +0 -1
  237. package/dist/chunk-HG76BZ6G.js.map +0 -1
  238. package/dist/chunk-RNXJ2VO6.js.map +0 -1
  239. package/dist/chunk-YCXKFB7J.js.map +0 -1
  240. package/dist/collection-facade-DWA735G2.js +0 -39
  241. package/dist/delegation-YVSHMIZZ.js +0 -25
  242. package/dist/derive-BUO555L5.js +0 -21
  243. package/dist/executor-6OCCE5VM.js +0 -25
  244. package/dist/executor-JFVAJY42.js +0 -9
  245. package/dist/executor-UPSH5O3F.js +0 -9
  246. package/dist/export-accessible-L4A2QZA5.js +0 -23
  247. package/dist/extract-partition-ZHQ4RGGM.js +0 -36
  248. package/dist/find-ZFZDZQ2U.js +0 -11
  249. package/dist/issue-OG4V23SQ.js +0 -19
  250. package/dist/liberate-GFUQJRK5.js +0 -24
  251. package/dist/noydb-AKH4AMCR.js +0 -67
  252. package/dist/register-ZI7YI7US.js +0 -23
  253. package/dist/registry-DZ2T4LL7.js +0 -9
  254. package/dist/registry-NVWEQ2GU.js +0 -18
  255. package/dist/registry-SEKL74ET.js +0 -19
  256. package/dist/request-withdrawal-ZP3JIRVS.js +0 -31
  257. package/dist/reveal-NPVROV4D.js.map +0 -1
  258. package/dist/revoke-VF6TAJK2.js +0 -24
  259. package/dist/signer-DRDDCCHW.js +0 -25
  260. package/dist/stale-TJXFQKOJ.js +0 -14
  261. package/dist/storage-GR7KZRH4.js +0 -23
  262. package/dist/verify-QK76NJ4G.js.map +0 -1
  263. package/dist/withdraw-accessible-FMN4LPJZ.js +0 -28
  264. /package/dist/{api-3TH2GOFQ.js.map → api-ZGS6LCOF.js.map} +0 -0
  265. /package/dist/{chunk-ZL6PRULX.js.map → chunk-2NVOWNI3.js.map} +0 -0
  266. /package/dist/{chunk-WEHIQYQH.js.map → chunk-3B5VVCNY.js.map} +0 -0
  267. /package/dist/{chunk-FF7S3XDW.js.map → chunk-3LQ2YKO4.js.map} +0 -0
  268. /package/dist/{chunk-NAR6IDKH.js.map → chunk-3QCRBHCY.js.map} +0 -0
  269. /package/dist/{chunk-SXPCZ6LL.js.map → chunk-454JXE57.js.map} +0 -0
  270. /package/dist/{chunk-JGUZMC3I.js.map → chunk-4JBYV75E.js.map} +0 -0
  271. /package/dist/{chunk-RXTMLYJS.js.map → chunk-4NXKCLLQ.js.map} +0 -0
  272. /package/dist/{chunk-GXLZXD2W.js.map → chunk-4VNQOLML.js.map} +0 -0
  273. /package/dist/{chunk-45QPICLE.js.map → chunk-5M5DVGV7.js.map} +0 -0
  274. /package/dist/{chunk-S2OWPVSO.js.map → chunk-5YOLBEQ6.js.map} +0 -0
  275. /package/dist/{chunk-BDEMZD4F.js.map → chunk-6CRIV7DE.js.map} +0 -0
  276. /package/dist/{chunk-VCUQR6ZL.js.map → chunk-6JTIAGHD.js.map} +0 -0
  277. /package/dist/{chunk-K2DLLK34.js.map → chunk-6QTNZJBS.js.map} +0 -0
  278. /package/dist/{chunk-UMC5QZTX.js.map → chunk-6T2SCQRU.js.map} +0 -0
  279. /package/dist/{chunk-J3JIYFYL.js.map → chunk-7DSTRQIJ.js.map} +0 -0
  280. /package/dist/{chunk-3QSOFZQB.js.map → chunk-AUSTCW2X.js.map} +0 -0
  281. /package/dist/{chunk-PN5332SU.js.map → chunk-B3HJYUOF.js.map} +0 -0
  282. /package/dist/{chunk-DIB2BOIH.js.map → chunk-BDVFPG6Y.js.map} +0 -0
  283. /package/dist/{chunk-FYNE4WLC.js.map → chunk-CAF6WOK7.js.map} +0 -0
  284. /package/dist/{chunk-37WOJQWY.js.map → chunk-CIE2BASH.js.map} +0 -0
  285. /package/dist/{chunk-BLUT46UP.js.map → chunk-CRI4OUV4.js.map} +0 -0
  286. /package/dist/{chunk-Y5TWPBNW.js.map → chunk-DLG74CNB.js.map} +0 -0
  287. /package/dist/{chunk-HIOLCOK3.js.map → chunk-E5ZQT5KX.js.map} +0 -0
  288. /package/dist/{chunk-IQIVD7TP.js.map → chunk-EQO2UVP4.js.map} +0 -0
  289. /package/dist/{chunk-US34HTOI.js.map → chunk-ERJ7FU55.js.map} +0 -0
  290. /package/dist/{chunk-S7TAPIRS.js.map → chunk-EVVGJDRQ.js.map} +0 -0
  291. /package/dist/{chunk-EXGDL2UQ.js.map → chunk-EVX47VBM.js.map} +0 -0
  292. /package/dist/{chunk-NT2ZO4KI.js.map → chunk-EXK7QRWF.js.map} +0 -0
  293. /package/dist/{chunk-7QLY64AD.js.map → chunk-EYZ6IQ6X.js.map} +0 -0
  294. /package/dist/{chunk-QF5LGMSV.js.map → chunk-F64IRYG3.js.map} +0 -0
  295. /package/dist/{chunk-PH7G7ZI7.js.map → chunk-FHS2HQSH.js.map} +0 -0
  296. /package/dist/{chunk-ZHOJOT6S.js.map → chunk-FUG4SFN3.js.map} +0 -0
  297. /package/dist/{chunk-JRKE5MAZ.js.map → chunk-GKEWXIV5.js.map} +0 -0
  298. /package/dist/{chunk-6MA575R5.js.map → chunk-GTGPO5KW.js.map} +0 -0
  299. /package/dist/{chunk-XS6DLN67.js.map → chunk-HB3RFQSC.js.map} +0 -0
  300. /package/dist/{chunk-VXZNSPJM.js.map → chunk-HDMEGAQE.js.map} +0 -0
  301. /package/dist/{chunk-DJ7MRXNA.js.map → chunk-HTJQYHZP.js.map} +0 -0
  302. /package/dist/{chunk-NRYNRSGV.js.map → chunk-I6GALP6E.js.map} +0 -0
  303. /package/dist/{chunk-GL3JV5EV.js.map → chunk-IL6POWCW.js.map} +0 -0
  304. /package/dist/{chunk-NEYKOESM.js.map → chunk-IU6SNYX6.js.map} +0 -0
  305. /package/dist/{chunk-CCIR2OJH.js.map → chunk-J2ZX2XUV.js.map} +0 -0
  306. /package/dist/{chunk-NM3IFKBL.js.map → chunk-JKJOGPZN.js.map} +0 -0
  307. /package/dist/{chunk-TVS6BUUR.js.map → chunk-JZUV47FZ.js.map} +0 -0
  308. /package/dist/{chunk-5HNX4OD4.js.map → chunk-K5IMOGHB.js.map} +0 -0
  309. /package/dist/{chunk-XJ47GS53.js.map → chunk-KSKBRKGJ.js.map} +0 -0
  310. /package/dist/{chunk-INBQ43DI.js.map → chunk-LASZKVMO.js.map} +0 -0
  311. /package/dist/{chunk-EVHBK7BN.js.map → chunk-LXEXBTKL.js.map} +0 -0
  312. /package/dist/{chunk-6S7ENMRE.js.map → chunk-M4CV6BRT.js.map} +0 -0
  313. /package/dist/{chunk-AAIIDAUC.js.map → chunk-MEFJPTZ4.js.map} +0 -0
  314. /package/dist/{chunk-WHQHTNT7.js.map → chunk-MV43JSJF.js.map} +0 -0
  315. /package/dist/{chunk-F44XWXOA.js.map → chunk-NJ4EOKMB.js.map} +0 -0
  316. /package/dist/{chunk-ILF5GARW.js.map → chunk-NR5LT5RY.js.map} +0 -0
  317. /package/dist/{chunk-TBNF5DV5.js.map → chunk-NWSGI5C7.js.map} +0 -0
  318. /package/dist/{chunk-EIZ4YOG2.js.map → chunk-Q5VNKNE5.js.map} +0 -0
  319. /package/dist/{chunk-TVOHSPVK.js.map → chunk-QLWMEXCT.js.map} +0 -0
  320. /package/dist/{chunk-7GAXFU2O.js.map → chunk-QRB7NAOJ.js.map} +0 -0
  321. /package/dist/{chunk-X6Y5MXTH.js.map → chunk-R4ROFEYC.js.map} +0 -0
  322. /package/dist/{chunk-4BQXMCE5.js.map → chunk-RCVJZMFE.js.map} +0 -0
  323. /package/dist/{chunk-QG5RQQBO.js.map → chunk-RIX4V2TB.js.map} +0 -0
  324. /package/dist/{chunk-SAL5X5OK.js.map → chunk-RYCTDFUX.js.map} +0 -0
  325. /package/dist/{chunk-CXC3JLAQ.js.map → chunk-RZ5SVOYR.js.map} +0 -0
  326. /package/dist/{chunk-44IL5G3W.js.map → chunk-SLFHQTTW.js.map} +0 -0
  327. /package/dist/{chunk-2MTRJCBI.js.map → chunk-T2FFWNPK.js.map} +0 -0
  328. /package/dist/{chunk-QYPDYN23.js.map → chunk-UNFPPVNI.js.map} +0 -0
  329. /package/dist/{chunk-7ZOSHEVS.js.map → chunk-VLFCJYTL.js.map} +0 -0
  330. /package/dist/{chunk-APHLCXVC.js.map → chunk-VN3DBQAU.js.map} +0 -0
  331. /package/dist/{chunk-HK4XTXO4.js.map → chunk-VZRODIM3.js.map} +0 -0
  332. /package/dist/{chunk-P2LJI6JE.js.map → chunk-W22IKJE2.js.map} +0 -0
  333. /package/dist/{chunk-FJTWLQV3.js.map → chunk-X3YYQWOQ.js.map} +0 -0
  334. /package/dist/{chunk-SGJGWBSC.js.map → chunk-YFXZEAYK.js.map} +0 -0
  335. /package/dist/{chunk-SIAIYP6G.js.map → chunk-ZAJAVEXY.js.map} +0 -0
  336. /package/dist/{chunk-KKYEZCQG.js.map → chunk-ZICD77GM.js.map} +0 -0
  337. /package/dist/{chunk-HGAB325C.js.map → chunk-ZJ2AK4V7.js.map} +0 -0
  338. /package/dist/{chunk-HYG32HCM.js.map → chunk-ZUODRVHY.js.map} +0 -0
  339. /package/dist/{classified-marker-M7K3OJPM.js.map → classified-marker-CZKA6ITE.js.map} +0 -0
  340. /package/dist/{collection-facade-DWA735G2.js.map → collection-facade-TLWIPI76.js.map} +0 -0
  341. /package/dist/{computed-ORCGM5JE.js.map → computed-ZIQTYTDI.js.map} +0 -0
  342. /package/dist/{dead-filter-OV5FGKOG.js.map → dead-filter-ESOBZZP5.js.map} +0 -0
  343. /package/dist/{delegation-YVSHMIZZ.js.map → delegation-W7ZV2322.js.map} +0 -0
  344. /package/dist/{derive-BUO555L5.js.map → derive-H2BJTLUI.js.map} +0 -0
  345. /package/dist/{enclave-XXFUKJC6.js.map → enclave-AP3I5W7O.js.map} +0 -0
  346. /package/dist/{executor-6OCCE5VM.js.map → executor-KGWTNEQD.js.map} +0 -0
  347. /package/dist/{executor-JFVAJY42.js.map → executor-OVEXR6RT.js.map} +0 -0
  348. /package/dist/{executor-UPSH5O3F.js.map → executor-UNEH5ZYH.js.map} +0 -0
  349. /package/dist/{export-accessible-L4A2QZA5.js.map → export-accessible-PCMXSUZS.js.map} +0 -0
  350. /package/dist/{extract-partition-ZHQ4RGGM.js.map → extract-partition-SWHFSYJO.js.map} +0 -0
  351. /package/dist/{fanout-sidecar-IO2KDUWV.js.map → fanout-sidecar-A765THMX.js.map} +0 -0
  352. /package/dist/{find-ZFZDZQ2U.js.map → find-N6345CNT.js.map} +0 -0
  353. /package/dist/{issue-OG4V23SQ.js.map → issue-5OKB6I2R.js.map} +0 -0
  354. /package/dist/{ledger-UAQKPWNM.js.map → ledger-GFBKVPV4.js.map} +0 -0
  355. /package/dist/{liberate-GFUQJRK5.js.map → liberate-3VDOT4KO.js.map} +0 -0
  356. /package/dist/{link-set-DGYUOFVD.js.map → link-set-Z7Q2ASFX.js.map} +0 -0
  357. /package/dist/{noydb-AKH4AMCR.js.map → noydb-ZCJOYBDI.js.map} +0 -0
  358. /package/dist/{policy-5POYLI26.js.map → policy-Z3RUAIY2.js.map} +0 -0
  359. /package/dist/{post-register-LB3P4XQU.js.map → post-register-JTH76BLY.js.map} +0 -0
  360. /package/dist/{public-envelope-GOG4LUM7.js.map → public-envelope-B4KNWVUA.js.map} +0 -0
  361. /package/dist/{register-ZI7YI7US.js.map → register-VAHONCID.js.map} +0 -0
  362. /package/dist/{registry-DZ2T4LL7.js.map → registry-7MLVD3VO.js.map} +0 -0
  363. /package/dist/{registry-NVWEQ2GU.js.map → registry-EPKPD5SM.js.map} +0 -0
  364. /package/dist/{registry-SEKL74ET.js.map → registry-P5T7PS2U.js.map} +0 -0
  365. /package/dist/{request-withdrawal-ZP3JIRVS.js.map → request-withdrawal-TI3A5WPS.js.map} +0 -0
  366. /package/dist/{revoke-VF6TAJK2.js.map → revoke-FDRD6GRW.js.map} +0 -0
  367. /package/dist/{seed-VI4FU7UH.js.map → seed-Q6M2IZFL.js.map} +0 -0
  368. /package/dist/{signer-DRDDCCHW.js.map → signer-MUWEIUI6.js.map} +0 -0
  369. /package/dist/{stale-TJXFQKOJ.js.map → stale-OSQ7J3AA.js.map} +0 -0
  370. /package/dist/{storage-GR7KZRH4.js.map → storage-MVFCGEQE.js.map} +0 -0
  371. /package/dist/{store-coordination-provider-5KDS2RRP.js.map → store-coordination-provider-64OEZFGS.js.map} +0 -0
  372. /package/dist/{walk-XQCBKSBW.js.map → walk-SVNDIZ62.js.map} +0 -0
  373. /package/dist/{withdraw-accessible-FMN4LPJZ.js.map → withdraw-accessible-WZIEVWSR.js.map} +0 -0
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/kernel/enclave/record-keys/lifecycle.ts","../src/kernel/enclave/record-keys/sealed-slots.ts","../src/kernel/enclave/classify/write.ts","../src/kernel/schema.ts","../src/kernel/enclave/record-keys/record-codec.ts","../src/kernel/enclave/record-keys/sealing.ts","../src/kernel/enclave/record-keys/deterministic.ts","../src/kernel/enclave/record-keys/envelope-body.ts","../src/kernel/enclave/broker/proof.ts"],"sourcesContent":["/**\n * Per-record CEK lifecycle helpers for the WRITE path.\n *\n * These are the collection-coupled CEK operations, lifted off `Collection`\n * behind narrow deps so the kernel file delegates instead of carrying the\n * crypto inline:\n *\n * - {@link resolveStableCek} — the stable-CEK rule: an update or history\n * snapshot reuses the record's existing CEK so a single CEK delete kills\n * the whole version chain; a genuine insert (or legacy record being\n * migrated) mints a fresh one.\n * - {@link rewrapBodyToDek} — move a record body's wrapping from one DEK to\n * another (tier elevate/demote, bundle re-key) WITHOUT changing the body\n * key: unwrap the CEK under the source DEK, re-encrypt the body under the\n * same CEK, re-wrap the CEK under the destination DEK. History-chain\n * identity is preserved because the body key never changes.\n *\n * Both are pure functions over their deps — the `cekCache` ownership stays on\n * the collection (its lifetime is tied to `load()`), so callers cache the\n * returned key themselves.\n */\nimport { encrypt, decrypt, generateDEK, wrapCek, unwrapCek, type EnclaveKey } from '../crypto.js'\nimport type { EncryptedEnvelope } from '../../types.js'\nimport type { Lru } from '../../cache/index.js'\n\n/** Dependencies {@link resolveStableCek} needs from its collection. */\nexport interface StableCekDeps {\n /** The collection's per-record CEK cache (`null` → no caching). */\n readonly cache: Lru<string, EnclaveKey> | null\n /** Read the record's live envelope (to recover an existing `_cek`). */\n getLive(id: string): Promise<EncryptedEnvelope | null>\n /** The DEK the CEK is wrapped under (the collection DEK on the normal path). */\n getDEK(): Promise<EnclaveKey>\n}\n\n/**\n * Resolve the stable CEK for a record on the write path. Caches the resolved\n * key under `id` so an update + its history snapshot share one CEK.\n */\nexport async function resolveStableCek(deps: StableCekDeps, id: string): Promise<EnclaveKey> {\n const cached = deps.cache?.get(id)\n if (cached) return cached\n\n const live = await deps.getLive(id)\n if (live?._cek !== undefined) {\n const cek = await unwrapCek(live._cek, await deps.getDEK())\n deps.cache?.set(id, cek, 1)\n return cek\n }\n\n const fresh = await generateDEK()\n deps.cache?.set(id, fresh, 1)\n return fresh\n}\n\n/** Re-wrapped body bytes + the (optional) CEK to cache. */\nexport interface RewrappedBody {\n readonly _iv: string\n readonly _data: string\n /** Present iff the source envelope carried a CEK (per-record-key record). */\n readonly _cek?: string\n /** The body key when one exists, so the caller can cache it; `null` for a legacy record. */\n readonly cek: EnclaveKey | null\n}\n\n/**\n * Move a record body from `fromDek` to `toDek`.\n *\n * - Per-record-key record (`_cek` present): unwrap the CEK under `fromDek`,\n * re-encrypt the body under the SAME CEK, re-wrap the CEK under `toDek`. The\n * body key is unchanged → history-chain identity preserved.\n * - Legacy record (no `_cek`): decrypt under `fromDek`, re-encrypt under `toDek`\n * directly — byte-for-byte the pre-CEK behaviour.\n */\nexport async function rewrapBodyToDek(\n envelope: Pick<EncryptedEnvelope, '_iv' | '_data' | '_cek'>,\n fromDek: EnclaveKey,\n toDek: EnclaveKey,\n): Promise<RewrappedBody> {\n if (envelope._cek !== undefined) {\n const cek = await unwrapCek(envelope._cek, fromDek)\n const plaintext = await decrypt(envelope._iv, envelope._data, cek)\n const { iv, data } = await encrypt(plaintext, cek)\n return { _iv: iv, _data: data, _cek: await wrapCek(cek, toDek), cek }\n }\n const plaintext = await decrypt(envelope._iv, envelope._data, fromDek)\n const { iv, data } = await encrypt(plaintext, toDek)\n return { _iv: iv, _data: data, cek: null }\n}\n","/**\n * Sealed-slot sub-step — the \"seal these declared fields into their own\n * `iv:data` slots\" mechanism, extracted byte-parity from `RecordCodec`\n * (#629 Task 1; see `.superpowers/sdd/seam-map-classified-blobs.md` §2).\n *\n * This is the ONE genuinely separable sub-step `RecordCodec.encryptRecord`/\n * `decryptRecord` perform (the digest-only `_vdig`/`_bidx` mechanism is NOT\n * separable the same way — it needs write-history `prev` context, see the\n * seam map's separability verdict). `sealFields`/`unsealFields` are the\n * natural shape of a `via` `encodeAtRest`/`decodeAtRest` hook for a \"seal\n * this field\" via-feature, and `makeSealedSlotCapability`/\n * `makeReservedEnvelopes` (below) are the `ViaCryptoCtx` capability\n * factories built on top of them.\n *\n * The capability factories live here (rather than a sibling\n * `kernel/via-crypto.ts`) so they can call `deriveSealedFieldKey`/\n * `deriveSealedFieldKeyFromCek`/`encrypt`/`decrypt` directly — this file IS\n * kernel enclave code (C3), so it is exempt from `enclave-barrel-only` the\n * same way `record-codec.ts`/`sealing.ts` are. A file outside\n * `kernel/enclave/**` would have had to go through the enclave barrel\n * (`kernel/enclave/index.ts`), which does not (yet) export these symbols.\n */\nimport { encrypt, decrypt, deriveSealedFieldKey, deriveSealedFieldKeyFromCek, type EnclaveKey } from '../crypto.js'\nimport { dualReadSealedSlot } from './sealed-slot.js'\nimport { NOYDB_FORMAT_VERSION, SealedHandle, type EncryptedEnvelope } from '../../types.js'\nimport { ValidationError } from '../../errors.js'\nimport type { SealedSlotRef, ViaCryptoCtx } from '../../via/index.js'\n\n/**\n * Key material for one collection's sealed-field derivation. `cek`, when\n * supplied, is preferred (record-scoped); `getDEK` is the fallback (and,\n * for legacy/no-CEK collections, the only key) — mirrors the dual-read\n * `RecordCodec` has always done.\n */\nexport interface SealKeyMaterial {\n readonly collection: string\n readonly cek?: EnclaveKey\n getDEK(): Promise<EnclaveKey>\n}\n\nexport interface SealFieldsResult {\n readonly openRecord: Record<string, unknown>\n readonly sealed: Record<string, string> | undefined\n}\n\n/** Seal one field's value to an `iv:data` slot under its derived per-field key. */\nasync function sealOneFieldWithDek(\n field: string,\n value: unknown,\n dek: EnclaveKey,\n keyMaterial: SealKeyMaterial,\n): Promise<SealedSlotRef> {\n const fieldKey = keyMaterial.cek !== undefined\n ? await deriveSealedFieldKeyFromCek(keyMaterial.cek, keyMaterial.collection, field)\n : await deriveSealedFieldKey(dek, keyMaterial.collection, field)\n return encrypt(JSON.stringify(value), fieldKey)\n}\n\n/**\n * Seal one field's value on demand — fetches the DEK itself. A singular\n * counterpart to {@link sealFields} for callers that seal one field at a\n * time rather than a whole record. Not exported — `makeSealedSlotCapability`\n * is the only caller (#629 Task 11: knip-flagged unused export, dropped;\n * intra-module use only).\n */\nasync function sealOneField(\n field: string,\n value: unknown,\n keyMaterial: SealKeyMaterial,\n): Promise<SealedSlotRef> {\n const dek = await keyMaterial.getDEK()\n return sealOneFieldWithDek(field, value, dek, keyMaterial)\n}\n\n/**\n * Peel declared `sensitiveFields` out of `record` BEFORE building `_data`,\n * sealing each into its own `iv:data` slot. Extracted byte-parity from\n * `RecordCodec.encryptRecord` step 2 (pre-#629 `record-codec.ts:261-288`).\n * Returns `record` untouched (and `sealed: undefined`) when no declared\n * field is present with a defined value — the envelope stays byte-identical\n * to legacy output, exactly as before.\n */\nexport async function sealFields(\n record: Record<string, unknown>,\n sensitiveFields: ReadonlySet<string>,\n keyMaterial: SealKeyMaterial,\n): Promise<SealFieldsResult> {\n const dek = await keyMaterial.getDEK()\n const open: Record<string, unknown> = { ...record }\n const slots: Record<string, string> = {}\n for (const field of sensitiveFields) {\n if (!(field in record)) continue\n const value = record[field]\n if (value === undefined) continue\n const { iv, data } = await sealOneFieldWithDek(field, value, dek, keyMaterial)\n slots[field] = `${iv}:${data}`\n delete open[field]\n }\n if (Object.keys(slots).length === 0) return { openRecord: record, sealed: undefined }\n return { openRecord: open, sealed: slots }\n}\n\n/**\n * Unseal one `_sealed[field]` slot to its plaintext value: dual-read (try\n * the CEK-derived key, fall back to the DEK-derived key for legacy\n * records), AES-GCM-decrypt the `iv:data` blob, and JSON-parse the result.\n * Extracted byte-parity from `RecordCodec.unsealField`\n * (pre-#629 `record-codec.ts:485-493`).\n */\nexport async function unsealOneField(field: string, blob: string, keyMaterial: SealKeyMaterial): Promise<unknown> {\n const dek = await keyMaterial.getDEK()\n return JSON.parse(await dualReadSealedSlot(blob, field, keyMaterial.collection, keyMaterial.cek, dek))\n}\n\n/**\n * Build a non-leaking {@link SealedHandle} producer over sealed key\n * material: each call captures only the ciphertext `blob` and a closure to\n * {@link unsealOneField} — the plaintext is never stored on the handle, and\n * (crucially) the DEK is fetched lazily on `reveal()`, never at handle-\n * construction time. Extracted byte-parity from\n * `RecordCodec.makeSealedHandle` (pre-#629 `record-codec.ts:552-554`).\n */\nexport function makeHandleProducer(keyMaterial: SealKeyMaterial): (field: string, blob: string) => SealedHandle<unknown> {\n return (field, blob) => new SealedHandle(() => unsealOneField(field, blob, keyMaterial))\n}\n\n/**\n * Restore every `[field]: blob` entry in `sealed` onto `record` (mutated in\n * place, matching the original's mutate-then-return shape): eagerly unseal\n * to plaintext (`opts.asHandles` falsy) or wrap each in a\n * {@link SealedHandle} (`opts.asHandles: true`) so the plaintext is never\n * materialised. Extracted byte-parity from `RecordCodec.decryptRecord`'s\n * sealed-handle block (pre-#629 `record-codec.ts:615-623`).\n */\nexport async function unsealFields(\n record: Record<string, unknown>,\n sealed: Record<string, string>,\n keyMaterial: SealKeyMaterial,\n opts?: { asHandles?: boolean },\n): Promise<Record<string, unknown>> {\n const makeHandle = opts?.asHandles === true ? makeHandleProducer(keyMaterial) : undefined\n for (const [field, blob] of Object.entries(sealed)) {\n record[field] = makeHandle !== undefined ? makeHandle(field, blob) : await unsealOneField(field, blob, keyMaterial)\n }\n return record\n}\n\n// ─────────────────────────────────────────────────────────────────────────\n// ViaCryptoCtx capability factories (#629 Task 1, second commit)\n// ─────────────────────────────────────────────────────────────────────────\n\n/** What {@link makeSealedSlotCapability} needs — a subset of `RecordCodecContext`. */\nexport interface SealedSlotCapabilityCtx {\n readonly name: string\n getDEK(): Promise<EnclaveKey>\n}\n\n/**\n * Build a `ViaCryptoCtx.sealedSlots` capability pre-bound to one\n * `(collection, recordId)`. The key material (`cek`/DEK resolver) is closed\n * over and never placed on the returned object — `Object.keys`/\n * `Object.values` of the capability expose only the three methods, never a\n * key (zero-knowledge: capabilities never expose keys).\n *\n * `delete(field)` is **advisory and capability-instance-local, not real\n * erasure** — it has no store to reach and never will by itself: it just\n * adds `field` to a `Set` closed over by THIS ONE capability object, so a\n * later `unseal` call on that SAME object refuses. No I/O happens. A fresh\n * capability for the same record (e.g. `seal()`'d again, or a new\n * `makeSealedSlotCapability(...)` call) has an empty set and sees no\n * deletion at all — `seal()` on a given field also clears any prior mark\n * for it. Do not build production logic on `delete()` persisting anything.\n * The real erasure guarantee for a forgotten record is `vault.forget()`'s\n * unconditional `_writeTombstone` call, which replaces the whole live\n * envelope with a fresh, `_sealed`-free tombstone — `_sealed` disappears\n * from the store regardless of what any capability's `delete()` marked.\n * (#629 Task 10 traced this end-to-end; see task-10-report.md \"sealedSlots\n * .delete()\" section. Removing/changing this method's shape is a phase-E\n * decision, not implied by this comment.)\n */\nexport function makeSealedSlotCapability(\n ctx: SealedSlotCapabilityCtx,\n recordId: string,\n cek?: EnclaveKey,\n): ViaCryptoCtx['sealedSlots'] {\n const keyMaterial: SealKeyMaterial = {\n collection: ctx.name,\n ...(cek !== undefined ? { cek } : {}),\n getDEK: () => ctx.getDEK(),\n }\n const deletedFields = new Set<string>()\n\n return {\n async seal(field, plaintext) {\n const ref = await sealOneField(field, plaintext, keyMaterial)\n deletedFields.delete(field)\n return ref\n },\n async unseal(field, ref) {\n if (deletedFields.has(field)) {\n throw new ValidationError(`sealedSlots.unseal: field \"${field}\" on record \"${recordId}\" was deleted from this capability`)\n }\n return unsealOneField(field, `${ref.iv}:${ref.data}`, keyMaterial)\n },\n async delete(field) {\n deletedFields.add(field)\n },\n }\n}\n\n/** Resolve the DEK for an arbitrary (possibly synthetic, e.g. `_dict_en`) collection name. */\nexport type ReservedEnvelopeDekResolver = (collection: string) => Promise<EnclaveKey>\n\n/**\n * Build a `ViaCryptoCtx.reservedEnvelopes` capability: a whole-envelope\n * encrypt/decrypt door scoped to collection names under a declared prefix\n * (e.g. `_dict_`) — the shape `via/i18n/dictionary.ts`'s\n * `DictionaryHandle` needs (seam map Part 3): build JSON → encrypt under a\n * DEK → wrap in an `EncryptedEnvelope`; decrypt → JSON text. No per-record\n * CEK — reserved envelopes are whole-DEK-encrypted, same as the dictionary\n * grandfather.\n *\n * `reservedEnvelopes(prefix)` throws `ValidationError` immediately when\n * `prefix` is not in `declaredPrefixes`; each `encrypt`/`decrypt` call\n * throws `ValidationError` when its `collection` argument does not start\n * with `prefix`.\n */\nexport function makeReservedEnvelopes(\n dekResolver: ReservedEnvelopeDekResolver,\n declaredPrefixes: readonly string[],\n): ViaCryptoCtx['reservedEnvelopes'] {\n const declared = new Set(declaredPrefixes)\n\n return (prefix: string) => {\n if (!declared.has(prefix)) {\n throw new ValidationError(`reservedEnvelopes: prefix \"${prefix}\" is not declared by this binding's reservedPrefixes`)\n }\n\n const assertPrefixed = (collection: string, method: 'encrypt' | 'decrypt'): void => {\n if (!collection.startsWith(prefix)) {\n throw new ValidationError(\n `reservedEnvelopes('${prefix}').${method}: collection \"${collection}\" does not start with \"${prefix}\"`,\n )\n }\n }\n\n const encryptForPrefix = async (collection: string, json: string, v: number): Promise<EncryptedEnvelope> => {\n assertPrefixed(collection, 'encrypt')\n const dek = await dekResolver(collection)\n const { iv, data } = await encrypt(json, dek)\n return {\n _noydb: NOYDB_FORMAT_VERSION,\n _v: v,\n _ts: new Date().toISOString(),\n _iv: iv,\n _data: data,\n }\n }\n\n const decryptForPrefix = async (collection: string, env: EncryptedEnvelope): Promise<string> => {\n assertPrefixed(collection, 'decrypt')\n const dek = await dekResolver(collection)\n return decrypt(env._iv, env._data, dek)\n }\n\n return { encrypt: encryptForPrefix, decrypt: decryptForPrefix }\n }\n}\n","/**\n * Digest-only write engine (C6 rotate branch + notLastN ring, spec §2/§4).\n * Called ONLY by RecordCodec.encryptRecord — both live inside the enclave.\n * @module\n */\nimport { generateSalt, bufferToBase64, base64ToBuffer, type EnclaveKey } from '../crypto.js'\nimport { pbkdf2VerifyDigest, VDIG_ITERATIONS } from './digest.js'\nimport { normalizeForVerify } from './normalize.js'\nimport { blindedEqual } from './compare.js'\nimport { sealVdigPayload, openVdigPayload, type VdigPayload, type VdigDigestEntry } from './vdig.js'\nimport { ClassifiedRotationError, TamperedError } from '../../errors.js'\nimport type { VdigFieldPolicy } from '../../types.js'\n\nexport async function mintVdigSlot(\n rawValue: string,\n policy: VdigFieldPolicy,\n prevBlob: string | undefined,\n cek: EnclaveKey,\n collection: string,\n recordId: string,\n field: string,\n): Promise<string> {\n const normalized = normalizeForVerify(policy.normalize, rawValue)\n\n let prev: VdigPayload | null = null\n if (prevBlob !== undefined) {\n try {\n prev = await openVdigPayload(prevBlob, cek, collection, recordId, field)\n } catch {\n // Any failure opening a prior _vdig slot (auth-tag mismatch, malformed\n // JSON, wrong AAD) is a tamper/false condition — normalize to\n // TamperedError so the write path never sees a distinct error shape.\n throw new TamperedError()\n }\n if (!Number.isInteger(prev.iter) || prev.iter < 1) {\n // A corrupted/tampered iter count would silently weaken (or break)\n // the reuse-check PBKDF2 pass — floor/validate before trusting it.\n throw new TamperedError()\n }\n }\n\n // notLastN reuse refusal: candidate vs cur + every ring entry, each a full\n // PBKDF2 at the payload's own iteration count (n × 600K is the documented\n // write-time cost ceiling, cap 8 — spec Q4).\n if (prev !== null && policy.notLastN > 0) {\n const history: readonly VdigDigestEntry[] = [prev.cur, ...(prev.ring ?? [])]\n for (const entry of history) {\n const digest = await pbkdf2VerifyDigest(normalized, base64ToBuffer(entry.salt), prev.iter)\n if (await blindedEqual(digest, base64ToBuffer(entry.hash))) {\n throw new ClassifiedRotationError(collection, field, 'password was used recently')\n }\n }\n }\n\n const salt = generateSalt()\n const hash = await pbkdf2VerifyDigest(normalized, salt, VDIG_ITERATIONS)\n const ring = prev !== null && policy.notLastN > 0\n ? [...(prev.ring ?? []), { salt: prev.cur.salt, hash: prev.cur.hash }].slice(-policy.notLastN)\n : undefined\n\n const payload: VdigPayload = {\n v: 1,\n alg: 'PBKDF2-SHA256',\n iter: VDIG_ITERATIONS,\n cur: { salt: bufferToBase64(salt), hash: bufferToBase64(hash), at: new Date().toISOString() },\n ...(ring !== undefined && ring.length > 0 ? { ring } : {}),\n }\n return sealVdigPayload(payload, cek, collection, recordId, field)\n}\n","/**\n * Standard Schema v1 integration.\n *\n * This file is the entry point for **schema validation**. Any\n * validator that implements the [Standard Schema v1\n * protocol](https://standardschema.dev) — Zod, Valibot, ArkType, Effect\n * Schema, etc. — can be attached to a `Collection` or `defineNoydbStore`\n * and will:\n *\n * 1. Validate the record BEFORE encryption on `put()` — bad data is\n * rejected at the store boundary with a rich issue list.\n * 2. Validate the record AFTER decryption on `get()`/`list()`/`query()`\n * — stored data that has drifted from the current schema throws\n * loudly instead of silently propagating garbage to the UI.\n *\n * ## Why vendor the types?\n *\n * Standard Schema is a protocol, not a library. The spec is <200 lines of\n * TypeScript and has no runtime. There's an official `@standard-schema/spec`\n * types package on npm, but pulling it in would add a dependency edge\n * purely for type definitions. Vendoring the minimal surface keeps\n * `@noy-db/core` at **zero runtime dependencies** and gives us freedom to\n * evolve the helpers without a version-lock on the spec package.\n *\n * If the spec changes in a breaking way (unlikely — it's frozen at v1),\n * we update this file and bump our minor.\n *\n * ## Why not just run `schema.parse(value)` directly?\n *\n * Because then we'd be locked to whichever validator happens to have\n * `.parse`. Standard Schema's `'~standard'.validate` contract is the same\n * across every implementation and includes a structured issues list,\n * which is much more useful than a thrown error for programmatic error\n * handling (e.g., rendering field-level messages in a Vue component).\n */\n\nimport { SchemaValidationError } from './errors.js'\n\n/**\n * The Standard Schema v1 protocol. A schema is any object that exposes a\n * `'~standard'` property with `version: 1` and a `validate` function.\n *\n * The type parameters are:\n * - `Input` — the type accepted by `validate` (what the user passes in)\n * - `Output` — the type produced by `validate` (what we store/return,\n * may differ from Input if the schema transforms or coerces)\n *\n * In most cases `Input === Output`, but validators that transform\n * (Zod's `.transform`, Valibot's `transform`, etc.) can narrow or widen.\n *\n * We intentionally keep the `types` field `readonly` and optional — the\n * spec marks it as optional because it's only used for inference, and\n * not every implementation bothers populating it at runtime.\n */\nexport interface StandardSchemaV1<Input = unknown, Output = Input> {\n readonly '~standard': {\n readonly version: 1\n readonly vendor: string\n readonly validate: (\n value: unknown,\n ) =>\n | StandardSchemaV1SyncResult<Output>\n | Promise<StandardSchemaV1SyncResult<Output>>\n readonly types?:\n | {\n readonly input: Input\n readonly output: Output\n }\n | undefined\n }\n}\n\n/**\n * The result of a single call to `schema['~standard'].validate`. Either\n * `{ value }` on success or `{ issues }` on failure — never both.\n *\n * The spec allows `issues` to be undefined on success (and some\n * validators leave it that way), so consumers should discriminate on\n * `issues?.length` rather than on truthiness of `value`.\n */\nexport type StandardSchemaV1SyncResult<Output> =\n | { readonly value: Output; readonly issues?: undefined }\n | {\n readonly value?: undefined\n readonly issues: readonly StandardSchemaV1Issue[]\n }\n\n/**\n * A single validation issue. The `message` is always present; the `path`\n * is optional and points at the offending field when the schema tracks\n * it (virtually every validator does for object types).\n *\n * The path is deliberately permissive — both a plain `PropertyKey` and a\n * `{ key }` wrapper are allowed so validators that wrap path segments in\n * objects (Zod does this in some modes) don't need special handling.\n */\nexport interface StandardSchemaV1Issue {\n readonly message: string\n readonly path?:\n | ReadonlyArray<PropertyKey | { readonly key: PropertyKey }>\n | undefined\n}\n\n/**\n * Infer the output type of a Standard Schema. Consumers use this to\n * pull the type out of a schema instance when they want to declare a\n * Collection<T> or defineNoydbStore<T> with `T` derived from the schema.\n *\n * Example:\n * ```ts\n * const InvoiceSchema = z.object({ id: z.string(), amount: z.number() })\n * type Invoice = InferOutput<typeof InvoiceSchema>\n * ```\n */\nexport type InferOutput<T extends StandardSchemaV1> =\n T extends StandardSchemaV1<unknown, infer O> ? O : never\n\n/**\n * Validate an input value against a schema. Throws\n * `SchemaValidationError` if the schema rejects, with the rich issue\n * list attached. Otherwise returns the (possibly transformed) output\n * value.\n *\n * The `context` string is included in the thrown error's message so the\n * caller knows where the failure happened (e.g. `\"put(inv-001)\"`) without\n * every caller having to wrap the throw in a try/catch.\n *\n * This function is ALWAYS async because some validators (notably Effect\n * Schema and Zod's `.refine` with async predicates) can return a\n * Promise. We `await` the result unconditionally to normalize the\n * contract — the extra microtask is free compared to the cost of an\n * encrypt/decrypt round-trip.\n */\nexport async function validateSchemaInput<Output>(\n schema: StandardSchemaV1<unknown, Output>,\n value: unknown,\n context: string,\n): Promise<Output> {\n const result = await schema['~standard'].validate(value)\n if (result.issues !== undefined && result.issues.length > 0) {\n throw new SchemaValidationError(\n `Schema validation failed on ${context}: ${summarizeIssues(result.issues)}`,\n result.issues,\n 'input',\n )\n }\n // Safe: the spec guarantees `value` is present when `issues` is absent.\n return result.value as Output\n}\n\n/**\n * Validate an already-stored value coming OUT of the collection. This\n * is a distinct helper from `validateSchemaInput` because the error\n * semantics differ: an output-validation failure means the data in\n * storage has drifted from the current schema (an unexpected state),\n * whereas an input-validation failure means the user passed bad data\n * (an expected state for a UI that isn't guarding its inputs).\n *\n * We still throw — silently returning bad data would be worse — but\n * the error carries `direction: 'output'` so upstream code (and a\n * potential migrate hook) can distinguish the two cases.\n */\nexport async function validateSchemaOutput<Output>(\n schema: StandardSchemaV1<unknown, Output>,\n value: unknown,\n context: string,\n): Promise<Output> {\n const result = await schema['~standard'].validate(value)\n if (result.issues !== undefined && result.issues.length > 0) {\n throw new SchemaValidationError(\n `Stored data for ${context} does not match the current schema — ` +\n `schema drift? ${summarizeIssues(result.issues)}`,\n result.issues,\n 'output',\n )\n }\n return result.value as Output\n}\n\n/**\n * Produce a short human-readable summary of an issue list for the\n * thrown error's message. The full issue array is still attached to the\n * error as a property — this is only for the `.message` string that\n * shows up in console.error / stack traces.\n *\n * Format: `field: message; field2: message2` (up to 3 issues, then `…`).\n * Issues without a path are shown as `root: message`.\n */\nfunction summarizeIssues(\n issues: readonly StandardSchemaV1Issue[],\n): string {\n const shown = issues.slice(0, 3).map((issue) => {\n const pathStr = formatPath(issue.path)\n return `${pathStr}: ${issue.message}`\n })\n const suffix = issues.length > 3 ? ` (+${issues.length - 3} more)` : ''\n return shown.join('; ') + suffix\n}\n\nfunction formatPath(\n path: StandardSchemaV1Issue['path'],\n): string {\n if (!path || path.length === 0) return 'root'\n return path\n .map((segment) =>\n typeof segment === 'object' && segment !== null\n ? String(segment.key)\n : String(segment),\n )\n .join('.')\n}\n","/**\n * RecordCodec — the per-record envelope build + encrypt/decrypt + per-record-CEK\n * + sealed-field crypto.\n *\n * `Collection` holds one `RecordCodec` instance and delegates the crypto write\n * path (`encryptRecord` / `encryptJsonString` / `buildDebugEnvelope`), the read\n * path (`decryptRecord` / `decryptJsonString` / `resolveEnvelopeCek`), and the\n * sealed-field helpers (`unsealField` / `makeSealedHandle` / `toCacheRecord` /\n * `classifySealedShred`) to it. The codec receives every `this.*` dependency\n * it needs via {@link RecordCodecContext}.\n *\n * The crux is `cekCache`: it is the SAME `Lru` reference `Collection` owns (not\n * a copy). `resolveEnvelopeCek` reads/writes it, and tier methods +\n * `vault.invalidateRecordCaches` keep mutating that same object — a copy would\n * silently break the \"single CEK delete kills the version chain\" invariant.\n *\n * Internal service — not exported as a `@noy-db/hub/*` subpath.\n */\nimport { encrypt, decrypt, encryptDeterministic, deriveDeterministicKey, wrapCek, unwrapCek, deriveSealedFieldKeyFromCek, type EnclaveKey } from '../crypto.js'\nimport { NOYDB_FORMAT_VERSION, type EncryptedEnvelope, type CrdtMode, type CrdtState, type CrdtStrategy, type VdigFieldPolicy, type SealedHandle } from '../../types.js'\nimport { isTombstone, isDeleteMarker } from './tombstone.js'\nimport { parseSealedSlot } from './sealed-slot.js'\nimport { sealFields, unsealOneField, unsealFields, makeHandleProducer, makeSealedSlotCapability, makeReservedEnvelopes, type SealKeyMaterial } from './sealed-slots.js'\nimport { DebugReservedFieldError, ClassifiedConfigError, ValidationError } from '../../errors.js'\nimport { mintVdigSlot } from '../classify/write.js'\nimport { mintBidxTag } from '../classify/bidx.js'\nimport { normalizeForVerify } from '../classify/normalize.js'\nimport { validateSchemaOutput, type StandardSchemaV1 } from '../../schema.js'\nimport type { Lru } from '../../cache/index.js'\nimport type { ViaCryptoCtx, SealedSlotRef } from '../../via/index.js'\nimport type { ViaPipeline } from '../../via/pipeline.js'\n\n/**\n * One classified per-slot verdict from {@link RecordCodec.classifySealedShred}.\n * `shreddable` — CEK-only, tombstone makes it undecryptable. `dekResidue` —\n * collection-DEK-keyed, survives in synced/backup copies. The third class is\n * BOTH: a `_bidx` blind-index tag is live-dropped by the tombstone yet retained\n * under the surviving DEK in any pre-forget backup (honest dual accounting).\n */\nexport type SealedShredSlot = {\n readonly field: string\n readonly class: 'shreddable' | 'dekResidue' | 'live-shreddable+dekResidue-in-backups'\n}\n\n/** Everything the moving crypto methods touched on `this.*`, as a flat context. */\nexport interface RecordCodecContext<T> {\n /** Collection name — the crypto AAD scope and schema-error context. */\n readonly name: string\n /** Actor id stamped on `_by` (the collection's keyring.userId). */\n readonly actor: string\n /** False on plaintext collections — selects the no-ciphertext envelope branch. */\n readonly storeCiphertext: boolean\n /** keyring.debugPlaintext — debug-inline envelope on user collections. */\n readonly debugPlaintext: boolean\n /** Emit `_source`/`_sourceTs` provenance fields when a source is supplied. */\n readonly provenance: boolean\n /** Declared `sensitive` fields → sealed into `_sealed[field]`. */\n readonly sensitiveFields: ReadonlySet<string>\n /** Declared deterministic-index fields, or null. */\n readonly deterministicFields: ReadonlySet<string> | null\n /** Digest-only classified fields → verify policy (stage 2). Null when none. */\n readonly vdigFields: ReadonlyMap<string, VdigFieldPolicy> | null\n /**\n * C-A / R10 config-drift signal: the persisted `x-classified` marker's\n * declared digest-only field set (empty when the collection carries no\n * marker). Lazy, memoized (O(1) per handle after the first call). The R10\n * superset guard refuses whenever a field in this set is absent from\n * `vdigFields`. Consulted once per handle on any encrypted write path — the\n * cross-session, prev-free signal a fully-naive create has no `prev` for.\n * Undefined on codecs with no store access (direct-construction tests).\n */\n classifiedMarkerDigestOnly?(): Promise<readonly string[]>\n /** CRDT mode (decrypt resolves CrdtState→snapshot when set). */\n readonly crdtMode: CrdtMode | undefined\n /** CRDT strategy seam (resolveCrdtSnapshot). */\n readonly crdtStrategy: CrdtStrategy\n /** Output-schema validator, or undefined. */\n readonly schema: StandardSchemaV1<unknown, T> | undefined\n /**\n * Resolve a collection's DEK. Called with no argument for `this.name`'s\n * own DEK (every ordinary codec use); `viaCryptoCtx`'s `reservedEnvelopes`\n * door passes an explicit OTHER collection name (e.g. a reserved\n * `_dict_status` collection) so a binding's at-rest hook can address a\n * different collection's DEK without ever holding the resolver itself.\n */\n getDEK(collection?: string): Promise<EnclaveKey>\n /**\n * The collection's per-record CEK cache (SHARED reference, not a copy).\n * Ownership/lifetime stays on Collection; codec reads+writes it in\n * resolveEnvelopeCek exactly as the inline code did. `null` → no caching.\n */\n readonly cekCache: Lru<string, EnclaveKey> | null\n /**\n * Compiled Via pipeline (money, i18n, …), or undefined for a collection\n * with none declared. `encryptRecord`/`decryptRecord` consult\n * `via?.hasAtRestHooks` at the sealed-slot sub-step to choose between a\n * binding's `encodeAtRest`/`decodeAtRest` hooks and today's inline path.\n * The zero-via fast path (`via` undefined, or no binding declares an\n * at-rest hook) stays byte/behavior-identical (#629 Task 3).\n *\n * Mutable (not `readonly`, #638 Task 3): `Collection.via` can be\n * reassigned post-construction (the taint overlay, `via/graph-wiring.ts#\n * applyTaintOverlay`) — `RecordCodec.setVia` below keeps this in sync so\n * at-rest hooks read the LIVE pipeline, not a stale construction-time\n * snapshot.\n */\n via: ViaPipeline | undefined\n}\n\nexport class RecordCodec<T> {\n constructor(private readonly ctx: RecordCodecContext<T>) {}\n\n /**\n * @internal Update the live Via pipeline this codec's at-rest hooks read\n * (#638 Task 3) — called by `via/graph-wiring.ts#applyTaintOverlay` right\n * after it reassigns `Collection.via`, so `hasAtRestHooks`/`encodeAtRest`/\n * `decodeAtRest` see the newly-added `taint` binding instead of the\n * pipeline snapshot captured when this codec was constructed.\n */\n setVia(via: ViaPipeline | undefined): void {\n this.ctx.via = via\n }\n\n /** Sealed-slot key material for this codec's collection, for a given per-record CEK (or none). */\n private sealKeyMaterial(cek: EnclaveKey | undefined): SealKeyMaterial {\n return {\n collection: this.ctx.name,\n ...(cek !== undefined ? { cek } : {}),\n getDEK: () => this.ctx.getDEK(),\n }\n }\n\n /**\n * Build the `ViaCryptoCtx` handed to a binding's `encodeAtRest`/\n * `decodeAtRest` hook: `sealedSlots` pre-bound to `(this.ctx.name,\n * recordId)` — always threading `cek` when the caller has one, so the\n * capability's record-binding stays cryptographic rather than degrading\n * to collection-scope. `reservedEnvelopes`'s DEK resolver forwards its\n * `collection` argument straight to `this.ctx.getDEK` (#629 Task 4) — it\n * resolves the RESERVED collection's own DEK (e.g. `_dict_status`), never\n * `this.ctx.name`'s, matching `Vault.getDEK`'s per-collection-name\n * resolution (the same resolver `DictionaryHandle` used pre-cutover).\n */\n private viaCryptoCtx(recordId: string, cek: EnclaveKey | undefined): ViaCryptoCtx {\n const declaredPrefixes = this.ctx.via?.bindings.flatMap((b) => b.reservedPrefixes ?? []) ?? []\n return {\n sealedSlots: makeSealedSlotCapability(this.ctx, recordId, cek),\n reservedEnvelopes: makeReservedEnvelopes((collection) => this.ctx.getDEK(collection), declaredPrefixes),\n }\n }\n\n // ──────────────────────────────────────────────────────────────────────\n // Low-level literal builders\n // ──────────────────────────────────────────────────────────────────────\n\n /**\n * Assemble a canonical ciphertext body envelope literal from already-computed\n * parts. Pure, no crypto. Each call stamps its own `_ts` — provenance carries\n * a SEPARATE timestamp (computed by the caller), so the two never share one.\n */\n static buildEnvelope(p: {\n version: number\n iv: string\n data: string\n by?: string\n cek?: string\n provenance?: { source: string; sourceTs: string } | undefined\n extra?: Partial<Pick<EncryptedEnvelope, '_tier' | '_det' | '_sealed'>>\n }): EncryptedEnvelope {\n return {\n _noydb: NOYDB_FORMAT_VERSION,\n _v: p.version,\n _ts: new Date().toISOString(),\n _iv: p.iv,\n _data: p.data,\n ...(p.by !== undefined ? { _by: p.by } : {}),\n ...(p.cek !== undefined ? { _cek: p.cek } : {}),\n ...(p.provenance !== undefined ? { _source: p.provenance.source, _sourceTs: p.provenance.sourceTs } : {}),\n ...(p.extra ?? {}),\n }\n }\n\n /** Plaintext (`_iv:''`) body envelope — the `!storeCiphertext` shape. */\n static buildPlaintextEnvelope(p: {\n version: number\n data: string\n by?: string\n provenance?: { source: string; sourceTs: string } | undefined\n }): EncryptedEnvelope {\n return {\n _noydb: NOYDB_FORMAT_VERSION,\n _v: p.version,\n _ts: new Date().toISOString(),\n _iv: '',\n _data: p.data,\n ...(p.by !== undefined ? { _by: p.by } : {}),\n ...(p.provenance !== undefined ? { _source: p.provenance.source, _sourceTs: p.provenance.sourceTs } : {}),\n }\n }\n\n // ──────────────────────────────────────────────────────────────────────\n // Write path\n // ──────────────────────────────────────────────────────────────────────\n\n /**\n * Build a debug-plaintext envelope: the record's own fields inlined as\n * top-level keys beside the reserved `_`-metadata, with `_debug: 1` and an\n * empty `_data`. Lets native store tooling read the record without\n * unwrapping. Only reached for user collections under `debugPlaintext`\n * (see {@link encryptRecord}). Rejects `_`-prefixed record fields, which\n * would collide with the reserved metadata namespace.\n */\n buildDebugEnvelope(record: T, version: number, source?: string, sourceTs?: string): EncryptedEnvelope {\n const rec = record as unknown as Record<string, unknown>\n for (const key of Object.keys(rec)) {\n if (key.startsWith('_')) throw new DebugReservedFieldError(this.ctx.name, key)\n }\n return {\n _noydb: NOYDB_FORMAT_VERSION,\n _v: version,\n _ts: new Date().toISOString(),\n _iv: '',\n _data: '',\n _by: this.ctx.actor,\n _debug: NOYDB_FORMAT_VERSION,\n ...(this.ctx.provenance && source !== undefined ? { _source: source, _sourceTs: sourceTs ?? new Date().toISOString() } : {}),\n ...rec,\n } as unknown as EncryptedEnvelope\n }\n\n /**\n * Encrypt a JSON body into an envelope.\n *\n * When `cek` is supplied (per-record CEK collections), the body is\n * encrypted under the CEK and the CEK is AES-KW-wrapped under the\n * collection DEK and stamped on `_cek`. When `cek` is omitted, the legacy\n * path encrypts the body directly under the collection DEK — byte-identical\n * to pre-CEK behaviour, so non-adopting collections pay nothing.\n */\n async encryptJsonString(\n json: string,\n version: number,\n cek?: EnclaveKey,\n source?: string,\n sourceTs?: string,\n ): Promise<EncryptedEnvelope> {\n const by = this.ctx.actor\n const provenance = this.ctx.provenance && source !== undefined\n ? { source, sourceTs: sourceTs ?? new Date().toISOString() }\n : undefined\n\n if (!this.ctx.storeCiphertext) {\n return RecordCodec.buildPlaintextEnvelope({ version, data: json, by, provenance })\n }\n\n const dek = await this.ctx.getDEK()\n\n if (cek !== undefined) {\n const { iv, data } = await encrypt(json, cek)\n const wrapped = await wrapCek(cek, dek)\n return RecordCodec.buildEnvelope({ version, iv, data, by, cek: wrapped, provenance })\n }\n\n const { iv, data } = await encrypt(json, dek)\n return RecordCodec.buildEnvelope({ version, iv, data, by, provenance })\n }\n\n async encryptRecord(\n record: T,\n version: number,\n cek?: EnclaveKey,\n source?: string,\n sourceTs?: string,\n vdig?: { readonly id: string; readonly prev: EncryptedEnvelope | null },\n id?: string,\n ): Promise<EncryptedEnvelope> {\n // Debug-plaintext: write user-collection records with their fields inlined\n // beside the envelope metadata so native store tools read them directly.\n // Internal (`_`-prefixed) collections keep the classic shape — some store\n // `_`-prefixed fields that the inline layout would collide with.\n if (!this.ctx.storeCiphertext && this.ctx.debugPlaintext && !this.ctx.name.startsWith('_')) {\n return this.buildDebugEnvelope(record, version, source, sourceTs)\n }\n\n // ── C-A / R10 config-drift guard (superset) ────────────────────────\n // A handle may write a classified collection only if its DECLARED\n // digest-only set is a SUPERSET of the collection's stored/marked\n // classified set. Any classified field this handle does NOT declare is a\n // field it cannot maintain: the `_vdig` loop below iterates only declared\n // fields, so an undeclared classified field is neither stripped nor tagged —\n // its value would be serialized into `_data` as DEK-recoverable plaintext\n // (the C-A leak), or its stored tag silently dropped. Fail loud.\n //\n // This subsumes the old naive-only guard (a fully-naive handle has an empty\n // declared set, so ANY stored classified field violates the superset) AND\n // closes the partial-handle door (declares Y, not X, over a record carrying\n // `_vdig[X]`/`_bidx[X]`): the narrow `vdigFields === null || size === 0`\n // gate skipped that entire case. The stored/marked set is the union of:\n // 1. the persisted `x-classified` marker's declared digest-only set\n // (cross-session, prev-free — the only signal a fully-naive create has);\n // 2. the keys the target `prev` already carries in `_vdig`/`_bidx` (the\n // overwrite arm; a classified-declaring handle DOES pass `prev`).\n // Only encrypted collections can be classified, so plaintext collections skip.\n if (this.ctx.storeCiphertext) {\n const declared = this.ctx.vdigFields\n const storedClassified = new Set<string>()\n if (vdig?.prev?._vdig) for (const k of Object.keys(vdig.prev._vdig)) storedClassified.add(k)\n if (vdig?.prev?._bidx) for (const k of Object.keys(vdig.prev._bidx)) storedClassified.add(k)\n if (this.ctx.classifiedMarkerDigestOnly !== undefined) {\n for (const k of await this.ctx.classifiedMarkerDigestOnly()) storedClassified.add(k)\n }\n let undeclared: string | undefined\n for (const field of storedClassified) {\n if (declared === null || !declared.has(field)) { undeclared = field; break }\n }\n if (undeclared !== undefined) {\n throw new ClassifiedConfigError(\n this.ctx.name,\n `this collection has classified digest-only field \"${undeclared}\" but this handle does not declare it — ` +\n 'refusing to write (would drop its tag or serialize the secret as plaintext)',\n )\n }\n }\n\n // Structural group-encryption: peel declared sensitive fields out\n // of the record BEFORE building `_data`, sealing each into its own\n // `_sealed[field]` slot under a per-field key. Default-off — with no\n // sensitive fields the open record is unchanged and no `_sealed` is\n // emitted, so the envelope stays byte-identical to legacy output.\n // A collection whose via pipeline declares at-rest hooks (#629 Task 3)\n // seals through THOSE instead — the inline `sensitiveFields` path and\n // the hook path are mutually exclusive per collection, gated on\n // `hasAtRestHooks` (the inline path now serves only the plain\n // `sensitive: [...]` collection option; since #629, `classifiedFields`-\n // declared fields seal through their own via-binding hook, and since\n // #638 so does any field the taint graph seals — `via/classified/\n // binding.ts` and `kernel/via/taint-binding.ts`, respectively). The\n // zero-via fast path (no `via`, or a pipeline with no at-rest hooks,\n // e.g. money-only) always takes the `else if` branch, unchanged.\n let openRecord = record\n let sealed: Record<string, string> | undefined\n if (this.ctx.storeCiphertext && this.ctx.via?.hasAtRestHooks) {\n if (id === undefined) {\n throw new Error(\n `RecordCodec.encryptRecord: collection \"${this.ctx.name}\" has via at-rest hooks but this write ` +\n 'path supplied no record id (needed to scope the sealed-slot capability) — caller bug.',\n )\n }\n const src = record as unknown as Record<string, unknown>\n const result = await this.ctx.via.encodeAtRest(src, this.viaCryptoCtx(id, cek))\n openRecord = result.record as unknown as T\n if (result.sealed !== undefined) {\n sealed = {}\n for (const [field, ref] of Object.entries(result.sealed)) sealed[field] = `${ref.iv}:${ref.data}`\n }\n } else if (this.ctx.storeCiphertext && this.ctx.sensitiveFields.size > 0) {\n const src = record as unknown as Record<string, unknown>\n const result = await sealFields(src, this.ctx.sensitiveFields, this.sealKeyMaterial(cek))\n if (result.sealed !== undefined) {\n sealed = result.sealed\n openRecord = result.openRecord as unknown as T\n }\n }\n\n // ── Digest-only classified fields (stage 2, C6) ────────────────────\n // Per field, exactly one of: carry-forward (absent) / rotate (string) /\n // clear (null) / loud error (anything else). Runs on a CLONE so the\n // caller's record object is never mutated.\n let vdigOut: Record<string, string> | undefined\n let bidxOutMap: Record<string, string> | undefined\n if (this.ctx.vdigFields !== null && this.ctx.vdigFields.size > 0 && this.ctx.storeCiphertext) {\n if (vdig === undefined) {\n throw new Error(\n `RecordCodec.encryptRecord: collection \"${this.ctx.name}\" declares digest-only classified ` +\n `fields but this write path supplied no { id, prev } context — it would silently destroy _vdig (C6). Caller bug.`,\n )\n }\n if (cek === undefined) {\n throw new Error(\n `RecordCodec.encryptRecord: digest-only fields require a per-record CEK (R1 invariant) — ` +\n `collection \"${this.ctx.name}\" wrote without one. Caller bug.`,\n )\n }\n const open: Record<string, unknown> = { ...(openRecord as unknown as Record<string, unknown>) }\n const out: Record<string, string> = {}\n // _bidx (blind-index equality tags) rides the SAME iteration as _vdig,\n // so the structural invariant _bidx[field] ⇒ _vdig[field] holds by\n // construction: a tag is only ever written where the _vdig slot is\n // written/carried. DEK is fetched lazily — only an equatable rotate needs it.\n const bidxOut: Record<string, string> = {}\n let bidxDek: EnclaveKey | undefined\n for (const [field, policy] of this.ctx.vdigFields) {\n const value = open[field]\n const prevBlob = vdig.prev?._vdig?.[field]\n if (vdig.prev?._sealed?.[field] !== undefined) {\n // R6 transition evidence: never silently delete recoverable\n // plaintext. Checked BEFORE the value-type dispatch so the\n // carry-forward (absent) and clear (null) branches refuse too —\n // both would otherwise drop the `_sealed` slot from the new\n // envelope, silently destroying the recoverable ciphertext.\n throw new ClassifiedConfigError(\n this.ctx.name,\n `field \"${field}\" carries a recoverable _sealed slot from a previous storage form — ` +\n `recoverable ↔ digest-only transitions are refused (R6); migrate explicitly`,\n )\n }\n if (value === undefined) {\n // 1. carry-forward: verbatim bytes (CEK version-stable, AAD _v-free;\n // byte-identity keeps the ledger payload hash deterministic).\n // I-3 monotonic carry: whenever the _vdig slot is carried, ALSO copy\n // prev._bidx[field] verbatim — regardless of THIS handle's equatable\n // knob. An unrelated put by a handle with equatable removed must not\n // drop another handle's tag (anti-flip-flop). If prev has no tag,\n // there is nothing to mint from (no plaintext) → stays uncovered.\n if (prevBlob !== undefined) {\n out[field] = prevBlob\n const prevTag = vdig.prev?._bidx?.[field]\n if (prevTag !== undefined) bidxOut[field] = prevTag\n }\n continue\n }\n if (value === null) {\n // 3. clear: the defined deletion short of forget().\n delete open[field]\n continue\n }\n if (typeof value !== 'string') {\n // 4. caller bug, fail-loud.\n throw new ValidationError(\n `digest-only classified field \"${field}\" in \"${this.ctx.name}\" must be a string (rotate) or null (clear), got ${typeof value}`,\n )\n }\n // 2. rotate: validate ran in the stage-1 write seam; digest + ring here.\n out[field] = await mintVdigSlot(value, policy, prevBlob, cek, this.ctx.name, vdig.id, field)\n // _bidx branch 2: if this handle is equatable, mint a fresh tag through\n // the SAME normalization pipeline mintVdigSlot uses (normalizeForVerify)\n // — the tag provably equals computeBidxTarget for this value. If the\n // knob is OFF, emit nothing: the stale prev tag points at the now-\n // superseded value (carrying it would be wrong) and minting is disallowed\n // (knob off) → the tag is dropped. Confirm-by-verify keeps this sound.\n if (policy.equatable === true) {\n if (bidxDek === undefined) bidxDek = await this.ctx.getDEK()\n const normalized = normalizeForVerify(policy.normalize, value)\n bidxOut[field] = await mintBidxTag(normalized, bidxDek, this.ctx.name, field)\n }\n delete open[field] // strip from _data — digest-only never persists plaintext\n }\n openRecord = open as unknown as T\n if (Object.keys(out).length > 0) vdigOut = out\n if (Object.keys(bidxOut).length > 0) bidxOutMap = bidxOut\n }\n\n const base = await this.encryptJsonString(JSON.stringify(openRecord), version, cek, source, sourceTs)\n const withSealed = sealed ? { ...base, _sealed: sealed } : base\n const withVdig = vdigOut ? { ...withSealed, _vdig: vdigOut } : withSealed\n const withBidx = bidxOutMap ? { ...withVdig, _bidx: bidxOutMap } : withVdig\n if (!this.ctx.deterministicFields || !this.ctx.storeCiphertext) return withBidx\n\n // compute deterministic-ciphertext slots for every\n // declared field. Non-primitive values are JSON-stringified so\n // objects/arrays still dedupe on structural equality. Sealed fields are\n // excluded — they live only in `_sealed`, never the `_det` index.\n // L-1: `_det` encrypts under a dedicated HKDF-derived key (salt\n // `noydb-det`), never the raw DEK — the DEK's randomized-IV `_data`\n // regime and `_det`'s deterministic-IV regime must not share a key.\n const dek = await this.ctx.getDEK()\n const detKey = await deriveDeterministicKey(dek)\n const rec = record as unknown as Record<string, unknown>\n const det: Record<string, string> = {}\n for (const field of this.ctx.deterministicFields) {\n if (this.ctx.sensitiveFields.has(field)) continue\n if (this.ctx.vdigFields?.has(field)) continue // I5: digest-only never equality-correlatable\n const value = rec[field]\n if (value === undefined || value === null) continue\n const plaintext = typeof value === 'string' ? value : JSON.stringify(value)\n const { iv, data } = await encryptDeterministic(plaintext, detKey, `${this.ctx.name}/${field}`)\n det[field] = `${iv}:${data}`\n }\n if (Object.keys(det).length === 0) return withBidx\n return { ...withBidx, _det: det }\n }\n\n // ──────────────────────────────────────────────────────────────────────\n // Read path\n // ──────────────────────────────────────────────────────────────────────\n\n /**\n * Resolve the per-record CEK for a stored envelope, or `undefined` for a\n * legacy (`_cek`-absent) envelope. Unwraps `_cek` under the collection DEK and\n * memoises it in the CEK cache under `id` (when supplied) so repeated reads of\n * the same record skip the unwrap. Shared by the body-decrypt path\n * ({@link decryptJsonString}) and the sealed-field path ({@link decryptRecord}\n * / {@link toCacheRecord}) so both agree on the record's key.\n */\n async resolveEnvelopeCek(envelope: EncryptedEnvelope, id?: string): Promise<EnclaveKey | undefined> {\n if (envelope._cek === undefined) return undefined\n const cached = id !== undefined ? this.ctx.cekCache?.get(id) : undefined\n if (cached !== undefined) return cached\n const dek = await this.ctx.getDEK()\n const cek = await unwrapCek(envelope._cek, dek)\n if (id !== undefined) this.ctx.cekCache?.set(id, cek, 1)\n return cek\n }\n\n /**\n * Low-level: decrypt an envelope and return the raw JSON string.\n *\n * `_cek` presence is the format discriminant (NOT `this.perRecordCek`),\n * so a mixed vault — and a recipient that never opted into\n * `perRecordKeys` — decrypts both legacy and CEK records:\n * - `_cek` present → unwrap the CEK under the collection DEK, decrypt the\n * body under the CEK (cache the unwrapped CEK so repeated reads skip it).\n * - `_cek` absent → legacy path, body decrypts directly under the\n * collection DEK.\n *\n * The optional `id` lets reads populate the CEK cache; it is omitted by\n * callers (history, conflict merge) that have only the envelope.\n */\n async decryptJsonString(envelope: EncryptedEnvelope, id?: string): Promise<string | null> {\n // RISK #1 (forget cascade): a shred tombstone carries `_data: ''` and no\n // `_cek`. Decrypting it would call `decrypt('', '', dek)` → AES-GCM\n // OperationError → TamperedError. Return null so every read callsite\n // treats it as \"absent / skip\", matching how get()/list already drop\n // tombstones. Legacy plaintext collections (`!this.storeCiphertext`) legitimately\n // have empty `_iv`/`_data`, so `isTombstone` is false for them — preserved.\n if (isTombstone(envelope, this.ctx.storeCiphertext) || isDeleteMarker(envelope)) return null\n if (!this.ctx.storeCiphertext) {\n // Debug-plaintext layout: record fields were inlined as top-level keys\n // (see buildDebugEnvelope). Reconstruct the record from the non-`_`\n // keys. Self-describing via `_debug`, so a classic plaintext reader\n // handles debug-written envelopes too.\n if (envelope._debug !== undefined) {\n const rec: Record<string, unknown> = {}\n for (const [key, value] of Object.entries(envelope)) {\n if (!key.startsWith('_')) rec[key] = value\n }\n return JSON.stringify(rec)\n }\n // #598: a real record never serializes to '' (JSON.stringify's floor is\n // '{}'), so an empty `_data` here is the sync engine's shape-only\n // tombstone (`isTombstoneShape` in team/sync.ts) landing on an\n // unencrypted collection, where the encryption-gated `isTombstone()`\n // check above doesn't fire. Treat it the same as an encrypted\n // tombstone: null, not a `JSON.parse('')` crash.\n return envelope._data === '' ? null : envelope._data\n }\n const cek = await this.resolveEnvelopeCek(envelope, id)\n if (cek !== undefined) return decrypt(envelope._iv, envelope._data, cek)\n const dek = await this.ctx.getDEK()\n return decrypt(envelope._iv, envelope._data, dek)\n }\n\n /**\n * Unseal a single `_sealed[field]` slot to its plaintext value: derive the\n * per-field key off the collection DEK, AES-GCM-decrypt the `iv:data` blob,\n * and JSON-parse the result. Shared by both the inline-decrypt path and a\n * {@link Sealed} handle's `reveal()` — so the on-demand reveal and the eager\n * materialisation always agree byte-for-byte.\n */\n async unsealField(field: string, blob: string, cek?: EnclaveKey): Promise<unknown> {\n // Dual-read. Current writes seal under a key derived from the record's\n // per-record CEK; legacy records (even ones whose body is\n // CEK-encrypted) are sealed under the collection-DEK key. Try the CEK key\n // first; on its AES-GCM auth failure, fall back to the DEK key. Without this\n // fallback every legacy `_sealed` record would throw TamperedError (data loss).\n return unsealOneField(field, blob, this.sealKeyMaterial(cek))\n }\n\n /**\n * Build the `ViaCryptoCtx` for forget()'s per-ref via-erase fold (#629\n * Task 10) — resolves the live envelope's CEK exactly like\n * `decryptRecord` does, then delegates to the same capability factory\n * `encodeAtRest`/`decodeAtRest` use.\n */\n async eraseCryptoCtx(id: string, live: EncryptedEnvelope): Promise<ViaCryptoCtx> {\n return this.viaCryptoCtx(id, await this.resolveEnvelopeCek(live, id))\n }\n\n /**\n * Classify a live envelope's `_sealed` slots for crypto-shred completeness.\n * `forget()` drops `_cek`/`_sealed` but\n * RETAINS the collection DEK, so only a slot keyed off the per-record CEK is\n * genuinely shredded by the tombstone; a legacy slot keyed off the\n * collection DEK survives in any synced/backup copy.\n *\n * Mirrors {@link unsealField}'s dual-read split: try the CEK-derived key per\n * slot — success → `shreddable`, AES-GCM auth failure → `dekResidue`. With no\n * `_cek` (pure legacy collection-DEK sealing) ALL slots are residue.\n */\n async classifySealedShred(\n live: EncryptedEnvelope,\n ): Promise<{ readonly slots: readonly SealedShredSlot[] }> {\n const slots: SealedShredSlot[] = []\n // Verify-digest slots are CEK-only by construction (I3): dropping `_cek`\n // makes every `_vdig[field]` permanently undecryptable — shreddable\n // unconditionally, no vdig-dekResidue class (spec §2 forget()). Same\n // honesty caveats as #306 D5 for synced/backup copies of the ciphertext.\n // A `_vdig` field ALSO carrying a `_bidx` tag is the third category: the\n // tombstone drops it from the live store, but a pre-forget backup retains\n // the blind-index tag under the surviving collection DEK — live-shreddable\n // yet dekResidue-in-backups. `_bidx[field] ⇒ _vdig[field]` (I4), so we\n // decide per vdig field and emit exactly one slot each (count-preserving).\n if (live._vdig !== undefined && live._cek !== undefined) {\n for (const field of Object.keys(live._vdig)) {\n slots.push({\n field,\n class: live._bidx?.[field] !== undefined\n ? 'live-shreddable+dekResidue-in-backups'\n : 'shreddable',\n })\n }\n }\n const sealed = live._sealed\n if (sealed === undefined) return { slots }\n const cek = await this.resolveEnvelopeCek(live)\n for (const [field, blob] of Object.entries(sealed)) {\n if (cek === undefined) { slots.push({ field, class: 'dekResidue' }); continue }\n const { iv, data } = parseSealedSlot(blob)\n try {\n await decrypt(iv, data, await deriveSealedFieldKeyFromCek(cek, this.ctx.name, field))\n slots.push({ field, class: 'shreddable' })\n } catch {\n slots.push({ field, class: 'dekResidue' })\n }\n }\n return { slots }\n }\n\n /**\n * Build a non-leaking {@link Sealed} handle over a sealed field's ciphertext.\n * The handle captures only the ciphertext `blob` and a closure to\n * {@link unsealField}; the plaintext is never stored on it — so the handle\n * may sit in the working-set cache (or be logged/serialised) without\n * exposing the value, which decrypts only on `reveal()`.\n */\n makeSealedHandle(field: string, blob: string, cek?: EnclaveKey): SealedHandle<unknown> {\n return makeHandleProducer(this.sealKeyMaterial(cek))(field, blob)\n }\n\n /**\n * Replace every field the just-written envelope actually sealed with a\n * {@link Sealed} handle, leaving every other field as its plaintext value.\n * Used to populate the cache on the write path without ever materialising\n * sealed plaintext into it. Returns `record` untouched when nothing was\n * sealed. Keys off `envelope._sealed` itself (#642 fix) — NOT\n * `this.ctx.sensitiveFields`, which only names the inline `sensitive:[...]`/\n * classified-recoverable field set: a collection whose sealing comes\n * entirely from the `taint` via-binding (a derivation/MV output or rollup\n * target folded sealed from a classified SOURCE collection, #642) declares\n * no local `sensitiveFields` at all, so that stale gate skipped this\n * conversion and left a just-written taint-sealed field as cached\n * plaintext — read back correctly ONLY after a cold cache miss (`get()`'s\n * own `decodeAtRest` call, unaffected by this gate) forced a fresh decrypt.\n */\n async toCacheRecord(record: T, envelope: EncryptedEnvelope, id?: string): Promise<T> {\n const sealed = envelope._sealed\n if (sealed === undefined || !this.ctx.storeCiphertext) {\n return record\n }\n const cek = await this.resolveEnvelopeCek(envelope, id)\n const clone = { ...(record as unknown as Record<string, unknown>) }\n for (const [field, blob] of Object.entries(sealed)) {\n clone[field] = this.makeSealedHandle(field, blob, cek)\n }\n return clone as unknown as T\n }\n\n /**\n * Apply `_sealed`-slot post-processing to an ALREADY-DECRYPTED `record`:\n * restore each `_sealed[field]` blob inline (`sealedAsHandles` falsy) or\n * as an opaque {@link Sealed} handle (`sealedAsHandles: true`), routing\n * through a via pipeline's `decodeAtRest` hook when one is declared.\n * Extracted byte-parity from `decryptRecord`'s inline block (#635) so a\n * caller that resolves its OWN per-record CEK under a DIFFERENT DEK than\n * this codec's `this.ctx.getDEK()` — namely the tier>0 `getAtTier` leg in\n * `with-audit/tiers/index.ts`, whose `_cek` is wrapped under the TIER DEK,\n * not the collection DEK `decryptRecord`/`resolveEnvelopeCek` always\n * unwrap under — can still get correct sealed-slot handling without\n * routing its whole read through `decryptRecord` (which would resolve the\n * wrong DEK for it).\n *\n * Zero-knowledge: takes only the already-resolved per-record `cek` (or\n * `undefined` for a legacy/no-CEK record) — the same shape `makeSealedHandle`\n * already takes above. Never the keyring, never a raw DEK: the legacy\n * fallback key stays fully internal to `sealKeyMaterial`/`viaCryptoCtx`,\n * which close over THIS codec's own `getDEK` (permanently bound to\n * `this.ctx.name`'s tier-0 DEK at construction) — so even when called from\n * a tier>0 context, the legacy-fallback key resolution is correct: sealed\n * fields are always sealed under the CEK or the collection's tier-0 DEK,\n * never a tier DEK (tier writes don't seal fields; `_sealed` reaches a\n * tiered envelope only via a tier-0 write later elevated).\n */\n async applySealedSlots(\n record: T,\n sealed: Record<string, string>,\n cek: EnclaveKey | undefined,\n opts: { id?: string; sealedAsHandles?: boolean } = {},\n ): Promise<T> {\n if (this.ctx.via?.hasAtRestHooks) {\n if (opts.id === undefined) {\n throw new Error(\n `RecordCodec.decryptRecord: collection \"${this.ctx.name}\" has via at-rest hooks but this read ` +\n 'path supplied no record id (needed to scope the sealed-slot capability) — caller bug.',\n )\n }\n const sealedRefs: Record<string, SealedSlotRef> = {}\n for (const [field, blob] of Object.entries(sealed)) {\n const { iv, data } = parseSealedSlot(blob)\n sealedRefs[field] = { iv, data }\n }\n return await this.ctx.via.decodeAtRest(\n record as unknown as Record<string, unknown>,\n sealedRefs,\n this.viaCryptoCtx(opts.id, cek),\n { asHandles: opts.sealedAsHandles === true },\n ) as unknown as T\n }\n return await unsealFields(\n record as unknown as Record<string, unknown>,\n sealed,\n this.sealKeyMaterial(cek),\n { asHandles: opts.sealedAsHandles === true },\n ) as unknown as T\n }\n\n /**\n * Decrypt an envelope into a record of type `T`.\n *\n * When a schema is attached, the decrypted value is validated before\n * being returned. A divergence between the stored bytes and the\n * current schema throws `SchemaValidationError` with\n * `direction: 'output'` — silently returning drifted data would\n * propagate garbage into the UI and break the whole point of having\n * a schema.\n *\n * `skipValidation` exists for history reads: when calling\n * `getVersion()` the caller is explicitly asking for an old snapshot\n * that may predate a schema change, so validating it would be a\n * false positive. Every non-history read leaves this flag `false`.\n */\n async decryptRecord(\n envelope: EncryptedEnvelope,\n opts: { skipValidation?: boolean; id?: string; sealedAsHandles?: boolean } = {},\n ): Promise<T | null> {\n const json = await this.decryptJsonString(envelope, opts.id)\n // Tombstone (shredded record) → null, propagated from decryptJsonString.\n // Callers skip null exactly as they already skip a tombstone envelope.\n if (json === null) return null\n let parsed: unknown = JSON.parse(json)\n\n // CRDT resolution: if this collection is in CRDT mode, the\n // stored JSON is a CrdtState, not T directly. Resolve to the snapshot.\n if (this.ctx.crdtMode && parsed !== null && typeof parsed === 'object' && '_crdt' in parsed) {\n parsed = this.ctx.crdtStrategy.resolveCrdtSnapshot(parsed as CrdtState)\n }\n\n let record = parsed as T\n\n // Structural group-encryption + sealed access gate.\n // Each `_sealed[field]` slot is restored under its own per-field key.\n // `sealedAsHandles: false` (default — internal callers that compute on\n // real values) inline-decrypts to the plaintext value; `true` (the\n // public / cache path) yields an opaque {@link Sealed} handle so the\n // plaintext is never materialised into the working-set cache. A\n // collection whose via pipeline declares at-rest hooks (#629 Task 3)\n // decodes through THOSE instead of the inline dual-read below — the\n // zero-via fast path (no `via`, or a pipeline with no at-rest hooks)\n // always takes the `else` branch, unchanged.\n if (envelope._sealed !== undefined && this.ctx.storeCiphertext) {\n const sealedCek = await this.resolveEnvelopeCek(envelope, opts.id)\n record = await this.applySealedSlots(record, envelope._sealed, sealedCek, {\n ...(opts.id !== undefined ? { id: opts.id } : {}),\n ...(opts.sealedAsHandles !== undefined ? { sealedAsHandles: opts.sealedAsHandles } : {}),\n })\n }\n\n // Skip output validation when sealed fields are returned as handles:\n // the record carries opaque `Sealed` handles in place of the declared\n // values, so a whole-record schema check would false-positive on them.\n // (The values were validated on input/write; the open fields are\n // unchanged from the validated body.) The inline-value path below still\n // validates fully.\n const sealedAsHandles = opts.sealedAsHandles === true && envelope._sealed !== undefined\n if (this.ctx.schema !== undefined && !opts.skipValidation && !sealedAsHandles) {\n // Context string deliberately avoids leaking the record id — the\n // envelope only carries the version, not the id (the id lives in\n // the adapter-side key). `<collection>@v<n>` is enough for the\n // developer to find the offending record.\n record = await validateSchemaOutput(\n this.ctx.schema,\n record,\n `${this.ctx.name}@v${envelope._v}`,\n )\n }\n\n return record\n }\n}\n","/**\n * Record-scoped CEK sealing — grantor side.\n *\n * The **grantor** holds the collection DEK and seals ONE record's CEK to an\n * `at-*` host so that host — and only that host — can decrypt exactly that\n * record, with no access to the vault DEK and no ability to read any other\n * record. This is the counterpart to the host-side `openSealedRecord`\n * (`@noy-db/hub/sealed-record`), which holds no DEK.\n *\n * These functions are the orchestration behind `vault.sealRecordToHost` /\n * `vault.revokeSealedRecord` / `vault.rotateRecordCek`, lifted off `Vault`\n * behind a narrow {@link SealingContext} so the kernel file delegates. They\n * live in `record-keys/` (the vault-side CEK policy layer), NOT in\n * `sealed-record/`, so the host-side subpath stays DEK-free — the wire\n * *types* they share are spine-owned (`kernel/types.ts`), not imported from\n * `sealed-record/` directly.\n */\nimport { encrypt, decrypt, generateDEK, wrapCek, unwrapCek, bufferToBase64, deriveSealedFieldKeyFromCek, type EnclaveKey } from '../crypto.js'\nimport {\n NOYDB_FORMAT_VERSION,\n type EncryptedEnvelope,\n type NoydbStore,\n type RecipientSealer,\n type SealedCekBinding,\n type SealedCekDeliveryEnvelope,\n} from '../../types.js'\nimport { dualReadSealedSlot } from './sealed-slot.js'\nimport { openVdigPayload, sealVdigPayload } from '../classify/vdig.js'\nimport { RecordCekNotFoundError, ValidationError } from '../../errors.js'\n\nconst subtle = globalThis.crypto.subtle\n\n/** The `_sealed_cek` delivery namespace (one envelope per `collection/id/pid`). */\nexport const SEALED_CEK_NS = '_sealed_cek'\n\n/** What the grantor functions need from their `Vault`. */\nexport interface SealingContext {\n readonly adapter: NoydbStore\n /** Vault id (the adapter's first coordinate). */\n readonly vault: string\n /** Resolve the DEK a record's CEK is wrapped under. */\n getDEK(collection: string): Promise<EnclaveKey>\n /** Actor id stamped on `_by` (empty string → omit). */\n readonly actor: string\n /** Evict the per-record CEK cache + decrypted-record cache after a rotation. */\n invalidateRecordCaches(collection: string, id: string): Promise<void>\n}\n\n/**\n * Seal a record's CEK to a host. See `vault.sealRecordToHost` for the contract.\n * Returns `{ pid, envelopeKey }`.\n */\nexport async function sealRecordToHost(\n ctx: SealingContext,\n collection: string,\n id: string,\n hostSealer: RecipientSealer,\n opts: { expiresAt: string },\n): Promise<{ pid: string; envelopeKey: string }> {\n // Guard separators: the `_sealed_cek` id is `collection/id/pid`, so a `/` in\n // any component would make the prefix-delete in rotateRecordCek() (which\n // matches `${collection}/${id}/`) ambiguous and could over- or under-match.\n if (collection.includes('/')) throw new ValidationError(`sealRecordToHost: collection \"${collection}\" must not contain \"/\"`)\n if (id.includes('/')) throw new ValidationError(`sealRecordToHost: id \"${id}\" must not contain \"/\"`)\n // M-4: reject a malformed/empty expiry at seal time. `Date.parse('')` (and any\n // non-ISO string) is NaN, and the open-time check `NaN <= now` is `false` — so\n // a malformed expiry would silently mint an ETERNAL grant. A *past* (but valid)\n // timestamp is still allowed: it seals, then fails closed at open time.\n if (Number.isNaN(Date.parse(opts.expiresAt))) {\n throw new ValidationError(`sealRecordToHost: expiresAt \"${opts.expiresAt}\" is not a valid ISO 8601 timestamp`)\n }\n\n const live = await ctx.adapter.get(ctx.vault, collection, id)\n if (!live || live._cek === undefined) {\n throw new RecordCekNotFoundError(collection, id)\n }\n\n const dek = await ctx.getDEK(collection)\n const cek = await unwrapCek(live._cek, dek)\n const rawCek = await subtle.exportKey('raw', cek)\n const cekB64 = bufferToBase64(rawCek)\n\n const hint = await hostSealer.publishRecipientHint()\n if (hint.pid.includes('/')) throw new ValidationError(`sealRecordToHost: recipient pid \"${hint.pid}\" must not contain \"/\"`)\n\n const binding: SealedCekBinding = {\n collection,\n id,\n cek: cekB64,\n expiresAt: opts.expiresAt,\n }\n const sealed = await hostSealer.sealForRecipient(\n new TextEncoder().encode(JSON.stringify(binding)),\n hint,\n )\n\n const delivery: SealedCekDeliveryEnvelope = {\n v: 1,\n _noydb_sealed_cek: 1,\n pid: hint.pid,\n payload: bufferToBase64(sealed),\n expiresAt: opts.expiresAt,\n }\n\n const envelopeKey = `${collection}/${id}/${hint.pid}`\n const prior = await ctx.adapter.get(ctx.vault, SEALED_CEK_NS, envelopeKey)\n const env: EncryptedEnvelope = {\n _noydb: NOYDB_FORMAT_VERSION,\n _v: (prior?._v ?? 0) + 1,\n _ts: new Date().toISOString(),\n // AES-GCM bypassed — the sealing layer is the security boundary, exactly\n // like the managed-passphrase `_meta/sealed-passphrase` envelope.\n _iv: '',\n _data: JSON.stringify(delivery),\n ...(ctx.actor ? { _by: ctx.actor } : {}),\n }\n await ctx.adapter.put(ctx.vault, SEALED_CEK_NS, envelopeKey, env)\n\n return { pid: hint.pid, envelopeKey }\n}\n\n/**\n * Revoke one sealed-CEK grant.\n *\n * **Default (`{ hard: false }`) is SOFT** — it only deletes the delivery\n * envelope from the store. A host that already fetched (or cached the unsealed\n * CEK from) that envelope KEEPS decrypt capability for the record; soft revoke\n * is a \"stop new fetches\" control, not a cryptographic cutoff.\n *\n * **`{ hard: true }`** additionally rotates the record's CEK (via\n * {@link rotateRecordCek}): the live body is re-encrypted under a fresh CEK and\n * EVERY sealed-CEK delivery envelope for the record (all pids) is deleted, so\n * any previously-sealed CEK fails the AES-GCM auth tag on the rotated body —\n * future opens are cryptographically denied. (PRE-rotation history versions,\n * which keep their old `_cek`, remain openable — same semantics as\n * `rotateRecordCek`.)\n */\nexport async function revokeSealedRecord(\n ctx: SealingContext,\n collection: string,\n id: string,\n pid: string,\n opts?: { hard?: boolean },\n): Promise<void> {\n if (opts?.hard === true) {\n // Hard revocation supersedes the single-pid soft delete: rotateRecordCek\n // re-keys the record and drops every delivery envelope for it.\n await rotateRecordCek(ctx, collection, id)\n return\n }\n await ctx.adapter.delete(ctx.vault, SEALED_CEK_NS, `${collection}/${id}/${pid}`)\n}\n\n/**\n * HARD-rotate a record's CEK: re-encrypt the live body under a fresh CEK, evict\n * caches, and delete EVERY sealed-CEK delivery envelope for the record. See\n * `vault.rotateRecordCek` for why this bypasses `Collection.put` (no guards, no\n * history bump — a key rotation must not re-encrypt prior history under the new\n * CEK).\n */\nexport async function rotateRecordCek(\n ctx: SealingContext,\n collection: string,\n id: string,\n): Promise<void> {\n const live = await ctx.adapter.get(ctx.vault, collection, id)\n if (!live || live._cek === undefined) {\n throw new RecordCekNotFoundError(collection, id)\n }\n\n const dek = await ctx.getDEK(collection)\n const oldCek = await unwrapCek(live._cek, dek)\n const json = await decrypt(live._iv, live._data, oldCek)\n\n const newCek = await generateDEK()\n const { iv, data } = await encrypt(json, newCek)\n\n // Sealed fields are keyed off the per-record CEK, so a rotation\n // must RE-ENCRYPT each `_sealed[field]` under the new CEK (carrying the old\n // slots forward would orphan them — the old CEK is gone after this rotate).\n // Dual-read the old slot: try the old-CEK-derived key, fall\n // back to the collection-DEK key (legacy), then re-seal under newCek.\n let sealedOut: Record<string, string> | undefined\n if (live._sealed !== undefined) {\n const out: Record<string, string> = {}\n for (const [field, slot] of Object.entries(live._sealed)) {\n const plaintext = await dualReadSealedSlot(slot, field, collection, oldCek, dek)\n const resealed = await encrypt(plaintext, await deriveSealedFieldKeyFromCek(newCek, collection, field))\n out[field] = `${resealed.iv}:${resealed.data}`\n }\n sealedOut = out\n }\n\n // Verify-digest slots are keyed off the per-record CEK too (I3: CEK-only,\n // no DEK fallback → single read, not a dual-read). Rotation must re-seal\n // each `_vdig[field]` under the new CEK with the SAME reconstructed AAD, or\n // the correct password would false-reject forever (C3).\n let vdigOut: Record<string, string> | undefined\n if (live._vdig !== undefined) {\n const out: Record<string, string> = {}\n for (const [field, blob] of Object.entries(live._vdig)) {\n const payload = await openVdigPayload(blob, oldCek, collection, id, field)\n out[field] = await sealVdigPayload(payload, newCek, collection, id, field)\n }\n vdigOut = out\n }\n\n const env: EncryptedEnvelope = {\n _noydb: NOYDB_FORMAT_VERSION,\n _v: live._v + 1,\n _ts: new Date().toISOString(),\n _iv: iv,\n _data: data,\n _cek: await wrapCek(newCek, dek),\n ...(ctx.actor ? { _by: ctx.actor } : {}),\n ...(live._tier !== undefined ? { _tier: live._tier } : {}),\n ...(live._det !== undefined ? { _det: live._det } : {}),\n // `_bidx` (equality-index tag) is DEK-rooted and CEK-independent, so it is\n // carried forward VERBATIM — not recomputed — unlike `_vdig`/`_sealed`\n // below, which are CEK-bound and must be re-sealed under the new CEK.\n // Dropping this line would silently orphan the equality index on rotation\n // (the #306 Slice-A data-loss bug, replayed on the index).\n ...(live._bidx !== undefined ? { _bidx: live._bidx } : {}),\n // Re-encrypt sealed (`sensitive`) fields under the new CEK. Sealed\n // keys derive off the per-record CEK, so the rotated record's old CEK is\n // destroyed here — carrying the old `_sealed` slots forward verbatim would\n // orphan them (unreadable under the new CEK). `sealedOut` holds the slots\n // dual-read from the old CEK (or the legacy DEK) and re-sealed under newCek.\n ...(sealedOut !== undefined ? { _sealed: sealedOut } : {}),\n ...(vdigOut !== undefined ? { _vdig: vdigOut } : {}),\n }\n await ctx.adapter.put(ctx.vault, collection, id, env)\n\n // Evict BOTH caches synchronously so no read returns the stale old-CEK record.\n await ctx.invalidateRecordCaches(collection, id)\n\n // Delete every sealed-CEK delivery envelope for this record — all pids.\n const prefix = `${collection}/${id}/`\n const keys = await ctx.adapter.list(ctx.vault, SEALED_CEK_NS)\n for (const key of keys) {\n if (key.startsWith(prefix)) {\n await ctx.adapter.delete(ctx.vault, SEALED_CEK_NS, key)\n }\n }\n}\n","/**\n * Deterministic-index lookups (`findByDet` / `queryByDet`).\n *\n * A collection that declares `deterministicFields` stamps a deterministic\n * AES-GCM ciphertext for each such field on the envelope's `_det` slot at write\n * time (the encrypt side lives in {@link RecordCodec.encryptRecord}). These\n * helpers are the READ side: recompute the deterministic ciphertext for a query\n * value and scan the adapter for envelopes whose `_det[field]` matches — no\n * record bodies are decrypted during the scan, which is the whole point of a\n * deterministic index.\n *\n * Both functions take a small {@link DeterministicContext} (the exact `this.*`\n * the moving methods touched) instead of `this`, mirroring the `record-keys/`\n * siblings. Behaviour is byte-identical to the inline code they replaced.\n *\n * Internal service — not exported as a `@noy-db/hub/*` subpath.\n */\nimport { encryptDeterministic, deriveDeterministicKey, type EnclaveKey } from '../crypto.js'\nimport type { NoydbStore } from '../../types.js'\nimport type { RecordCodec } from './record-codec.js'\n\n/** Everything the moving deterministic-index methods touched on `this.*`. */\nexport interface DeterministicContext<T> {\n /** Collection name — the deterministic-encryption AAD scope. */\n readonly name: string\n /** Vault namespace the records live under. */\n readonly vault: string\n /** The ciphertext store (scanned envelope-by-envelope). */\n readonly adapter: NoydbStore\n /** Declared deterministic-index fields, or null when the feature is off. */\n readonly deterministicFields: ReadonlySet<string> | null\n /** False on plaintext collections — det lookups are encrypted-only. */\n readonly storeCiphertext: boolean\n /** The collection DEK resolver. */\n getDEK(): Promise<EnclaveKey>\n /** The record codec — decrypts a matched envelope to T. */\n readonly codec: RecordCodec<T>\n}\n\n/**\n * Recompute the deterministic ciphertext targets for a query value.\n *\n * L-1: _det migrated to a dedicated HKDF key (salt noydb-det); dual-query\n * keeps pre-migration envelopes findable — they self-heal to the new key on\n * their next write. Index 0 is the current (derived-key) target, index 1 the\n * legacy raw-DEK target. Both are derived once per query call, not per\n * envelope, so the extra derivation is a fixed cost.\n */\nasync function detTargets<T>(ctx: DeterministicContext<T>, field: string, value: unknown): Promise<[string, string]> {\n const dek = await ctx.getDEK()\n const detKey = await deriveDeterministicKey(dek)\n const plaintext = typeof value === 'string' ? value : JSON.stringify(value)\n const context = `${ctx.name}/${field}`\n const [current, legacy] = await Promise.all([\n encryptDeterministic(plaintext, detKey, context),\n encryptDeterministic(plaintext, dek, context),\n ])\n return [`${current.iv}:${current.data}`, `${legacy.iv}:${legacy.data}`]\n}\n\nfunction assertDetField<T>(ctx: DeterministicContext<T>, field: string, method: string): void {\n if (!ctx.deterministicFields || !ctx.deterministicFields.has(field)) {\n throw new Error(\n `Collection \"${ctx.name}\": field \"${field}\" is not declared in deterministicFields`,\n )\n }\n if (!ctx.storeCiphertext) {\n throw new Error(\n `Collection \"${ctx.name}\": ${method} is only meaningful on encrypted collections`,\n )\n }\n}\n\n/**\n * Find the first record whose deterministic field matches the given plaintext.\n * Returns `null` when no match exists.\n *\n * Reads every envelope via the adapter and compares the stored `_det[field]` to\n * a freshly computed deterministic ciphertext — no record bodies are decrypted\n * during the search.\n *\n * Throws when the field is not declared in `deterministicFields`, so a typo\n * fails loudly at the call site rather than silently returning null forever.\n */\nexport async function findByDet<T>(ctx: DeterministicContext<T>, field: string, value: unknown): Promise<T | null> {\n assertDetField(ctx, field, 'findByDet')\n const [target, legacyTarget] = await detTargets(ctx, field, value)\n\n const ids = await ctx.adapter.list(ctx.vault, ctx.name)\n for (const id of ids) {\n const env = await ctx.adapter.get(ctx.vault, ctx.name, id)\n if (!env || !env._det) continue\n if (env._det[field] === target || env._det[field] === legacyTarget) {\n return ctx.codec.decryptRecord(env, { id })\n }\n }\n return null\n}\n\n/**\n * Return every record whose deterministic field matches. Same semantics as\n * {@link findByDet} but without the short-circuit.\n */\nexport async function queryByDet<T>(ctx: DeterministicContext<T>, field: string, value: unknown): Promise<T[]> {\n assertDetField(ctx, field, 'queryByDet')\n const [target, legacyTarget] = await detTargets(ctx, field, value)\n\n const ids = await ctx.adapter.list(ctx.vault, ctx.name)\n const matches: T[] = []\n for (const id of ids) {\n const env = await ctx.adapter.get(ctx.vault, ctx.name, id)\n if (!env || !env._det) continue\n if (env._det[field] === target || env._det[field] === legacyTarget) {\n const rec = await ctx.codec.decryptRecord(env, { id })\n if (rec !== null) matches.push(rec) // skip tombstone (defensive)\n }\n }\n return matches\n}\n","/**\n * Enclave body helpers — the C1 protected-body access contract.\n *\n * `EncryptedEnvelope` splits into a protocol header (family-owned; `_noydb`,\n * `_v`, `_ts`, `_by`, `_source`, `_sourceTs`, `_tier`, `_elevatedBy`) and a\n * protected body (enclave-owned; `_iv`, `_data`, `_cek`, `_det`, `_sealed`,\n * `_debug`). The four helpers below are the ONLY sanctioned way for code\n * outside `kernel/enclave/**` to read or construct the protected body —\n * later migration batches move the ~121 direct `_iv`/`_data`/`_cek`/`_sealed`\n * access sites in `with-*` services onto these.\n *\n * Each helper reproduces an EXISTING behavior byte-for-byte (see the\n * per-function doc for its oracle call site) — this file introduces no new\n * crypto or semantics, only a narrower door onto what already runs.\n */\nimport { encrypt, decrypt, generateDEK, wrapCek, unwrapCek, type EnclaveKey } from '../crypto.js'\nimport type { EncryptedEnvelope } from '../../types.js'\n\n/**\n * Open an envelope's protected body to its JSON text.\n *\n * Mirrors the dominant direct-decrypt call-site shape (e.g.\n * `with-audit/consent/consent.ts`'s `decryptEntry`):\n * - `opts.encrypted === false` (default `true`) → plaintext collection;\n * returns `env._data` as-is, `key` untouched.\n * - `env._cek` present → per-record-key envelope (mirrors\n * `record-codec.ts`'s `resolveEnvelopeCek`): unwrap the CEK under `key`\n * (the collection DEK), decrypt the body under the unwrapped CEK.\n * - `env._cek` absent → legacy path, decrypt the body directly under `key`.\n */\nexport async function openEnvelopeJson(\n env: EncryptedEnvelope,\n key: EnclaveKey,\n opts?: { encrypted?: boolean },\n): Promise<string> {\n if (opts?.encrypted === false) return env._data\n if (env._cek !== undefined) {\n const cek = await unwrapCek(env._cek, key)\n return decrypt(env._iv, env._data, cek)\n }\n return decrypt(env._iv, env._data, key)\n}\n\n/**\n * Produce the protected-body fields (`_iv`/`_data`/`_cek`) for an envelope a\n * caller is assembling.\n *\n * - `opts.encrypted === false` (default `true`) → plaintext collection;\n * emits `{ _iv: '', _data: json }` (today's `buildPlaintextEnvelope`\n * shape), `key` untouched.\n * - `opts.perRecordKey === true` → mints a fresh per-record CEK, encrypts\n * the body under it, and AES-KW-wraps the CEK under `key` (mirrors\n * `encryptJsonString`'s `cek !== undefined` branch, except the CEK is\n * generated here rather than supplied by the caller).\n * - otherwise → legacy path, body encrypted directly under `key`.\n */\nexport async function writeEnvelopeBody(\n json: string,\n key: EnclaveKey,\n opts?: { encrypted?: boolean; perRecordKey?: boolean },\n): Promise<Pick<EncryptedEnvelope, '_iv' | '_data' | '_cek'>> {\n if (opts?.encrypted === false) return { _iv: '', _data: json }\n\n if (opts?.perRecordKey === true) {\n const cek = await generateDEK()\n const { iv, data } = await encrypt(json, cek)\n const wrapped = await wrapCek(cek, key)\n return { _iv: iv, _data: data, _cek: wrapped }\n }\n\n const { iv, data } = await encrypt(json, key)\n return { _iv: iv, _data: data }\n}\n\n/**\n * Discriminant: does this envelope carry a per-record key?\n *\n * Replaces raw `envelope._cek !== undefined` checks scattered across\n * services — `_cek` presence is the format discriminant (see\n * `record-codec.ts`'s `resolveEnvelopeCek` doc).\n */\nexport function hasPerRecordKey(env: EncryptedEnvelope): boolean {\n return env._cek !== undefined\n}\n\n/**\n * Canonical body string for the ledger hash chain — the exact bytes\n * `with-commit/history/ledger/hash.ts`'s `envelopePayloadHash` derives from\n * `_data` + `_sealed` + `_vdig` + `_bidx` before hashing:\n * - no `_sealed`, no `_vdig`, no `_bidx` → `_data` alone (back-compat: every\n * pre-existing ledger entry and non-sealed backup hashes byte-identically).\n * - any map present → canonical JSON of `{ _data, _sealed?, _vdig?, _bidx? }`\n * with sorted keys at every level, each map bound ONLY when present, so\n * the result is independent of the maps' field-insertion / store-\n * serialization order. `_bidx` is always the LAST segment, so a legacy\n * `_vdig`-only / `_bidx`-absent envelope hashes byte-identically to its\n * stage-2 value.\n *\n * Deliberately reimplements the two-key-object canonicalization inline\n * rather than importing `with-commit/history/ledger/entry.ts`'s general\n * `canonicalJson` — `kernel/enclave/**` may import only spine types (C3),\n * never a `with-*` service. For this fixed `{ _data: string; _sealed:\n * Record<string, string> }` shape the two produce byte-identical output\n * (verified against that exact call site's oracle expression in\n * `envelope-body.test.ts`).\n */\nexport function envelopeBodyForHash(env: EncryptedEnvelope): string {\n // Conditional widen (stage 2): bind `_vdig` exactly the way `_sealed` is\n // bound — only when present. No existing envelope carries `_vdig`, so this\n // ships with no flag-day PROVIDED it lands in the same slice as the first\n // `_vdig` writer (it does — Tasks 7/8/11 are one branch). This binding is\n // the temporal-rollback detector completing C1: AAD stops cross-record/\n // cross-field splices; the ledger hash catches same-slot rollbacks.\n //\n // Conditional widen (slice 2b, SM #5): bind `_bidx` the same way, appended\n // LAST (after `_vdig`) so a `_bidx`-absent envelope keeps its stage-2\n // byte-identical hash.\n if (env._sealed === undefined && env._vdig === undefined && env._bidx === undefined) return env._data\n const mapPart = (key: '_sealed' | '_vdig' | '_bidx', map: Record<string, string>): string => {\n const parts = Object.keys(map).sort().map(\n (k) => `${JSON.stringify(k)}:${JSON.stringify(map[k])}`,\n )\n return `${JSON.stringify(key)}:{${parts.join(',')}}`\n }\n const segments = [`\"_data\":${JSON.stringify(env._data)}`]\n if (env._sealed !== undefined) segments.push(mapPart('_sealed', env._sealed))\n if (env._vdig !== undefined) segments.push(mapPart('_vdig', env._vdig))\n if (env._bidx !== undefined) segments.push(mapPart('_bidx', env._bidx))\n return `{${segments.join(',')}}`\n}\n","/**\n * Credential-broker proof crypto (#479, credential-broker spec §3).\n *\n * The `_broker` seed (32 random bytes, decrypted from `_broker/<brokerId>` by\n * the party layer) is never transmitted. Instead, a **proof key** is\n * HKDF-derived from it, domain-separated per `(vaultId, brokerId)`, and the\n * derived bits are registered with the broker host once at enrol. Steady\n * state, the client re-derives the same key from the seed and signs a\n * **canonical** string binding the request's identity/context, so the broker\n * can verify a fresh HMAC on every mint without ever holding the seed itself.\n *\n * ## Derivation (spec §3, follow verbatim)\n *\n * ```\n * hkdf = importKey('raw', seed, 'HKDF', false, ['deriveBits'])\n * proofBits = deriveBits({ name:'HKDF', hash:'SHA-256',\n * salt: utf8(BROKER_PROOF_DOMAIN),\n * info: utf8(JSON.stringify([BROKER_PROOF_DOMAIN, vaultId, brokerId])) },\n * hkdf, 256)\n * proofKey = importKey('raw', proofBits, { name:'HMAC', hash:'SHA-256' }, false, ['sign'])\n * proof = base64(sign('HMAC', proofKey, utf8(canonical)))\n * ```\n *\n * `BROKER_PROOF_DOMAIN` is both the HKDF salt and the first `info` array\n * element (the injective-JSON-array convention `deriveClassifyIndexKey`\n * established — see `classify/bidx.ts`). `BROKER_PROOF_VERSION` is the\n * separate MAC version tag, the first element of the `canonical` array.\n *\n * ## Canonical (spec §2 step 3)\n *\n * `JSON.stringify([BROKER_PROOF_VERSION, vaultId, endpointOrigin, brokerId,\n * profile ?? '', instancePid ?? '', challenge, expiresAt])` — every binding\n * field folded into ONE MAC input:\n * - `vaultId` (V1), `challenge` (V3), `expiresAt` (V4) — verbatim strings,\n * never reparsed/reformatted (F8): a byte-identical ISO string is\n * required, so e.g. `Z` vs `+00:00` for the same instant fails.\n * - `profile ?? ''` (F1) — a proof captured at one profile cannot be\n * replayed at another.\n * - `endpointOrigin` (F4) — a proof cannot be relayed to a different broker\n * endpoint reusing the same `brokerId`.\n * - `instancePid ?? ''` (F7) — an EMPTY STRING `instancePid` is forbidden in\n * both {@link computeBrokerProof} and {@link verifyBrokerProof}: absent\n * (the field omitted) and `''` must stay distinguishable, or `?? ''`\n * would alias \"no instance\" onto a real registered instance.\n *\n * ## Verify order (spec §3, non-negotiable, F2/F6)\n *\n * {@link verifyBrokerProof} MUST, in this exact order: (1) burn the challenge\n * via `consumeChallenge` FIRST and short-circuit `false` before any MAC work\n * (F2 — burn-on-presentation, not burn-on-success); (2) recompute the\n * canonical and compare with `crypto.subtle.verify('HMAC', …)` — portable,\n * constant-time, never a manual byte compare or Node `timingSafeEqual` (F6);\n * (3) assert `now < expiresAt` — the client clock is never trusted, so this\n * check only matters once the MAC (which covers `expiresAt`) has verified.\n *\n * ## Zeroing (F5)\n *\n * `seed` and the transient HKDF output are raw, durable secret material —\n * the `recovery.ts:276-307` `try{}finally{secret.fill(0)}` discipline, not\n * the ephemeral-CEK happy-path zeroing elsewhere. {@link computeBrokerProof}\n * zeroes the `seed` argument in a `finally` on every path including a throw;\n * {@link deriveBrokerProofKey} zeroes its own transient HKDF-output bytes the\n * same way. {@link deriveBrokerProofBits} does not zero `seed` — it exists\n * so the party layer can register the raw bits at one-time enrol (the value\n * that must survive the call to be base64-encoded and POSTed).\n *\n * @module\n */\nimport type { EnclaveKey } from '../crypto.js'\nimport { base64ToBuffer, bufferToBase64 } from '../crypto.js'\nimport { ValidationError } from '../../errors.js'\n\nconst subtle = globalThis.crypto.subtle\n\n/** HKDF salt domain for the broker proof key — also the first `info` array element. */\nexport const BROKER_PROOF_DOMAIN = 'noydb-broker-proof'\n\n/** MAC version tag — the first element of the `canonical` array signed/verified. */\nexport const BROKER_PROOF_VERSION = 'noydb-broker-proof-v1'\n\n/** Default challenge TTL (spec §2 step 2: \"TTL ≤ 60 s\"). */\nconst DEFAULT_CHALLENGE_TTL_MS = 60_000\n\n/** The canonical-binding fields a client supplies alongside `vaultId`/`brokerId` (spec §2 step 3). */\nexport interface BrokerProofCanonicalParts {\n readonly endpointOrigin: string\n readonly profile?: string | undefined\n readonly instancePid?: string | undefined\n readonly challenge: string\n readonly expiresAt: string\n}\n\n/** A freshly-minted, server-random single-use challenge (spec §2 step 2). */\nexport interface IssuedChallenge {\n readonly challenge: string\n readonly expiresAt: string\n}\n\n/** Arguments the broker host passes to verify a submitted proof (spec §3). */\nexport interface VerifyBrokerProofArgs {\n /**\n * Atomically test-and-delete the challenge, returning whether it was still\n * fresh (unconsumed and unexpired-from-the-store's-perspective). MUST burn\n * on presentation, not on successful verification (F2).\n */\n readonly consumeChallenge: (challenge: string) => Promise<boolean>\n /** The registered proof key's raw bytes, already KMS-unwrapped by the host (§7). */\n readonly registeredProofKey: Uint8Array\n readonly vaultId: string\n readonly endpointOrigin: string\n readonly brokerId: string\n readonly profile?: string | undefined\n readonly instancePid?: string | undefined\n readonly challenge: string\n readonly expiresAt: string\n /** base64(HMAC-SHA-256(proofKey, canonical)), as submitted by the client. */\n readonly proof: string\n}\n\n/** Build the canonical MAC input verbatim — never reparses/reformats a field (F8). */\nfunction buildCanonical(vaultId: string, brokerId: string, parts: BrokerProofCanonicalParts): string {\n return JSON.stringify([\n BROKER_PROOF_VERSION,\n vaultId,\n parts.endpointOrigin,\n brokerId,\n parts.profile ?? '',\n parts.instancePid ?? '',\n parts.challenge,\n parts.expiresAt,\n ])\n}\n\n/**\n * The HKDF step: derive the 256-bit proof bits from the `_broker` seed,\n * domain-separated per `(vaultId, brokerId)`. Returns the raw bits — the\n * value registered with the broker host once at enrol (spec §2 step 2/3).\n * Does not mutate or zero `seed`; the caller owns the seed's lifecycle.\n */\nexport async function deriveBrokerProofBits(\n seed: Uint8Array,\n vaultId: string,\n brokerId: string,\n): Promise<Uint8Array> {\n const hkdfKey = await subtle.importKey('raw', seed as BufferSource, 'HKDF', false, ['deriveBits'])\n const salt = new TextEncoder().encode(BROKER_PROOF_DOMAIN)\n const info = new TextEncoder().encode(JSON.stringify([BROKER_PROOF_DOMAIN, vaultId, brokerId]))\n const bits = await subtle.deriveBits({ name: 'HKDF', hash: 'SHA-256', salt, info }, hkdfKey, 256)\n return new Uint8Array(bits)\n}\n\n/**\n * Derive the steady-state, non-extractable `['sign']` HMAC proof key from\n * the `_broker` seed. Zeroes the transient HKDF-output bytes in a `finally`\n * once the key has been imported (F5) — the raw bits never leave this\n * function in usable form after import.\n */\nexport async function deriveBrokerProofKey(\n seed: Uint8Array,\n vaultId: string,\n brokerId: string,\n): Promise<EnclaveKey> {\n const proofBits = await deriveBrokerProofBits(seed, vaultId, brokerId)\n try {\n return await subtle.importKey('raw', proofBits as BufferSource, { name: 'HMAC', hash: 'SHA-256' }, false, ['sign'])\n } finally {\n proofBits.fill(0)\n }\n}\n\n/**\n * Compose derive + sign for the client: re-derive the proof key from the\n * seed and sign the canonical over `canonicalParts` (spec §2 step 3).\n *\n * Rejects an empty-string `instancePid` (F7 — absent is fine, `''` is not).\n * Zeroes the `seed` argument in a `finally` on EVERY path including a throw\n * (F5, durable-secret discipline) — the transient HKDF bits are zeroed one\n * layer down, inside {@link deriveBrokerProofKey}.\n */\nexport async function computeBrokerProof(\n seed: Uint8Array,\n vaultId: string,\n brokerId: string,\n canonicalParts: BrokerProofCanonicalParts,\n): Promise<string> {\n try {\n if (canonicalParts.instancePid === '') {\n throw new ValidationError(\n \"computeBrokerProof: instancePid must not be an empty string — omit the field entirely for \" +\n \"'no instance' (F7); '' would alias onto a registered instance.\",\n )\n }\n const canonical = buildCanonical(vaultId, brokerId, canonicalParts)\n const key = await deriveBrokerProofKey(seed, vaultId, brokerId)\n const mac = await subtle.sign('HMAC', key, new TextEncoder().encode(canonical) as BufferSource)\n return bufferToBase64(new Uint8Array(mac))\n } finally {\n seed.fill(0)\n }\n}\n\n/**\n * Host-side nonce mint: 32 random bytes, base64-encoded, plus an ISO\n * `expiresAt` at most `ttlMs` (default 60 s) in the future. The caller\n * (the broker host) persists both returned strings **verbatim** and MACs\n * those exact bytes on verify — never a reparsed/reformatted copy (F8).\n */\nexport function issueChallenge(opts?: { ttlMs?: number }): IssuedChallenge {\n const ttlMs = opts?.ttlMs ?? DEFAULT_CHALLENGE_TTL_MS\n const bytes = globalThis.crypto.getRandomValues(new Uint8Array(32))\n return {\n challenge: bufferToBase64(bytes),\n expiresAt: new Date(Date.now() + ttlMs).toISOString(),\n }\n}\n\n/**\n * Verify a submitted broker proof. Order is NON-NEGOTIABLE (spec §3):\n *\n * 1. `await consumeChallenge(challenge)` FIRST — burn-and-check-fresh; a\n * not-fresh (already-consumed or unknown) challenge short-circuits to\n * `false` before any MAC work (F2).\n * 2. Empty-string `instancePid` is rejected (F7), then the canonical is\n * recomputed over the verbatim `endpointOrigin`/`brokerId`/`profile`/\n * `instancePid`/`challenge`/`expiresAt` and compared via\n * `crypto.subtle.verify('HMAC', …)` — portable, constant-time, never a\n * manual compare or Node `timingSafeEqual` (F6).\n * 3. `now < expiresAt` — the client clock is never trusted; `expiresAt`\n * rides inside the already-verified MAC (F4/F8).\n */\nexport async function verifyBrokerProof(args: VerifyBrokerProofArgs): Promise<boolean> {\n const fresh = await args.consumeChallenge(args.challenge)\n if (!fresh) return false\n\n if (args.instancePid === '') return false\n\n const canonical = buildCanonical(args.vaultId, args.brokerId, {\n endpointOrigin: args.endpointOrigin,\n profile: args.profile,\n instancePid: args.instancePid,\n challenge: args.challenge,\n expiresAt: args.expiresAt,\n })\n const key = await subtle.importKey(\n 'raw',\n args.registeredProofKey as BufferSource,\n { name: 'HMAC', hash: 'SHA-256' },\n false,\n ['verify'],\n )\n const ok = await subtle.verify(\n 'HMAC',\n key,\n base64ToBuffer(args.proof) as BufferSource,\n new TextEncoder().encode(canonical) as BufferSource,\n )\n if (!ok) return false\n\n return Date.now() < Date.parse(args.expiresAt)\n}\n"],"mappings":"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","names":["plaintext","iv","data","iv","data","iv","data","subtle"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/with-formula/materialized-views/executor.ts"],"sourcesContent":["import type { Collection } from '../../kernel/collection.js'\nimport type { TxContext } from '../../with-commit/tx/transaction.js'\nimport type { EncryptedEnvelope } from '../../kernel/types.js'\nimport { MaterializedViewTooLargeError, LocaleNotSpecifiedError } from '../../kernel/errors.js'\nimport type { MaterializedFromMeta, MVQueryContext, MaterializedViewStrategy } from './types.js'\nimport type { RegisteredMV } from './registry.js'\nimport { wrapDbWithPredicates } from './registry.js'\nimport { groupAndReduce } from '../../with-lookup/aggregate/groupby.js'\nimport { canonicalGroupKey } from '../../with-lookup/aggregate/canonical-key.js'\nimport { applyI18nLocale, type I18nTextDescriptor } from '../../via/i18n/core.js'\nimport { putDerivedOutput, type PutDerivedOutputCtx } from '../../kernel/via/dispatch.js'\n\n/**\n * Accessor shape passed in from the owning Vault. Mirrors v1's\n * `DerivationStaleAccessor` — provides the per-collection resolver\n * and the active TxContext so refresh writes/tombstones register on\n * `_executed` for rollback symmetry.\n */\nexport interface MVExecutorAccessor {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n getCollection(name: string): Collection<any>\n getActiveTxContext(): TxContext | null\n /**\n * Vault-shaped accessor passed to the MV's `query()` callback at\n * each refresh. Same instance the registry used at registration\n * time; threading through the executor lets the refresh path\n * re-evaluate the closure against the live vault state.\n */\n getQueryContext(): MVQueryContext\n /**\n * #638 Task 5 — ctx for `putDerivedOutput`'s frozen-period skip+audit. #641: the lazy\n * resolve-on-read caller (`stale.ts#resolveStaleMVOnRead`) now supplies one too — built at\n * the `Collection.get()`/`list()` entry point with a `'resolve-on-read'` sentinel id (no\n * real \"reacting write\" for a read-triggered materialize, mirroring `refreshView()`'s\n * `'refreshView'` sentinel). Still declared optional here since `MVExecutorAccessor` is a\n * general shape and a future caller could reasonably omit it.\n */\n dispatchCtx?: PutDerivedOutputCtx\n}\n\nexport interface RefreshResult {\n /** Rows newly written / overwritten. */\n written: number\n /** Rows tombstoned via `_internalDelete` (only when `onEmpty: 'delete'`). */\n deleted: number\n /** Failed row writes (non-strict mode). */\n failed: number\n}\n\n/** Default cost ceiling — overridable per-MV via `spec.maxRows`. */\nconst DEFAULT_MAX_ROWS = 100_000\n\n/**\n * Materialize a query terminal that may be a `Query<T>` (call\n * `.toArray()`), an `Aggregation<R>` (call `.run()` returning a\n * single object — wrap as a one-row array), or a `GroupedAggregation<R>`\n * (call `.run()` returning an array of grouped rows). Branches on\n * available terminal at runtime — no type-discrimination at registration.\n */\nasync function materializeQueryResult(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n q: any,\n mvName: string,\n i18nLocale?: string,\n i18nFields?: Record<string, I18nTextDescriptor>,\n): Promise<ReadonlyArray<Record<string, unknown>>> {\n if (typeof q?.toArray === 'function') {\n // Query<T> — non-aggregate path. `.toArray()` returns Promise<T[]>.\n return await q.toArray()\n }\n if (typeof q?.run === 'function') {\n // Aggregation<R> or GroupedAggregation<R>. `.run()` is synchronous\n // and returns either a single object (Aggregation) or an array of\n // rows (GroupedAggregation). Promise.resolve() normalizes both\n // sync and async (future) variants.\n // Query-form MV grouping: when the MV declares i18nLocale, pass it +\n // i18nFields so a GroupedAggregation resolves i18n group keys before\n // bucketing (the Aggregation path ignores the extra arg).\n const runOpts = i18nLocale !== undefined ? { locale: i18nLocale, i18nFields } : undefined\n const result: unknown = await Promise.resolve(q.run(runOpts))\n if (Array.isArray(result)) {\n return result as ReadonlyArray<Record<string, unknown>>\n }\n // Single-aggregate result — wrap as one-row array. The consumer's\n // `rowKey()` should return a stable identity (often a literal\n // constant like `'total'`) since there's only one row.\n return [result as Record<string, unknown>]\n }\n throw new Error(\n `MV \"${mvName}\": query() must return a Query<T>, Aggregation, or GroupedAggregation. ` +\n `Got something without a .toArray() or .run() terminal.`,\n )\n}\n\n/**\n * Materialize a UNION-form MV: read every arm's source\n * collection, apply each arm's `map` to project rows into the unified\n * MV row shape, concatenate the mapped streams, then optionally run\n * `groupBy` + `aggregate` over the result.\n *\n * Modes (driven by `spec.groupBy` / `spec.aggregate`):\n *\n * - No `groupBy` → return the concatenated mapped rows unchanged.\n * - `groupBy` without `aggregate` → dedupe by composite group key,\n * keep the first row seen per key (later arms don't overwrite\n * earlier arms — Map insertion order rules).\n * - `groupBy` + `aggregate` → delegate to the shared `groupAndReduce`\n * pipeline used by `Query.groupBy().aggregate()`.\n *\n * Per-arm `map` is the schema-unification boundary; the strategy's\n * `TRow` type parameter enforces that every arm projects into the\n * same shape at compile time.\n *\n * @internal\n */\nasync function materializeUnionResult<TRow extends Record<string, unknown>>(\n spec: MaterializedViewStrategy<TRow>,\n db: MVQueryContext,\n): Promise<ReadonlyArray<Record<string, unknown>>> {\n const unified: TRow[] = []\n for (const arm of spec.unionSources!) {\n const coll = db.collection<Record<string, unknown>>(arm.collection)\n // Optional per-arm FK joins: chain `.join(field, { as, ... })` for\n // each declared leg before terminating. The aliased right-side\n // record lands at `sourceRow[leg.as]`, where the arm's `map` reads\n // it. Cast to `any` because the chained join widens the row type but\n // the executor treats every row as `Record<string, unknown>` — same\n // pattern the query-form join path uses.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let q: any = coll.query()\n if (arm.join?.length) {\n for (const leg of arm.join) {\n q = q.join(leg.field, { as: leg.as, maxRows: leg.maxRows, strategy: leg.strategy })\n }\n }\n const sourceRows = q.toArray() as ReadonlyArray<Record<string, unknown>>\n for (const r of sourceRows) {\n const mapped = arm.map(r)\n // null / undefined means \"omit this source row\" — skip without\n // pushing so groupBy/aggregate never see a null entry.\n if (mapped == null) continue\n unified.push(mapped)\n }\n }\n\n if (!spec.groupBy) return unified\n\n const groupFields: readonly string[] =\n typeof spec.groupBy === 'string' ? [spec.groupBy] : spec.groupBy\n\n // i18n-aware group keys. An `i18nText` group field carries a raw\n // `{ locale: string }` map, an unstable object key. When `i18nLocale` is\n // declared (with `i18nFields` describing those fields), resolve the declared\n // group-key i18n fields to it at the `mv` layer FIRST — the same unified-rows\n // boundary where money is threaded — so buckets are stable strings and the\n // `mv`-layer `onMissing` policy fires here.\n if (spec.i18nLocale !== undefined && spec.i18nFields !== undefined) {\n const groupI18n: Record<string, I18nTextDescriptor> = {}\n for (const f of groupFields) {\n const d = spec.i18nFields[f]\n if (d !== undefined) groupI18n[f] = d\n }\n if (Object.keys(groupI18n).length > 0) {\n for (let i = 0; i < unified.length; i++) {\n unified[i] = applyI18nLocale(unified[i] as Record<string, unknown>, groupI18n, spec.i18nLocale, undefined, 'mv') as TRow\n }\n }\n }\n // Guard (always): a remaining object-valued group key — an undeclared i18n\n // field or a locale-less MV — would bucket on a map. Refuse, don't bucket wrong.\n for (const f of groupFields) {\n for (const row of unified) {\n const v = (row as Record<string, unknown>)[f]\n if (v !== null && typeof v === 'object') {\n throw new LocaleNotSpecifiedError(\n f,\n `Materialized view \"${spec.name}\" groups by \"${f}\", whose value is a raw i18n locale map — ` +\n `an unstable object group key. Declare { i18nLocale, i18nFields } on the MV to resolve it at ` +\n `the 'mv' layer, or group by a dictKey/staticDict code (the stable key) and resolve the label at read time.`,\n )\n }\n }\n }\n\n // groupBy without aggregate — dedupe by composite key, keep first\n // seen row per key. Useful for cross-arm uniqueness (e.g. unify two\n // sibling collections, keeping one row per natural key).\n if (!spec.aggregate) {\n const seen = new Map<string, TRow>()\n for (const row of unified) {\n const k = canonicalGroupKey(groupFields, row as Record<string, unknown>)\n if (!seen.has(k)) seen.set(k, row)\n }\n return [...seen.values()]\n }\n\n // groupBy + aggregate — delegate to the shared pipeline used by\n // `Query.groupBy().aggregate()`. Result rows carry each grouped\n // field in declaration order followed by the spec's reducer outputs.\n return groupAndReduce<Record<string, unknown>>(unified, groupFields, spec.aggregate, spec.moneyFields)\n}\n\n/**\n * Run an MV's `query()` and write the result rows to the output\n * collection. Same-DEK encryption: routes through the standard\n * `Collection.put` pipeline, so the output collection's DEK is what\n * gets used (matches the v2 spec's \"same DEK as the left-most source\"\n * invariant — `Collection.put` looks up the DEK by collection name,\n * and the output collection IS the MV's owned collection).\n *\n * Stamps `_materializedFrom` onto every emitted row.\n *\n * **Tombstoning:** when `spec.onEmpty: 'delete'` (default), rows\n * that existed in a prior refresh but no longer appear in the new\n * materialized result are deleted via `Collection._internalDelete` —\n * the housekeeping bypass primitive prevents user\n * `onDelete` guards on the output collection from firing on these\n * system-internal deletes. `onEmpty: 'keep'` opts out (rows from\n * prior refreshes linger even when the new result lacks them).\n *\n * **Cost ceiling:** if the materialized row count exceeds\n * `spec.maxRows` (default 100k), throws `MaterializedViewTooLargeError`\n * before any writes hit the store — so strict-mode rollback is\n * clean.\n *\n * **Strict mode:** `spec.strict === true` re-throws on any\n * row-write failure; the active TxContext registration means the\n * source-write rolls back atomically via `revertExecuted`.\n *\n * @internal\n */\nexport const MaterializedViewExecutor = {\n async refresh(\n reg: RegisteredMV,\n accessor: MVExecutorAccessor,\n ): Promise<RefreshResult> {\n const spec = reg.spec\n const outputColl = accessor.getCollection(reg.outputCollection)\n const maxRows = spec.maxRows ?? DEFAULT_MAX_ROWS\n const onEmpty = spec.onEmpty ?? 'delete'\n const strict = spec.strict ?? false\n\n // 1. Materialize the query (branches on terminal shape). If the\n // MV declared predicates, wrap the query context the same way\n // the registry did at registration time so `.wherePredicate()`\n // calls resolve to the registered functions.\n const baseCtx = accessor.getQueryContext()\n const ctxForQuery: MVQueryContext = spec.predicates\n ? wrapDbWithPredicates(baseCtx, spec.predicates)\n : baseCtx\n // UNION-form strategies: read every arm, map to the unified\n // row shape, concatenate, then optionally groupBy + aggregate. The\n // single-source `query()` path is untouched.\n let rows: ReadonlyArray<Record<string, unknown>>\n if (spec.unionSources) {\n rows = await materializeUnionResult(spec, ctxForQuery)\n } else {\n const q = spec.query!(ctxForQuery)\n rows = await materializeQueryResult(q, spec.name, spec.i18nLocale, spec.i18nFields)\n }\n\n // 2. Cost ceiling check BEFORE any writes — keeps the rollback\n // clean if the source-write is wrapped in a transaction.\n if (rows.length > maxRows) {\n throw new MaterializedViewTooLargeError(spec.name, rows.length, maxRows)\n }\n\n const txCtx = accessor.getActiveTxContext()\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const adapter = (outputColl as any).adapter as {\n get(v: string, c: string, i: string): Promise<EncryptedEnvelope | null>\n }\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const vaultName = (outputColl as any).vault as string\n\n // 3. Compute the post-refresh id set so we can diff against the\n // prior-emitted id set for tombstoning (when onEmpty === 'delete').\n const newIds = new Set<string>()\n const enrichedRows: Array<{ id: string; record: Record<string, unknown> }> = []\n for (const row of rows) {\n const id = spec.rowKey(row)\n newIds.add(id)\n const meta: MaterializedFromMeta = {\n mvName: spec.name,\n queryHash: reg.queryHash,\n sourceVersions: {},\n materializedAt: new Date().toISOString(),\n }\n enrichedRows.push({ id, record: { ...row, _materializedFrom: meta } })\n }\n\n // 4. Write the new rows.\n let written = 0\n let failed = 0\n for (const { id, record } of enrichedRows) {\n try {\n if (txCtx !== null) {\n const prior = await adapter.get(vaultName, reg.outputCollection, id)\n txCtx._executed.push({\n op: { type: 'put', vaultName, collectionName: reg.outputCollection, id },\n priorEnvelope: prior,\n })\n }\n if (accessor.dispatchCtx) {\n if (await putDerivedOutput(outputColl, id, record, accessor.dispatchCtx) === 'written') written++\n } else {\n await outputColl.put(id, record)\n written++\n }\n } catch (err) {\n failed++\n if (strict) throw err\n \n console.warn(`[mv] \"${spec.name}\" row write failed:`, err)\n }\n }\n\n // 5. Tombstone rows that existed before but don't appear now.\n // `onEmpty: 'keep'` skips this pass entirely. Uses\n // `_internalDelete` so a user-registered `onDelete` on the\n // output collection does NOT fire on housekeeping (composition fix).\n let deleted = 0\n if (onEmpty === 'delete') {\n const priorIds = await listOutputIds(outputColl)\n for (const priorId of priorIds) {\n if (newIds.has(priorId)) continue\n try {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const outAny = outputColl as any\n if (typeof outAny._internalDelete === 'function') {\n await outAny._internalDelete(priorId, txCtx)\n deleted++\n } else {\n // Defensive fallback — should never hit in real flow since\n // every Collection has `_internalDelete`.\n await outputColl.delete(priorId)\n deleted++\n }\n } catch (err) {\n failed++\n if (strict) throw err\n \n console.warn(`[mv] \"${spec.name}\" tombstone failed for id=\"${priorId}\":`, err)\n }\n }\n }\n\n return { written, deleted, failed }\n },\n}\n\n/**\n * List ids currently present in the MV's output collection via the\n * adapter directly (avoids triggering the lazy resolve-on-read path\n * we're INSIDE). Returns an empty array if the collection doesn't\n * exist or the adapter doesn't surface a list method.\n *\n * @internal\n */\nasync function listOutputIds(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n outputColl: Collection<any>,\n): Promise<string[]> {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const cAny = outputColl as any\n const adapter = cAny.adapter as { list?: (v: string, c: string) => Promise<readonly string[]> }\n const vault = cAny.vault as string\n const name = cAny.name as string\n if (typeof adapter?.list !== 'function') return []\n try {\n const ids = await adapter.list(vault, name)\n return [...ids]\n } catch {\n return []\n }\n}\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/with-commit/history/history.ts","../src/with-commit/history/time-machine.ts"],"sourcesContent":["import type { NoydbStore, EncryptedEnvelope, HistoryOptions, PruneOptions } from '../../kernel/types.js'\nimport { NOYDB_FORMAT_VERSION } from '../../kernel/types.js'\nimport { isTombstone } from '../../kernel/enclave/index.js'\n\n/**\n * History storage convention:\n * Collection: `_history`\n * ID format: `{collection}:{recordId}:{paddedVersion}`\n * Version is zero-padded to 10 digits for lexicographic sorting.\n */\n\nconst HISTORY_COLLECTION = '_history'\nconst VERSION_PAD = 10\n\nfunction historyId(collection: string, recordId: string, version: number): string {\n return `${collection}:${recordId}:${String(version).padStart(VERSION_PAD, '0')}`\n}\n\n// Unused today, kept for future history-id parsing utilities.\n// eslint-disable-next-line @typescript-eslint/no-unused-vars\nfunction parseHistoryId(id: string): { collection: string; recordId: string; version: number } | null {\n const lastColon = id.lastIndexOf(':')\n if (lastColon < 0) return null\n const versionStr = id.slice(lastColon + 1)\n const rest = id.slice(0, lastColon)\n const firstColon = rest.indexOf(':')\n if (firstColon < 0) return null\n return {\n collection: rest.slice(0, firstColon),\n recordId: rest.slice(firstColon + 1),\n version: parseInt(versionStr, 10),\n }\n}\n\nfunction matchesPrefix(id: string, collection: string, recordId?: string): boolean {\n if (recordId) {\n return id.startsWith(`${collection}:${recordId}:`)\n }\n return id.startsWith(`${collection}:`)\n}\n\n/** Save a history entry (a complete encrypted envelope snapshot). */\nexport async function saveHistory(\n adapter: NoydbStore,\n vault: string,\n collection: string,\n recordId: string,\n envelope: EncryptedEnvelope,\n): Promise<void> {\n const id = historyId(collection, recordId, envelope._v)\n await adapter.put(vault, HISTORY_COLLECTION, id, envelope)\n}\n\n/** Get history entries for a record, sorted newest-first. */\nexport async function getHistory(\n adapter: NoydbStore,\n vault: string,\n collection: string,\n recordId: string,\n options?: HistoryOptions,\n): Promise<EncryptedEnvelope[]> {\n const allIds = await adapter.list(vault, HISTORY_COLLECTION)\n const matchingIds = allIds\n .filter(id => matchesPrefix(id, collection, recordId))\n .sort()\n .reverse() // newest first\n\n const entries: EncryptedEnvelope[] = []\n\n for (const id of matchingIds) {\n const envelope = await adapter.get(vault, HISTORY_COLLECTION, id)\n if (!envelope) continue\n\n // Apply time filters\n if (options?.from && envelope._ts < options.from) continue\n if (options?.to && envelope._ts > options.to) continue\n\n entries.push(envelope)\n\n if (options?.limit && entries.length >= options.limit) break\n }\n\n return entries\n}\n\n/** Get a specific version's envelope from history. */\nexport async function getVersionEnvelope(\n adapter: NoydbStore,\n vault: string,\n collection: string,\n recordId: string,\n version: number,\n): Promise<EncryptedEnvelope | null> {\n const id = historyId(collection, recordId, version)\n return adapter.get(vault, HISTORY_COLLECTION, id)\n}\n\n/** Prune history entries. Returns the number of entries deleted. */\nexport async function pruneHistory(\n adapter: NoydbStore,\n vault: string,\n collection: string,\n recordId: string | undefined,\n options: PruneOptions,\n): Promise<number> {\n const allIds = await adapter.list(vault, HISTORY_COLLECTION)\n const matchingIds = allIds\n .filter(id => recordId ? matchesPrefix(id, collection, recordId) : matchesPrefix(id, collection))\n .sort()\n\n let toDelete: string[] = []\n\n if (options.keepVersions !== undefined) {\n // Keep only the N most recent, delete the rest\n const keep = options.keepVersions\n if (matchingIds.length > keep) {\n toDelete = matchingIds.slice(0, matchingIds.length - keep)\n }\n }\n\n if (options.beforeDate) {\n // Delete entries older than the specified date\n for (const id of matchingIds) {\n if (toDelete.includes(id)) continue\n const envelope = await adapter.get(vault, HISTORY_COLLECTION, id)\n if (envelope && envelope._ts < options.beforeDate) {\n toDelete.push(id)\n }\n }\n }\n\n // Deduplicate\n const uniqueDeletes = [...new Set(toDelete)]\n\n for (const id of uniqueDeletes) {\n await adapter.delete(vault, HISTORY_COLLECTION, id)\n }\n\n return uniqueDeletes.length\n}\n\n/** Clear all history for a vault, optionally scoped to a collection or record. */\nexport async function clearHistory(\n adapter: NoydbStore,\n vault: string,\n collection?: string,\n recordId?: string,\n): Promise<number> {\n const allIds = await adapter.list(vault, HISTORY_COLLECTION)\n let toDelete: string[]\n\n if (collection && recordId) {\n toDelete = allIds.filter(id => matchesPrefix(id, collection, recordId))\n } else if (collection) {\n toDelete = allIds.filter(id => matchesPrefix(id, collection))\n } else {\n toDelete = allIds\n }\n\n for (const id of toDelete) {\n await adapter.delete(vault, HISTORY_COLLECTION, id)\n }\n\n return toDelete.length\n}\n\n/**\n * Crypto-shred every `_history` version of a record. Each non-tombstone\n * history envelope is OVERWRITTEN in place with a tombstone\n * `{ _noydb, _v, _ts: now, _by: actor, _iv: '', _data: '' }` — dropping\n * `_iv`/`_data`/`_cek`/`_det`, so the prior ciphertext (and the wrapped CEK\n * that could decrypt it) is gone everywhere this store reaches. The version\n * counter (`_v`) is preserved so the audit trail still shows \"N versions\n * existed and were erased.\"\n *\n * Overwrite — NOT delete — so the history key itself survives as proof the\n * version existed. Already-tombstoned versions (re-run / idempotent forget)\n * are left untouched and not counted.\n *\n * Returns the number of history versions newly tombstoned.\n */\nexport async function tombstoneHistory(\n adapter: NoydbStore,\n vault: string,\n collection: string,\n recordId: string,\n actor: string,\n encrypted: boolean,\n): Promise<number> {\n const allIds = await adapter.list(vault, HISTORY_COLLECTION)\n const matchingIds = allIds.filter(id => matchesPrefix(id, collection, recordId))\n\n const now = new Date().toISOString()\n let count = 0\n for (const id of matchingIds) {\n const env = await adapter.get(vault, HISTORY_COLLECTION, id)\n if (!env) continue\n // Already a tombstone (no body and no wrapped CEK)? Skip — idempotent.\n if (isTombstone(env, encrypted)) continue\n const tombstone: EncryptedEnvelope = {\n _noydb: NOYDB_FORMAT_VERSION,\n _v: env._v,\n _ts: now,\n _iv: '',\n _data: '',\n ...(actor ? { _by: actor } : {}),\n }\n await adapter.put(vault, HISTORY_COLLECTION, id, tombstone)\n count++\n }\n return count\n}\n","/**\n * Time-machine queries — point-in-time reads reconstructed from the\n * existing history + ledger infrastructure.\n *\n * ## Usage\n *\n * ```ts\n * const vault = await db.openVault('acme', { passphrase })\n * const q1End = vault.at('2026-03-31T23:59:59Z')\n * const invoice = await q1End.collection<Invoice>('invoices').get('inv-001')\n * // → the record as it stood at the close of Q1 2026\n * ```\n *\n * ## How it works\n *\n * Every write path already fans out into two persistence lanes:\n *\n * 1. `saveHistory(...)` persists a **full encrypted envelope snapshot**\n * per version under the `_history` collection (one envelope per\n * version, keyed by `{collection}:{id}:{paddedVersion}`). Each\n * envelope carries its own `_ts` (the write timestamp).\n * 2. `ledger.append(...)` appends a hash-chained audit entry that\n * records the `op` (put / delete), `version`, and `ts`.\n *\n * Reconstruction at a target timestamp T is therefore:\n *\n * - Find the newest history envelope for `(collection, id)` whose\n * `_ts ≤ T` — that's the state the record was in at T.\n * - Check the ledger for any `op: 'delete'` entry for the same\n * `(collection, id)` with `entry.ts` in `(latestEnvelope._ts, T]` —\n * if present, the record was deleted before T, so return `null`.\n * - Decrypt the surviving envelope with the current collection DEK\n * (DEKs are per-collection but stable across versions — the same\n * key encrypts v1 and v15 of a record).\n *\n * No delta replay. The existing `history.ts` module already stores\n * complete snapshots; we just pick the right one.\n *\n * ## Read-only contract\n *\n * Every write method on `CollectionInstant` throws\n * {@link ReadOnlyAtInstantError}. A historical view is a *read*\n * surface — mutating the past would require either a branch/shadow\n * mechanism (tracked under shadow vaults) or a rewrite of\n * history, which breaks the ledger's tamper-evidence guarantee.\n *\n * @module\n */\nimport type { EncryptedEnvelope, NoydbStore } from '../../kernel/types.js'\nimport type { LedgerStore } from './ledger/store.js'\nimport { getHistory } from './history.js'\nimport { decrypt, type EnclaveKey } from '../../kernel/enclave/index.js'\nimport { ReadOnlyAtInstantError } from '../../kernel/errors.js'\n\n/**\n * Narrow view of a {@link Vault}'s internals that\n * {@link VaultInstant} needs. Passed in by `Vault.at()` rather than\n * constructed here so all crypto + adapter access stays inside the\n * Vault class.\n *\n * Not exported from the public barrel — consumers should get a\n * `VaultInstant` via `vault.at(ts)`, never by constructing one\n * directly.\n */\nexport interface VaultEngine {\n readonly adapter: NoydbStore\n /** Vault name (the compartment). */\n readonly name: string\n /**\n * `true` when the vault was opened with a passphrase (the normal\n * case). `false` in plaintext-mode vaults (`encrypt: false`) — in\n * that case `envelope._data` is raw JSON and we skip the DEK lookup.\n */\n readonly encrypted: boolean\n /**\n * Resolves the DEK used to decrypt a given collection's envelopes.\n * Not called when `encrypted` is false.\n */\n getDEK(collection: string): Promise<EnclaveKey>\n /**\n * Lazily-initialised ledger. We consult it to detect deletes that\n * happened between the latest history snapshot and the target\n * timestamp. `null` when history is disabled for this vault — in\n * that case time-machine reads fall back to history-only\n * reconstruction (which may miss deletes).\n */\n getLedger(): LedgerStore | null\n}\n\n/**\n * A vault at a fixed instant. Produced by `vault.at(timestamp)`.\n * Carries no session state of its own — every read is a fresh\n * lookup through the vault's adapter.\n *\n * Cheap to construct; safe to throw away. Create one per query.\n */\nexport class VaultInstant {\n constructor(\n private readonly engine: VaultEngine,\n /** Fully-resolved target timestamp (ISO-8601 UTC). */\n public readonly timestamp: string,\n ) {}\n\n /** Get a point-in-time view of a collection. */\n collection<T = unknown>(name: string): CollectionInstant<T> {\n return new CollectionInstant<T>(this.engine, this.timestamp, name)\n }\n}\n\n/**\n * A read-only collection view anchored to a past instant.\n *\n * Every write method throws {@link ReadOnlyAtInstantError} — see the\n * module docstring for why. The read surface is intentionally smaller\n * than the live {@link Collection}: `get` and `list` cover the\n * \"what did the books look like on date X\" use case without pulling\n * in the full query DSL / joins / aggregates at this stage. Follow-up\n * work tracked under.\n */\nexport class CollectionInstant<T = unknown> {\n constructor(\n private readonly engine: VaultEngine,\n private readonly targetTs: string,\n public readonly name: string,\n ) {}\n\n /**\n * Return the record as it existed at the target timestamp, or\n * `null` if the record had not been created yet or had already been\n * deleted by then.\n */\n async get(id: string): Promise<T | null> {\n const envelope = await this.resolveEnvelope(id)\n if (!envelope) return null\n const plaintext = this.engine.encrypted\n ? await decrypt(envelope._iv, envelope._data, await this.engine.getDEK(this.name))\n : envelope._data\n return JSON.parse(plaintext) as T\n }\n\n /**\n * IDs of records that existed (had at least one `put` and were not\n * subsequently deleted) at the target timestamp.\n *\n * Implemented as a linear scan over history + ledger. Performance\n * is bounded by total history size (not live-vault size), so the\n * memory-first vault-scale cap (1K–50K records × average history\n * depth) still applies.\n */\n async list(): Promise<string[]> {\n const historyIds = await collectHistoryIds(this.engine.adapter, this.engine.name, this.name)\n const liveIds = await this.engine.adapter.list(this.engine.name, this.name)\n const candidateIds = new Set<string>([...historyIds, ...liveIds])\n const alive: string[] = []\n for (const id of candidateIds) {\n const env = await this.resolveEnvelope(id)\n if (env) alive.push(id)\n }\n return alive.sort()\n }\n\n // ── write guards ───────────────────────────────────────────────────\n\n async put(_id: string, _record: T): Promise<never> {\n throw new ReadOnlyAtInstantError('put', this.targetTs)\n }\n async delete(_id: string): Promise<never> {\n throw new ReadOnlyAtInstantError('delete', this.targetTs)\n }\n async update(_id: string, _patch: Partial<T>): Promise<never> {\n throw new ReadOnlyAtInstantError('update', this.targetTs)\n }\n\n // ── internals ─────────────────────────────────────────────────────\n\n /**\n * Return the envelope that represents the record's state at\n * `targetTs`, accounting for deletes. `null` if the record didn't\n * exist at that instant.\n *\n * ## Why we use the ledger as the authoritative timeline\n *\n * The per-version history snapshots saved by `saveHistory()` do\n * carry a `_ts` field, but that timestamp is the moment the\n * snapshot was *captured* (i.e. the instant right before the\n * subsequent overwrite), not the original write time. The ledger,\n * by contrast, records `ts` at the moment of each `put` / `delete`\n * — it's the only source that tracks the real timeline. So:\n *\n * 1. Walk the ledger; find the latest entry for `(collection, id)`\n * with `ts ≤ targetTs`.\n * 2. If that entry is a `delete`, the record was gone at the\n * target instant — return null.\n * 3. Otherwise it's a `put` with a specific `version`. Load the\n * envelope for that version from history, falling back to the\n * live collection for the most recent version.\n *\n * ## Fallback when the ledger is disabled\n *\n * If the vault has history disabled, `getLedger()` returns null and\n * we fall back to comparing envelope `_ts` fields. This is\n * approximate and gets the *last write* right but may confuse the\n * intermediate versions; adopters needing accurate time-machine\n * reads should leave history enabled.\n */\n private async resolveEnvelope(id: string): Promise<EncryptedEnvelope | null> {\n const ledger = this.engine.getLedger()\n if (ledger) {\n return this.resolveViaLedger(id, ledger)\n }\n return this.resolveViaEnvelopeTs(id)\n }\n\n private async resolveViaLedger(id: string, ledger: LedgerStore): Promise<EncryptedEnvelope | null> {\n const entries = await ledger.entries()\n // Entries are already ordered by index which is the mutation order.\n let latest: { op: 'put' | 'delete'; version: number } | null = null\n for (const e of entries) {\n if (e.collection !== this.name || e.id !== id) continue\n if (e.ts > this.targetTs) break // entries are time-ordered by index\n // `amendment` + `lifecycle` entries are audit-only summaries — they\n // carry no (collection, id) tuple of their own and would never match\n // the filter above. The narrow here is a type guard, not a runtime\n // skip.\n // `forget` is a subject-erasure summary with empty (collection, id) —\n // never matches the filter above; the narrow is a type guard.\n if (e.op === 'amendment' || e.op === 'lifecycle' || e.op === 'forget') continue\n // `migration` is a record rewrite (cutover) — resolve it like a put.\n latest = { op: e.op === 'migration' ? 'put' : e.op, version: e.version }\n }\n if (!latest) return null\n if (latest.op === 'delete') return null\n return this.loadVersion(id, latest.version)\n }\n\n private async resolveViaEnvelopeTs(id: string): Promise<EncryptedEnvelope | null> {\n const history = await getHistory(\n this.engine.adapter, this.engine.name, this.name, id,\n )\n const live = await this.engine.adapter.get(this.engine.name, this.name, id)\n const byVersion = new Map<number, EncryptedEnvelope>()\n for (const e of history) byVersion.set(e._v, e)\n if (live) byVersion.set(live._v, live)\n const sorted = [...byVersion.values()].sort((a, b) =>\n a._ts < b._ts ? 1 : a._ts > b._ts ? -1 : 0,\n )\n return sorted.find((e) => e._ts <= this.targetTs) ?? null\n }\n\n /**\n * Fetch the envelope for a specific version. The live record (most\n * recent put) lives in the main collection; prior versions live in\n * `_history`. We check live first because the common case after a\n * delete is that we're trying to load the last-live version from\n * history, and skipping live for the current-version case avoids a\n * redundant lookup.\n */\n private async loadVersion(id: string, version: number): Promise<EncryptedEnvelope | null> {\n const live = await this.engine.adapter.get(this.engine.name, this.name, id)\n if (live && live._v === version) return live\n\n // Direct lookup by (collection, id, version) — avoids scanning all history.\n const historyId = `${this.name}:${id}:${String(version).padStart(10, '0')}`\n return await this.engine.adapter.get(this.engine.name, '_history', historyId)\n }\n}\n\n/**\n * Scan the `_history` collection once and collect every distinct\n * `recordId` for the given collection. History keys follow the\n * shape `<collection>:<recordId>:<paddedVersion>`; we split on the\n * last two colons (delimiter-safe because `paddedVersion` is\n * exactly 10 digits).\n */\nasync function collectHistoryIds(\n adapter: NoydbStore,\n vault: string,\n collection: string,\n): Promise<string[]> {\n const all = await adapter.list(vault, '_history')\n const prefix = `${collection}:`\n const seen = new Set<string>()\n for (const key of all) {\n if (!key.startsWith(prefix)) continue\n const lastColon = key.lastIndexOf(':')\n if (lastColon <= prefix.length) continue\n const middle = key.slice(prefix.length, lastColon)\n seen.add(middle)\n }\n return [...seen]\n}\n"],"mappings":"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","names":["historyId"]}