@noy-db/hub 0.3.0-pre.8 → 0.3.0-pre.9

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 (400) hide show
  1. package/dist/adapter/index.d.ts +2 -3
  2. package/dist/adapter/index.js +1 -1
  3. package/dist/aggregate/index.d.ts +3 -4
  4. package/dist/aggregate/index.js +9 -8
  5. package/dist/aggregate/index.js.map +1 -1
  6. package/dist/api-HV4PDUWF.js +20 -0
  7. package/dist/as/index.d.ts +4 -5
  8. package/dist/as/index.js +11 -11
  9. package/dist/at/index.d.ts +2 -3
  10. package/dist/attestation/index.d.ts +3 -4
  11. package/dist/attestation/index.js +18 -18
  12. package/dist/{backup-WRGNTGJF.js → backup-ODXO7OGA.js} +12 -12
  13. package/dist/blobs/index.d.ts +4 -5
  14. package/dist/blobs/index.js +11 -11
  15. package/dist/blobs/index.js.map +1 -1
  16. package/dist/bundle/index.d.ts +5 -6
  17. package/dist/bundle/index.js +20 -20
  18. package/dist/{bundle-DzWtVw7w.d.ts → bundle-DlSxo31R.d.ts} +1 -1
  19. package/dist/by/index.js +2 -2
  20. package/dist/cargo/index.d.ts +4 -5
  21. package/dist/cargo/index.js +23 -22
  22. package/dist/{chunk-X6VNIRHS.js → chunk-2644KEGW.js} +18 -10
  23. package/dist/chunk-2644KEGW.js.map +1 -0
  24. package/dist/{chunk-XW5LZYV4.js → chunk-2HVUKRNE.js} +2 -2
  25. package/dist/{chunk-VFQOE76I.js → chunk-2KOSJTMU.js} +2 -2
  26. package/dist/{chunk-2PJB6H3H.js → chunk-2UQ4RMHV.js} +4 -4
  27. package/dist/{chunk-5GQE5IDB.js → chunk-2XE6LIXT.js} +13 -11
  28. package/dist/chunk-2XE6LIXT.js.map +1 -0
  29. package/dist/{chunk-VIWOGJ2O.js → chunk-34RRRIC3.js} +2 -2
  30. package/dist/{chunk-BASCPHNM.js → chunk-3C5CKXFI.js} +289 -48
  31. package/dist/chunk-3C5CKXFI.js.map +1 -0
  32. package/dist/{chunk-LFGJG4XZ.js → chunk-3I4KYWNP.js} +2 -2
  33. package/dist/{chunk-723XB5NX.js → chunk-42KVY3OC.js} +4 -4
  34. package/dist/{chunk-56JB2BGI.js → chunk-4C4WBS4V.js} +1739 -920
  35. package/dist/chunk-4C4WBS4V.js.map +1 -0
  36. package/dist/{chunk-62APEMF3.js → chunk-4G3IT7J7.js} +7 -7
  37. package/dist/{chunk-W3JABOVB.js → chunk-4RQ62VSR.js} +2 -2
  38. package/dist/{chunk-WPNP3NF5.js → chunk-4ZSGSRBI.js} +8 -8
  39. package/dist/{chunk-C4NOLA5X.js → chunk-5GAX2QOH.js} +2 -2
  40. package/dist/{chunk-5HRJQSKN.js → chunk-5SAZMBW7.js} +7 -7
  41. package/dist/{chunk-II6DLDZM.js → chunk-5TZZUL5X.js} +2 -2
  42. package/dist/{chunk-TUL5YQWF.js → chunk-6CU3FN74.js} +8 -8
  43. package/dist/chunk-6G2SCQYS.js +29 -0
  44. package/dist/chunk-6G2SCQYS.js.map +1 -0
  45. package/dist/{chunk-UWZ7O4NR.js → chunk-6MWUANRF.js} +2 -2
  46. package/dist/{chunk-U7LV2DUN.js → chunk-6ULCYCMU.js} +2 -2
  47. package/dist/{chunk-6Z435B6N.js → chunk-7HAPHVRJ.js} +131 -92
  48. package/dist/chunk-7HAPHVRJ.js.map +1 -0
  49. package/dist/{chunk-GDOOG5YQ.js → chunk-7IZQTCTR.js} +3 -3
  50. package/dist/chunk-7ZANJ6JZ.js +265 -0
  51. package/dist/chunk-7ZANJ6JZ.js.map +1 -0
  52. package/dist/{chunk-VVLOWY74.js → chunk-A2APWS32.js} +6 -6
  53. package/dist/chunk-A2APWS32.js.map +1 -0
  54. package/dist/{chunk-TIAYXOEI.js → chunk-A4MAD6PO.js} +5 -5
  55. package/dist/chunk-ANDBBA2P.js +36 -0
  56. package/dist/chunk-ANDBBA2P.js.map +1 -0
  57. package/dist/{chunk-5HZULLHE.js → chunk-AUR7MRHA.js} +2 -2
  58. package/dist/{chunk-44DV37ZV.js → chunk-AYGZMQP5.js} +3 -3
  59. package/dist/chunk-AYGZMQP5.js.map +1 -0
  60. package/dist/{chunk-P2PEPSPK.js → chunk-CJEMA2Z6.js} +2 -2
  61. package/dist/{chunk-L6Q5NA22.js → chunk-CTSFKDGA.js} +2 -2
  62. package/dist/{chunk-OJ64XMPJ.js → chunk-CU62BBRB.js} +32 -1
  63. package/dist/chunk-CU62BBRB.js.map +1 -0
  64. package/dist/{chunk-J5YUA6JB.js → chunk-CWCCYH34.js} +2 -2
  65. package/dist/{chunk-NTPWQH4S.js → chunk-DEYAO3N6.js} +10 -10
  66. package/dist/{chunk-M3OYTM5Z.js → chunk-DFIBOSOQ.js} +8 -8
  67. package/dist/{chunk-FZK3JJJK.js → chunk-E5AHO65E.js} +7 -7
  68. package/dist/{chunk-F3EKFGUS.js → chunk-FC3XK5JT.js} +8 -8
  69. package/dist/{chunk-XBPUVQDO.js → chunk-FGOGS7CE.js} +10 -12
  70. package/dist/chunk-FGOGS7CE.js.map +1 -0
  71. package/dist/{chunk-3KBK5LU4.js → chunk-GX2BYNCT.js} +7 -7
  72. package/dist/{chunk-YTCCC6NG.js → chunk-GZ2MOPVX.js} +34 -63
  73. package/dist/chunk-GZ2MOPVX.js.map +1 -0
  74. package/dist/chunk-H57P2SJB.js +126 -0
  75. package/dist/chunk-H57P2SJB.js.map +1 -0
  76. package/dist/{chunk-55LSDABW.js → chunk-HBTT7YQX.js} +2 -2
  77. package/dist/{chunk-PYIUDUGS.js → chunk-HOCEGVGQ.js} +2 -2
  78. package/dist/{chunk-5JQMX2EH.js → chunk-ICV32FPK.js} +2 -2
  79. package/dist/{chunk-PMMV5SQC.js → chunk-IIP5WFVT.js} +138 -92
  80. package/dist/chunk-IIP5WFVT.js.map +1 -0
  81. package/dist/{chunk-37JZR7CY.js → chunk-IUKGIB2Q.js} +4 -4
  82. package/dist/{chunk-OWGAXPRE.js → chunk-IWTYFS3D.js} +2 -2
  83. package/dist/{chunk-FCOZ7DEC.js → chunk-JC5R7P44.js} +3 -3
  84. package/dist/{chunk-IARAK3PK.js → chunk-KBBWY46Y.js} +2 -2
  85. package/dist/{chunk-RNYDHIQL.js → chunk-KBF5GSGX.js} +3 -3
  86. package/dist/{chunk-6P5AJ2ZS.js → chunk-KCG3BHFG.js} +2 -23
  87. package/dist/chunk-KCG3BHFG.js.map +1 -0
  88. package/dist/{chunk-GLVXQU3E.js → chunk-KNXN47MO.js} +3 -3
  89. package/dist/{chunk-VK6OC22R.js → chunk-KWLD2D75.js} +4 -3
  90. package/dist/chunk-KWLD2D75.js.map +1 -0
  91. package/dist/{chunk-J7OH6R4K.js → chunk-KXKLKJW4.js} +6 -6
  92. package/dist/{chunk-PAJCRV7O.js → chunk-KYC4LIEU.js} +2 -2
  93. package/dist/{chunk-R4IVPJHN.js → chunk-LEOXZY36.js} +8 -8
  94. package/dist/{chunk-25G7PUTA.js → chunk-M5I354EE.js} +9 -9
  95. package/dist/{chunk-26FSVNBL.js → chunk-MK2BSS44.js} +2 -2
  96. package/dist/{chunk-UMH35AQV.js → chunk-MSVY7ULZ.js} +3 -3
  97. package/dist/{chunk-5YBUFCFF.js → chunk-NNWK3PSY.js} +9 -9
  98. package/dist/{chunk-X5QZAJDB.js → chunk-NSZ6J5DF.js} +7 -7
  99. package/dist/{chunk-I6URUNTD.js → chunk-NUEFXLZU.js} +7 -7
  100. package/dist/{chunk-PPPZIBKY.js → chunk-NYVQCZ54.js} +5 -5
  101. package/dist/chunk-NYVQCZ54.js.map +1 -0
  102. package/dist/{chunk-DCVVCJVB.js → chunk-OQAODTYR.js} +2 -2
  103. package/dist/{chunk-QYMMMKOO.js → chunk-OV4CMJGR.js} +5 -5
  104. package/dist/{chunk-V76PHWLE.js → chunk-PEDZFFKW.js} +2 -2
  105. package/dist/chunk-PF3FQCCF.js +164 -0
  106. package/dist/chunk-PF3FQCCF.js.map +1 -0
  107. package/dist/{chunk-BJ2XF2RC.js → chunk-Q2KVKTC2.js} +5 -5
  108. package/dist/{chunk-RUIMKQTA.js → chunk-QJR3W3MF.js} +1 -1
  109. package/dist/chunk-QJR3W3MF.js.map +1 -0
  110. package/dist/{chunk-QABECTNN.js → chunk-QTXQ6RNZ.js} +3 -3
  111. package/dist/{chunk-P6TZ3IVN.js → chunk-RJ5522XX.js} +12 -12
  112. package/dist/{chunk-BM6E4MUT.js → chunk-RMUXPHIH.js} +56 -7
  113. package/dist/chunk-RMUXPHIH.js.map +1 -0
  114. package/dist/{chunk-CHK5VVMI.js → chunk-S6AQXDN4.js} +2 -2
  115. package/dist/{chunk-4VDFUBTS.js → chunk-SSFC3IYA.js} +9 -9
  116. package/dist/chunk-SSFC3IYA.js.map +1 -0
  117. package/dist/{chunk-MOQ5M46U.js → chunk-SZCHEDML.js} +2 -2
  118. package/dist/{chunk-UB3XHGFG.js → chunk-TDPEJINP.js} +14 -7
  119. package/dist/chunk-TDPEJINP.js.map +1 -0
  120. package/dist/{chunk-REWTMYSF.js → chunk-UNJU345Q.js} +1 -1
  121. package/dist/chunk-UNJU345Q.js.map +1 -0
  122. package/dist/chunk-V7RNIND6.js +265 -0
  123. package/dist/chunk-V7RNIND6.js.map +1 -0
  124. package/dist/{chunk-FWART3VA.js → chunk-VJ3NQK4K.js} +5 -5
  125. package/dist/{chunk-3YKBGS3I.js → chunk-WNA6T3MA.js} +8 -8
  126. package/dist/{chunk-7CZ6YXMO.js → chunk-WVCQ75OK.js} +2 -2
  127. package/dist/{chunk-BTFTHF6Q.js → chunk-X66MXMR5.js} +2 -2
  128. package/dist/{chunk-7TAV3BFQ.js → chunk-XHD3L3JO.js} +199 -35
  129. package/dist/chunk-XHD3L3JO.js.map +1 -0
  130. package/dist/{chunk-6PALJTC2.js → chunk-XJK2WIYN.js} +3 -3
  131. package/dist/{chunk-5UOPIPZC.js → chunk-XQ7UYPVN.js} +3 -3
  132. package/dist/{chunk-HU55NPHZ.js → chunk-Y2OCKFZ3.js} +2 -2
  133. package/dist/{chunk-TCGVRKOS.js → chunk-YBFOBRTO.js} +3 -3
  134. package/dist/{chunk-I6JJ6VJS.js → chunk-YLN33ADH.js} +2 -2
  135. package/dist/{chunk-PVC2FBCQ.js → chunk-Z5YFRMG5.js} +2 -2
  136. package/dist/{chunk-EU7HDMT3.js → chunk-ZWQFOXBR.js} +2 -2
  137. package/dist/classified/index.d.ts +4 -5
  138. package/dist/classified/index.js +5 -3
  139. package/dist/{config-drift-RFEVGBKR.js → classified-marker-IO5EBW7Q.js} +4 -4
  140. package/dist/classified-marker-IO5EBW7Q.js.map +1 -0
  141. package/dist/collection-facade-3RAXG2OU.js +38 -0
  142. package/dist/{computed-6FOFXQPE.js → computed-R62HGCO4.js} +3 -3
  143. package/dist/consent/index.d.ts +3 -4
  144. package/dist/consent/index.js +9 -9
  145. package/dist/crdt/index.d.ts +3 -4
  146. package/dist/{dead-filter-RHMNYOXP.js → dead-filter-VNA4M3GP.js} +2 -2
  147. package/dist/delegation-ZKII5UBI.js +25 -0
  148. package/dist/derivations/index.d.ts +4 -5
  149. package/dist/derivations/index.js +12 -11
  150. package/dist/derive-5D2C3WG4.js +21 -0
  151. package/dist/describe/index.d.ts +4 -1
  152. package/dist/{dev-unlock-wgQ7Ci5I.d.ts → dev-unlock-DDTafVO6.d.ts} +1 -1
  153. package/dist/{enclave-KGEUW2GL.js → enclave-EUBEJGPG.js} +11 -9
  154. package/dist/executor-IL7SRNUZ.js +23 -0
  155. package/dist/executor-OUA2Y6YC.js +9 -0
  156. package/dist/executor-WAEI6PDV.js +9 -0
  157. package/dist/export-accessible-O55RKAGC.js +23 -0
  158. package/dist/extract-partition-VBGCBA6Q.js +36 -0
  159. package/dist/{fanout-sidecar-NBDKKCHT.js → fanout-sidecar-CI5BCMQM.js} +11 -11
  160. package/dist/find-FISGUWSK.js +11 -0
  161. package/dist/forget/index.d.ts +2 -2
  162. package/dist/forget/index.js +9 -9
  163. package/dist/guards/index.d.ts +4 -5
  164. package/dist/guards/index.js +4 -4
  165. package/dist/{hash-CwHnoKyW.d.ts → hash-DBkZfIBW.d.ts} +1 -1
  166. package/dist/history/index.d.ts +4 -5
  167. package/dist/history/index.js +11 -11
  168. package/dist/i18n/index.d.ts +24 -5
  169. package/dist/i18n/index.js +45 -34
  170. package/dist/i18n/index.js.map +1 -1
  171. package/dist/in/index.d.ts +2 -3
  172. package/dist/{index-CTvUp94-.d.ts → index-CyclmyDr.d.ts} +2 -3
  173. package/dist/{index-Dn5GzPg_.d.ts → index-DzhNI2vE.d.ts} +7114 -5992
  174. package/dist/index.d.ts +51 -17
  175. package/dist/index.js +403 -125
  176. package/dist/index.js.map +1 -1
  177. package/dist/indexing/index.d.ts +3 -4
  178. package/dist/indexing/index.js +4 -4
  179. package/dist/issue-EQAWIYFQ.js +19 -0
  180. package/dist/kernel/index.d.ts +3 -4
  181. package/dist/kernel/index.js +13 -12
  182. package/dist/lazy/index.d.ts +3 -4
  183. package/dist/{ledger-3LR3YO2W.js → ledger-AJA2SR3C.js} +11 -11
  184. package/dist/liberate-SEGR4JLY.js +24 -0
  185. package/dist/{link-set-NSUIX6BR.js → link-set-YBAC2DQT.js} +11 -11
  186. package/dist/materialized-views/index.d.ts +14 -5
  187. package/dist/materialized-views/index.js +17 -15
  188. package/dist/{mime-magic-CYbFNQ4h.d.ts → mime-magic-CbV1quyp.d.ts} +1 -1
  189. package/dist/noydb-AMEOU456.js +65 -0
  190. package/dist/on/index.d.ts +2 -3
  191. package/dist/on/index.js +9 -9
  192. package/dist/overlay-views/index.d.ts +4 -5
  193. package/dist/overlay-views/index.js +4 -4
  194. package/dist/periods/index.d.ts +3 -4
  195. package/dist/periods/index.js +12 -12
  196. package/dist/pod/index.d.ts +3 -4
  197. package/dist/pod/index.js +11 -11
  198. package/dist/{policy-Y7HL2KGA.js → policy-SDGRWVIL.js} +5 -5
  199. package/dist/portability/index.d.ts +3 -4
  200. package/dist/portability/index.js +8 -8
  201. package/dist/{post-register-KOFRTCDD.js → post-register-MROIUA5O.js} +6 -6
  202. package/dist/{public-envelope-NC7SPNMV.js → public-envelope-J5PLQOW2.js} +4 -4
  203. package/dist/query/index.d.ts +2 -3
  204. package/dist/query/index.js +9 -6
  205. package/dist/{read-only-facade-5ADRJVTM.js → read-only-facade-UCYNRE56.js} +2 -2
  206. package/dist/register-OVJKW7BS.js +23 -0
  207. package/dist/registry-5RJSK5AJ.js +9 -0
  208. package/dist/registry-DQRNTWRL.js +18 -0
  209. package/dist/registry-LTGLERS7.js +19 -0
  210. package/dist/request-withdrawal-27O77D2V.js +31 -0
  211. package/dist/{reveal-5Y777N5Q.js → reveal-LT3I2K4P.js} +6 -6
  212. package/dist/revoke-4N45G7D2.js +24 -0
  213. package/dist/satellites/index.d.ts +2 -3
  214. package/dist/satellites/index.js +1 -1
  215. package/dist/sealed-record/index.d.ts +3 -4
  216. package/dist/sealed-record/index.js +12 -12
  217. package/dist/session/index.d.ts +4 -5
  218. package/dist/session/index.js +9 -9
  219. package/dist/shadow/index.d.ts +3 -4
  220. package/dist/shadow/index.js +2 -2
  221. package/dist/signer-XJCOV7JD.js +25 -0
  222. package/dist/snapshots/index.d.ts +3 -4
  223. package/dist/snapshots/index.js +10 -10
  224. package/dist/{stale-77LP2EEF.js → stale-IYYXEH3U.js} +2 -2
  225. package/dist/storage-Y44CAPE6.js +23 -0
  226. package/dist/{store-coordination-provider-NJDDZ264.js → store-coordination-provider-WKGAE2WI.js} +3 -3
  227. package/dist/{subject-index-O75wKshW.d.ts → subject-index-CyOgGMJL.d.ts} +18 -0
  228. package/dist/sync/index.d.ts +2 -3
  229. package/dist/sync/index.js +9 -9
  230. package/dist/team/index.d.ts +3 -4
  231. package/dist/team/index.js +15 -15
  232. package/dist/tiers/index.d.ts +2 -3
  233. package/dist/tiers/index.js +10 -10
  234. package/dist/to/index.d.ts +2 -3
  235. package/dist/to/index.js +1 -1
  236. package/dist/{transition-guard-D5RLJv8S.d.ts → transition-guard-BOUkzQU4.d.ts} +1 -1
  237. package/dist/tx/index.d.ts +3 -4
  238. package/dist/tx/index.js +3 -3
  239. package/dist/ui/index.d.ts +4 -1
  240. package/dist/util/index.js +1 -1
  241. package/dist/{validators-C3lrVF-c.d.ts → validators-B3SalxlJ.d.ts} +1 -1
  242. package/dist/{vault-diff-_LSIcOMv.d.ts → vault-diff-Dt9NkLzw.d.ts} +1 -1
  243. package/dist/{verify-CAYGXYNO.js → verify-GH6CWVSW.js} +5 -5
  244. package/dist/{walk-SMAE4IEI.js → walk-76UCFXYV.js} +11 -11
  245. package/dist/with/index.d.ts +3 -4
  246. package/dist/with/index.js +11 -11
  247. package/dist/{with-materialized-view-Xv1-9Bh0.d.ts → with-materialized-view-BP33v1Z6.d.ts} +1 -1
  248. package/dist/{with-overlayed-view-CuWhq_-C.d.ts → with-overlayed-view-DntFpOKN.d.ts} +1 -1
  249. package/dist/{with-rollup-K_iwal4b.d.ts → with-rollup-ClCHO6-4.d.ts} +1 -1
  250. package/dist/withdraw-accessible-6J2UH3IN.js +28 -0
  251. package/package.json +3 -3
  252. package/dist/api-JDWL67WL.js +0 -20
  253. package/dist/chunk-44DV37ZV.js.map +0 -1
  254. package/dist/chunk-47C4CI7R.js +0 -108
  255. package/dist/chunk-47C4CI7R.js.map +0 -1
  256. package/dist/chunk-4VDFUBTS.js.map +0 -1
  257. package/dist/chunk-56JB2BGI.js.map +0 -1
  258. package/dist/chunk-5GQE5IDB.js.map +0 -1
  259. package/dist/chunk-6P5AJ2ZS.js.map +0 -1
  260. package/dist/chunk-6Z435B6N.js.map +0 -1
  261. package/dist/chunk-7KTL4RHZ.js +0 -120
  262. package/dist/chunk-7KTL4RHZ.js.map +0 -1
  263. package/dist/chunk-7TAV3BFQ.js.map +0 -1
  264. package/dist/chunk-BASCPHNM.js.map +0 -1
  265. package/dist/chunk-BM6E4MUT.js.map +0 -1
  266. package/dist/chunk-OJ64XMPJ.js.map +0 -1
  267. package/dist/chunk-PMMV5SQC.js.map +0 -1
  268. package/dist/chunk-PPPZIBKY.js.map +0 -1
  269. package/dist/chunk-REWTMYSF.js.map +0 -1
  270. package/dist/chunk-RUIMKQTA.js.map +0 -1
  271. package/dist/chunk-UB3XHGFG.js.map +0 -1
  272. package/dist/chunk-VK6OC22R.js.map +0 -1
  273. package/dist/chunk-VVLOWY74.js.map +0 -1
  274. package/dist/chunk-X6VNIRHS.js.map +0 -1
  275. package/dist/chunk-XBPUVQDO.js.map +0 -1
  276. package/dist/chunk-YTCCC6NG.js.map +0 -1
  277. package/dist/collection-facade-EJVIN2BY.js +0 -39
  278. package/dist/config-drift-RFEVGBKR.js.map +0 -1
  279. package/dist/delegation-IHXOASHV.js +0 -25
  280. package/dist/derive-CD3ZVIXK.js +0 -21
  281. package/dist/describe-C6iHNlqJ.d.ts +0 -162
  282. package/dist/executor-EUPYHCLU.js +0 -9
  283. package/dist/executor-KZ4B3PRT.js +0 -9
  284. package/dist/executor-MHUM2VZK.js +0 -21
  285. package/dist/export-accessible-Z3NSZZHN.js +0 -23
  286. package/dist/extract-partition-VSKT35JG.js +0 -36
  287. package/dist/find-OH4BEULF.js +0 -11
  288. package/dist/issue-AEOL6RXA.js +0 -19
  289. package/dist/liberate-XSEPRWEA.js +0 -24
  290. package/dist/noydb-POVTIVQ6.js +0 -62
  291. package/dist/register-3D6R2MKN.js +0 -23
  292. package/dist/registry-AI4E46PD.js +0 -19
  293. package/dist/registry-HSNQ4NBP.js +0 -9
  294. package/dist/registry-WIX6QCV5.js +0 -17
  295. package/dist/request-withdrawal-GU4XS2R5.js +0 -31
  296. package/dist/revoke-ZFETCLYV.js +0 -24
  297. package/dist/signer-GAIMQQ2J.js +0 -25
  298. package/dist/storage-KEXIXLGM.js +0 -23
  299. package/dist/withdraw-accessible-Q7BULAN5.js +0 -28
  300. /package/dist/{api-JDWL67WL.js.map → api-HV4PDUWF.js.map} +0 -0
  301. /package/dist/{backup-WRGNTGJF.js.map → backup-ODXO7OGA.js.map} +0 -0
  302. /package/dist/{chunk-XW5LZYV4.js.map → chunk-2HVUKRNE.js.map} +0 -0
  303. /package/dist/{chunk-VFQOE76I.js.map → chunk-2KOSJTMU.js.map} +0 -0
  304. /package/dist/{chunk-2PJB6H3H.js.map → chunk-2UQ4RMHV.js.map} +0 -0
  305. /package/dist/{chunk-VIWOGJ2O.js.map → chunk-34RRRIC3.js.map} +0 -0
  306. /package/dist/{chunk-LFGJG4XZ.js.map → chunk-3I4KYWNP.js.map} +0 -0
  307. /package/dist/{chunk-723XB5NX.js.map → chunk-42KVY3OC.js.map} +0 -0
  308. /package/dist/{chunk-62APEMF3.js.map → chunk-4G3IT7J7.js.map} +0 -0
  309. /package/dist/{chunk-W3JABOVB.js.map → chunk-4RQ62VSR.js.map} +0 -0
  310. /package/dist/{chunk-WPNP3NF5.js.map → chunk-4ZSGSRBI.js.map} +0 -0
  311. /package/dist/{chunk-C4NOLA5X.js.map → chunk-5GAX2QOH.js.map} +0 -0
  312. /package/dist/{chunk-5HRJQSKN.js.map → chunk-5SAZMBW7.js.map} +0 -0
  313. /package/dist/{chunk-II6DLDZM.js.map → chunk-5TZZUL5X.js.map} +0 -0
  314. /package/dist/{chunk-TUL5YQWF.js.map → chunk-6CU3FN74.js.map} +0 -0
  315. /package/dist/{chunk-UWZ7O4NR.js.map → chunk-6MWUANRF.js.map} +0 -0
  316. /package/dist/{chunk-U7LV2DUN.js.map → chunk-6ULCYCMU.js.map} +0 -0
  317. /package/dist/{chunk-GDOOG5YQ.js.map → chunk-7IZQTCTR.js.map} +0 -0
  318. /package/dist/{chunk-TIAYXOEI.js.map → chunk-A4MAD6PO.js.map} +0 -0
  319. /package/dist/{chunk-5HZULLHE.js.map → chunk-AUR7MRHA.js.map} +0 -0
  320. /package/dist/{chunk-P2PEPSPK.js.map → chunk-CJEMA2Z6.js.map} +0 -0
  321. /package/dist/{chunk-L6Q5NA22.js.map → chunk-CTSFKDGA.js.map} +0 -0
  322. /package/dist/{chunk-J5YUA6JB.js.map → chunk-CWCCYH34.js.map} +0 -0
  323. /package/dist/{chunk-NTPWQH4S.js.map → chunk-DEYAO3N6.js.map} +0 -0
  324. /package/dist/{chunk-M3OYTM5Z.js.map → chunk-DFIBOSOQ.js.map} +0 -0
  325. /package/dist/{chunk-FZK3JJJK.js.map → chunk-E5AHO65E.js.map} +0 -0
  326. /package/dist/{chunk-F3EKFGUS.js.map → chunk-FC3XK5JT.js.map} +0 -0
  327. /package/dist/{chunk-3KBK5LU4.js.map → chunk-GX2BYNCT.js.map} +0 -0
  328. /package/dist/{chunk-55LSDABW.js.map → chunk-HBTT7YQX.js.map} +0 -0
  329. /package/dist/{chunk-PYIUDUGS.js.map → chunk-HOCEGVGQ.js.map} +0 -0
  330. /package/dist/{chunk-5JQMX2EH.js.map → chunk-ICV32FPK.js.map} +0 -0
  331. /package/dist/{chunk-37JZR7CY.js.map → chunk-IUKGIB2Q.js.map} +0 -0
  332. /package/dist/{chunk-OWGAXPRE.js.map → chunk-IWTYFS3D.js.map} +0 -0
  333. /package/dist/{chunk-FCOZ7DEC.js.map → chunk-JC5R7P44.js.map} +0 -0
  334. /package/dist/{chunk-IARAK3PK.js.map → chunk-KBBWY46Y.js.map} +0 -0
  335. /package/dist/{chunk-RNYDHIQL.js.map → chunk-KBF5GSGX.js.map} +0 -0
  336. /package/dist/{chunk-GLVXQU3E.js.map → chunk-KNXN47MO.js.map} +0 -0
  337. /package/dist/{chunk-J7OH6R4K.js.map → chunk-KXKLKJW4.js.map} +0 -0
  338. /package/dist/{chunk-PAJCRV7O.js.map → chunk-KYC4LIEU.js.map} +0 -0
  339. /package/dist/{chunk-R4IVPJHN.js.map → chunk-LEOXZY36.js.map} +0 -0
  340. /package/dist/{chunk-25G7PUTA.js.map → chunk-M5I354EE.js.map} +0 -0
  341. /package/dist/{chunk-26FSVNBL.js.map → chunk-MK2BSS44.js.map} +0 -0
  342. /package/dist/{chunk-UMH35AQV.js.map → chunk-MSVY7ULZ.js.map} +0 -0
  343. /package/dist/{chunk-5YBUFCFF.js.map → chunk-NNWK3PSY.js.map} +0 -0
  344. /package/dist/{chunk-X5QZAJDB.js.map → chunk-NSZ6J5DF.js.map} +0 -0
  345. /package/dist/{chunk-I6URUNTD.js.map → chunk-NUEFXLZU.js.map} +0 -0
  346. /package/dist/{chunk-DCVVCJVB.js.map → chunk-OQAODTYR.js.map} +0 -0
  347. /package/dist/{chunk-QYMMMKOO.js.map → chunk-OV4CMJGR.js.map} +0 -0
  348. /package/dist/{chunk-V76PHWLE.js.map → chunk-PEDZFFKW.js.map} +0 -0
  349. /package/dist/{chunk-BJ2XF2RC.js.map → chunk-Q2KVKTC2.js.map} +0 -0
  350. /package/dist/{chunk-QABECTNN.js.map → chunk-QTXQ6RNZ.js.map} +0 -0
  351. /package/dist/{chunk-P6TZ3IVN.js.map → chunk-RJ5522XX.js.map} +0 -0
  352. /package/dist/{chunk-CHK5VVMI.js.map → chunk-S6AQXDN4.js.map} +0 -0
  353. /package/dist/{chunk-MOQ5M46U.js.map → chunk-SZCHEDML.js.map} +0 -0
  354. /package/dist/{chunk-FWART3VA.js.map → chunk-VJ3NQK4K.js.map} +0 -0
  355. /package/dist/{chunk-3YKBGS3I.js.map → chunk-WNA6T3MA.js.map} +0 -0
  356. /package/dist/{chunk-7CZ6YXMO.js.map → chunk-WVCQ75OK.js.map} +0 -0
  357. /package/dist/{chunk-BTFTHF6Q.js.map → chunk-X66MXMR5.js.map} +0 -0
  358. /package/dist/{chunk-6PALJTC2.js.map → chunk-XJK2WIYN.js.map} +0 -0
  359. /package/dist/{chunk-5UOPIPZC.js.map → chunk-XQ7UYPVN.js.map} +0 -0
  360. /package/dist/{chunk-HU55NPHZ.js.map → chunk-Y2OCKFZ3.js.map} +0 -0
  361. /package/dist/{chunk-TCGVRKOS.js.map → chunk-YBFOBRTO.js.map} +0 -0
  362. /package/dist/{chunk-I6JJ6VJS.js.map → chunk-YLN33ADH.js.map} +0 -0
  363. /package/dist/{chunk-PVC2FBCQ.js.map → chunk-Z5YFRMG5.js.map} +0 -0
  364. /package/dist/{chunk-EU7HDMT3.js.map → chunk-ZWQFOXBR.js.map} +0 -0
  365. /package/dist/{collection-facade-EJVIN2BY.js.map → collection-facade-3RAXG2OU.js.map} +0 -0
  366. /package/dist/{computed-6FOFXQPE.js.map → computed-R62HGCO4.js.map} +0 -0
  367. /package/dist/{dead-filter-RHMNYOXP.js.map → dead-filter-VNA4M3GP.js.map} +0 -0
  368. /package/dist/{delegation-IHXOASHV.js.map → delegation-ZKII5UBI.js.map} +0 -0
  369. /package/dist/{derive-CD3ZVIXK.js.map → derive-5D2C3WG4.js.map} +0 -0
  370. /package/dist/{enclave-KGEUW2GL.js.map → enclave-EUBEJGPG.js.map} +0 -0
  371. /package/dist/{executor-EUPYHCLU.js.map → executor-IL7SRNUZ.js.map} +0 -0
  372. /package/dist/{executor-KZ4B3PRT.js.map → executor-OUA2Y6YC.js.map} +0 -0
  373. /package/dist/{executor-MHUM2VZK.js.map → executor-WAEI6PDV.js.map} +0 -0
  374. /package/dist/{export-accessible-Z3NSZZHN.js.map → export-accessible-O55RKAGC.js.map} +0 -0
  375. /package/dist/{extract-partition-VSKT35JG.js.map → extract-partition-VBGCBA6Q.js.map} +0 -0
  376. /package/dist/{fanout-sidecar-NBDKKCHT.js.map → fanout-sidecar-CI5BCMQM.js.map} +0 -0
  377. /package/dist/{find-OH4BEULF.js.map → find-FISGUWSK.js.map} +0 -0
  378. /package/dist/{issue-AEOL6RXA.js.map → issue-EQAWIYFQ.js.map} +0 -0
  379. /package/dist/{ledger-3LR3YO2W.js.map → ledger-AJA2SR3C.js.map} +0 -0
  380. /package/dist/{liberate-XSEPRWEA.js.map → liberate-SEGR4JLY.js.map} +0 -0
  381. /package/dist/{link-set-NSUIX6BR.js.map → link-set-YBAC2DQT.js.map} +0 -0
  382. /package/dist/{noydb-POVTIVQ6.js.map → noydb-AMEOU456.js.map} +0 -0
  383. /package/dist/{policy-Y7HL2KGA.js.map → policy-SDGRWVIL.js.map} +0 -0
  384. /package/dist/{post-register-KOFRTCDD.js.map → post-register-MROIUA5O.js.map} +0 -0
  385. /package/dist/{public-envelope-NC7SPNMV.js.map → public-envelope-J5PLQOW2.js.map} +0 -0
  386. /package/dist/{read-only-facade-5ADRJVTM.js.map → read-only-facade-UCYNRE56.js.map} +0 -0
  387. /package/dist/{register-3D6R2MKN.js.map → register-OVJKW7BS.js.map} +0 -0
  388. /package/dist/{registry-AI4E46PD.js.map → registry-5RJSK5AJ.js.map} +0 -0
  389. /package/dist/{registry-HSNQ4NBP.js.map → registry-DQRNTWRL.js.map} +0 -0
  390. /package/dist/{registry-WIX6QCV5.js.map → registry-LTGLERS7.js.map} +0 -0
  391. /package/dist/{request-withdrawal-GU4XS2R5.js.map → request-withdrawal-27O77D2V.js.map} +0 -0
  392. /package/dist/{reveal-5Y777N5Q.js.map → reveal-LT3I2K4P.js.map} +0 -0
  393. /package/dist/{revoke-ZFETCLYV.js.map → revoke-4N45G7D2.js.map} +0 -0
  394. /package/dist/{signer-GAIMQQ2J.js.map → signer-XJCOV7JD.js.map} +0 -0
  395. /package/dist/{stale-77LP2EEF.js.map → stale-IYYXEH3U.js.map} +0 -0
  396. /package/dist/{storage-KEXIXLGM.js.map → storage-Y44CAPE6.js.map} +0 -0
  397. /package/dist/{store-coordination-provider-NJDDZ264.js.map → store-coordination-provider-WKGAE2WI.js.map} +0 -0
  398. /package/dist/{verify-CAYGXYNO.js.map → verify-GH6CWVSW.js.map} +0 -0
  399. /package/dist/{walk-SMAE4IEI.js.map → walk-76UCFXYV.js.map} +0 -0
  400. /package/dist/{withdraw-accessible-Q7BULAN5.js.map → withdraw-accessible-6J2UH3IN.js.map} +0 -0
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  loadVaultPolicy
3
- } from "./chunk-BTFTHF6Q.js";
3
+ } from "./chunk-X66MXMR5.js";
4
4
  import {
5
5
  jsonSchemaToFields
6
6
  } from "./chunk-TQ3REHVF.js";
@@ -9,31 +9,31 @@ import {
9
9
  SEQUENCE_COLLECTION,
10
10
  compileSequenceFormat,
11
11
  resolveSequenceKey
12
- } from "./chunk-5HRJQSKN.js";
12
+ } from "./chunk-5SAZMBW7.js";
13
13
  import {
14
14
  LinkIntegrityError,
15
15
  isLinkCollectionName,
16
16
  linkCollectionName,
17
17
  linkRowKey
18
- } from "./chunk-V76PHWLE.js";
18
+ } from "./chunk-PEDZFFKW.js";
19
19
  import {
20
20
  hashFields,
21
21
  validateSatelliteDeclaration
22
- } from "./chunk-5JQMX2EH.js";
22
+ } from "./chunk-ICV32FPK.js";
23
23
  import {
24
24
  loadFence
25
- } from "./chunk-L6Q5NA22.js";
25
+ } from "./chunk-CTSFKDGA.js";
26
26
  import {
27
27
  InvertedIndex,
28
28
  MemoryIndexStore,
29
29
  NO_SEARCH,
30
30
  VectorSet,
31
31
  buildPersistedIndexCallbacks
32
- } from "./chunk-XBPUVQDO.js";
32
+ } from "./chunk-FGOGS7CE.js";
33
33
  import {
34
34
  derivePersistedSchema,
35
35
  isZod4Schema
36
- } from "./chunk-55LSDABW.js";
36
+ } from "./chunk-HBTT7YQX.js";
37
37
  import {
38
38
  isBaseLive,
39
39
  liveBaseIdSet
@@ -42,7 +42,7 @@ import {
42
42
  CustodyApi,
43
43
  NO_CARGO,
44
44
  NO_CUSTODY
45
- } from "./chunk-723XB5NX.js";
45
+ } from "./chunk-42KVY3OC.js";
46
46
  import {
47
47
  ServiceBus,
48
48
  WriteHookRegistry,
@@ -50,49 +50,59 @@ import {
50
50
  assertCanImport,
51
51
  canExport,
52
52
  canImport
53
- } from "./chunk-QABECTNN.js";
53
+ } from "./chunk-QTXQ6RNZ.js";
54
54
  import {
55
55
  NO_ATTESTATION
56
- } from "./chunk-LFGJG4XZ.js";
56
+ } from "./chunk-3I4KYWNP.js";
57
57
  import {
58
58
  NO_CLASSIFIED,
59
- enforceClassifiedWrite,
59
+ guardClassifiedCompat,
60
60
  resolveClassifiedFields
61
- } from "./chunk-47C4CI7R.js";
61
+ } from "./chunk-V7RNIND6.js";
62
62
  import {
63
63
  NO_TIERS,
64
64
  classifySealedShred
65
- } from "./chunk-4VDFUBTS.js";
65
+ } from "./chunk-SSFC3IYA.js";
66
66
  import {
67
67
  NO_PORTABILITY
68
- } from "./chunk-II6DLDZM.js";
68
+ } from "./chunk-5TZZUL5X.js";
69
69
  import {
70
70
  runDrainBarrier
71
- } from "./chunk-EU7HDMT3.js";
71
+ } from "./chunk-ZWQFOXBR.js";
72
72
  import {
73
73
  PERIODS_COLLECTION,
74
74
  PERIOD_ARCHIVES_COLLECTION,
75
75
  PERIOD_FREEZES_COLLECTION,
76
76
  PERIOD_TARGET_PURGES_COLLECTION,
77
77
  periodExclusiveUpperBound
78
- } from "./chunk-37JZR7CY.js";
78
+ } from "./chunk-IUKGIB2Q.js";
79
79
  import {
80
80
  TxContext,
81
81
  revertExecuted
82
- } from "./chunk-FCOZ7DEC.js";
82
+ } from "./chunk-JC5R7P44.js";
83
+ import {
84
+ DEFAULT_POSTURE,
85
+ ViaGraph
86
+ } from "./chunk-7ZANJ6JZ.js";
87
+ import {
88
+ forgetDerivedFanout,
89
+ ledgerAuditHook,
90
+ putDerivedOutput,
91
+ runGraphDispatchWave
92
+ } from "./chunk-H57P2SJB.js";
83
93
  import {
84
94
  OverlayedCollection
85
- } from "./chunk-MOQ5M46U.js";
95
+ } from "./chunk-SZCHEDML.js";
86
96
  import {
87
97
  EXPORT_AUDIT_COLLECTION,
88
98
  createExportBlobsHandle,
89
99
  runCompaction
90
- } from "./chunk-PPPZIBKY.js";
100
+ } from "./chunk-NYVQCZ54.js";
91
101
  import {
92
102
  LazyQuery,
93
103
  decodeIdxId,
94
104
  encodeIdxId
95
- } from "./chunk-6PALJTC2.js";
105
+ } from "./chunk-XJK2WIYN.js";
96
106
  import {
97
107
  IMPLICIT_LAZY,
98
108
  Lru,
@@ -101,11 +111,10 @@ import {
101
111
  import {
102
112
  resolveManagedSecret,
103
113
  saveSealedPassphrase
104
- } from "./chunk-P2PEPSPK.js";
114
+ } from "./chunk-CJEMA2Z6.js";
105
115
  import {
106
- isDictCollectionName,
107
- isStaticDictDescriptor
108
- } from "./chunk-PMMV5SQC.js";
116
+ dictCollectionName
117
+ } from "./chunk-IIP5WFVT.js";
109
118
  import {
110
119
  NO_TEAM,
111
120
  enrollAuthenticator,
@@ -116,7 +125,7 @@ import {
116
125
  rotatePassphrase,
117
126
  updateAuthenticator,
118
127
  writeMagicLinkGrant
119
- } from "./chunk-NTPWQH4S.js";
128
+ } from "./chunk-DEYAO3N6.js";
120
129
  import {
121
130
  hasRecoveryEnrolled,
122
131
  hasStrongRecoveryEnrolled,
@@ -126,7 +135,7 @@ import {
126
135
  mintShamirRecoveryEntry,
127
136
  savePaperRecoveryEntries,
128
137
  saveShamirRecoveryEntries
129
- } from "./chunk-FWART3VA.js";
138
+ } from "./chunk-VJ3NQK4K.js";
130
139
  import {
131
140
  INDEXED_STORE_POLICY
132
141
  } from "./chunk-VQNJ7UZL.js";
@@ -142,17 +151,17 @@ import {
142
151
  listUsers,
143
152
  loadKeyring,
144
153
  updateKeyringIdentity
145
- } from "./chunk-TUL5YQWF.js";
154
+ } from "./chunk-6CU3FN74.js";
146
155
  import {
147
156
  persistDirectoryConfig,
148
157
  readDirectoryConfig
149
- } from "./chunk-FZK3JJJK.js";
158
+ } from "./chunk-E5AHO65E.js";
150
159
  import {
151
160
  generateULID
152
161
  } from "./chunk-ZJADGNYF.js";
153
162
  import {
154
163
  sha256Hex
155
- } from "./chunk-UMH35AQV.js";
164
+ } from "./chunk-MSVY7ULZ.js";
156
165
  import {
157
166
  NO_FORGET,
158
167
  addSubjectRef,
@@ -161,50 +170,59 @@ import {
161
170
  readDottedPath,
162
171
  rebuildSubjectIndex,
163
172
  removeSubjectRef
164
- } from "./chunk-VVLOWY74.js";
173
+ } from "./chunk-A2APWS32.js";
165
174
  import {
166
175
  NO_SEALED_RECORD
167
- } from "./chunk-XW5LZYV4.js";
176
+ } from "./chunk-2HVUKRNE.js";
168
177
  import {
169
178
  RecordCodec,
170
179
  SEALED_CEK_NS,
171
180
  findByDet,
172
181
  hasPerRecordKey,
182
+ makeReservedEnvelopes,
173
183
  openEnvelopeJson,
174
184
  queryByDet,
175
185
  resolveStableCek,
176
186
  validateSchemaInput
177
- } from "./chunk-7TAV3BFQ.js";
187
+ } from "./chunk-XHD3L3JO.js";
178
188
  import {
179
189
  buildDeleteMarker,
180
190
  buildTombstone,
181
191
  isDeleteMarker,
182
192
  isTombstone
183
- } from "./chunk-PVC2FBCQ.js";
184
- import {
185
- NOYDB_FORMAT_VERSION
186
- } from "./chunk-REWTMYSF.js";
193
+ } from "./chunk-Z5YFRMG5.js";
187
194
  import {
188
195
  encrypt
189
- } from "./chunk-5HZULLHE.js";
196
+ } from "./chunk-AUR7MRHA.js";
197
+ import {
198
+ NOYDB_FORMAT_VERSION,
199
+ SealedHandle
200
+ } from "./chunk-UNJU345Q.js";
190
201
  import {
191
202
  NO_AGGREGATE,
192
203
  Query,
193
204
  ScanBuilder
194
- } from "./chunk-6Z435B6N.js";
205
+ } from "./chunk-7HAPHVRJ.js";
195
206
  import {
196
207
  canonicalGroupKey
197
- } from "./chunk-5GQE5IDB.js";
208
+ } from "./chunk-2XE6LIXT.js";
198
209
  import {
199
- moneyRuntime,
200
210
  readPath
201
- } from "./chunk-6P5AJ2ZS.js";
211
+ } from "./chunk-KCG3BHFG.js";
202
212
  import {
213
+ NO_I18N,
203
214
  getAtPath,
204
- resolvePolicy,
205
- setAtPathInPlace,
206
- stripI18nFilled
207
- } from "./chunk-BASCPHNM.js";
215
+ isDictCollectionName,
216
+ isDictKeyDescriptor,
217
+ isI18nTextDescriptor,
218
+ isStaticDictDescriptor,
219
+ presentI18nForJoin
220
+ } from "./chunk-3C5CKXFI.js";
221
+ import {
222
+ installViaBinder,
223
+ isViaInstalled,
224
+ viaBinder
225
+ } from "./chunk-6G2SCQYS.js";
208
226
  import {
209
227
  AlreadyElevatedError,
210
228
  AttestationError,
@@ -214,12 +232,13 @@ import {
214
232
  ConflictError,
215
233
  DebugPlaintextError,
216
234
  DerivationCapExceededError,
235
+ DictKeyInUseError,
217
236
  ElevationExpiredError,
237
+ FieldNotQueryableError,
218
238
  ForgetStrategyNotConfiguredError,
219
239
  IndexWriteFailureError,
220
240
  InvalidKeyError,
221
241
  KeyringCorruptError,
222
- LocaleNotSpecifiedError,
223
242
  ManagedRecoveryNotEnrolledError,
224
243
  MigrationRequiredError,
225
244
  NoAccessError,
@@ -236,85 +255,281 @@ import {
236
255
  StaticDictReadonlyError,
237
256
  StoreCapabilityError,
238
257
  TierNotGrantedError,
239
- TranslatorNotConfiguredError,
240
258
  USER_ENVELOPE_COLLECTION,
241
259
  UniqueConstraintError,
242
260
  UnknownDictCodeError,
243
261
  UnsupportedIndexOptionError,
244
262
  ValidationError
245
- } from "./chunk-OJ64XMPJ.js";
263
+ } from "./chunk-CU62BBRB.js";
246
264
 
247
265
  // src/port/by/default-provider.ts
248
266
  async function createDefaultCoordinationProvider(store) {
249
- const { StoreCoordinationProvider } = await import("./store-coordination-provider-NJDDZ264.js");
267
+ const { StoreCoordinationProvider } = await import("./store-coordination-provider-WKGAE2WI.js");
250
268
  return new StoreCoordinationProvider(store);
251
269
  }
252
270
 
253
- // src/with-shape/classified/guards.ts
254
- function guardClassifiedCompat(collection, byField, ctx) {
255
- const digestOnly = Object.entries(byField).filter(([, s]) => s.storage === "digest-only");
256
- const protectedForms = Object.values(byField).filter((s) => s.storage === "digest-only" || s.storage === "recoverable");
257
- if (protectedForms.length > 0 && (ctx.crdt || ctx.hasConflictPolicy)) {
258
- throw new ClassifiedConfigError(
259
- collection,
260
- "digest-only/recoverable classified fields cannot combine with a crdt mode or a conflictPolicy resolver \u2014 merge paths bypass write enforcement (R2)"
261
- );
262
- }
263
- for (const [f, spec] of Object.entries(byField)) {
264
- if (spec.equatable === true && spec.storage !== "digest-only") {
265
- throw new ClassifiedConfigError(
266
- collection,
267
- `field "${f}" declares equatable but storage is '${spec.storage}' \u2014 equatable is a digest-only knob; recoverable equality is _det's job (R7)`
268
- );
271
+ // src/kernel/via-pipeline.ts
272
+ var ViaPipeline = class _ViaPipeline {
273
+ constructor(bindings, taint) {
274
+ this.bindings = bindings;
275
+ this.taint = taint;
276
+ }
277
+ bindings;
278
+ taint;
279
+ /** undefined when there is nothing to enforce — the zero-via fast path is
280
+ * `this.via === undefined` (#553: keeps an all-plain collection sync). */
281
+ static build(bindings, taint) {
282
+ if (bindings.length === 0 && (!taint || taint.postures.size === 0)) return void 0;
283
+ return new _ViaPipeline(bindings, taint);
284
+ }
285
+ /** Refuse a write before crypto runs: awaits each binding's `enforceWrite` in order first throw wins. */
286
+ async enforceWrite(record, ctx) {
287
+ for (const b of this.bindings) if (b.enforceWrite) await b.enforceWrite(record, ctx);
288
+ }
289
+ ingest(record) {
290
+ let r = record;
291
+ for (const b of this.bindings) if (b.ingest) r = b.ingest(r);
292
+ return r;
293
+ }
294
+ canonicalizeStored(record) {
295
+ let r = record;
296
+ for (const b of this.bindings) if (b.canonicalizeStored) r = b.canonicalizeStored(r);
297
+ return r;
298
+ }
299
+ async encodeWrite(record, ctx) {
300
+ let r = record;
301
+ for (const b of this.bindings) if (b.encodeWrite) r = await b.encodeWrite(r, ctx);
302
+ return r;
303
+ }
304
+ /**
305
+ * Final write-pipeline stage: folds each binding's `encodeAtRest` in
306
+ * order, threading the record and accumulating the sealed-slot map. A
307
+ * field belongs to exactly one via feature — two bindings sealing the
308
+ * same field is a brand-keyed collision and throws.
309
+ */
310
+ async encodeAtRest(record, crypto) {
311
+ let r = record;
312
+ let sealed;
313
+ const sealedBy = /* @__PURE__ */ new Map();
314
+ for (const b of this.bindings) {
315
+ if (!b.encodeAtRest) continue;
316
+ const result = await b.encodeAtRest(r, crypto);
317
+ r = result.record;
318
+ if (!result.sealed) continue;
319
+ for (const [field, ref2] of Object.entries(result.sealed)) {
320
+ const priorBrand = sealedBy.get(field);
321
+ if (priorBrand !== void 0) {
322
+ throw new ValidationError(
323
+ `via encodeAtRest: field "${field}" sealed by binding "${priorBrand}" is also sealed by binding "${b.brand}"`
324
+ );
325
+ }
326
+ sealedBy.set(field, b.brand);
327
+ sealed ??= {};
328
+ sealed[field] = ref2;
329
+ }
269
330
  }
331
+ return sealed ? { record: r, sealed } : { record: r };
270
332
  }
271
- const hasEquatable = Object.values(byField).some((s) => s.equatable === true);
272
- if (hasEquatable && ctx.acknowledgeEquatableRisk !== true) {
273
- throw new ClassifiedConfigError(
274
- collection,
275
- `classified equatable fields require \`acknowledgeEquatableRisk: true\` \u2014 equal values produce equal store-visible index tags (a partition leak a DEK holder can offline-dictionary at the PBKDF2 floor); the door is the real control for low-entropy fields (R8)`
276
- );
333
+ /** First read-pipeline stage: folds each binding's `decodeAtRest` in order. */
334
+ async decodeAtRest(record, sealed, crypto, opts) {
335
+ let r = record;
336
+ for (const b of this.bindings) if (b.decodeAtRest) r = await b.decodeAtRest(r, sealed, crypto, opts);
337
+ return r;
277
338
  }
278
- if (digestOnly.length === 0) return;
279
- if (!ctx.storeCiphertext) {
280
- throw new ClassifiedConfigError(
281
- collection,
282
- `storage:'digest-only' requires an encrypted collection \u2014 with encrypt: false the write path skips the digest slot entirely and the secret would persist as raw plaintext`
283
- );
339
+ async present(record, ctx) {
340
+ let r = record;
341
+ for (const b of this.bindings) if (b.present) r = await b.present(r, ctx);
342
+ return r;
343
+ }
344
+ buildClause(field, op, value) {
345
+ for (const b of this.bindings) {
346
+ const payload = b.buildClause?.(field, op, value);
347
+ if (payload !== void 0) return { brand: b.brand, payload };
348
+ }
349
+ return void 0;
350
+ }
351
+ evaluateClause(clause, actual, op) {
352
+ const b = this.bindings.find((x) => x.brand === clause.brand);
353
+ return b?.evaluateClause ? b.evaluateClause(actual, op, clause.payload) : false;
354
+ }
355
+ decodeResults(record) {
356
+ let r = record;
357
+ for (const b of this.bindings) if (b.decodeResults) r = b.decodeResults(r);
358
+ return r;
284
359
  }
285
- if (!ctx.perRecordKeys) {
286
- throw new ClassifiedConfigError(
287
- collection,
288
- `storage:'digest-only' requires perRecordKeys: true \u2014 vdig keys derive from the per-record CEK so forget() shreds them (R1)`
289
- );
360
+ /** undefined = no binding covers the field → caller falls back to generic compare. */
361
+ compareForOrder(field, a, b) {
362
+ for (const bind of this.bindings) {
363
+ const c = bind.compareForOrder?.(field, a, b);
364
+ if (c !== void 0) return c;
365
+ }
366
+ return void 0;
367
+ }
368
+ /** undefined = no binding resolves an order-sort label for `key` at `field`/`locale` (#650 Task 7 —
369
+ * the `orderBy(..., {by:'label'})` per-call-locale channel `compareForOrder` above can't serve). */
370
+ resolveOrderLabel(field, key, locale) {
371
+ for (const bind of this.bindings) {
372
+ const label = bind.resolveOrderLabel?.(field, key, locale);
373
+ if (label !== void 0) return label;
374
+ }
375
+ return void 0;
376
+ }
377
+ /**
378
+ * Which posture governs `field`, if any binding covers it — `undefined`
379
+ * means no binding declares `field` (the generic, non-via query path
380
+ * applies). #629 Task 8's posture consumer: `.where()`/`.orderBy()`
381
+ * consult this before building/evaluating a clause, refusing fields whose
382
+ * posture is `queryable: 'none'` (e.g. blob); every other posture
383
+ * ('det-exact'/'ordered'/'full') is left to the existing per-binding
384
+ * buildClause/evaluateClause/compareForOrder machinery, unchanged.
385
+ *
386
+ * #638 Task 3: the graph-computed taint overlay is consulted FIRST — a
387
+ * derived field's assigned (most-restrictive-of-sources) posture wins over
388
+ * whatever a binding would otherwise report for that field name (no
389
+ * binding covers a computed/derived field today, so this never actually
390
+ * shadows one — see `via-taint-binding.ts`'s `taintBinding.covers`, which
391
+ * only claims the sealed subset for `encodeAtRest`/`decodeAtRest`, not for
392
+ * this lookup).
393
+ */
394
+ postureFor(field) {
395
+ const t = this.taint?.postures.get(field);
396
+ if (t) return t;
397
+ for (const b of this.bindings) {
398
+ if (b.covers?.(field)) return b.posture;
399
+ }
400
+ return void 0;
401
+ }
402
+ /**
403
+ * Refuse any field-based reducer over a `queryable: 'none'` field (blob) —
404
+ * metadata-only: walks each reducer's `.field` and checks posture, no
405
+ * rewriting. Reducers with no `.field` (e.g. `count()`) are skipped.
406
+ *
407
+ * Shared by `wrapReducers` below (which ALSO applies each binding's
408
+ * rewrite, e.g. money's exact-BigInt reducer swap) and by
409
+ * `ScanBuilder.aggregate()` (#629 Task 8 review fix wave 1), which must
410
+ * call THIS, not `wrapReducers` — wiring full `wrapReducers` into
411
+ * `ScanBuilder.aggregate()` would newly activate money/i18n reducer
412
+ * wrapping on a path that has never run it, a parity-breaking behavior
413
+ * change for existing brands.
414
+ */
415
+ refuseUnqueryableReducers(spec) {
416
+ for (const reducer of Object.values(spec)) {
417
+ const field = reducer?.field;
418
+ if (field !== void 0 && this.postureFor(field)?.queryable === "none") {
419
+ throw new FieldNotQueryableError(field);
420
+ }
421
+ }
422
+ }
423
+ /**
424
+ * Rewrite an aggregate spec (money exact reducers), then refuse any
425
+ * field-based reducer over a `queryable: 'none'` field (blob) — the same
426
+ * posture gate `.where()`/`.orderBy()` apply, extended to `.aggregate()`
427
+ * (both the bare-spec and builder forms funnel through here).
428
+ */
429
+ wrapReducers(spec) {
430
+ this.refuseUnqueryableReducers(spec);
431
+ let s = spec;
432
+ for (const b of this.bindings) if (b.wrapReducers) s = b.wrapReducers(s);
433
+ return s;
434
+ }
435
+ /**
436
+ * Deliberate export-layer redaction (#629 Task 9's posture consumer):
437
+ * for every OWN field the decoded record carries, if the covering
438
+ * binding's posture declares `exportable: false` (classified today;
439
+ * money/i18n/blob are all `true`), replace the value with
440
+ * {@link EXPORT_REDACTION_MARKER} — the same `'[sealed]'` string
441
+ * `SealedHandle.toJSON()` (`kernel/types.ts`) already emits, so this is
442
+ * byte-parity with today's accidental redaction, now independent of the
443
+ * field's runtime shape. `SealedHandle.toJSON()` stays untouched as
444
+ * defense-in-depth: a record read via `collection.get()` and
445
+ * JSON.stringify'd outside `exportStream()` still gets the accident's
446
+ * redaction on its own. Never mutates the input; returns a new object
447
+ * only when a field was actually redacted.
448
+ */
449
+ redactForExport(record) {
450
+ let out;
451
+ for (const field of Object.keys(record)) {
452
+ if (this.postureFor(field)?.exportable === false) {
453
+ out ??= { ...record };
454
+ out[field] = EXPORT_REDACTION_MARKER;
455
+ }
456
+ }
457
+ return out ?? record;
458
+ }
459
+ /**
460
+ * Shared fold over a subset of `this.bindings`: runs each binding's
461
+ * `erase`, concatenating shredded/retainedShared counts and residue.
462
+ * Consults `posture.forgettable` (#629 Task 10) — a binding declaring
463
+ * `forgettable: false` is skipped even if it defines `erase` (none does
464
+ * today; a future non-forgettable binding is supported without a brand check).
465
+ */
466
+ async foldErase(ctx, bindings) {
467
+ let shredded = 0;
468
+ let retainedShared = 0;
469
+ const residue = [];
470
+ for (const b of bindings) {
471
+ if (!b.erase || b.posture.forgettable === false) continue;
472
+ const report = await b.erase(ctx);
473
+ shredded += report.shredded;
474
+ retainedShared += report.retainedShared ?? 0;
475
+ residue.push(...report.residue);
476
+ }
477
+ return retainedShared > 0 ? { shredded, residue, retainedShared } : { shredded, residue };
478
+ }
479
+ /** `forget()`'s per-ref erasure door: folds every binding's `erase`. */
480
+ async erase(ctx) {
481
+ return this.foldErase(ctx, this.bindings);
482
+ }
483
+ /**
484
+ * `forget()`'s SEALED-posture-only erase fold (#629 Task 10, `Collection._onViaErase`'s
485
+ * one caller) — classified today; metadata-filtered on
486
+ * `posture.encryptedAtRest`, not brand-checked. `undefined` when no
487
+ * sealed-posture binding is compiled in — `vault.ts` then falls back to
488
+ * its own bare-`sensitive` classification, which no via binding covers.
489
+ */
490
+ async eraseSealed(ctx) {
491
+ const sealed = this.bindings.filter((b) => b.posture.encryptedAtRest === "sealed");
492
+ return sealed.length === 0 ? void 0 : this.foldErase(ctx, sealed);
290
493
  }
291
- for (const [f, spec] of digestOnly) {
292
- const n = spec.notLastN ?? 0;
293
- if (!Number.isInteger(n) || n < 0 || n > 8) {
294
- throw new ClassifiedConfigError(
295
- collection,
296
- `digest-only field "${f}" declares notLastN ${n} \u2014 must be an integer 0..8 (write cost is n \xD7 600K PBKDF2; ring blast radius is documented)`
297
- );
298
- }
299
- if (ctx.bareSensitiveFields.has(f)) {
300
- throw new ClassifiedConfigError(
301
- collection,
302
- `field "${f}" is digest-only AND listed in sensitive[] \u2014 storage forms are mutually exclusive per field (R5)`
303
- );
304
- }
305
- if (ctx.deterministicFields?.has(f)) {
306
- throw new ClassifiedConfigError(
307
- collection,
308
- `digest-only field "${f}" cannot be deterministic \u2014 equality-correlatable ciphertext defeats per-write salts (R3)`
309
- );
310
- }
311
- if (ctx.indexedFields.has(f) || ctx.textIndexFields.has(f) || ctx.vectorSourceFields.has(f) || ctx.subjectKeyField === f) {
312
- throw new ClassifiedConfigError(
313
- collection,
314
- `digest-only field "${f}" cannot be indexed, text-indexed, embedded, or a forget-subject key (R4)`
315
- );
494
+ /** True iff any binding implements decodeResults (query fast-path gating). */
495
+ get hasResultDecode() {
496
+ return this.bindings.some((b) => b.decodeResults !== void 0);
497
+ }
498
+ /**
499
+ * True iff any binding declares an at-rest hook (`encodeAtRest`/
500
+ * `decodeAtRest`) — the codec boundary's async-stack gate (#629 Task 3
501
+ * consults this to choose between the hook path and today's inline
502
+ * sealed-slot path; a stack with only sync hooks, e.g. money-only, stays
503
+ * `false`).
504
+ */
505
+ get hasAtRestHooks() {
506
+ return this.bindings.some((b) => b.encodeAtRest !== void 0 || b.decodeAtRest !== void 0);
507
+ }
508
+ /**
509
+ * Fold every binding's `describeFragment()` into one `brand -> fragment`
510
+ * map (#650 Task 7 — the first-ever consumer; `describeFragment` was
511
+ * declared at `via.ts:136` since #623 with zero callers until this task).
512
+ * `undefined` when no compiled binding implements it. Consumed by
513
+ * `Collection.describe()`/`describeAsync()`, threaded to `buildDescription`
514
+ * as `BuildDescriptionInput.viaFragments`.
515
+ */
516
+ describeFragments() {
517
+ let out;
518
+ for (const b of this.bindings) {
519
+ const f = b.describeFragment?.();
520
+ if (f !== void 0) {
521
+ out ??= {};
522
+ out[b.brand] = f;
523
+ }
316
524
  }
525
+ return out;
317
526
  }
527
+ };
528
+ var EXPORT_REDACTION_MARKER = "[sealed]";
529
+ function exportRedact(coll, record) {
530
+ if (record === null || typeof record !== "object") return record;
531
+ const via2 = coll.via;
532
+ return via2 ? via2.redactForExport(record) : record;
318
533
  }
319
534
 
320
535
  // src/with-lookup/indexing/collection-facade.ts
@@ -402,7 +617,7 @@ async function rebuildIndexes(ctx) {
402
617
  for (const recordId of canonicalIds) {
403
618
  const envelope = await ctx.adapter.get(ctx.vault, ctx.name, recordId);
404
619
  if (!envelope) continue;
405
- const record = await ctx.codec.decryptRecord(envelope, { skipValidation: true });
620
+ const record = await ctx.codec.decryptRecord(envelope, { skipValidation: true, id: recordId });
406
621
  if (record === null) continue;
407
622
  await maintainPersistedIndexesOnPut(ctx, recordId, record, null, envelope._v);
408
623
  }
@@ -455,7 +670,7 @@ async function reconcileIndex(ctx, field, opts = {}) {
455
670
  if (id.startsWith("_")) continue;
456
671
  const env = await ctx.adapter.get(ctx.vault, ctx.name, id);
457
672
  if (!env) continue;
458
- const record = await ctx.codec.decryptRecord(env, { skipValidation: true });
673
+ const record = await ctx.codec.decryptRecord(env, { skipValidation: true, id });
459
674
  if (record === null) continue;
460
675
  const live = readPersistedValue(record, field);
461
676
  const stored = sidecar.get(id);
@@ -879,13 +1094,14 @@ function deriveWidget(opts) {
879
1094
  case "percent":
880
1095
  return "number";
881
1096
  }
882
- if (opts.dict !== void 0) return "select";
1097
+ if (opts.dict !== void 0 || opts.lookup !== void 0) return "select";
883
1098
  if (opts.type === "boolean") return "checkbox";
884
1099
  if (opts.type === "number") return "number";
885
1100
  return "text";
886
1101
  }
887
1102
  function buildDescription(input) {
888
- const { collection, fieldMeta, moneyFields, dictKeyFields, computed, refs, zodFields, dictLabels, meta, i18nFields, classified } = input;
1103
+ const { collection, fieldMeta, moneyFields, dictKeyFields, lookupFields, computed, refs, zodFields, dictLabels, meta, i18nFields, classified, taint } = input;
1104
+ const lookupFragments = input.viaFragments?.["lookup"]?.lookupFields;
889
1105
  if (zodFields !== void 0 && Object.keys(zodFields).length > 0 && fieldMeta !== void 0) {
890
1106
  const knownFields = /* @__PURE__ */ new Set([
891
1107
  ...Object.keys(moneyFields ?? {}),
@@ -893,13 +1109,15 @@ function buildDescription(input) {
893
1109
  ...Object.keys(refs),
894
1110
  ...Object.keys(computed ?? {}),
895
1111
  ...Object.keys(zodFields),
896
- ...Object.keys(i18nFields ?? {})
1112
+ ...Object.keys(i18nFields ?? {}),
1113
+ ...Object.keys(lookupFields ?? {})
897
1114
  ]);
898
1115
  validateFieldMetaKeys(collection, fieldMeta, knownFields);
899
1116
  }
900
1117
  const allKeys = /* @__PURE__ */ new Set([
901
1118
  ...Object.keys(moneyFields ?? {}),
902
1119
  ...Object.keys(dictKeyFields ?? {}),
1120
+ ...Object.keys(lookupFields ?? {}),
903
1121
  ...Object.keys(refs),
904
1122
  ...Object.keys(computed ?? {}),
905
1123
  ...Object.keys(fieldMeta ?? {}),
@@ -912,15 +1130,18 @@ function buildDescription(input) {
912
1130
  const zod = zodFields?.[key];
913
1131
  const money = moneyFields?.[key];
914
1132
  const dict = dictKeyFields?.[key];
1133
+ const lookupDesc = lookupFields?.[key];
915
1134
  const refDesc = refs[key];
916
1135
  const isComputed = computed !== void 0 && key in computed;
917
1136
  const i18nDesc = i18nFields?.[key];
918
1137
  const cls = classified?.[key];
1138
+ const taintPosture = taint?.postures.get(key);
919
1139
  let type = zod?.type ?? "unknown";
920
1140
  const inferred = {};
921
1141
  let moneyBlock;
922
1142
  let dictBlock;
923
1143
  let refBlock;
1144
+ let lookupBlock;
924
1145
  if (money) {
925
1146
  type = "number";
926
1147
  inferred.semanticType = "currency";
@@ -967,10 +1188,49 @@ function buildDescription(input) {
967
1188
  dictBlock = { name: dict.name, static: false };
968
1189
  }
969
1190
  }
1191
+ } else if (lookupDesc) {
1192
+ type = "enum";
1193
+ const lookupLabelMap = dictLabels?.[lookupDesc.dimension];
1194
+ const lookupLabelMapHasEntries = lookupLabelMap !== void 0 && Object.keys(lookupLabelMap).length > 0;
1195
+ if (lookupDesc.table !== void 0) {
1196
+ const displayLocale = lookupDesc.displayLocale;
1197
+ const table = lookupDesc.table;
1198
+ const values = (lookupDesc.keys ?? Object.keys(table)).map((k) => {
1199
+ const localeMap = table[k];
1200
+ const label = displayLocale !== void 0 ? localeMap?.[displayLocale] ?? void 0 : void 0;
1201
+ return label !== void 0 ? { value: k, label } : { value: k };
1202
+ });
1203
+ dictBlock = { name: lookupDesc.dimension, static: true, values };
1204
+ } else if (lookupLabelMapHasEntries) {
1205
+ const values = Object.entries(lookupLabelMap).map(([value, label]) => ({ value, label }));
1206
+ dictBlock = { name: lookupDesc.dimension, static: false, values };
1207
+ } else if (lookupDesc.keys !== void 0) {
1208
+ const values = lookupDesc.keys.map((k) => {
1209
+ const label = lookupDesc.labels?.[k];
1210
+ return label !== void 0 ? { value: k, label } : { value: k };
1211
+ });
1212
+ dictBlock = { name: lookupDesc.dimension, static: false, values };
1213
+ } else {
1214
+ dictBlock = { name: lookupDesc.dimension, static: false };
1215
+ }
970
1216
  } else if (i18nDesc) {
971
1217
  type = "string";
972
1218
  } else if (isComputed) {
973
1219
  }
1220
+ const lookupFragmentEntry = lookupFragments?.[key];
1221
+ if (lookupFragmentEntry !== void 0) {
1222
+ lookupBlock = {
1223
+ ...lookupFragmentEntry.dimension !== void 0 ? { dimension: lookupFragmentEntry.dimension } : {},
1224
+ backing: lookupFragmentEntry.backing,
1225
+ vocabulary: lookupFragmentEntry.vocabulary,
1226
+ key: lookupFragmentEntry.key,
1227
+ onDelete: lookupFragmentEntry.onDelete,
1228
+ ...lookupFragmentEntry.altKeys !== void 0 ? { altKeys: lookupFragmentEntry.altKeys } : {},
1229
+ ...lookupFragmentEntry.present !== void 0 ? { present: lookupFragmentEntry.present } : {},
1230
+ ...lookupFragmentEntry.sortBy !== void 0 ? { sortBy: lookupFragmentEntry.sortBy } : {},
1231
+ ...lookupFragmentEntry.keys !== void 0 ? { keys: lookupFragmentEntry.keys } : {}
1232
+ };
1233
+ }
974
1234
  if (cls !== void 0) inferred.sensitivity = cls.sensitivity;
975
1235
  const channelEntry = fieldMeta?.[key];
976
1236
  const zodMeta = zod?.meta;
@@ -992,6 +1252,7 @@ function buildDescription(input) {
992
1252
  type,
993
1253
  ...resolved.semanticType !== void 0 ? { semanticType: resolved.semanticType } : {},
994
1254
  ...dictBlock !== void 0 ? { dict: dictBlock } : {},
1255
+ ...lookupBlock !== void 0 ? { lookup: lookupBlock } : {},
995
1256
  ...resolved.widget !== void 0 ? { resolvedWidget: resolved.widget } : {}
996
1257
  });
997
1258
  const editable = !isComputed && key !== "id";
@@ -1013,6 +1274,7 @@ function buildDescription(input) {
1013
1274
  ...refBlock !== void 0 ? { ref: refBlock } : {},
1014
1275
  ...moneyBlock !== void 0 ? { money: moneyBlock } : {},
1015
1276
  ...dictBlock !== void 0 ? { dict: dictBlock } : {},
1277
+ ...lookupBlock !== void 0 ? { lookup: lookupBlock } : {},
1016
1278
  ...isComputed ? { computed: true } : {},
1017
1279
  ...i18nBlock !== void 0 ? { i18n: i18nBlock } : {},
1018
1280
  widget,
@@ -1024,7 +1286,8 @@ function buildDescription(input) {
1024
1286
  list: cls.list.kind === "omit" ? "omit" : cls.list.kind === "mask" ? { mask: cls.list.pattern } : { rider: cls.list.rider },
1025
1287
  ...cls.equatable === true ? { equatable: true } : {}
1026
1288
  }
1027
- } : {}
1289
+ } : {},
1290
+ ...taintPosture !== void 0 ? { taint: { posture: taintPosture, forcedBy: taint?.provenance?.get(key) ?? [] } } : {}
1028
1291
  };
1029
1292
  fields.push(field);
1030
1293
  }
@@ -1075,7 +1338,7 @@ async function resolveStaleOnRead(accessor, outputCollection, id) {
1075
1338
  }
1076
1339
  const sourceWithId = { ...source, id };
1077
1340
  if (DerivationExecutor === null) {
1078
- ({ DerivationExecutor } = await import("./executor-EUPYHCLU.js"));
1341
+ ({ DerivationExecutor } = await import("./executor-WAEI6PDV.js"));
1079
1342
  }
1080
1343
  const ctx = { vault: accessor.getReadOnlyFacade() };
1081
1344
  const result = await DerivationExecutor.run(spec, sourceWithId, 0, strategyHash, ctx);
@@ -1172,51 +1435,64 @@ var NO_HISTORY = {
1172
1435
  }
1173
1436
  };
1174
1437
 
1175
- // src/with-shape/i18n/strategy.ts
1176
- function notEnabled2(op) {
1177
- return new Error(
1178
- `${op} requires the i18n strategy. Import \`{ withI18n }\` from "@noy-db/hub/i18n" and pass it to \`createNoydb({ i18nStrategy: withI18n() })\`.`
1179
- );
1180
- }
1181
- var NO_I18N = {
1182
- applyI18nLocale(record) {
1183
- return record;
1184
- },
1185
- validateI18nTextValue() {
1186
- throw notEnabled2("i18nText field validation");
1187
- },
1188
- enforceScript(value) {
1189
- return { value, warnings: [] };
1190
- },
1191
- computeExemptFills() {
1192
- return /* @__PURE__ */ new Map();
1193
- },
1194
- densify() {
1195
- },
1196
- buildDictionaryHandle() {
1197
- throw notEnabled2("vault.dictionary()");
1198
- }
1199
- };
1200
-
1201
1438
  // src/with-party/team/sync-strategy.ts
1202
- function notEnabled3(op) {
1439
+ function notEnabled2(op) {
1203
1440
  return new Error(
1204
1441
  `${op} requires the sync strategy. Import \`{ withSync }\` from "@noy-db/hub/sync" and pass it to \`createNoydb({ syncStrategy: withSync() })\`.`
1205
1442
  );
1206
1443
  }
1207
1444
  var NO_SYNC = {
1208
1445
  buildSyncEngine() {
1209
- throw notEnabled3("SyncEngine");
1446
+ throw notEnabled2("SyncEngine");
1210
1447
  },
1211
1448
  buildSyncTransaction() {
1212
- throw notEnabled3("SyncTransaction");
1449
+ throw notEnabled2("SyncTransaction");
1213
1450
  },
1214
1451
  buildPresence() {
1215
- throw notEnabled3("collection.presence()");
1452
+ throw notEnabled2("collection.presence()");
1216
1453
  }
1217
1454
  };
1218
1455
 
1219
- // src/with-shape/blobs/strategy.ts
1456
+ // src/shape/via-blob/binding.ts
1457
+ async function eraseBlobs(ctx, cfg) {
1458
+ if (cfg.purgeBlobsForRecord) return cfg.purgeBlobsForRecord(ctx.id);
1459
+ return { shredded: 0, residue: [] };
1460
+ }
1461
+ function buildBlobDescribeFragment(fields) {
1462
+ return {
1463
+ blobFields: Object.fromEntries(
1464
+ Object.entries(fields).map(([field, p]) => [field, {
1465
+ ...p.retainDays !== void 0 ? { retainDays: p.retainDays } : {},
1466
+ ...p.evictWhen !== void 0 ? { evictWhen: true } : {},
1467
+ ...p.legalHold !== void 0 ? { legalHold: true } : {},
1468
+ ...p.retainUntil !== void 0 ? { retainUntil: true } : {},
1469
+ ...p.external === true ? { external: true } : {},
1470
+ ...p.public === true ? { public: true } : {},
1471
+ ...p.backlink !== void 0 ? { backlink: p.backlink } : {}
1472
+ }])
1473
+ )
1474
+ };
1475
+ }
1476
+ function blobBinding(cfg) {
1477
+ return {
1478
+ brand: "blob",
1479
+ // encryptedAtRest: 'envelope' — blob chunks are AEAD-encrypted envelopes
1480
+ // in `_blob_chunks` (service-side), not sealed record fields; queryable:
1481
+ // 'none' — nothing indexes blob content; exportable — vault.exportBlobs()
1482
+ // is a first-class door; forgettable — shredAllForRecord participates in
1483
+ // vault.forget().
1484
+ posture: { encryptedAtRest: "envelope", queryable: "none", exportable: true, forgettable: true },
1485
+ covers: (field) => field in cfg.fields,
1486
+ erase: (ctx) => eraseBlobs(ctx, cfg),
1487
+ describeFragment: () => buildBlobDescribeFragment(cfg.fields)
1488
+ };
1489
+ }
1490
+ function linkBlobVia() {
1491
+ installViaBinder("blob", (c) => blobBinding(c));
1492
+ }
1493
+
1494
+ // src/port/with/blob-strategy.ts
1495
+ linkBlobVia();
1220
1496
  var NO_BLOBS = {
1221
1497
  openSlot() {
1222
1498
  throw new Error(
@@ -1225,23 +1501,502 @@ var NO_BLOBS = {
1225
1501
  }
1226
1502
  };
1227
1503
 
1504
+ // src/shape/via-lookup/registry.ts
1505
+ function enforceStaticDictOnPut(staticFields, record) {
1506
+ if (!staticFields || Object.keys(staticFields).length === 0) return;
1507
+ if (!record || typeof record !== "object") return;
1508
+ const obj = record;
1509
+ for (const [field, desc] of Object.entries(staticFields)) {
1510
+ if (desc.validateCodes === false) continue;
1511
+ const known = new Set(desc.keys);
1512
+ const values = getAtPath(obj, field);
1513
+ for (const value of values) {
1514
+ if (value === void 0 || value === null) continue;
1515
+ const codes = Array.isArray(value) ? value : [value];
1516
+ for (const code of codes) {
1517
+ if (typeof code !== "string") continue;
1518
+ if (!known.has(code)) {
1519
+ throw new UnknownDictCodeError(desc.name, field, code);
1520
+ }
1521
+ }
1522
+ }
1523
+ }
1524
+ }
1525
+ function resolveDictSource(leftCollection, field, staticDescriptorByField, dictKeyFieldRegistry, getDictionaryHandle) {
1526
+ const staticFields = staticDescriptorByField.get(leftCollection);
1527
+ if (staticFields && field in staticFields) {
1528
+ const desc = staticFields[field];
1529
+ const rows = Object.entries(desc.table).map(
1530
+ ([key, labels]) => ({ key, labels, ...labels })
1531
+ );
1532
+ const source = {
1533
+ snapshot() {
1534
+ return rows;
1535
+ },
1536
+ lookupById(id) {
1537
+ return rows.find((e) => e["key"] === id);
1538
+ }
1539
+ };
1540
+ if (desc.displayLocale !== void 0) {
1541
+ ;
1542
+ source.displayLocale = desc.displayLocale;
1543
+ }
1544
+ return source;
1545
+ }
1546
+ const dictFields = dictKeyFieldRegistry.get(leftCollection);
1547
+ if (!dictFields || !(field in dictFields)) return null;
1548
+ const dictName = dictFields[field];
1549
+ if (!dictName) return null;
1550
+ const handle = getDictionaryHandle(dictName);
1551
+ return {
1552
+ snapshot() {
1553
+ return handle.snapshotEntries();
1554
+ },
1555
+ lookupById(id) {
1556
+ const entries = handle.snapshotEntries();
1557
+ return entries.find((e) => e["key"] === id);
1558
+ }
1559
+ };
1560
+ }
1561
+ async function updateReferencingRecords(registry, getCollection, name, oldKey, newKey) {
1562
+ for (const [collectionName, dictFields] of registry) {
1563
+ const fields = Object.entries(dictFields).filter(([, dn]) => dn === name).map(([field]) => field);
1564
+ if (fields.length === 0) continue;
1565
+ const coll = getCollection(collectionName);
1566
+ const records = await coll.list();
1567
+ for (const record of records) {
1568
+ let changed = false;
1569
+ const updated = { ...record };
1570
+ for (const field of fields) {
1571
+ if (updated[field] === oldKey) {
1572
+ updated[field] = newKey;
1573
+ changed = true;
1574
+ }
1575
+ }
1576
+ if (changed) {
1577
+ const id = record["id"];
1578
+ if (id !== void 0) {
1579
+ await coll.put(id, updated);
1580
+ }
1581
+ }
1582
+ }
1583
+ }
1584
+ }
1585
+ function resolveLabelFromMap(labels, locale, fallback) {
1586
+ if (labels[locale] !== void 0) return labels[locale];
1587
+ const chain = Array.isArray(fallback) ? fallback : fallback ? [fallback] : [];
1588
+ for (const fb of chain) {
1589
+ if (fb === "any") {
1590
+ const any = Object.values(labels)[0];
1591
+ if (any !== void 0) return any;
1592
+ } else if (labels[fb] !== void 0) {
1593
+ return labels[fb];
1594
+ }
1595
+ }
1596
+ return void 0;
1597
+ }
1598
+ function lookupToStaticDictCompat(desc) {
1599
+ if (desc.backing !== "static" || desc.table === void 0) return void 0;
1600
+ return {
1601
+ _noydbStaticDict: true,
1602
+ _viaBrand: "i18n",
1603
+ name: desc.dimension,
1604
+ table: desc.table,
1605
+ keys: desc.keys ?? Object.keys(desc.table),
1606
+ ...desc.displayLocale !== void 0 ? { displayLocale: desc.displayLocale } : {},
1607
+ ...desc.onMissing !== void 0 ? { onMissing: desc.onMissing } : {},
1608
+ ...desc.substitute !== void 0 ? { substitute: desc.substitute } : {},
1609
+ validateCodes: desc.vocabulary === "closed"
1610
+ };
1611
+ }
1612
+ function collectLookupDictCompat(lookupFields) {
1613
+ const dictFieldMap = {};
1614
+ const staticEntries = [];
1615
+ for (const [field, desc] of Object.entries(lookupFields ?? {})) {
1616
+ if (desc.backing === "reserved") {
1617
+ dictFieldMap[field] = desc.dimension;
1618
+ } else {
1619
+ const compat = lookupToStaticDictCompat(desc);
1620
+ if (compat) staticEntries.push([field, compat]);
1621
+ }
1622
+ }
1623
+ return { dictFieldMap, staticEntries };
1624
+ }
1625
+ function materializeBackingTable(descriptor, rows) {
1626
+ const keys = new Set(rows.keys());
1627
+ const owner = /* @__PURE__ */ new Map();
1628
+ for (const key of keys) owner.set(key, key);
1629
+ const altIndex = /* @__PURE__ */ new Map();
1630
+ const altFields = descriptor.altKeys ?? [];
1631
+ for (const [canonicalKey, row] of rows) {
1632
+ for (const altField of altFields) {
1633
+ const value = row[altField];
1634
+ if (typeof value !== "string" || value === "") continue;
1635
+ const existingOwner = owner.get(value);
1636
+ if (existingOwner !== void 0 && existingOwner !== canonicalKey) {
1637
+ throw new ValidationError(
1638
+ `lookup "${descriptor.dimension}": altKey field "${altField}" value "${value}" is claimed by both "${existingOwner}" and "${canonicalKey}" \u2014 key/altKey values must be unique across the dimension.`
1639
+ );
1640
+ }
1641
+ owner.set(value, canonicalKey);
1642
+ altIndex.set(value, canonicalKey);
1643
+ }
1644
+ }
1645
+ return { keys, altIndex };
1646
+ }
1647
+ function checkLookupMembership(descriptor, key, getDictionary, getCollection) {
1648
+ if (descriptor.backing === "static") {
1649
+ const known = descriptor.keys ?? (descriptor.table ? Object.keys(descriptor.table) : []);
1650
+ return known.includes(key);
1651
+ }
1652
+ if (descriptor.backing === "reserved") {
1653
+ if ((descriptor.keys ?? []).includes(key)) return true;
1654
+ return getDictionary(descriptor.dimension).snapshotEntries().some((e) => e["key"] === key);
1655
+ }
1656
+ return buildLookupAltIndex(descriptor, getDictionary, getCollection).keys.has(key);
1657
+ }
1658
+ function buildLookupAltIndex(descriptor, getDictionary, getCollection) {
1659
+ if (descriptor.backing === "static") {
1660
+ return materializeBackingTable(descriptor, new Map(Object.entries(descriptor.table ?? {})));
1661
+ }
1662
+ if (descriptor.backing === "reserved") {
1663
+ const entries = getDictionary(descriptor.dimension).snapshotEntries();
1664
+ return materializeBackingTable(descriptor, new Map(entries.map((e) => [String(e["key"]), e])));
1665
+ }
1666
+ const rows = getCollection(descriptor.dimension).querySourceForJoin().snapshot();
1667
+ return materializeBackingTable(
1668
+ descriptor,
1669
+ new Map(
1670
+ rows.map((r) => {
1671
+ const row = r;
1672
+ return [String(row[descriptor.key]), row];
1673
+ })
1674
+ )
1675
+ );
1676
+ }
1677
+ function collectLookupRefEdges(collectionName, lookupFields) {
1678
+ const edges = [];
1679
+ for (const [field, desc] of Object.entries(lookupFields ?? {})) {
1680
+ if (desc.backing === "static") continue;
1681
+ const backing = desc.backing === "reserved" ? dictCollectionName(desc.dimension) : desc.dimension;
1682
+ edges.push({
1683
+ referencing: { collection: collectionName, field },
1684
+ sources: [
1685
+ { collection: backing, field: "*" },
1686
+ ...desc.present?.label !== void 0 ? [{ collection: backing, field: desc.present.label }] : []
1687
+ ],
1688
+ onDelete: desc.onDelete,
1689
+ keyField: desc.key
1690
+ });
1691
+ }
1692
+ return edges;
1693
+ }
1694
+ function registerLookupRefEdges(graph, collectionName, lookupFields) {
1695
+ for (const e of collectLookupRefEdges(collectionName, lookupFields)) {
1696
+ graph.registerDerived(e.referencing, e.sources, "ref", "record", e.onDelete, e.keyField);
1697
+ }
1698
+ }
1699
+ function buildLookupSnapshotRows(descriptor, isReservedDimension, getDictionary, getCollection) {
1700
+ const dimension = descriptor.dimension;
1701
+ if (isReservedDimension(dimension)) {
1702
+ const rows = /* @__PURE__ */ new Map();
1703
+ for (const entry of getDictionary(dimension).snapshotEntries()) {
1704
+ const key = entry["key"];
1705
+ if (typeof key === "string") rows.set(key, entry);
1706
+ }
1707
+ return rows;
1708
+ }
1709
+ if (descriptor.backing === "collection") {
1710
+ const rawRows = getCollection(dimension).querySourceForJoin().snapshot();
1711
+ const rows = /* @__PURE__ */ new Map();
1712
+ for (const r of rawRows) {
1713
+ const row = r;
1714
+ rows.set(String(row[descriptor.key]), row);
1715
+ }
1716
+ return rows;
1717
+ }
1718
+ return void 0;
1719
+ }
1720
+
1721
+ // src/shape/via-lookup/snapshot.ts
1722
+ function readRowField(row, field, by, locale, fallback) {
1723
+ if (!row || field === void 0) return void 0;
1724
+ const raw = row[field];
1725
+ if (by === void 0) return typeof raw === "string" ? raw : void 0;
1726
+ if (!raw || typeof raw !== "object") return void 0;
1727
+ const map = raw;
1728
+ const val = map[locale];
1729
+ if (typeof val === "string") return val;
1730
+ if (typeof fallback === "string" && typeof map[fallback] === "string") return map[fallback];
1731
+ return void 0;
1732
+ }
1733
+ function buildLookupSnapshot(dimension, rows, descriptor) {
1734
+ void dimension;
1735
+ return {
1736
+ row: (key) => rows.get(key),
1737
+ label: (key, locale, fallback) => readRowField(rows.get(key), descriptor.present?.label, descriptor.present?.by, locale, fallback),
1738
+ compareKeys: (a, b, locale) => {
1739
+ const sortField = descriptor.sortBy ?? descriptor.present?.label;
1740
+ const av = readRowField(rows.get(a), sortField, descriptor.present?.by, locale) ?? a;
1741
+ const bv = readRowField(rows.get(b), sortField, descriptor.present?.by, locale) ?? b;
1742
+ return av < bv ? -1 : av > bv ? 1 : 0;
1743
+ }
1744
+ };
1745
+ }
1746
+ function presentLookupForJoin(record, locale, lookupFields, getSnapshotRows) {
1747
+ let result = record;
1748
+ for (const [field, desc] of Object.entries(lookupFields)) {
1749
+ if (desc.present === void 0) continue;
1750
+ const raw = record[field];
1751
+ if (typeof raw !== "string") continue;
1752
+ const rows = desc.backing === "static" ? desc.table ? new Map(Object.entries(desc.table)) : void 0 : getSnapshotRows(desc);
1753
+ if (!rows) continue;
1754
+ const label = buildLookupSnapshot(desc.dimension, rows, desc).label(raw, locale);
1755
+ if (label === void 0) continue;
1756
+ if (result === record) result = { ...record };
1757
+ result[`${field}Label`] = label;
1758
+ }
1759
+ return result;
1760
+ }
1761
+ function buildPresentForJoin(i18nFields, lookupFields, getSnapshotRows) {
1762
+ const hasI18n = i18nFields !== void 0 && Object.keys(i18nFields).length > 0;
1763
+ const hasLookup = lookupFields !== void 0 && Object.keys(lookupFields).length > 0;
1764
+ if (!hasI18n && !hasLookup) return void 0;
1765
+ const resolveRows = getSnapshotRows ?? (() => void 0);
1766
+ return (record, locale) => {
1767
+ if (record === null || typeof record !== "object") return record;
1768
+ let result = record;
1769
+ if (hasI18n) result = presentI18nForJoin(result, i18nFields, locale);
1770
+ if (hasLookup) result = presentLookupForJoin(result, locale, lookupFields, resolveRows);
1771
+ return result;
1772
+ };
1773
+ }
1774
+
1775
+ // src/port/with/lookup-strategy.ts
1776
+ function isLookupDescriptor(x) {
1777
+ return typeof x === "object" && x !== null && x._viaBrand === "lookup";
1778
+ }
1779
+
1780
+ // src/kernel/via-compose.ts
1781
+ function via(...descriptors) {
1782
+ if (descriptors.length === 0) {
1783
+ throw new ValidationError("via(): at least one descriptor is required, e.g. via(money({ currency: 'EUR' }))");
1784
+ }
1785
+ return { _noydbVia: true, descriptors };
1786
+ }
1787
+ function isViaFieldSpec(x) {
1788
+ return typeof x === "object" && x !== null && x._noydbVia === true;
1789
+ }
1790
+ function mergeViaFields(sources) {
1791
+ for (const field of Object.keys(sources.dictKeyFields ?? {})) {
1792
+ if (sources.lookupFields && field in sources.lookupFields) {
1793
+ throw new ValidationError(
1794
+ `mergeViaFields(): field "${field}" is declared in both \`dictKeyFields\` and \`lookupFields\` \u2014 declare it in one place only.`
1795
+ );
1796
+ }
1797
+ }
1798
+ if (!sources.viaFields || Object.keys(sources.viaFields).length === 0) {
1799
+ return {
1800
+ moneyFields: sources.moneyFields,
1801
+ i18nFields: sources.i18nFields,
1802
+ dictKeyFields: sources.dictKeyFields,
1803
+ computedFields: void 0,
1804
+ lookupFields: sources.lookupFields
1805
+ };
1806
+ }
1807
+ const sugarFieldNames = /* @__PURE__ */ new Set([
1808
+ ...Object.keys(sources.moneyFields ?? {}),
1809
+ ...Object.keys(sources.i18nFields ?? {}),
1810
+ ...Object.keys(sources.dictKeyFields ?? {}),
1811
+ ...Object.keys(sources.lookupFields ?? {})
1812
+ ]);
1813
+ const viaMoney = {};
1814
+ const viaI18nText = {};
1815
+ const viaDictKey = {};
1816
+ const viaComputed = {};
1817
+ const viaLookup = {};
1818
+ for (const [field, spec] of Object.entries(sources.viaFields)) {
1819
+ if (sugarFieldNames.has(field)) {
1820
+ throw new ValidationError(
1821
+ `via(): field "${field}" is declared via both a sugar key and \`viaFields\` \u2014 declare it in one place only.`
1822
+ );
1823
+ }
1824
+ for (const descriptor of spec.descriptors) {
1825
+ if (descriptor._viaBrand === "money") {
1826
+ viaMoney[field] = descriptor;
1827
+ } else if (descriptor._viaBrand === "i18n") {
1828
+ if (isI18nTextDescriptor(descriptor)) viaI18nText[field] = descriptor;
1829
+ else if (isDictKeyDescriptor(descriptor) || isStaticDictDescriptor(descriptor)) viaDictKey[field] = descriptor;
1830
+ } else if (descriptor._viaBrand === "computed") {
1831
+ viaComputed[field] = descriptor;
1832
+ } else if (descriptor._viaBrand === "lookup" && isLookupDescriptor(descriptor)) {
1833
+ viaLookup[field] = descriptor;
1834
+ } else {
1835
+ throw new ValidationError(`via(): field "${field}" has a descriptor with unrecognized _viaBrand "${descriptor._viaBrand}" \u2014 via() only supports money/i18n/computed/lookup descriptors today.`);
1836
+ }
1837
+ }
1838
+ }
1839
+ return {
1840
+ moneyFields: Object.keys(viaMoney).length > 0 ? { ...sources.moneyFields ?? {}, ...viaMoney } : sources.moneyFields,
1841
+ i18nFields: Object.keys(viaI18nText).length > 0 ? { ...sources.i18nFields ?? {}, ...viaI18nText } : sources.i18nFields,
1842
+ dictKeyFields: Object.keys(viaDictKey).length > 0 ? { ...sources.dictKeyFields ?? {}, ...viaDictKey } : sources.dictKeyFields,
1843
+ computedFields: Object.keys(viaComputed).length > 0 ? viaComputed : void 0,
1844
+ lookupFields: Object.keys(viaLookup).length > 0 ? { ...sources.lookupFields ?? {}, ...viaLookup } : sources.lookupFields
1845
+ };
1846
+ }
1847
+
1228
1848
  // src/kernel/collection-config.ts
1849
+ function computedEntryParts(entry) {
1850
+ if (typeof entry === "function") return { fn: entry, mode: "materialized" };
1851
+ return { fn: entry.fn, ...entry.deps !== void 0 ? { deps: entry.deps } : {}, mode: entry.mode ?? "materialized" };
1852
+ }
1853
+ function unifyComputedFields(opts, viaComputedFields) {
1854
+ return { ...opts.computed ?? {}, ...viaComputedFields ?? {} };
1855
+ }
1856
+ function compileViaBindings(opts, classifiedGuardCtx, eraseCfgOut) {
1857
+ const { moneyFields, i18nFields, dictKeyFields, computedFields, lookupFields } = mergeViaFields(opts);
1858
+ const bindings = [];
1859
+ if (moneyFields) bindings.push(viaBinder("money")(moneyFields));
1860
+ if (i18nFields || dictKeyFields) {
1861
+ const densify = i18nFields ? Object.fromEntries(
1862
+ Object.entries(i18nFields).filter(([, d]) => d.options.densifyOnWrite === true)
1863
+ ) : {};
1864
+ const i18nDensifyFields = Object.keys(densify).length > 0 ? densify : void 0;
1865
+ bindings.push(viaBinder("i18n")({
1866
+ ...i18nFields !== void 0 ? { i18nFields } : {},
1867
+ ...dictKeyFields !== void 0 ? { dictKeyFields } : {},
1868
+ ...i18nDensifyFields !== void 0 ? { i18nDensifyFields } : {},
1869
+ strategy: opts.i18nStrategy ?? NO_I18N,
1870
+ ...opts.defaultLocale !== void 0 ? { defaultLocale: opts.defaultLocale } : {},
1871
+ ...opts.autoTranslateHook !== void 0 ? { autoTranslateHook: opts.autoTranslateHook } : {},
1872
+ ...opts.dictLabelResolver !== void 0 ? { dictLabelResolver: opts.dictLabelResolver } : {},
1873
+ ...opts.i18nPutValidator !== void 0 ? { i18nPutValidator: opts.i18nPutValidator } : {},
1874
+ collectionName: opts.name
1875
+ }));
1876
+ }
1877
+ if (lookupFields !== void 0) {
1878
+ bindings.push(viaBinder("lookup")({
1879
+ lookupFields,
1880
+ ...opts.lookupLabelResolver !== void 0 ? { lookupLabelResolver: opts.lookupLabelResolver } : {},
1881
+ ...opts.getLookupBacking !== void 0 ? { getLookupBacking: opts.getLookupBacking } : {},
1882
+ ...opts.membership !== void 0 ? { membership: opts.membership } : {},
1883
+ ...opts.getAltIndex !== void 0 ? { getAltIndex: opts.getAltIndex } : {},
1884
+ ...opts.snapshotFor !== void 0 ? { snapshotFor: opts.snapshotFor } : {},
1885
+ collectionName: opts.name
1886
+ }));
1887
+ }
1888
+ if (opts.classifiedFields !== void 0) {
1889
+ const classifiedCfg = {
1890
+ entries: opts.classifiedFields,
1891
+ collectionName: opts.name,
1892
+ guardCtx: classifiedGuardCtx
1893
+ };
1894
+ if (eraseCfgOut) eraseCfgOut.classified = classifiedCfg;
1895
+ bindings.push(viaBinder("classified")(classifiedCfg));
1896
+ }
1897
+ if (opts.blobFields !== void 0) {
1898
+ bindings.push(viaBinder("blob")({
1899
+ fields: opts.blobFields,
1900
+ collectionName: opts.name
1901
+ }));
1902
+ }
1903
+ const virtualFields = /* @__PURE__ */ new Map();
1904
+ for (const [field, entry] of Object.entries(unifyComputedFields(opts, computedFields))) {
1905
+ const parts = computedEntryParts(entry);
1906
+ if (parts.mode === "virtual") {
1907
+ virtualFields.set(field, { _viaBrand: "computed", fn: parts.fn, mode: "virtual", ...parts.deps !== void 0 ? { deps: parts.deps } : {} });
1908
+ }
1909
+ }
1910
+ if (virtualFields.size > 0) {
1911
+ bindings.push(viaBinder("computed")({ virtualFields }));
1912
+ }
1913
+ return bindings;
1914
+ }
1915
+ function collectKnownFieldNames(parts) {
1916
+ return /* @__PURE__ */ new Set([
1917
+ ...Object.keys(parts.moneyFields ?? {}),
1918
+ ...Object.keys(parts.i18nFields ?? {}),
1919
+ ...Object.keys(parts.dictKeyFields ?? {}),
1920
+ ...Object.keys(parts.classifiedFields ?? {}),
1921
+ ...Object.keys(parts.computed ?? {}),
1922
+ ...Object.keys(parts.lookupFields ?? {})
1923
+ ]);
1924
+ }
1925
+ var EMPTY_KNOWN_FIELDS = /* @__PURE__ */ new Set();
1926
+ function resolveComputedEdges(collectionName, computed, hasClassifiedFields, knownFields = EMPTY_KNOWN_FIELDS) {
1927
+ if (!computed) return [];
1928
+ const edges = [];
1929
+ for (const [field, entry] of Object.entries(computed)) {
1930
+ const parts = computedEntryParts(entry);
1931
+ const deps = parts.deps;
1932
+ if (deps === void 0) {
1933
+ if (hasClassifiedFields) {
1934
+ throw new ValidationError(
1935
+ `Collection "${collectionName}": computed field "${field}" has no declared \`deps\` and the collection declares classified fields \u2014 an opaque computed function could silently copy a classified field's plaintext into an ordinary, unredacted field. Declare \`deps\` naming the source fields it reads, e.g. computed: { ${field}: { fn, deps: [...] } } or via(computed(fn, { deps: [...] })).`
1936
+ );
1937
+ }
1938
+ continue;
1939
+ }
1940
+ if (deps.length === 0) {
1941
+ throw new ValidationError(`Collection "${collectionName}": computed field "${field}"'s \`deps\` must be non-empty.`);
1942
+ }
1943
+ for (const dep of deps) {
1944
+ if (typeof dep !== "string" || dep.length === 0) {
1945
+ throw new ValidationError(`Collection "${collectionName}": computed field "${field}"'s \`deps\` entries must be non-empty strings.`);
1946
+ }
1947
+ if (hasClassifiedFields && !knownFields.has(dep)) {
1948
+ throw new ValidationError(
1949
+ `Collection "${collectionName}": computed field "${field}"'s \`deps\` entry "${dep}" does not name a declared field, and the collection declares classified fields \u2014 an opaque computed function could silently copy a classified field's plaintext into an ordinary, unredacted field via a mistyped/unknown dep name. Declare \`deps\` naming only known fields (money/i18n/dictKey/classified/computed) \u2014 check "${dep}" for a typo.`
1950
+ );
1951
+ }
1952
+ }
1953
+ edges.push({
1954
+ target: { collection: collectionName, field },
1955
+ sources: deps.map((d) => ({ collection: collectionName, field: d })),
1956
+ grain: parts.mode === "virtual" ? "virtual" : "record"
1957
+ });
1958
+ }
1959
+ return edges;
1960
+ }
1961
+ function resolveViaBindingDepsEdges(collectionName, bindings, knownFields) {
1962
+ const edges = [];
1963
+ for (const binding of bindings) {
1964
+ if (!binding.deps || binding.deps.length === 0) continue;
1965
+ for (const dep of binding.deps) {
1966
+ if (!knownFields.has(dep)) {
1967
+ throw new ValidationError(
1968
+ `Collection "${collectionName}": via binding "${binding.brand}" deps references undeclared field "${dep}".`
1969
+ );
1970
+ }
1971
+ }
1972
+ const sources = binding.deps.map((d) => ({ collection: collectionName, field: d }));
1973
+ for (const field of knownFields) {
1974
+ if (binding.covers?.(field)) edges.push({ target: { collection: collectionName, field }, sources });
1975
+ }
1976
+ }
1977
+ return edges;
1978
+ }
1229
1979
  function resolveCollectionConfig(opts) {
1230
1980
  if (opts.embeddings && opts.crdt) {
1231
1981
  throw new Error(
1232
1982
  `Collection "${opts.name}": embeddings are not supported on CRDT collections (L2). Use a non-CRDT collection for semantic search.`
1233
1983
  );
1234
1984
  }
1235
- if (opts.moneyFields) moneyRuntime().validateMoneyFieldPaths(opts.moneyFields);
1985
+ const effectiveViaFields = mergeViaFields(opts);
1236
1986
  const resolvedClassified = opts.classifiedFields !== void 0 ? resolveClassifiedFields(opts.name, opts.classifiedFields) : void 0;
1237
- let mergedComputed = opts.computed;
1987
+ const allComputed = unifyComputedFields(opts, effectiveViaFields.computedFields);
1988
+ const materializedComputed = {};
1989
+ for (const [field, entry] of Object.entries(allComputed)) {
1990
+ if (computedEntryParts(entry).mode !== "virtual") materializedComputed[field] = entry;
1991
+ }
1992
+ let mergedComputed = Object.keys(materializedComputed).length > 0 ? materializedComputed : void 0;
1238
1993
  if (resolvedClassified !== void 0) {
1239
- for (const key of Object.keys(opts.computed ?? {})) {
1994
+ for (const key of Object.keys(allComputed)) {
1240
1995
  if (resolvedClassified.riderComputed[key] !== void 0) {
1241
1996
  throw new ClassifiedConfigError(opts.name, `computed field "${key}" collides with a rider companion`);
1242
1997
  }
1243
1998
  }
1244
- mergedComputed = { ...resolvedClassified.riderComputed, ...opts.computed ?? {} };
1999
+ mergedComputed = { ...resolvedClassified.riderComputed, ...materializedComputed };
1245
2000
  }
1246
2001
  let deterministicFields;
1247
2002
  if (opts.deterministicFields && opts.deterministicFields.length > 0) {
@@ -1254,10 +2009,6 @@ function resolveCollectionConfig(opts) {
1254
2009
  } else {
1255
2010
  deterministicFields = null;
1256
2011
  }
1257
- const densifyFields = opts.i18nFields ? Object.fromEntries(
1258
- Object.entries(opts.i18nFields).filter(([, d]) => d.options.densifyOnWrite === true)
1259
- ) : {};
1260
- const i18nDensifyFields = Object.keys(densifyFields).length > 0 ? densifyFields : void 0;
1261
2012
  const guardIndexedFields = /* @__PURE__ */ new Set();
1262
2013
  for (const ix of opts.indexes ?? []) {
1263
2014
  if (typeof ix === "string") guardIndexedFields.add(ix);
@@ -1293,6 +2044,18 @@ function resolveCollectionConfig(opts) {
1293
2044
  const vdigFields = vdigEntries.length > 0 ? new Map(vdigEntries) : null;
1294
2045
  const perRecordCek = opts.perRecordKeys === true;
1295
2046
  const cekCache = perRecordCek ? new Lru({ maxRecords: 4096 }) : null;
2047
+ const viaEraseCfgOut = {};
2048
+ const via2 = ViaPipeline.build(compileViaBindings(opts, classifiedGuardCtx, viaEraseCfgOut));
2049
+ const knownFields = collectKnownFieldNames({
2050
+ moneyFields: effectiveViaFields.moneyFields,
2051
+ i18nFields: effectiveViaFields.i18nFields,
2052
+ dictKeyFields: effectiveViaFields.dictKeyFields,
2053
+ classifiedFields: resolvedClassified?.byField,
2054
+ computed: allComputed,
2055
+ lookupFields: effectiveViaFields.lookupFields
2056
+ });
2057
+ const computedEdges = resolveComputedEdges(opts.name, allComputed, resolvedClassified !== void 0, knownFields);
2058
+ const viaDepsEdges = resolveViaBindingDepsEdges(opts.name, via2?.bindings ?? [], knownFields);
1296
2059
  return {
1297
2060
  adapter: opts.adapter,
1298
2061
  vault: opts.vault,
@@ -1324,20 +2087,34 @@ function resolveCollectionConfig(opts) {
1324
2087
  ledger: opts.ledger,
1325
2088
  refEnforcer: opts.refEnforcer,
1326
2089
  joinResolver: opts.joinResolver,
1327
- i18nFields: opts.i18nFields,
2090
+ i18nFields: effectiveViaFields.i18nFields,
1328
2091
  textIndexes: opts.textIndexes,
1329
- i18nDensifyFields,
1330
2092
  embeddings: opts.embeddings,
1331
2093
  vectorSet: opts.embeddings ? new VectorSet() : void 0,
1332
- dictKeyFields: opts.dictKeyFields,
2094
+ dictKeyFields: effectiveViaFields.dictKeyFields,
2095
+ lookupFields: effectiveViaFields.lookupFields,
2096
+ // #650 Task 6 — the sync join-dressing hook (#626 retirement, spec §5);
2097
+ // `undefined` when this collection declares neither i18nText nor lookup
2098
+ // fields, matching today's `i18nFields`-absent JoinableSource shape.
2099
+ presentForJoin: buildPresentForJoin(effectiveViaFields.i18nFields, effectiveViaFields.lookupFields, opts.snapshotFor),
1333
2100
  fieldMeta: opts.fieldMeta,
1334
2101
  meta: opts.meta,
1335
2102
  _refs: opts.declaredRefs ?? {},
1336
- moneyFields: opts.moneyFields,
2103
+ via: via2,
2104
+ classifiedEraseCfg: viaEraseCfgOut.classified,
2105
+ moneyFields: effectiveViaFields.moneyFields,
1337
2106
  classified: resolvedClassified,
1338
2107
  classifiedGuardCtx,
1339
2108
  classifiedStrategy: opts.classifiedStrategy ?? NO_CLASSIFIED,
1340
2109
  computed: mergedComputed,
2110
+ computedEdges,
2111
+ viaDepsEdges,
2112
+ // #638 Task 7 — every declared computed field name (both modes, both surfaces); the
2113
+ // reconcile path's cross-call depsless-computed leak-guard memory
2114
+ // (`via-graph-wiring.ts#registerCollectionGraphSources`) reads this instead of
2115
+ // `Object.keys(opts.computed ?? {})` alone, so a via(computed(...))-declared field
2116
+ // participates in that guard identically to a sugar-declared one.
2117
+ computedFieldNames: Object.keys(allComputed),
1341
2118
  dictLabelResolver: opts.dictLabelResolver,
1342
2119
  getDictionary: opts.getDictionary,
1343
2120
  i18nPutValidator: opts.i18nPutValidator,
@@ -1348,6 +2125,7 @@ function resolveCollectionConfig(opts) {
1348
2125
  onAccess: opts.onAccess,
1349
2126
  derivationSource: opts.derivationSource,
1350
2127
  materializedViewSource: opts.materializedViewSource,
2128
+ graphDispatch: opts.graphDispatch,
1351
2129
  tiers: opts.tiers && opts.tiers.length > 0 ? new Set(opts.tiers) : null,
1352
2130
  tiersStrategy: opts.tiersStrategy ?? NO_TIERS,
1353
2131
  searchStrategy: opts.searchStrategy ?? NO_SEARCH,
@@ -1365,7 +2143,7 @@ function resolveCollectionConfig(opts) {
1365
2143
  // src/with-formula/computed/lazy.ts
1366
2144
  var fn = null;
1367
2145
  async function loadEvalComputedFields() {
1368
- fn ??= (await import("./computed-6FOFXQPE.js")).evalComputedFields;
2146
+ fn ??= (await import("./computed-R62HGCO4.js")).evalComputedFields;
1369
2147
  return fn;
1370
2148
  }
1371
2149
 
@@ -1424,7 +2202,6 @@ var Collection = class {
1424
2202
  tiersStrategy;
1425
2203
  searchStrategy;
1426
2204
  historyStrategy;
1427
- i18nStrategy;
1428
2205
  syncStrategy;
1429
2206
  // In-memory cache of decrypted records (eager mode only). Lazy mode
1430
2207
  // uses `lru` instead. Both fields exist so a single Collection instance
@@ -1526,70 +2303,38 @@ var Collection = class {
1526
2303
  * throw `LocaleNotSpecifiedError` unless a per-call locale is passed.
1527
2304
  */
1528
2305
  defaultLocale;
1529
- /**
1530
- * Map of field name → `I18nTextDescriptor` for fields declared with
1531
- * `i18nText()`. Used by:
1532
- * - `put()` via `i18nPutValidator` to enforce required translations
1533
- * - `get()`/`list()` to apply locale resolution after decryption
1534
- *
1535
- * Declared via the `i18nFields` collection option.
1536
- */
2306
+ /** Field name → `I18nTextDescriptor` for `i18nText()` fields (`i18nFields` option); write/read
2307
+ * runs through the compiled `via` i18n binding this remains for `describe()`
2308
+ * and the search-index build path. */
1537
2309
  i18nFields;
1538
- /**
1539
- * The configured string fields exposed to `retrieve()`. `undefined`
1540
- * for ordinary collections, so the search path costs nothing when unused.
1541
- */
2310
+ /** The configured string fields exposed to `retrieve()`; `undefined` for ordinary collections (zero-cost). */
1542
2311
  textIndexes;
1543
- /**
1544
- * The session-scoped lexical index store. `undefined` (so the dirty
1545
- * poke + retrieve are zero-cost) unless `textIndexes` is non-empty.
1546
- */
2312
+ /** Session-scoped lexical index store; `undefined` (zero-cost) unless `textIndexes` is non-empty. */
1547
2313
  searchIndexStore;
1548
- /**
1549
- * The densify-enabled subset of {@link i18nFields} (fields whose
1550
- * descriptor opts in via `densifyOnWrite: true`). `undefined` when none opt
1551
- * in, so the write path skips all densify work for ordinary collections.
1552
- */
1553
- i18nDensifyFields;
1554
- /**
1555
- * Embedding config for write-time vector derivation. `undefined`
1556
- * for ordinary collections (zero cost). When set, `put()` encodes the
1557
- * source field(s) and stores an encrypted `_vec` sidecar.
1558
- */
2314
+ /** Embedding config for write-time vector derivation; `undefined` (zero-cost) for ordinary
2315
+ * collections. When set, `put()` encodes the source field(s) and stores an encrypted `_vec` sidecar. */
1559
2316
  embeddings;
1560
- /**
1561
- * In-memory vector set, populated lazily from `_vec` sidecars.
1562
- * `undefined` when no embedding config is declared.
1563
- */
2317
+ /** In-memory vector set, populated lazily from `_vec` sidecars; `undefined` when no embedding config is declared. */
1564
2318
  vectorSet;
1565
- /**
1566
- * Map of field name `DictKeyDescriptor` for fields declared with
1567
- * `dictKey()`. Used by `get()`/`list()` to add `<field>Label` virtual
1568
- * fields when a locale is requested.
1569
- */
2319
+ /** Field name → `DictKeyDescriptor` for `dictKey()` fields; used by `get()`/`list()` to add
2320
+ * `<field>Label` virtual fields when a locale is requested. */
1570
2321
  dictKeyFields;
1571
- /**
1572
- * Consumer-neutral per-field descriptors declared via the `fieldMeta`
1573
- * collection option. Read by `getFieldMeta()`; merged by `collection.describe()`.
1574
- */
2322
+ /** Field name → `LookupDescriptor` for native `lookup()`/`enumOf()`/`dict()` fields (#650 Task 2) — describe()-only in this task. */
2323
+ lookupFields;
2324
+ /** Sync join-dressing hook (#650 Task 6, #626 retirement) `querySourceForJoin()`'s `presentForJoin`. */
2325
+ presentForJoin;
2326
+ /** Consumer-neutral per-field descriptors declared via `fieldMeta`; read by `getFieldMeta()`, merged by `describe()`. */
1575
2327
  fieldMeta;
1576
- /**
1577
- * Collection-level descriptive metadata declared via the `meta` collection
1578
- * option. Read by `getMeta()`; surfaced in `collection.describe()`.
1579
- */
2328
+ /** Collection-level descriptive metadata declared via `meta`; read by `getMeta()`, surfaced in `describe()`. */
1580
2329
  meta;
1581
- /**
1582
- * Outbound ref declarations for this collection (snapshot from vault
1583
- * refRegistry at construction time). Used by `describe()` (sync, config-only).
1584
- */
2330
+ /** Outbound ref declarations (snapshot from vault refRegistry at construction time); used by `describe()`. */
1585
2331
  _refs;
1586
- /**
1587
- * Money field descriptors keyed by field path. Declared via the
1588
- * `moneyFields` collection option: `put()` quantizes to a scaled-int
1589
- * string, `get()`/`list()` decode back. Mutable so {@link _applyMoneyFields}
1590
- * can attach descriptors to a collection MV-analysis pre-created.
1591
- */
2332
+ /** Money field descriptors keyed by field path, typed as the opaque {@link ViaDescriptor} marker
2333
+ * (the kernel never inspects the concrete shape); `put()` quantizes to a scaled-int string,
2334
+ * `get()`/`list()` decode back. Mutable so {@link _applyMoneyFields} can attach. */
1592
2335
  moneyFields;
2336
+ via;
2337
+ // compiled Via pipeline (money, i18n); rebuilt by {@link _applyMoneyFields}
1593
2338
  /**
1594
2339
  * Computed scalar fields, evaluated first on every `put()`. Mutable for
1595
2340
  * the same MV-pre-creation reconcile as {@link moneyFields}.
@@ -1624,27 +2369,12 @@ var Collection = class {
1624
2369
  * `createNoydb()` option (opt in with `withClassified()`).
1625
2370
  */
1626
2371
  classifiedStrategy;
1627
- /**
1628
- * Async callback provided by the Vault that resolves a dict key
1629
- * to its label for a given locale. Used by the locale-read path for
1630
- * dictKey fields.
1631
- *
1632
- * Signature: `(dictName, key, locale, fallback?) => Promise<string | undefined>`
1633
- */
1634
- dictLabelResolver;
1635
2372
  /**
1636
2373
  * Async callback provided by the Vault to open a dynamic
1637
2374
  * dictionary handle (for label-map pre-computation in the search index).
1638
2375
  * Only used in `resolveDictLabelMaps()`; static dicts bypass this entirely.
1639
2376
  */
1640
2377
  getDictionary;
1641
- /**
1642
- * Synchronous callback provided by the Vault that validates
1643
- * i18nText fields on `put()`. Throws `MissingTranslationError` when
1644
- * a required translation is absent. Called after schema validation,
1645
- * before encryption.
1646
- */
1647
- i18nPutValidator;
1648
2378
  /**
1649
2379
  * declared deterministic fields. `null` when the feature
1650
2380
  * is inactive for this collection; a frozen `Set` otherwise.
@@ -1699,14 +2429,6 @@ var Collection = class {
1699
2429
  tiers;
1700
2430
  tierMode;
1701
2431
  onCrossTierAccess;
1702
- /**
1703
- * Async translator callback provided by Noydb via Vault for
1704
- * `i18nText` fields with `autoTranslate: true`. Called
1705
- * before i18n validation so translated values are present when the
1706
- * validator runs. `undefined` when no `plaintextTranslator` was
1707
- * configured on `createNoydb()`.
1708
- */
1709
- autoTranslateHook;
1710
2432
  /**
1711
2433
  * Optional reference to the vault-level hash-chained audit
1712
2434
  * log. When present, every successful `put()` and `delete()` appends
@@ -1744,6 +2466,8 @@ var Collection = class {
1744
2466
  * commits + after `dispatchDerivations` has run.
1745
2467
  */
1746
2468
  materializedViewSource;
2469
+ /** #638 Task 4 — `Vault._collectGraphTouch`; a no-op absent an open sync/cutover/restore batch. */
2470
+ graphDispatch;
1747
2471
  /**
1748
2472
  * Optional back-reference to the owning compartment's ref
1749
2473
  * enforcer. When present, `Collection.put` calls
@@ -1806,7 +2530,6 @@ var Collection = class {
1806
2530
  this.tiersStrategy = cfg.tiersStrategy;
1807
2531
  this.searchStrategy = cfg.searchStrategy;
1808
2532
  this.historyStrategy = cfg.historyStrategy;
1809
- this.i18nStrategy = cfg.i18nStrategy;
1810
2533
  this.syncStrategy = cfg.syncStrategy;
1811
2534
  this.reconcileOnOpen = cfg.reconcileOnOpen;
1812
2535
  this.getDEK = cfg.getDEK;
@@ -1820,29 +2543,29 @@ var Collection = class {
1820
2543
  this.i18nFields = cfg.i18nFields;
1821
2544
  this.textIndexes = cfg.textIndexes;
1822
2545
  this.searchIndexStore = opts.textIndexes && opts.textIndexes.length > 0 ? opts.textIndexPersist ? new PersistedIndexStore(buildPersistedIndexCallbacks(() => this.searchContext())) : new MemoryIndexStore() : void 0;
1823
- this.i18nDensifyFields = cfg.i18nDensifyFields;
1824
2546
  this.embeddings = cfg.embeddings;
1825
2547
  this.vectorSet = cfg.vectorSet;
1826
2548
  this.dictKeyFields = cfg.dictKeyFields;
2549
+ this.lookupFields = cfg.lookupFields;
2550
+ this.presentForJoin = cfg.presentForJoin;
1827
2551
  this.fieldMeta = cfg.fieldMeta;
1828
2552
  this.meta = cfg.meta;
1829
2553
  this._refs = cfg._refs;
1830
2554
  this.moneyFields = cfg.moneyFields;
2555
+ this.via = cfg.via;
1831
2556
  this.classified = cfg.classified;
1832
2557
  this.classifiedGuardCtx = cfg.classifiedGuardCtx;
1833
2558
  this.vdigFields = cfg.vdigFields;
1834
2559
  this.classifiedStrategy = cfg.classifiedStrategy;
1835
2560
  this.computed = cfg.computed;
1836
- this.dictLabelResolver = cfg.dictLabelResolver;
1837
2561
  this.getDictionary = cfg.getDictionary;
1838
- this.i18nPutValidator = cfg.i18nPutValidator;
1839
- this.autoTranslateHook = cfg.autoTranslateHook;
1840
2562
  this.defaultLocale = cfg.defaultLocale;
1841
2563
  this.crdtMode = cfg.crdtMode;
1842
2564
  this.syncAdapter = cfg.syncAdapter;
1843
2565
  this.onAccess = cfg.onAccess;
1844
2566
  this.derivationSource = cfg.derivationSource;
1845
2567
  this.materializedViewSource = cfg.materializedViewSource;
2568
+ this.graphDispatch = cfg.graphDispatch;
1846
2569
  this.tiers = cfg.tiers;
1847
2570
  this.tierMode = cfg.tierMode;
1848
2571
  this.onCrossTierAccess = cfg.onCrossTierAccess;
@@ -1882,10 +2605,12 @@ var Collection = class {
1882
2605
  crdtMode: this.crdtMode,
1883
2606
  crdtStrategy: this.crdtStrategy,
1884
2607
  schema: this.schema,
1885
- getDEK: () => this.getDEK(this.name),
2608
+ getDEK: (collection) => this.getDEK(collection ?? this.name),
1886
2609
  cekCache: this.cekCache,
1887
- classifiedMarkerDigestOnly: () => this._classifiedMarkerDigestOnly()
2610
+ classifiedMarkerDigestOnly: () => this._classifiedMarkerDigestOnly(),
2611
+ via: this.via
1888
2612
  });
2613
+ if (cfg.classifiedEraseCfg) cfg.classifiedEraseCfg.classifySealedShred = (live) => this.codec.classifySealedShred(live);
1889
2614
  if (opts.crdt && opts.onRegisterConflictResolver) {
1890
2615
  const crdtMode = opts.crdt;
1891
2616
  const crdtResolver = async (id, local, remote) => {
@@ -1949,7 +2674,7 @@ var Collection = class {
1949
2674
  const merged = mergeFn(localRecord, remoteRecord);
1950
2675
  const mergedVersion = Math.max(local._v, remote._v) + 1;
1951
2676
  const cek = this.perRecordCek ? await this.resolveRecordCek(id) : void 0;
1952
- return this.codec.encryptRecord(merged, mergedVersion, cek);
2677
+ return this.codec.encryptRecord(merged, mergedVersion, cek, void 0, void 0, void 0, id);
1953
2678
  };
1954
2679
  }
1955
2680
  opts.onRegisterConflictResolver(collectionName, resolver);
@@ -2039,35 +2764,41 @@ var Collection = class {
2039
2764
  fieldMeta: this.fieldMeta,
2040
2765
  moneyFields: this.moneyFields,
2041
2766
  dictKeyFields: this.dictKeyFields,
2767
+ ...this.lookupFields !== void 0 ? { lookupFields: this.lookupFields } : {},
2042
2768
  computed: this.computed,
2043
2769
  refs: this._refs,
2044
2770
  zodFields: void 0,
2045
2771
  ...this.meta !== void 0 ? { meta: this.meta } : {},
2046
2772
  ...this.i18nFields !== void 0 ? { i18nFields: this.i18nFields } : {},
2047
- ...this.classified !== void 0 ? { classified: this.classified.byField } : {}
2773
+ ...this.classified !== void 0 ? { classified: this.classified.byField } : {},
2774
+ ...this.via?.taint !== void 0 ? { taint: this.via.taint } : {},
2775
+ ...this.via ? { viaFragments: this.via.describeFragments() } : {}
2776
+ // #650 Task 7
2048
2777
  });
2049
2778
  }
2050
2779
  /**
2051
- * Async describe implementation.
2052
- * Derives validator-exact types via deriveZodFields (lazy, no static zod import),
2053
- * optionally resolves dynamic-dict labels from vault.dictionary(name).list(),
2054
- * then delegates to buildDescription which also runs fieldMeta key-validation.
2780
+ * Async describe implementation. Derives validator-exact types via deriveZodFields
2781
+ * (lazy, no static zod import), optionally resolves dynamic-dict labels from vault.dictionary(name).list(),
2782
+ * then delegates to buildDescription (which also runs fieldMeta key-validation).
2055
2783
  */
2056
2784
  async describeAsync(opts) {
2057
2785
  const zodFields = this.schema !== void 0 ? await deriveZodFields(this.schema) : void 0;
2058
2786
  let dictLabels;
2059
- if (opts.resolveDictLabels === true && this.dictKeyFields !== void 0) {
2060
- dictLabels = {};
2061
- for (const [, desc] of Object.entries(this.dictKeyFields)) {
2062
- if (!isStaticDictDescriptor(desc) && this.getDictionary !== void 0) {
2063
- const handle = await this.getDictionary(desc.name);
2787
+ if (opts.resolveDictLabels === true && this.getDictionary !== void 0) {
2788
+ const names = /* @__PURE__ */ new Set();
2789
+ for (const desc of Object.values(this.dictKeyFields ?? {})) if (!isStaticDictDescriptor(desc)) names.add(desc.name);
2790
+ for (const desc of Object.values(this.lookupFields ?? {})) if (desc.backing === "reserved") names.add(desc.dimension);
2791
+ if (names.size > 0) {
2792
+ dictLabels = {};
2793
+ for (const name of names) {
2794
+ const handle = await this.getDictionary(name);
2064
2795
  const entries = await handle.list();
2065
2796
  const valueToLabel = {};
2066
2797
  for (const entry of entries) {
2067
2798
  const label = Object.values(entry.labels)[0];
2068
2799
  if (label !== void 0) valueToLabel[entry.key] = label;
2069
2800
  }
2070
- dictLabels[desc.name] = valueToLabel;
2801
+ dictLabels[name] = valueToLabel;
2071
2802
  }
2072
2803
  }
2073
2804
  }
@@ -2076,13 +2807,17 @@ var Collection = class {
2076
2807
  fieldMeta: this.fieldMeta,
2077
2808
  moneyFields: this.moneyFields,
2078
2809
  dictKeyFields: this.dictKeyFields,
2810
+ ...this.lookupFields !== void 0 ? { lookupFields: this.lookupFields } : {},
2079
2811
  computed: this.computed,
2080
2812
  refs: this._refs,
2081
2813
  zodFields,
2082
2814
  ...dictLabels !== void 0 ? { dictLabels } : {},
2083
2815
  ...this.meta !== void 0 ? { meta: this.meta } : {},
2084
2816
  ...this.i18nFields !== void 0 ? { i18nFields: this.i18nFields } : {},
2085
- ...this.classified !== void 0 ? { classified: this.classified.byField } : {}
2817
+ ...this.classified !== void 0 ? { classified: this.classified.byField } : {},
2818
+ ...this.via?.taint !== void 0 ? { taint: this.via.taint } : {},
2819
+ ...this.via ? { viaFragments: this.via.describeFragments() } : {}
2820
+ // #650 Task 7
2086
2821
  });
2087
2822
  }
2088
2823
  /** JSON Schema for this collection with describe() metadata as x- extensions. */
@@ -2203,7 +2938,7 @@ var Collection = class {
2203
2938
  }
2204
2939
  await this.onAccess?.("find", "*");
2205
2940
  if (hits.length === 0) return [];
2206
- const { verifyDigestField } = await import("./verify-CAYGXYNO.js");
2941
+ const { verifyDigestField } = await import("./verify-GH6CWVSW.js");
2207
2942
  const policy = {
2208
2943
  normalize: spec.verifyNormalize ?? "password",
2209
2944
  notLastN: spec.notLastN ?? 0,
@@ -2290,10 +3025,17 @@ var Collection = class {
2290
3025
  * analysis auto-creates a source collection (without options) during
2291
3026
  * `openVault`, before the user's `collection(name, { moneyFields })`
2292
3027
  * declaration; this reconciles that ordering. First-wins. Not public.
3028
+ *
3029
+ * PREPENDS money rather than appending: {@link compileViaBindings} always
3030
+ * compiles money before i18n (money-first pipeline order — see its
3031
+ * docstring), and by the time this reconcile runs an i18n binding may
3032
+ * already be sitting in `this.via.bindings` (declared at construction).
3033
+ * Appending here would yield `[i18n, money]` on this path vs `[money,
3034
+ * i18n]` from compile — prepending keeps both paths money-first.
2293
3035
  */
2294
3036
  _applyMoneyFields(moneyFields) {
2295
3037
  if (this.moneyFields !== void 0) return;
2296
- moneyRuntime().validateMoneyFieldPaths(moneyFields);
3038
+ this.via = ViaPipeline.build([viaBinder("money")(moneyFields), ...this.via?.bindings ?? []]);
2297
3039
  this.moneyFields = moneyFields;
2298
3040
  }
2299
3041
  /** @internal — attach computed fields post-construction. See {@link _applyMoneyFields}. */
@@ -2353,6 +3095,13 @@ var Collection = class {
2353
3095
  }
2354
3096
  this.classified = resolved;
2355
3097
  this.computed = { ...resolved.riderComputed, ...this.computed ?? {} };
3098
+ this.via = ViaPipeline.build([...this.via?.bindings ?? [], viaBinder("classified")({
3099
+ entries: classifiedFields,
3100
+ collectionName: this.name,
3101
+ guardCtx: this.classifiedGuardCtx,
3102
+ classifySealedShred: (live) => this.codec.classifySealedShred(live)
3103
+ // #629 T10
3104
+ })]);
2356
3105
  }
2357
3106
  /** @internal — used only in tests; do not read in production code. */
2358
3107
  get _ramCiphertext() {
@@ -2379,7 +3128,7 @@ var Collection = class {
2379
3128
  }
2380
3129
  }
2381
3130
  if (this.materializedViewSource !== void 0) {
2382
- const { resolveStaleMVOnRead } = await import("./stale-77LP2EEF.js");
3131
+ const { resolveStaleMVOnRead } = await import("./stale-IYYXEH3U.js");
2383
3132
  await resolveStaleMVOnRead(this.materializedViewSource, this.name);
2384
3133
  }
2385
3134
  let record;
@@ -2535,7 +3284,7 @@ var Collection = class {
2535
3284
  if (cached) return cached.record;
2536
3285
  const env = await this.adapter.get(this.vault, this.name, id);
2537
3286
  if (!env) return void 0;
2538
- const rec = await this.codec.decryptRecord(env);
3287
+ const rec = await this.codec.decryptRecord(env, { id });
2539
3288
  return rec === null ? void 0 : rec;
2540
3289
  }
2541
3290
  await this.ensureHydrated();
@@ -2583,7 +3332,7 @@ var Collection = class {
2583
3332
  if (cached2) return { record: cached2.record, version: cached2.version };
2584
3333
  const env = await this.adapter.get(this.vault, this.name, id);
2585
3334
  if (!env) return { record: null, version: 0 };
2586
- return { record: await this.codec.decryptRecord(env, { skipValidation: true }) ?? null, version: env._v };
3335
+ return { record: await this.codec.decryptRecord(env, { skipValidation: true, id }) ?? null, version: env._v };
2587
3336
  }
2588
3337
  await this.ensureHydrated();
2589
3338
  const cached = this.cache.get(id);
@@ -2617,7 +3366,7 @@ var Collection = class {
2617
3366
  const env = await this.adapter.get(this.vault, this.name, id);
2618
3367
  if (!env) return { env: null, record: null, elided: false };
2619
3368
  try {
2620
- return { env, record: await this.codec.decryptRecord(env, { skipValidation: true }), elided: false };
3369
+ return { env, record: await this.codec.decryptRecord(env, { skipValidation: true, id }), elided: false };
2621
3370
  } catch {
2622
3371
  return { env, record: null, elided: false };
2623
3372
  }
@@ -2642,7 +3391,7 @@ var Collection = class {
2642
3391
  if (!hasWritePermission(this.keyring, this.name)) {
2643
3392
  throw new ReadOnlyError();
2644
3393
  }
2645
- if (this.moneyFields) record = moneyRuntime().canonicalizeIncomingMoney(record, this.moneyFields);
3394
+ if (this.via) record = this.via.ingest(record);
2646
3395
  if (this.subsystemBus?.hasGateHandlers("beforePut")) {
2647
3396
  const { env: existingEnv, record: existingRecord } = await this.resolveGatePrior("beforePut", id);
2648
3397
  const gateEvent = {
@@ -2651,7 +3400,7 @@ var Collection = class {
2651
3400
  collection: this.name,
2652
3401
  docId: id,
2653
3402
  incoming: record,
2654
- existing: this.moneyFields ? moneyRuntime().canonicalizeStoredMoney(existingRecord, this.moneyFields) : existingRecord,
3403
+ existing: this.via ? this.via.canonicalizeStored(existingRecord) : existingRecord,
2655
3404
  existingVersion: existingEnv?._v ?? 0,
2656
3405
  existingTs: existingEnv?._ts,
2657
3406
  userId: this.keyring.userId,
@@ -2660,99 +3409,21 @@ var Collection = class {
2660
3409
  };
2661
3410
  await this.subsystemBus.dispatchGate("beforePut", gateEvent);
2662
3411
  }
2663
- if (this.classified !== void 0) {
2664
- enforceClassifiedWrite(record, this.classified.byField, this.name);
2665
- }
3412
+ const viaWriteCtx = {
3413
+ id,
3414
+ vault: this.vault,
3415
+ prior: async () => await this.resolveDensifyPrior(id) ?? null,
3416
+ emit: (e, p) => this.emitter.emit(e, p)
3417
+ };
3418
+ if (this.via) await this.via.enforceWrite(record, viaWriteCtx);
2666
3419
  if (this.computed !== void 0) {
2667
3420
  record = (await loadEvalComputedFields())(record, this.computed, id);
2668
3421
  }
2669
3422
  if (this.schema !== void 0) {
2670
3423
  record = await validateSchemaInput(this.schema, record, `put(${id})`);
2671
3424
  }
2672
- if (this.moneyFields) {
2673
- record = moneyRuntime().quantizeMoneyFields(record, this.moneyFields);
2674
- }
2675
- if (this.i18nFields) {
2676
- const obj = record;
2677
- for (const [field, descriptor] of Object.entries(this.i18nFields)) {
2678
- if (!descriptor.options.autoTranslate) continue;
2679
- const leafValues = getAtPath(obj, field);
2680
- if (leafValues.length !== 1) continue;
2681
- const value = leafValues[0];
2682
- if (!value || typeof value !== "object" || Array.isArray(value)) continue;
2683
- const map = value;
2684
- const { languages, required } = descriptor.options;
2685
- const missing = languages.filter(
2686
- (lang) => !(lang in map) || map[lang] === ""
2687
- );
2688
- if (missing.length === 0) continue;
2689
- const sourceLocale = languages.find((l) => l in map && map[l] !== "");
2690
- if (!sourceLocale) continue;
2691
- if (!this.autoTranslateHook) {
2692
- throw new TranslatorNotConfiguredError(field, this.name);
2693
- }
2694
- const toTranslate = required === "any" ? [] : required === "all" ? missing : missing.filter((l) => required.includes(l));
2695
- const translated = { ...map };
2696
- for (const targetLocale of toTranslate) {
2697
- translated[targetLocale] = await this.autoTranslateHook(
2698
- map[sourceLocale],
2699
- sourceLocale,
2700
- targetLocale,
2701
- field,
2702
- this.name
2703
- );
2704
- }
2705
- setAtPathInPlace(obj, field, translated);
2706
- }
2707
- }
2708
- let densifyPrior;
2709
- let exemptFills;
2710
- if (this.i18nDensifyFields) {
2711
- densifyPrior = await this.resolveDensifyPrior(id);
2712
- exemptFills = this.i18nStrategy.computeExemptFills(
2713
- densifyPrior,
2714
- record,
2715
- this.i18nDensifyFields
2716
- );
2717
- }
2718
- if (this.i18nFields) {
2719
- const obj = record;
2720
- for (const [field, descriptor] of Object.entries(this.i18nFields)) {
2721
- if (!descriptor.options.script) continue;
2722
- for (const leaf of getAtPath(obj, field)) {
2723
- if (!leaf || typeof leaf !== "object" || Array.isArray(leaf)) continue;
2724
- const leafMap = leaf;
2725
- const { value: cleaned, warnings } = this.i18nStrategy.enforceScript(
2726
- leafMap,
2727
- field,
2728
- descriptor,
2729
- exemptFills?.get(field)
2730
- );
2731
- if (cleaned !== leafMap) Object.assign(leafMap, cleaned);
2732
- const mode = descriptor.options.onScriptViolation;
2733
- if (mode === "warn" || mode === "filter") {
2734
- for (const w of warnings) {
2735
- this.emitter.emit("i18n:script-violation", {
2736
- vault: this.vault,
2737
- collection: this.name,
2738
- id,
2739
- mode,
2740
- warning: w
2741
- });
2742
- }
2743
- }
2744
- }
2745
- }
2746
- }
2747
- if (this.i18nPutValidator !== void 0) {
2748
- this.i18nPutValidator(record);
2749
- }
2750
- if (this.i18nDensifyFields) {
2751
- this.i18nStrategy.densify(
2752
- record,
2753
- densifyPrior,
2754
- this.i18nDensifyFields
2755
- );
3425
+ if (this.via) {
3426
+ record = await this.via.encodeWrite(record, viaWriteCtx);
2756
3427
  }
2757
3428
  if (this.refEnforcer !== void 0) {
2758
3429
  await this.refEnforcer.enforceRefsOnPut(this.name, record);
@@ -2799,7 +3470,7 @@ var Collection = class {
2799
3470
  const existingResolved = existingResolvedRecord !== null ? { record: existingResolvedRecord, version: existingVersion } : void 0;
2800
3471
  if (existingResolved && this.historyConfig.enabled !== false) {
2801
3472
  const vdigCtx2 = this.vdigFields !== null ? { id, prev: existingEnvelope } : void 0;
2802
- const histEnvelope = await this.codec.encryptRecord(existingResolved.record, existingResolved.version, cek2, void 0, void 0, vdigCtx2);
3473
+ const histEnvelope = await this.codec.encryptRecord(existingResolved.record, existingResolved.version, cek2, void 0, void 0, vdigCtx2, id);
2803
3474
  await this.historyStrategy.saveHistory(this.adapter, this.vault, this.name, id, histEnvelope);
2804
3475
  this.emitter.emit("history:save", { vault: this.vault, collection: this.name, id, version: existingResolved.version });
2805
3476
  if (this.historyConfig.maxVersions) {
@@ -2831,12 +3502,7 @@ var Collection = class {
2831
3502
  this.cache.set(id, { record: resolvedRecord, version: version2 });
2832
3503
  this.indexes?.upsert(id, resolvedRecord, existingResolved ? existingResolved.record : null);
2833
3504
  }
2834
- await this.onDirty?.(this.name, id, "put", version2);
2835
- this.emitter.emit("change", { vault: this.vault, collection: this.name, id, action: "put" });
2836
- this.searchIndexStore?.markDirty();
2837
- await this.onAccess?.("put", id);
2838
- await this.dispatchDerivations(id, record, version2);
2839
- await this.dispatchMaterializedViews(id, record);
3505
+ await this._onRecordMutated(id, "put", "local-write", { record, version: version2 });
2840
3506
  return;
2841
3507
  }
2842
3508
  let existing;
@@ -2846,7 +3512,7 @@ var Collection = class {
2846
3512
  if (!existing) {
2847
3513
  priorRaw = await this.adapter.get(this.vault, this.name, id);
2848
3514
  if (priorRaw) {
2849
- const previousRecord = await this.codec.decryptRecord(priorRaw);
3515
+ const previousRecord = await this.codec.decryptRecord(priorRaw, { id });
2850
3516
  if (previousRecord !== null) {
2851
3517
  existing = { record: previousRecord, version: priorRaw._v };
2852
3518
  }
@@ -2865,7 +3531,7 @@ var Collection = class {
2865
3531
  const cek = this.perRecordCek ? await this.resolveRecordCek(id) : void 0;
2866
3532
  const vdigCtx = this.vdigFields !== null ? { id, prev: await this.adapter.get(this.vault, this.name, id) } : void 0;
2867
3533
  if (existing && this.historyConfig.enabled !== false) {
2868
- const historyEnvelope = await this.codec.encryptRecord(existing.record, existing.version, cek, void 0, void 0, vdigCtx);
3534
+ const historyEnvelope = await this.codec.encryptRecord(existing.record, existing.version, cek, void 0, void 0, vdigCtx, id);
2869
3535
  await this.historyStrategy.saveHistory(this.adapter, this.vault, this.name, id, historyEnvelope);
2870
3536
  this.emitter.emit("history:save", {
2871
3537
  vault: this.vault,
@@ -2879,7 +3545,7 @@ var Collection = class {
2879
3545
  });
2880
3546
  }
2881
3547
  }
2882
- const envelope = await this.codec.encryptRecord(record, version, cek, options?.source, options?.sourceTs, vdigCtx);
3548
+ const envelope = await this.codec.encryptRecord(record, version, cek, options?.source, options?.sourceTs, vdigCtx, id);
2883
3549
  await this.adapter.put(this.vault, this.name, id, envelope);
2884
3550
  await this._ensureClassifiedMarker();
2885
3551
  if (this.embeddings) {
@@ -2908,17 +3574,7 @@ var Collection = class {
2908
3574
  this.indexes?.upsert(id, record, existing ? existing.record : null);
2909
3575
  this.uniqueConstraints?.upsert(id, record, existing?.record);
2910
3576
  }
2911
- await this.onDirty?.(this.name, id, "put", version);
2912
- this.emitter.emit("change", {
2913
- vault: this.vault,
2914
- collection: this.name,
2915
- id,
2916
- action: "put"
2917
- });
2918
- this.searchIndexStore?.markDirty();
2919
- await this.onAccess?.("put", id);
2920
- await this.dispatchDerivations(id, record, version);
2921
- await this.dispatchMaterializedViews(id, record);
3577
+ await this._onRecordMutated(id, "put", "local-write", { record, version });
2922
3578
  }
2923
3579
  /**
2924
3580
  * Fire registered MV strategies whose dependency set includes this
@@ -2930,10 +3586,10 @@ var Collection = class {
2930
3586
  * MV-emitted row (carries `_materializedFrom`) — defensive guard
2931
3587
  * against missed cycle detection.
2932
3588
  *
2933
- * @internal
3589
+ * @internal `wave` (#638 Task 4): when present (the sync/cutover/restore dispatch wave),
3590
+ * an eager MV already refreshed this wave is skipped (per-target dedup, keyed on spec name).
2934
3591
  */
2935
- async dispatchMaterializedViews(id, record) {
2936
- void id;
3592
+ async dispatchMaterializedViews(id, record, wave) {
2937
3593
  if (this.materializedViewSource === void 0) return;
2938
3594
  const incoming = record;
2939
3595
  if (incoming && typeof incoming === "object" && "_materializedFrom" in incoming) return;
@@ -2945,18 +3601,20 @@ var Collection = class {
2945
3601
  for (const reg of mvs) {
2946
3602
  const mode = reg.spec.refresh;
2947
3603
  if (mode === "eager") {
3604
+ if (wave?.seen(`mv\0${reg.spec.name}`)) continue;
2948
3605
  if (executor === null) {
2949
3606
  ;
2950
- ({ MaterializedViewExecutor: executor } = await import("./executor-MHUM2VZK.js"));
3607
+ ({ MaterializedViewExecutor: executor } = await import("./executor-IL7SRNUZ.js"));
2951
3608
  }
2952
3609
  await executor.refresh(reg, {
2953
3610
  getCollection: (name) => this.materializedViewSource.getCollection(name),
2954
3611
  getActiveTxContext: () => this.materializedViewSource.getActiveTxContext(),
2955
- getQueryContext: () => this.materializedViewSource.getQueryContext()
3612
+ getQueryContext: () => this.materializedViewSource.getQueryContext(),
3613
+ dispatchCtx: this.#dispatchCtx({ collection: this.name, id })
2956
3614
  });
2957
3615
  } else if (mode === "lazy") {
2958
3616
  if (staleHelpers === null) {
2959
- staleHelpers = await import("./stale-77LP2EEF.js");
3617
+ staleHelpers = await import("./stale-IYYXEH3U.js");
2960
3618
  }
2961
3619
  staleHelpers.markMVStale(registry, reg.spec.name);
2962
3620
  }
@@ -3008,7 +3666,15 @@ var Collection = class {
3008
3666
  raw = (await this.resolvePriorValues(id))?.record ?? null;
3009
3667
  }
3010
3668
  if (raw === null) return null;
3011
- return this.moneyFields ? moneyRuntime().canonicalizeStoredMoney(raw, this.moneyFields) : raw;
3669
+ return this.via ? this.via.canonicalizeStored(raw) : raw;
3670
+ }
3671
+ /** @internal #638 Task 4 — decrypted STORED-form record + envelope version for the sync/cutover/
3672
+ * restore dispatch wave (id threaded into decrypt, matching `_invalidateCacheEntry`'s contract). */
3673
+ async _getStoredRecordForDispatch(id) {
3674
+ const env = await this.adapter.get(this.vault, this.name, id);
3675
+ if (!env || isTombstone(env, this.storeCiphertext) || isDeleteMarker(env)) return null;
3676
+ const record = await this.codec.decryptRecord(env, { id });
3677
+ return record === null ? null : { record, version: env._v };
3012
3678
  }
3013
3679
  /**
3014
3680
  * @internal Ids of records whose top-level `field` equals `value`.
@@ -3039,19 +3705,22 @@ var Collection = class {
3039
3705
  }
3040
3706
  return out;
3041
3707
  }
3042
- /**
3043
- * @internal Recompute a rollup aggregate onto the parent.
3044
- * Gathers every child of `parentId`, runs `compute`, and patches only the
3045
- * rollup `field` onto the parent's raw stored record (value-equality
3046
- * guarded). No-op when the parent record does not exist.
3047
- */
3048
- async recomputeRollup(spec, parentId) {
3049
- if (this.derivationSource === void 0 || spec.rollup === void 0) return;
3708
+ /** @internal Recompute a rollup aggregate onto the parent from `parentId`'s current children
3709
+ * (value-equality guarded; no-op absent parent). `wave` (#638 T4): per-target dedup. Returns
3710
+ * the `putDerivedOutput` outcome, or `'noop'` when nothing needed recomputing (#638 T6 —
3711
+ * `dispatchRollupsOnDelete`'s forget-fanout caller needs this to fill the forget report). */
3712
+ /** @internal ctx for `putDerivedOutput`'s frozen-period skip+audit (#638 Task 5). */
3713
+ #dispatchCtx(source) {
3714
+ return { emit: (e, p) => this.emitter.emit(e, p), source, audit: ledgerAuditHook(this.ledger, this.keyring.userId) };
3715
+ }
3716
+ async recomputeRollup(spec, parentId, source, wave) {
3717
+ if (this.derivationSource === void 0 || spec.rollup === void 0) return "noop";
3050
3718
  const { from, key, field, compute } = spec.rollup;
3051
3719
  const into = spec.source;
3720
+ if (wave?.seen(`rollup\0${into}\0${parentId}\0${field}`)) return "noop";
3052
3721
  const intoColl = this.derivationSource.getCollection(into);
3053
3722
  const base = await intoColl._getStoredRecord(parentId);
3054
- if (base === null) return;
3723
+ if (base === null) return "noop";
3055
3724
  const fromColl = this.derivationSource.getCollection(from);
3056
3725
  const childIds = await fromColl._findMatchingIds(key, parentId);
3057
3726
  const children = [];
@@ -3060,7 +3729,7 @@ var Collection = class {
3060
3729
  if (c !== null && c !== void 0) children.push(c);
3061
3730
  }
3062
3731
  const newValue = compute(children);
3063
- if (selfWriteFieldEqual(base[field], newValue)) return;
3732
+ if (selfWriteFieldEqual(base[field], newValue)) return "noop";
3064
3733
  const patched = { ...base, [field]: newValue };
3065
3734
  const txCtx = this.derivationSource.getActiveTxContext();
3066
3735
  if (txCtx !== null) {
@@ -3070,28 +3739,32 @@ var Collection = class {
3070
3739
  priorEnvelope: prior
3071
3740
  });
3072
3741
  }
3073
- await intoColl.put(parentId, patched);
3742
+ return putDerivedOutput(intoColl, parentId, patched, this.#dispatchCtx(source));
3074
3743
  }
3075
3744
  /**
3076
- * @internal Fire any rollups for which THIS collection is the
3077
- * child `from`, recomputing the affected parent after a child delete. Called
3078
- * from the delete path with the just-removed record's key value. Other
3079
- * derivation kinds do not react to deletes (unchanged).
3745
+ * @internal Fire any rollups for which THIS collection is the child `from`, recomputing the
3746
+ * affected parent after a child delete/forget. Called from the delete path (return discarded)
3747
+ * and from `forgetDerivedFanout` (#638 Task 6), which needs the per-target outcome to fill
3748
+ * `ForgetResult.derivedAggregatesRecomputed`/`derivedResidueFrozen`.
3080
3749
  */
3081
- async dispatchRollupsOnDelete(deleted) {
3082
- if (this.derivationSource === void 0) return;
3750
+ async dispatchRollupsOnDelete(id, deleted) {
3751
+ if (this.derivationSource === void 0) return [];
3083
3752
  const registry = this.derivationSource.registry();
3084
3753
  const rec = deleted;
3754
+ const results = [];
3085
3755
  for (const { spec } of registry.strategiesForSource(this.name)) {
3086
3756
  if (!spec.rollup || spec.rollup.from !== this.name) continue;
3087
3757
  const kv = rec[spec.rollup.key];
3088
3758
  if (typeof kv !== "string" && typeof kv !== "number") continue;
3089
- await this.recomputeRollup(spec, String(kv));
3759
+ results.push({ into: spec.source, parentId: String(kv), outcome: await this.recomputeRollup(spec, String(kv), { collection: this.name, id }) });
3090
3760
  }
3761
+ return results;
3091
3762
  }
3092
- async dispatchDerivations(id, record, version) {
3763
+ /** @internal `wave` (#638 Task 4) — threaded to `recomputeRollup` for the sync/cutover/restore
3764
+ * dispatch wave's per-target dedup; `undefined` on the local-write path (byte-identical). */
3765
+ async dispatchDerivations(id, record, version, wave) {
3093
3766
  if (this.derivationSource === void 0) return;
3094
- const incoming = this.moneyFields ? moneyRuntime().canonicalizeStoredMoney(record, this.moneyFields) : record;
3767
+ const incoming = this.via ? this.via.canonicalizeStored(record) : record;
3095
3768
  if (incoming && typeof incoming === "object" && "_derivedFrom" in incoming) return;
3096
3769
  const registry = this.derivationSource.registry();
3097
3770
  const strategies = registry.strategiesForSource(this.name);
@@ -3108,7 +3781,7 @@ var Collection = class {
3108
3781
  } else {
3109
3782
  parentId = id;
3110
3783
  }
3111
- if (parentId !== null) await this.recomputeRollup(spec, parentId);
3784
+ if (parentId !== null) await this.recomputeRollup(spec, parentId, { collection: this.name, id }, wave);
3112
3785
  continue;
3113
3786
  }
3114
3787
  const isSource = spec.source === this.name;
@@ -3141,10 +3814,11 @@ var Collection = class {
3141
3814
  continue;
3142
3815
  }
3143
3816
  if (DerivationExecutor === null) {
3144
- ({ DerivationExecutor } = await import("./executor-EUPYHCLU.js"));
3817
+ ({ DerivationExecutor } = await import("./executor-WAEI6PDV.js"));
3145
3818
  }
3146
3819
  for (const run of runs) {
3147
3820
  const ctx = { vault: this.derivationSource.getReadOnlyFacade() };
3821
+ const outCtx = this.#dispatchCtx({ collection: spec.source, id: run.runId });
3148
3822
  const result = await DerivationExecutor.run(spec, run.input, run.version, strategyHash, ctx);
3149
3823
  for (const key of Object.keys(spec.outputs)) {
3150
3824
  const out = result.outputs[key];
@@ -3160,7 +3834,7 @@ var Collection = class {
3160
3834
  const outputCollection = this.derivationSource.getCollection(outSpec.collection);
3161
3835
  const txCtx = this.derivationSource.getActiveTxContext();
3162
3836
  if (out.kind === "array") {
3163
- const { loadFanoutSidecar, saveFanoutSidecar } = await import("./fanout-sidecar-NBDKKCHT.js");
3837
+ const { loadFanoutSidecar, saveFanoutSidecar } = await import("./fanout-sidecar-CI5BCMQM.js");
3164
3838
  const prior = await loadFanoutSidecar(this.adapter, this.vault, spec.source, run.runId, key, this.getDEK, this.storeCiphertext);
3165
3839
  const prevKeys = new Set(prior?.keys ?? []);
3166
3840
  const newKeysList = out.entries.map((e) => e.key);
@@ -3182,7 +3856,7 @@ var Collection = class {
3182
3856
  priorEnvelope
3183
3857
  });
3184
3858
  }
3185
- await outputCollection.put(entry.key, entry.value, { source: "derived" });
3859
+ await putDerivedOutput(outputCollection, entry.key, entry.value, outCtx, { source: "derived" });
3186
3860
  }
3187
3861
  await saveFanoutSidecar(this.adapter, this.vault, {
3188
3862
  source: spec.source,
@@ -3215,7 +3889,7 @@ var Collection = class {
3215
3889
  priorEnvelope: prior
3216
3890
  });
3217
3891
  }
3218
- await outputCollection.put(run.runId, patched, { source: "derived" });
3892
+ await putDerivedOutput(outputCollection, run.runId, patched, outCtx, { source: "derived" });
3219
3893
  continue;
3220
3894
  }
3221
3895
  if (txCtx !== null) {
@@ -3230,7 +3904,7 @@ var Collection = class {
3230
3904
  priorEnvelope: prior
3231
3905
  });
3232
3906
  }
3233
- await outputCollection.put(run.runId, out.value, { source: "derived" });
3907
+ await putDerivedOutput(outputCollection, run.runId, out.value, outCtx, { source: "derived" });
3234
3908
  }
3235
3909
  }
3236
3910
  }
@@ -3287,9 +3961,9 @@ var Collection = class {
3287
3961
  const next = transform(record);
3288
3962
  const nextVersion = (env._v ?? 0) + 1;
3289
3963
  const cek = this.perRecordCek ? await this.resolveRecordCek(id) : void 0;
3290
- const newEnv = await this.codec.encryptRecord(next, nextVersion, cek, void 0, void 0, this.vdigFields !== null ? { id, prev: env } : void 0);
3964
+ const newEnv = await this.codec.encryptRecord(next, nextVersion, cek, void 0, void 0, this.vdigFields !== null ? { id, prev: env } : void 0, id);
3291
3965
  await this.adapter.put(this.vault, this.name, id, newEnv);
3292
- await this._invalidateCacheEntry(id);
3966
+ await this._onRecordMutated(id, "put", "cutover");
3293
3967
  if (this.ledger) {
3294
3968
  await this.ledger.append({
3295
3969
  op: "migration",
@@ -3318,9 +3992,8 @@ var Collection = class {
3318
3992
  * history snapshot still fire so backup integrity and time-travel
3319
3993
  * reconstruction stay consistent.
3320
3994
  *
3321
- * Returns silently for delete-of-absent (idempotent contract — both
3322
- * paths honour this: the `txCtx === null` path also reads the prior
3323
- * envelope and short-circuits before the ledger/event side-effects).
3995
+ * Returns `true` when it erased a live record, `false` for delete-of-absent (idempotent
3996
+ * contract both txCtx-aware and txCtx===null callers honour this, short-circuiting before any side-effect).
3324
3997
  *
3325
3998
  * When a `txCtx` is supplied, the prior envelope is captured and
3326
3999
  * pushed onto `txCtx._executed` BEFORE the delete fires — mirrors
@@ -3349,7 +4022,7 @@ var Collection = class {
3349
4022
  */
3350
4023
  async _internalDelete(id, txCtx = null) {
3351
4024
  const prior = await this.adapter.get(this.vault, this.name, id);
3352
- if (prior === null) return;
4025
+ if (prior === null) return false;
3353
4026
  if (txCtx !== null) {
3354
4027
  txCtx._executed.push({
3355
4028
  op: {
@@ -3362,6 +4035,7 @@ var Collection = class {
3362
4035
  });
3363
4036
  }
3364
4037
  await this._doDelete(id, true);
4038
+ return true;
3365
4039
  }
3366
4040
  async _doDelete(id, internal) {
3367
4041
  if (!hasWritePermission(this.keyring, this.name)) {
@@ -3374,7 +4048,7 @@ var Collection = class {
3374
4048
  vault: this.vault,
3375
4049
  collection: this.name,
3376
4050
  docId: id,
3377
- existing: this.moneyFields ? moneyRuntime().canonicalizeStoredMoney(existingRecord, this.moneyFields) : existingRecord,
4051
+ existing: this.via ? this.via.canonicalizeStored(existingRecord) : existingRecord,
3378
4052
  existingVersion: existingEnv?._v ?? 0,
3379
4053
  existingTs: existingEnv?._ts,
3380
4054
  internal,
@@ -3392,7 +4066,7 @@ var Collection = class {
3392
4066
  if (!existing && this.historyConfig.enabled !== false) {
3393
4067
  const previousEnvelope2 = await this.adapter.get(this.vault, this.name, id);
3394
4068
  if (previousEnvelope2) {
3395
- const previousRecord = await this.codec.decryptRecord(previousEnvelope2);
4069
+ const previousRecord = await this.codec.decryptRecord(previousEnvelope2, { id });
3396
4070
  if (previousRecord !== null) {
3397
4071
  existing = { record: previousRecord, version: previousEnvelope2._v };
3398
4072
  }
@@ -3404,7 +4078,7 @@ var Collection = class {
3404
4078
  if (existing && this.historyConfig.enabled !== false) {
3405
4079
  const cek = this.perRecordCek ? await this.resolveRecordCek(id) : void 0;
3406
4080
  const prevForVdig = this.vdigFields !== null ? await this.adapter.get(this.vault, this.name, id) : null;
3407
- const historyEnvelope = await this.codec.encryptRecord(existing.record, existing.version, cek, void 0, void 0, this.vdigFields !== null ? { id, prev: prevForVdig } : void 0);
4081
+ const historyEnvelope = await this.codec.encryptRecord(existing.record, existing.version, cek, void 0, void 0, this.vdigFields !== null ? { id, prev: prevForVdig } : void 0, id);
3408
4082
  await this.historyStrategy.saveHistory(this.adapter, this.vault, this.name, id, historyEnvelope);
3409
4083
  }
3410
4084
  const previousEnvelope = await this.adapter.get(this.vault, this.name, id);
@@ -3440,19 +4114,11 @@ var Collection = class {
3440
4114
  this.uniqueConstraints?.remove(id, existing.record);
3441
4115
  }
3442
4116
  }
3443
- await this.onDirty?.(this.name, id, "put", markerVersion ?? (existing?.version ?? 0) + 1);
3444
- this.emitter.emit("change", {
3445
- vault: this.vault,
3446
- collection: this.name,
3447
- id,
3448
- action: "delete"
3449
- });
3450
- this.searchIndexStore?.markDirty();
3451
- await this.onAccess?.("delete", id);
4117
+ await this._onRecordMutated(id, "delete", "local-delete", { version: markerVersion ?? (existing?.version ?? 0) + 1 });
3452
4118
  if (!internal) {
3453
4119
  await this.dispatchMaterializedViewsOnDelete(id);
3454
4120
  await this.dispatchArrayDerivationsOnDelete(id);
3455
- if (existing) await this.dispatchRollupsOnDelete(existing.record);
4121
+ if (existing) await this.dispatchRollupsOnDelete(id, existing.record);
3456
4122
  }
3457
4123
  }
3458
4124
  /**
@@ -3501,75 +4167,81 @@ var Collection = class {
3501
4167
  return { previousVersion: live._v };
3502
4168
  }
3503
4169
  /**
3504
- * Cascade deletes of array-shape derived rows when a source row is
3505
- * deleted. Reads each registered strategy's fanout sidecar
3506
- * for this source id, deletes every listed derived row, then
3507
- * deletes the sidecar itself.
3508
- *
3509
- * Record-shape derivations are skipped — see _doDelete's comment
3510
- * for why the asymmetry is correct.
4170
+ * Cascade deletes of array-shape derived rows when a source row is deleted. Reads each
4171
+ * registered strategy's fanout sidecar for this source id, deletes every listed derived
4172
+ * row, then deletes the sidecar itself. Returns the REAL erased count (#622 review Finding 1).
3511
4173
  *
4174
+ * Record-shape derivations are skipped on the ordinary delete path (see _doDelete's comment).
4175
+ * `eraseRecordShapeToo` (#638 T6, default `false`) opts a same-id record-shape copy into
4176
+ * erasure too — forget()'s fanout, GDPR residue. A delete-of-absent contributes 0 either way.
3512
4177
  * @internal
3513
4178
  */
3514
- async dispatchArrayDerivationsOnDelete(id) {
3515
- if (this.derivationSource === void 0) return;
4179
+ async dispatchArrayDerivationsOnDelete(id, eraseRecordShapeToo = false) {
4180
+ if (this.derivationSource === void 0) return 0;
3516
4181
  const registry = this.derivationSource.registry();
3517
4182
  const strategies = registry.strategiesForSource(this.name);
3518
- if (strategies.length === 0) return;
4183
+ if (strategies.length === 0) return 0;
3519
4184
  let helpers = null;
3520
4185
  const txCtx = this.derivationSource.getActiveTxContext();
4186
+ let erased = 0;
3521
4187
  for (const { spec } of strategies) {
3522
4188
  for (const [outputKey, outSpec] of Object.entries(spec.outputs)) {
3523
- if (outSpec.shape !== "array") continue;
4189
+ if (outSpec.shape === "record") {
4190
+ if (eraseRecordShapeToo && spec.source === this.name && outSpec.collection !== this.name) {
4191
+ if (await this.derivationSource.getCollection(outSpec.collection)._internalDelete(id, txCtx)) erased += 1;
4192
+ }
4193
+ continue;
4194
+ }
3524
4195
  if (helpers === null) {
3525
- helpers = await import("./fanout-sidecar-NBDKKCHT.js");
4196
+ helpers = await import("./fanout-sidecar-CI5BCMQM.js");
3526
4197
  }
3527
4198
  const sidecar = await helpers.loadFanoutSidecar(this.adapter, this.vault, spec.source, id, outputKey, this.getDEK, this.storeCiphertext);
3528
4199
  if (!sidecar) continue;
3529
4200
  const outputCollection = this.derivationSource.getCollection(outSpec.collection);
3530
4201
  for (const derivedId of sidecar.keys) {
3531
- await outputCollection._internalDelete(derivedId, txCtx);
4202
+ if (await outputCollection._internalDelete(derivedId, txCtx)) erased += 1;
3532
4203
  }
3533
4204
  await helpers.deleteFanoutSidecar(this.adapter, this.vault, spec.source, id, outputKey);
3534
4205
  }
3535
4206
  }
4207
+ return erased;
3536
4208
  }
3537
4209
  /**
3538
- * Mirror of {@link dispatchMaterializedViews} for the delete path.
3539
- * No record content is available (it's gone), so the
3540
- * `_materializedFrom` skip used by the put-side dispatch doesn't
3541
- * apply here instead, the recursion guard is the `internal` gate
3542
- * at the `_doDelete` call site above.
3543
- *
4210
+ * Mirror of {@link dispatchMaterializedViews} for the delete/forget path — no `_materializedFrom`
4211
+ * skip (record's gone); the `internal` gate at `_doDelete` is the recursion guard. Returns the
4212
+ * row count TOMBSTONED across every eager MV sourced here (#638 T6 — `forgetDerivedFanout`'s
4213
+ * `derivedRecordsErased`); a lazy MV only flips its stale bit (0 contribution — resolved on read).
3544
4214
  * @internal
3545
4215
  */
3546
4216
  async dispatchMaterializedViewsOnDelete(id) {
3547
- void id;
3548
- if (this.materializedViewSource === void 0) return;
4217
+ if (this.materializedViewSource === void 0) return 0;
3549
4218
  const registry = this.materializedViewSource.registry();
3550
4219
  const mvs = registry.mvsForSource(this.name);
3551
- if (mvs.length === 0) return;
4220
+ if (mvs.length === 0) return 0;
3552
4221
  let executor = null;
3553
4222
  let staleHelpers = null;
4223
+ let deleted = 0;
3554
4224
  for (const reg of mvs) {
3555
4225
  const mode = reg.spec.refresh;
3556
4226
  if (mode === "eager") {
3557
4227
  if (executor === null) {
3558
4228
  ;
3559
- ({ MaterializedViewExecutor: executor } = await import("./executor-MHUM2VZK.js"));
4229
+ ({ MaterializedViewExecutor: executor } = await import("./executor-IL7SRNUZ.js"));
3560
4230
  }
3561
- await executor.refresh(reg, {
4231
+ deleted += (await executor.refresh(reg, {
3562
4232
  getCollection: (name) => this.materializedViewSource.getCollection(name),
3563
4233
  getActiveTxContext: () => this.materializedViewSource.getActiveTxContext(),
3564
- getQueryContext: () => this.materializedViewSource.getQueryContext()
3565
- });
4234
+ getQueryContext: () => this.materializedViewSource.getQueryContext(),
4235
+ dispatchCtx: this.#dispatchCtx({ collection: this.name, id })
4236
+ })).deleted;
3566
4237
  } else if (mode === "lazy") {
3567
4238
  if (staleHelpers === null) {
3568
- staleHelpers = await import("./stale-77LP2EEF.js");
4239
+ staleHelpers = await import("./stale-IYYXEH3U.js");
3569
4240
  }
3570
4241
  staleHelpers.markMVStale(registry, reg.spec.name);
3571
4242
  }
3572
4243
  }
4244
+ return deleted;
3573
4245
  }
3574
4246
  /**
3575
4247
  * List all records in the collection.
@@ -3588,22 +4260,14 @@ var Collection = class {
3588
4260
  );
3589
4261
  }
3590
4262
  if (this.materializedViewSource !== void 0) {
3591
- const { resolveStaleMVOnRead } = await import("./stale-77LP2EEF.js");
4263
+ const { resolveStaleMVOnRead } = await import("./stale-IYYXEH3U.js");
3592
4264
  await resolveStaleMVOnRead(this.materializedViewSource, this.name);
3593
4265
  }
3594
4266
  await this.ensureHydrated();
3595
4267
  const records = [...this.cache.values()].map((e) => e.record);
3596
- if (!this.hasReadTransforms()) return records;
4268
+ if (!this.via) return records;
3597
4269
  return Promise.all(records.map((r) => this.applyLocaleToRecord(r, locale)));
3598
4270
  }
3599
- /**
3600
- * @internal — whether any read-side record transform is registered
3601
- * (money decode, i18nText resolution, dictKey labels). Gates the
3602
- * no-transform fast path in {@link list}.
3603
- */
3604
- hasReadTransforms() {
3605
- return this.moneyFields !== void 0 && Object.keys(this.moneyFields).length > 0 || this.i18nFields !== void 0 && Object.keys(this.i18nFields).length > 0 || this.dictKeyFields !== void 0 && Object.keys(this.dictKeyFields).length > 0;
3606
- }
3607
4271
  /**
3608
4272
  * Scan-mode full-text search over a plain-text `field`. Decrypts the
3609
4273
  * collection in memory and ranks records by BM25 against the tokenized query.
@@ -3830,7 +4494,7 @@ var Collection = class {
3830
4494
  getIndexes: () => this.getIndexes(),
3831
4495
  lookupById: (id) => this.cache.get(id)?.record,
3832
4496
  snapshotEntries: () => [...this.cache.entries()].map(([id, e]) => ({ id, record: e.record })),
3833
- ...this.moneyFields ? { moneyFields: this.moneyFields } : {}
4497
+ ...this.via ? { via: this.via } : {}
3834
4498
  };
3835
4499
  const resolver = this.joinResolver;
3836
4500
  const leftCollection = this.name;
@@ -3910,8 +4574,7 @@ var Collection = class {
3910
4574
  * silently miss records — consistent with the `query()` /
3911
4575
  * `list()` lazy-mode policy. If this becomes a blocker for a
3912
4576
  * real consumer, the fix is to add an async `scan()`-backed
3913
- * variant of this method, which is exactly what streaming
3914
- * joins will need anyway.
4577
+ * variant of this method, which is exactly what streaming joins will need anyway.
3915
4578
  */
3916
4579
  querySourceForJoin() {
3917
4580
  if (this.lazy) {
@@ -3931,9 +4594,8 @@ var Collection = class {
3931
4594
  this.emitter.on("change", handler);
3932
4595
  return () => this.emitter.off("change", handler);
3933
4596
  },
3934
- // Expose this (right-side) collection's i18nText descriptors so
3935
- // the join executor can resolve joined i18n fields at the `join` layer.
3936
- ...this.i18nFields !== void 0 ? { i18nFields: this.i18nFields } : {}
4597
+ // Sync join dressing (#650 Task 6, #626 retirement) — i18n-text + lookup-label, from this collection's own bindings.
4598
+ ...this.presentForJoin !== void 0 ? { presentForJoin: this.presentForJoin } : {}
3937
4599
  };
3938
4600
  }
3939
4601
  /**
@@ -3969,7 +4631,7 @@ var Collection = class {
3969
4631
  );
3970
4632
  const entries = [];
3971
4633
  for (const env of envelopes) {
3972
- const record = await this.codec.decryptRecord(env, { skipValidation: true });
4634
+ const record = await this.codec.decryptRecord(env, { skipValidation: true, id });
3973
4635
  if (record === null) continue;
3974
4636
  entries.push({
3975
4637
  version: env._v,
@@ -3998,7 +4660,7 @@ var Collection = class {
3998
4660
  version
3999
4661
  );
4000
4662
  if (!envelope) return null;
4001
- return this.codec.decryptRecord(envelope, { skipValidation: true });
4663
+ return this.codec.decryptRecord(envelope, { skipValidation: true, id });
4002
4664
  }
4003
4665
  /** Revert a record to a past version. Creates a new version with the old content. */
4004
4666
  async revert(id, version) {
@@ -4106,7 +4768,7 @@ var Collection = class {
4106
4768
  const id = ids[i];
4107
4769
  const envelope = await this.adapter.get(this.vault, this.name, id);
4108
4770
  if (envelope) {
4109
- const record = await this.codec.decryptRecord(envelope, { sealedAsHandles: true });
4771
+ const record = await this.codec.decryptRecord(envelope, { id, sealedAsHandles: true });
4110
4772
  if (record === null) continue;
4111
4773
  items.push(record);
4112
4774
  if (!this.lazy && !this.cache.has(id)) {
@@ -4180,14 +4842,14 @@ var Collection = class {
4180
4842
  [],
4181
4843
  [],
4182
4844
  joinContext,
4183
- this.moneyFields
4845
+ this.via
4184
4846
  );
4185
4847
  }
4186
4848
  /** Decrypt a page of envelopes returned by `adapter.listPage`. */
4187
4849
  async decryptPage(items) {
4188
4850
  const out = [];
4189
4851
  for (const { id, envelope } of items) {
4190
- const record = await this.codec.decryptRecord(envelope, { sealedAsHandles: true });
4852
+ const record = await this.codec.decryptRecord(envelope, { id, sealedAsHandles: true });
4191
4853
  if (record === null) continue;
4192
4854
  out.push({ id, record, version: envelope._v });
4193
4855
  }
@@ -4247,7 +4909,7 @@ var Collection = class {
4247
4909
  }
4248
4910
  return;
4249
4911
  }
4250
- const record = await this.codec.decryptRecord(envelope, { sealedAsHandles: true });
4912
+ const record = await this.codec.decryptRecord(envelope, { id, sealedAsHandles: true });
4251
4913
  if (record === null) {
4252
4914
  this.cache.delete(id);
4253
4915
  if (previous) {
@@ -4267,9 +4929,63 @@ var Collection = class {
4267
4929
  * Never writes to the store and never fires write hooks, so it cannot loop.
4268
4930
  */
4269
4931
  async _applyRemoteChange(id, action) {
4270
- await this._invalidateCacheEntry(id);
4271
- this.emitter.emit("change", { vault: this.vault, collection: this.name, id, action });
4272
- this.searchIndexStore?.markDirty();
4932
+ await this._onRecordMutated(id, action, "tab-mirror");
4933
+ }
4934
+ /**
4935
+ * Origin-tagged mutation choke point (#623 task 10) — the single dispatch
4936
+ * point every put/delete path funnels through, AFTER its own store write,
4937
+ * to fire the side-effects that path performs today. Each case below
4938
+ * performs EXACTLY the side-effect set the seam-map Part-3 table
4939
+ * (`.superpowers/sdd/seam-map-i18n-pipeline.md`) records for `origin` —
4940
+ * this is a pure parity extraction of existing tails, not a behavior
4941
+ * change. Phase C (#621, #622) plugs the dependency-graph dispatch into
4942
+ * this socket, keyed off `origin`, without every call site having to
4943
+ * learn the graph.
4944
+ *
4945
+ * `restore` is never reached: `Vault.load` / `backup.ts#loadVault` drops
4946
+ * the whole `collectionCache` instead of dispatching per record — the
4947
+ * origin is reserved for phase C.
4948
+ *
4949
+ * @internal
4950
+ */
4951
+ async _onRecordMutated(id, action, origin, ctx) {
4952
+ switch (origin) {
4953
+ case "local-write": {
4954
+ const record = ctx.record;
4955
+ const version = ctx.version;
4956
+ await this.onDirty?.(this.name, id, "put", version);
4957
+ this.emitter.emit("change", { vault: this.vault, collection: this.name, id, action: "put" });
4958
+ this.searchIndexStore?.markDirty();
4959
+ await this.onAccess?.("put", id);
4960
+ await this.dispatchDerivations(id, record, version);
4961
+ await this.dispatchMaterializedViews(id, record);
4962
+ return;
4963
+ }
4964
+ case "local-delete": {
4965
+ await this.onDirty?.(this.name, id, "put", ctx.version);
4966
+ this.emitter.emit("change", { vault: this.vault, collection: this.name, id, action: "delete" });
4967
+ this.searchIndexStore?.markDirty();
4968
+ await this.onAccess?.("delete", id);
4969
+ return;
4970
+ }
4971
+ case "tab-mirror":
4972
+ await this._invalidateCacheEntry(id);
4973
+ this.emitter.emit("change", { vault: this.vault, collection: this.name, id, action });
4974
+ this.searchIndexStore?.markDirty();
4975
+ return;
4976
+ case "sync-apply":
4977
+ this._invalidateCekCacheEntry(id);
4978
+ await this._invalidateCacheEntry(id);
4979
+ this.graphDispatch?.collect(this.name, id);
4980
+ return;
4981
+ case "cutover":
4982
+ await this._invalidateCacheEntry(id);
4983
+ this.graphDispatch?.collect(this.name, id);
4984
+ return;
4985
+ case "restore":
4986
+ this.graphDispatch?.collect(this.name, id);
4987
+ return;
4988
+ }
4273
4989
  }
4274
4990
  /** @internal — the current in-memory record without a store read (for conflict capture). */
4275
4991
  _peekCached(id) {
@@ -4427,7 +5143,7 @@ var Collection = class {
4427
5143
  for (const [id, entry] of this.cache) {
4428
5144
  const cek = this.perRecordCek ? await this.resolveRecordCek(id) : void 0;
4429
5145
  const prevForVdig = this.vdigFields !== null ? await this.adapter.get(this.vault, this.name, id) : null;
4430
- result[id] = await this.codec.encryptRecord(entry.record, entry.version, cek, void 0, void 0, this.vdigFields !== null ? { id, prev: prevForVdig } : void 0);
5146
+ result[id] = await this.codec.encryptRecord(entry.record, entry.version, cek, void 0, void 0, this.vdigFields !== null ? { id, prev: prevForVdig } : void 0, id);
4431
5147
  }
4432
5148
  return result;
4433
5149
  }
@@ -4442,90 +5158,19 @@ var Collection = class {
4442
5158
  * map when `locale === 'raw'`).
4443
5159
  * - dictKey fields: `<field>Label` virtual fields added.
4444
5160
  *
4445
- * Returns the record unchanged when no locale is active and no i18n/dict
4446
- * fields are registered.
5161
+ * Returns the record unchanged when no Via pipeline is compiled (#638 Task 7:
5162
+ * was money/i18n/dictKey-flag-gated — missed any OTHER binding's `present`
5163
+ * hook, e.g. the `computed` via-binding's virtual-mode read-time compute;
5164
+ * `this.via` truthiness is the general condition every binding's presence
5165
+ * already implies).
4447
5166
  */
4448
5167
  async applyLocaleToRecord(record, localeOpts) {
4449
- const hasI18n = this.i18nFields && Object.keys(this.i18nFields).length > 0;
4450
- const hasDict = this.dictKeyFields && Object.keys(this.dictKeyFields).length > 0;
4451
- const hasMoney = this.moneyFields && Object.keys(this.moneyFields).length > 0;
4452
- if (!hasI18n && !hasDict && !hasMoney) return record;
5168
+ if (!this.via) return record;
4453
5169
  const locale = localeOpts?.locale ?? this.defaultLocale;
4454
- let result = record;
4455
- if (hasMoney && this.moneyFields) {
4456
- result = moneyRuntime().decodeMoneyFields(result, this.moneyFields, typeof locale === "string" ? locale : void 0);
4457
- }
4458
- const hasStaticDisplay = hasDict && this.dictKeyFields !== void 0 && Object.values(this.dictKeyFields).some(
4459
- (d) => isStaticDictDescriptor(d) && d.displayLocale !== void 0
4460
- );
4461
- if (!locale && !hasStaticDisplay) return stripI18nFilled(result);
4462
5170
  const layer = localeOpts?._layer ?? "read";
4463
- if (locale && hasI18n && this.i18nFields) {
4464
- result = this.i18nStrategy.applyI18nLocale(result, this.i18nFields, locale, localeOpts?.fallback, layer);
4465
- }
4466
- if (hasDict && this.dictKeyFields && this.dictLabelResolver && locale !== "raw") {
4467
- const withLabels = { ...result };
4468
- const resolver = this.dictLabelResolver;
4469
- for (const [field, desc] of Object.entries(this.dictKeyFields)) {
4470
- const policy = desc.onMissing ? resolvePolicy(desc.onMissing, layer) : "null";
4471
- const fallback = policy === "substitute" ? localeOpts?.fallback ?? desc.substitute : localeOpts?.fallback;
4472
- const effLocale = locale ?? (isStaticDictDescriptor(desc) ? desc.displayLocale : void 0);
4473
- const resolveKey = async (key) => {
4474
- if (!effLocale) {
4475
- if (policy === "throw") {
4476
- throw new LocaleNotSpecifiedError(
4477
- field,
4478
- `dictKey "${field}": no locale active to resolve key "${key}".`
4479
- );
4480
- }
4481
- return null;
4482
- }
4483
- const label = await resolver(desc.name, key, effLocale, fallback);
4484
- if (label === void 0) {
4485
- if (policy === "throw") {
4486
- throw new LocaleNotSpecifiedError(
4487
- field,
4488
- `dictKey "${field}": no label for key "${key}" in locale "${effLocale}".`
4489
- );
4490
- }
4491
- return null;
4492
- }
4493
- return label;
4494
- };
4495
- if (field.includes("[].")) {
4496
- const parts = field.split("[].");
4497
- const arrayKey = parts[0];
4498
- const leaf = parts[1];
4499
- if (!leaf || leaf.includes(".")) continue;
4500
- const arr = withLabels[arrayKey];
4501
- if (!Array.isArray(arr)) continue;
4502
- const labelKey = `${leaf}Label`;
4503
- withLabels[arrayKey] = await Promise.all(
4504
- arr.map(async (el) => {
4505
- if (!el || typeof el !== "object" || Array.isArray(el)) return el;
4506
- const k = el[leaf];
4507
- if (typeof k !== "string") return el;
4508
- return { ...el, [labelKey]: await resolveKey(k) };
4509
- })
4510
- );
4511
- continue;
4512
- }
4513
- const val = result[field];
4514
- if (Array.isArray(val)) {
4515
- withLabels[`${field}Label`] = await Promise.all(
4516
- val.map(async (k) => ({
4517
- key: k,
4518
- label: typeof k === "string" ? await resolveKey(k) : null
4519
- }))
4520
- );
4521
- } else if (typeof val === "string") {
4522
- const label = await resolveKey(val);
4523
- if (label !== null) withLabels[`${field}Label`] = label;
4524
- }
4525
- }
4526
- result = withLabels;
4527
- }
4528
- return stripI18nFilled(result);
5171
+ let result = record;
5172
+ result = await this.via.present(result, { locale, ...localeOpts?.fallback !== void 0 ? { fallback: localeOpts.fallback } : {}, layer });
5173
+ return result;
4529
5174
  }
4530
5175
  /**
4531
5176
  * Low-level: encrypt a pre-serialised JSON string into an EncryptedEnvelope.
@@ -4742,14 +5387,14 @@ var Collection = class {
4742
5387
  /** C-A/R10: persist the classified marker once per classified handle (store I/O in config-drift.ts). */
4743
5388
  async _ensureClassifiedMarker() {
4744
5389
  if (this._markerPersisted || this.vdigFields === null) return;
4745
- const { persistClassifiedMarkerForFields } = await import("./config-drift-RFEVGBKR.js");
5390
+ const { persistClassifiedMarkerForFields } = await import("./classified-marker-IO5EBW7Q.js");
4746
5391
  await persistClassifiedMarkerForFields(this.adapter, this.vault, this.name, this.vdigFields, await this.getDEK(this.name));
4747
5392
  this._markerPersisted = true;
4748
5393
  }
4749
5394
  /** C-A/R10 drift signal — the marker's declared digest-only set, memoized to one store read per handle (see config-drift.ts). */
4750
5395
  async _classifiedMarkerDigestOnly() {
4751
5396
  if (this._markerDigestOnlyCache !== void 0) return this._markerDigestOnlyCache;
4752
- const { readClassifiedMarkerDigestOnly } = await import("./config-drift-RFEVGBKR.js");
5397
+ const { readClassifiedMarkerDigestOnly } = await import("./classified-marker-IO5EBW7Q.js");
4753
5398
  this._markerDigestOnlyCache = await readClassifiedMarkerDigestOnly(this.adapter, this.vault, this.name, await this.getDEK(this.name));
4754
5399
  return this._markerDigestOnlyCache;
4755
5400
  }
@@ -4828,6 +5473,10 @@ var Collection = class {
4828
5473
  _classifySealedShred(live) {
4829
5474
  return classifySealedShred(this.tiersContext(), live);
4830
5475
  }
5476
+ async _onViaErase(id, live) {
5477
+ return this.via ? this.via.eraseSealed({ id, vault: this.vault, live, crypto: await this.codec.eraseCryptoCtx(id, live) }) : void 0;
5478
+ }
5479
+ // @internal forget()'s per-ref via-erase fold (#629 T10)
4831
5480
  /**
4832
5481
  * Bind the {@link TiersContext} the tier ops need. The `cekCache` is passed
4833
5482
  * by reference (the SAME `Lru` the kernel's read/write path owns) so an
@@ -5777,11 +6426,12 @@ var VaultLinks = class {
5777
6426
  * for the same (collection, id) returns immediately so two
5778
6427
  * mutually-cascading collections don't recurse forever.
5779
6428
  */
5780
- async enforceRefsOnDelete(collectionName, id) {
6429
+ async enforceRefsOnDelete(graph, collectionName, id) {
5781
6430
  const key = `${collectionName}/${id}`;
5782
6431
  if (this.cascadeInProgress.has(key)) return;
5783
6432
  this.cascadeInProgress.add(key);
5784
6433
  try {
6434
+ await this.enforceLookupRefsOnDelete(graph, collectionName, collectionName, id);
5785
6435
  const inbound = this.deps.refRegistry.getInbound(collectionName);
5786
6436
  for (const rule of inbound) {
5787
6437
  const fromCollection = this.deps.collection(rule.collection);
@@ -5836,6 +6486,109 @@ var VaultLinks = class {
5836
6486
  this.cascadeInProgress.delete(key);
5837
6487
  }
5838
6488
  }
6489
+ /**
6490
+ * Find the records in `collection` whose `field` currently equals `key` — the SAME
6491
+ * list()+filter machinery `enforceRefsOnDelete`'s classic ref-restrict/cascade loop above
6492
+ * already uses (bounded by the referencing collection's size, never by the whole vault or by
6493
+ * "which collections reference this" — that part is the graph's O(1) `_out` reverse index).
6494
+ */
6495
+ async findLookupReferencingRecords(collection, field, key) {
6496
+ const all = await collection.list();
6497
+ return all.filter((rec) => String(rec[field]) === key);
6498
+ }
6499
+ /**
6500
+ * Resolve the VALUE a referencing field actually stores for the row being deleted (`rawKey`,
6501
+ * the backing row's PUT-id) under a given edge's `keyField` — a referencing field always stores
6502
+ * `row[keyField]`, never the PUT-id when the two differ (matrix tier's `lookup(dim, {key})`;
6503
+ * mirrors Task 3's `checkLookupMembership` review fix, Important 1). `keyField === 'id'`
6504
+ * (reserved/static tiers, and the matrix default) needs no row read: `rawKey` IS the value.
6505
+ * Reads the backing row AT MOST ONCE per distinct non-`'id'` `keyField`, even across multiple
6506
+ * edges — `backingCollection` is only `.get()`-able for the matrix tier, which is exactly the
6507
+ * only tier that can ever have `keyField !== 'id'`.
6508
+ */
6509
+ async resolveLookupCompareKey(backingCollection, rawKey, keyField, cache) {
6510
+ if (keyField === "id") return rawKey;
6511
+ if (cache.has(keyField)) return cache.get(keyField);
6512
+ const row = await this.deps.collection(backingCollection).get(rawKey);
6513
+ const raw = row?.[keyField];
6514
+ const resolved = typeof raw === "string" || typeof raw === "number" ? String(raw) : void 0;
6515
+ cache.set(keyField, resolved);
6516
+ return resolved;
6517
+ }
6518
+ /**
6519
+ * Phase 1 (#650 Task 5, #648) — restrict check, never mutates. Throws
6520
+ * `DictKeyInUseError(dimension, key, usedBy, count)` naming the FIRST referencing collection
6521
+ * still holding a matching record, for every `onDelete:'restrict'` edge the graph's O(1)
6522
+ * `referencingEdgesOf(backingCollection)` reverse lookup finds pointing at this dimension. A
6523
+ * dimension with no declared lookup-referencing edges is a no-op — undeclared refs keep
6524
+ * dangling (today's behavior, unaffected). Called by `Vault.forget()` BEFORE any shred (the
6525
+ * spec §4 "restrict refuses before any shred" design decision) — cascade/nullify propagation
6526
+ * for the SAME edges happens separately, after the shred, via `forgetDerivedFanout`.
6527
+ *
6528
+ * Also resolves (and RETURNS) the live compare-key for EVERY referencing edge, restrict AND
6529
+ * cascade/nullify alike (#650 Task 5 review, Important fix): `forgetDerivedFanout`'s cascade/
6530
+ * nullify propagation runs AFTER `_writeTombstone`, when a non-`'id'` `keyField`'s value can no
6531
+ * longer be read off the (now-shredded) row — resolving it HERE, at the one point every
6532
+ * referencing edge's backing row is still guaranteed live, eliminates that post-shred
6533
+ * dependency. A `keyField` this can't resolve (row unreadable) maps to `undefined` in the
6534
+ * returned map so the caller can report it as residue instead of silently skipping the edge.
6535
+ */
6536
+ async checkLookupRefsRestrict(graph, dimension, backingCollection, key) {
6537
+ const keyCache = /* @__PURE__ */ new Map();
6538
+ for (const { referencing, onDelete, keyField } of graph.referencingEdgesOf(backingCollection)) {
6539
+ const compareKey = await this.resolveLookupCompareKey(backingCollection, key, keyField, keyCache);
6540
+ if (onDelete !== "restrict" || compareKey === void 0) continue;
6541
+ const coll = this.deps.collection(referencing.collection);
6542
+ const matches = await this.findLookupReferencingRecords(coll, referencing.field, compareKey);
6543
+ if (matches.length > 0) {
6544
+ throw new DictKeyInUseError(dimension, key, referencing.collection, matches.length);
6545
+ }
6546
+ }
6547
+ return keyCache;
6548
+ }
6549
+ /**
6550
+ * Phase 2 (#650 Task 5, #648) — apply `cascade`/`nullify` propagation for this dimension's
6551
+ * non-restrict referencing edges (restrict edges are skipped — phase 1 above already refused
6552
+ * or the caller already checked). `cascade` deletes each matching record through its ordinary
6553
+ * `collection.delete()` (an origin-tagged, fanout-visible delete — participates in sync/history/
6554
+ * the graph dispatch wave exactly like any other delete); `nullify` clears the referencing field
6555
+ * via an ordinary `collection.put()`. Returns the counts so callers (forget's `ForgetFanoutStats`)
6556
+ * can report the propagation additively.
6557
+ */
6558
+ async applyLookupRefsPropagation(graph, backingCollection, key) {
6559
+ let cascaded = 0;
6560
+ let nullified = 0;
6561
+ const keyCache = /* @__PURE__ */ new Map();
6562
+ for (const { referencing, onDelete, keyField } of graph.referencingEdgesOf(backingCollection)) {
6563
+ if (onDelete === "restrict") continue;
6564
+ const compareKey = await this.resolveLookupCompareKey(backingCollection, key, keyField, keyCache);
6565
+ if (compareKey === void 0) continue;
6566
+ const coll = this.deps.collection(referencing.collection);
6567
+ const matches = await this.findLookupReferencingRecords(coll, referencing.field, compareKey);
6568
+ for (const rec of matches) {
6569
+ const id = rec["id"];
6570
+ if (id === void 0) continue;
6571
+ if (onDelete === "cascade") {
6572
+ await coll.delete(id);
6573
+ cascaded++;
6574
+ } else if (onDelete === "nullify") {
6575
+ await coll.put(id, { ...rec, [referencing.field]: null });
6576
+ nullified++;
6577
+ }
6578
+ }
6579
+ }
6580
+ return { cascaded, nullified };
6581
+ }
6582
+ /**
6583
+ * Convenience: the full restrict-then-propagate sequence for an ORDINARY (non-forget) delete of
6584
+ * a lookup-referenced row — `checkLookupRefsRestrict` then `applyLookupRefsPropagation`. Used by
6585
+ * `enforceRefsOnDelete` above (matrix-tier: the backing row IS an ordinary `Collection` record)
6586
+ * and by `LookupHandle.delete()`'s injected `checkReferencesOnDelete` callback (reserved tier).
6587
+ */
6588
+ async enforceLookupRefsOnDelete(graph, dimension, backingCollection, key) {
6589
+ await this.checkLookupRefsRestrict(graph, dimension, backingCollection, key);
6590
+ return this.applyLookupRefsPropagation(graph, backingCollection, key);
6591
+ }
5839
6592
  /**
5840
6593
  * @internal — apply link `onDelete` policy when an endpoint record is
5841
6594
  * deleted. `'strict'` throws (blocks the delete), `'cascade'`
@@ -5992,7 +6745,7 @@ var VaultLinks = class {
5992
6745
  // src/with-shape/links/lazy-handle.ts
5993
6746
  function makeLazyLinkSetHandle(d) {
5994
6747
  let engine = null;
5995
- const load = () => engine ??= import("./link-set-NSUIX6BR.js").then(
6748
+ const load = () => engine ??= import("./link-set-YBAC2DQT.js").then(
5996
6749
  ({ LinkSet }) => new LinkSet(d.adapter, d.vault, d.name, d.spec, d.encrypted, d.getDEK, d.actor, d.emitter, d.endpointExists)
5997
6750
  );
5998
6751
  return {
@@ -6006,6 +6759,166 @@ function makeLazyLinkSetHandle(d) {
6006
6759
  };
6007
6760
  }
6008
6761
 
6762
+ // src/kernel/via-taint-binding.ts
6763
+ var EMPTY_STRING_SET = /* @__PURE__ */ new Set();
6764
+ function isDefaultPosture(p) {
6765
+ return p.encryptedAtRest === DEFAULT_POSTURE.encryptedAtRest && p.queryable === DEFAULT_POSTURE.queryable && p.exportable === DEFAULT_POSTURE.exportable && p.forgettable === DEFAULT_POSTURE.forgettable;
6766
+ }
6767
+ function buildTaintOverlay(rawPostures, sealFields) {
6768
+ let out;
6769
+ for (const [field, posture] of rawPostures) {
6770
+ if (isDefaultPosture(posture)) continue;
6771
+ out ??= /* @__PURE__ */ new Map();
6772
+ out.set(field, sealFields.has(field) ? { ...posture, queryable: "none" } : posture);
6773
+ }
6774
+ return out;
6775
+ }
6776
+ async function encodeTaintAtRest(record, crypto, fields) {
6777
+ let open = record;
6778
+ let sealed;
6779
+ for (const field of fields) {
6780
+ if (!(field in record)) continue;
6781
+ const value = record[field];
6782
+ if (value === void 0) continue;
6783
+ const ref2 = await crypto.sealedSlots.seal(field, value);
6784
+ if (open === record) open = { ...record };
6785
+ delete open[field];
6786
+ sealed ??= {};
6787
+ sealed[field] = ref2;
6788
+ }
6789
+ return sealed ? { record: open, sealed } : { record };
6790
+ }
6791
+ async function decodeTaintAtRest(record, sealed, crypto, opts, fields) {
6792
+ let out = record;
6793
+ for (const field of fields) {
6794
+ const ref2 = sealed[field];
6795
+ if (ref2 === void 0) continue;
6796
+ if (out === record) out = { ...record };
6797
+ out[field] = opts.asHandles ? new SealedHandle(() => crypto.sealedSlots.unseal(field, ref2)) : await crypto.sealedSlots.unseal(field, ref2);
6798
+ }
6799
+ return out;
6800
+ }
6801
+ function taintBinding(sealFields, presentRedactFields = EMPTY_STRING_SET) {
6802
+ const fields = [...sealFields];
6803
+ const redactFields = [...presentRedactFields];
6804
+ return {
6805
+ brand: "taint",
6806
+ posture: { encryptedAtRest: "sealed", queryable: "none", exportable: false, forgettable: true },
6807
+ covers: (field) => sealFields.has(field) || presentRedactFields.has(field),
6808
+ ...fields.length > 0 ? {
6809
+ encodeAtRest: (record, crypto) => encodeTaintAtRest(record, crypto, fields),
6810
+ decodeAtRest: (record, sealed, crypto, opts) => decodeTaintAtRest(record, sealed, crypto, opts, fields)
6811
+ } : {},
6812
+ ...redactFields.length > 0 ? {
6813
+ present: (record) => {
6814
+ let out = record;
6815
+ for (const field of redactFields) {
6816
+ if (field in out) {
6817
+ if (out === record) out = { ...record };
6818
+ out[field] = EXPORT_REDACTION_MARKER;
6819
+ }
6820
+ }
6821
+ return out;
6822
+ }
6823
+ } : {}
6824
+ };
6825
+ }
6826
+
6827
+ // src/kernel/via-graph-wiring.ts
6828
+ var CLASSIFIED_POSTURE = { encryptedAtRest: "sealed", queryable: "det-exact", exportable: false, forgettable: true };
6829
+ function registerCollectionGraphSources(graph, name, opts) {
6830
+ const cfg = resolveCollectionConfig(opts);
6831
+ const bindings = cfg.via?.bindings ?? [];
6832
+ const knownFields = /* @__PURE__ */ new Set([
6833
+ ...Object.keys(cfg.moneyFields ?? {}),
6834
+ ...Object.keys(cfg.i18nFields ?? {}),
6835
+ ...Object.keys(cfg.dictKeyFields ?? {}),
6836
+ // #650 Task 2 — native lookup()/enumOf()/dict() fields register their
6837
+ // posture (`lookupBinding().posture`) the same generic way every other
6838
+ // binding does; no `'ref'` EdgeKind / graph edge yet (Task 5).
6839
+ ...Object.keys(cfg.lookupFields ?? {}),
6840
+ ...cfg.classified !== void 0 ? Object.keys(cfg.classified.byField) : []
6841
+ ]);
6842
+ for (const field of knownFields) {
6843
+ for (const binding of bindings) {
6844
+ if (binding.covers?.(field)) {
6845
+ graph.registerField(name, field, binding.posture);
6846
+ break;
6847
+ }
6848
+ }
6849
+ }
6850
+ for (const edge of cfg.computedEdges) graph.registerDerived(edge.target, edge.sources, "computed", edge.grain ?? "record");
6851
+ for (const edge of cfg.viaDepsEdges) graph.registerDerived(edge.target, edge.sources, "computed", edge.grain ?? "record");
6852
+ if (cfg.classified !== void 0) graph.markClassified(name);
6853
+ const depFields = new Set(cfg.computedEdges.map((edge) => edge.target.field));
6854
+ for (const field of cfg.computedFieldNames) {
6855
+ if (!depFields.has(field)) graph.markDepslessComputed(name, field);
6856
+ }
6857
+ }
6858
+ function validateReconcileGraphEdges(graph, name, options) {
6859
+ const resolvedClassified = options.classifiedFields !== void 0 ? resolveClassifiedFields(name, options.classifiedFields) : void 0;
6860
+ if (resolvedClassified !== void 0 && Object.values(resolvedClassified.byField).some((spec) => spec.storage !== "never")) {
6861
+ const riderNames = new Set(Object.keys(resolvedClassified.riderComputed));
6862
+ const leaking = [...graph.depslessComputedFields(name)].filter((field) => !riderNames.has(field));
6863
+ if (leaking.length > 0) {
6864
+ const field = leaking[0];
6865
+ throw new ValidationError(
6866
+ `Collection "${name}": computed field "${field}" has no declared \`deps\` and the collection declares classified fields \u2014 an opaque computed function could silently copy a classified field's plaintext into an ordinary, unredacted field. Declare \`deps\` naming the source fields it reads, e.g. computed: { ${field}: { fn, deps: [...] } }.`
6867
+ );
6868
+ }
6869
+ }
6870
+ const combinedHasClassified = graph.isClassified(name) || resolvedClassified !== void 0;
6871
+ let edges = [];
6872
+ let depslessComputedFields = [];
6873
+ if (options.computed !== void 0) {
6874
+ for (const [field, entry] of Object.entries(options.computed)) {
6875
+ if (computedEntryParts(entry).mode === "virtual") {
6876
+ throw new ValidationError(
6877
+ `Collection "${name}": computed field "${field}" declares mode: 'virtual' on a reconcile call \u2014 virtual computed fields are construction-only (no late-attach reconcile door); declare them in the collection's first vault.collection("${name}", { ... }) call.`
6878
+ );
6879
+ }
6880
+ }
6881
+ const knownFields = collectKnownFieldNames({
6882
+ moneyFields: options.moneyFields,
6883
+ classifiedFields: resolvedClassified?.byField,
6884
+ computed: options.computed
6885
+ });
6886
+ edges = resolveComputedEdges(name, options.computed, combinedHasClassified, knownFields);
6887
+ const depFields = new Set(edges.map((edge) => edge.target.field));
6888
+ depslessComputedFields = Object.keys(options.computed).filter((field) => !depFields.has(field));
6889
+ }
6890
+ return {
6891
+ edges,
6892
+ depslessComputedFields,
6893
+ classifiedFieldNames: resolvedClassified !== void 0 ? Object.keys(resolvedClassified.byField) : []
6894
+ };
6895
+ }
6896
+ function commitReconcileGraphEdges(graph, name, plan) {
6897
+ for (const edge of plan.edges) {
6898
+ if (!graph.hasDerived(edge.target)) graph.registerDerived(edge.target, edge.sources, "computed", edge.grain ?? "record");
6899
+ }
6900
+ for (const field of plan.depslessComputedFields) graph.markDepslessComputed(name, field);
6901
+ for (const field of plan.classifiedFieldNames) graph.registerField(name, field, CLASSIFIED_POSTURE);
6902
+ if (plan.classifiedFieldNames.length > 0) graph.markClassified(name);
6903
+ }
6904
+ function applyTaintOverlay(coll, graph, name) {
6905
+ const postures = buildTaintOverlay(graph.taintedPostures(name), graph.taintSealedFields(name));
6906
+ if (!postures) return;
6907
+ const sealFields = graph.taintSealedFields(name);
6908
+ const provenance = graph.taintProvenance(name);
6909
+ const virtualFields = graph.virtualFields(name);
6910
+ const virtualExportRedact = /* @__PURE__ */ new Set();
6911
+ for (const [field, posture] of postures) {
6912
+ if (posture.exportable === false && virtualFields.has(field)) virtualExportRedact.add(field);
6913
+ }
6914
+ const c = coll;
6915
+ const needsTaintBinding = sealFields.size > 0 || virtualExportRedact.size > 0;
6916
+ const bindings = needsTaintBinding ? [...c.via?.bindings ?? [], taintBinding(sealFields, virtualExportRedact)] : c.via?.bindings ?? [];
6917
+ const taint = { postures, sealFields, provenance };
6918
+ c.via = ViaPipeline.build(bindings, taint);
6919
+ c.codec.setVia(c.via);
6920
+ }
6921
+
6009
6922
  // src/with-shape/satellites/registry.ts
6010
6923
  var SatelliteRegistry = class {
6011
6924
  byBase = /* @__PURE__ */ new Map();
@@ -6116,7 +7029,7 @@ function declareSatellite(ctx, collectionName, options, existingRegistry) {
6116
7029
  ctx.forEachSyncEngine((e) => e.remirrorPairResolvers([spec.base, spec.satellite]));
6117
7030
  const registry = reg;
6118
7031
  const baseSchema = ctx.getBaseSchema(spec.base);
6119
- void import("./post-register-KOFRTCDD.js").then((m) => m.postRegister(ctx.adapter, ctx.vaultName, spec, ctx.getDEK, baseSchema, registry)).catch(() => {
7032
+ void import("./post-register-MROIUA5O.js").then((m) => m.postRegister(ctx.adapter, ctx.vaultName, spec, ctx.getDEK, baseSchema, registry)).catch(() => {
6120
7033
  });
6121
7034
  return reg;
6122
7035
  }
@@ -6609,38 +7522,15 @@ var VaultAttestation = class {
6609
7522
  };
6610
7523
 
6611
7524
  // src/kernel/vault.ts
6612
- function resolveLabelFromMap(labels, locale, fallback) {
6613
- if (labels[locale] !== void 0) return labels[locale];
6614
- const chain = Array.isArray(fallback) ? fallback : fallback ? [fallback] : [];
6615
- for (const fb of chain) {
6616
- if (fb === "any") {
6617
- const any = Object.values(labels)[0];
6618
- if (any !== void 0) return any;
6619
- } else if (labels[fb] !== void 0) {
6620
- return labels[fb];
6621
- }
6622
- }
6623
- return void 0;
6624
- }
6625
7525
  var Vault = class {
6626
7526
  adapter;
6627
7527
  /** The vault's name as passed to `openVault()`. Stable for the instance lifetime. */
6628
7528
  name;
6629
- /**
6630
- * Backreference to the parent `Noydb`. Lets vault-scoped services
6631
- * (e.g. `as-*` reader `apply()` paths gating on `withTransactions()`)
6632
- * reach the strategy seam without threading `db` through every API.
6633
- *
6634
- * Type-only Noydb import keeps the module graph acyclic at runtime.
6635
- */
7529
+ /** Backreference to the parent `Noydb` — lets vault-scoped services reach the strategy seam
7530
+ * without threading `db` through every API (type-only import keeps the module graph acyclic). */
6636
7531
  noydb;
6637
- /**
6638
- * The active in-memory keyring. NOT readonly because `load()`
6639
- * needs to refresh it after restoring a different keyring file —
6640
- * otherwise the in-memory DEKs (from the pre-load session) and
6641
- * the on-disk wrapped DEKs (from the loaded backup) drift apart
6642
- * and every subsequent decrypt fails with TamperedError.
6643
- */
7532
+ /** The active in-memory keyring. NOT readonly — `load()` refreshes it after restoring a
7533
+ * different keyring file, or in-memory/on-disk DEKs drift apart and decrypt fails with TamperedError. */
6644
7534
  keyring;
6645
7535
  encrypted;
6646
7536
  emitter;
@@ -6649,11 +7539,8 @@ var Vault = class {
6649
7539
  syncAdapter;
6650
7540
  getPurgeableTargets;
6651
7541
  historyConfig;
6652
- /**
6653
- * tree-shake seam for the optional blob service. Undefined
6654
- * means "blobs are off for this vault"; every `collection.blob(id)`
6655
- * call throws with a pointer at `@noy-db/hub/blobs`.
6656
- */
7542
+ /** tree-shake seam for the optional blob service; `undefined` means "blobs are off for this
7543
+ * vault" every `collection.blob(id)` call throws with a pointer at `@noy-db/hub/blobs`. */
6657
7544
  blobStrategy;
6658
7545
  objectStore;
6659
7546
  /** Cold-storage archival strategy (the archive target store). */
@@ -6666,11 +7553,8 @@ var Vault = class {
6666
7553
  crdtStrategy;
6667
7554
  tiersStrategy;
6668
7555
  searchStrategy;
6669
- /**
6670
- * Cargo (partition extraction) strategy `NO_CARGO` (throwing) unless
6671
- * `withCargo()` was passed. Public so the `extractPartition` free function
6672
- * (which takes a `Vault`) routes through it.
6673
- */
7556
+ /** Cargo (partition extraction) strategy — `NO_CARGO` (throwing) unless `withCargo()` was passed.
7557
+ * Public so the `extractPartition` free function (which takes a `Vault`) routes through it. */
6674
7558
  cargoStrategy;
6675
7559
  sealedRecordStrategy;
6676
7560
  portabilityStrategy;
@@ -6684,42 +7568,26 @@ var Vault = class {
6684
7568
  i18nStrategy;
6685
7569
  syncStrategy;
6686
7570
  classifiedStrategy;
6687
- /**
6688
- * Per-vault guard registry. `null` until `_initGuards()` runs; stays
6689
- * `null` for vaults that never register any guard strategy. The
6690
- * runtime class is dynamic-imported on demand so consumers that
6691
- * never use guards don't pull `GuardRegistry`/`GuardExecutor` into
6692
- * their bundle.
6693
- */
7571
+ /** Per-vault guard registry; `null` until `_initGuards()` runs (or for vaults that never register
7572
+ * one) — the runtime class is dynamic-imported on demand to keep it out of the floor bundle. */
6694
7573
  guardRegistry = null;
6695
- /**
6696
- * Per-vault derivation registry. Same lazy-load contract as
6697
- * `guardRegistry` `null` until `_initDerivations()` runs with at
6698
- * least one strategy handle.
6699
- */
7574
+ /** Per-vault derivation/MV/overlay registries — same lazy-load contract as
7575
+ * `guardRegistry`: each stays `null` until its `_init*()` runs with at
7576
+ * least one strategy/MV/overlay handle. */
6700
7577
  derivationRegistry = null;
6701
- /**
6702
- * Per-vault materialized-view registry. Same lazy-load
6703
- * contract as `derivationRegistry` — `null` until
6704
- * `_initMaterializedViews()` runs with at least one MV handle.
6705
- */
6706
7578
  materializedViewRegistry = null;
6707
- /**
6708
- * Per-vault overlay registry. Same lazy-load contract as
6709
- * `materializedViewRegistry` — `null` until `_initOverlayedViews()`
6710
- * runs with at least one handle.
6711
- */
6712
7579
  overlayedViewRegistry = null;
6713
- /**
6714
- * Cached read-only facades handed to guard callbacks via `ctx.vault`
6715
- * and to derivation callbacks via `derive(source, ctx)`. Split by
6716
- * resolution layer: the guard facade reads at `layer:'guard'`,
6717
- * the derivation facade at `layer:'derivation'`, so i18nText / dictKey
6718
- * fields resolve under that layer's `onMissing` policy. Allocated
6719
- * eagerly inside `_initGuards()` / `_initDerivations()` so read
6720
- * accessors stay synchronous (callers in `tx/transaction.ts` rely on
6721
- * that). Each stays `null` for vaults without that service.
6722
- */
7580
+ /** Per-vault dependency graph (#638) — field postures + derivation/MV/overlay/
7581
+ * computed/via-deps edges. Metadata-only (field names, postures, grains
7582
+ * never values or key material); always present, unlike the registries above. */
7583
+ graph;
7584
+ /** #638 Task 4 open sync/cutover/restore touch collector, or `undefined` between batches. */
7585
+ _graphBatch;
7586
+ /** Cached read-only facades for guard/derivation callbacks split by
7587
+ * resolution layer (`layer:'guard'` vs `'derivation'`, each field's
7588
+ * `onMissing` policy differs). Allocated eagerly inside `_initGuards()`/
7589
+ * `_initDerivations()` so read accessors stay synchronous; `null` for
7590
+ * vaults without that service. */
6723
7591
  guardFacade = null;
6724
7592
  derivationFacade = null;
6725
7593
  getDEK;
@@ -6748,18 +7616,9 @@ var Vault = class {
6748
7616
  * @see docs/superpowers/specs/2026-06-17-fr6-deed-custodian-liberate-design.md
6749
7617
  */
6750
7618
  custody;
6751
- /**
6752
- * Optional callback that re-derives an UnlockedKeyring from the
6753
- * adapter using the active user's passphrase. Called by `load()`
6754
- * after the on-disk keyring file has been replaced — refreshes
6755
- * `this.keyring` so the next DEK access uses the loaded wrapped
6756
- * DEKs instead of the stale pre-load ones.
6757
- *
6758
- * Provided by Noydb at openVault() time. Tests that
6759
- * construct Vault directly can pass `undefined`; load()
6760
- * skips the refresh in that case (which is fine for plaintext
6761
- * compartments — there's nothing to re-unwrap).
6762
- */
7619
+ /** Re-derives an UnlockedKeyring from the adapter using the active user's passphrase; called by
7620
+ * `load()` after the on-disk keyring file has been replaced. Provided by Noydb at openVault()
7621
+ * time; `undefined` in tests that construct Vault directly (load() then skips the refresh). */
6763
7622
  reloadKeyring;
6764
7623
  collectionCache = /* @__PURE__ */ new Map();
6765
7624
  satelliteRegistry = null;
@@ -6841,46 +7700,22 @@ var Vault = class {
6841
7700
  * should use separate Vault instances.
6842
7701
  */
6843
7702
  consentContext = null;
6844
- /**
6845
- * Registry of dictKey fields declared across all collections in this
6846
- * vault. Keyed by collection name → field name → dictionary name.
6847
- * Used by `DictionaryHandle.rename()` to find and update all records
6848
- * referencing a renamed key.
6849
- *
6850
- * Populated by `collection()` when the `dictKeyFields` option is passed.
6851
- */
7703
+ /** dictKeyField registry: collection name → field name → dictionary name — `DictionaryHandle.rename()`'s reference-update source; populated by `collection()` from `dictKeyFields`. */
6852
7704
  dictKeyFieldRegistry = /* @__PURE__ */ new Map();
6853
- /**
6854
- * Names of dictionaries backed by a `staticDict()` descriptor.
6855
- * A static dict skips the `dictKeyFieldRegistry` rename machinery, but the
6856
- * vault must still *know* a name is static so `vault.dictionary(name)` can
6857
- * refuse mutation (`StaticDictReadonlyError`). Populated at `collection()`
6858
- * config time whenever a `StaticDictDescriptor` is seen.
6859
- */
7705
+ /** Names of `staticDict()`-backed dictionaries (skip rename tracking; `vault.dictionary(name)` refuses mutation on these via `StaticDictReadonlyError`). Populated at `collection()` config time. */
6860
7706
  staticDictNames = /* @__PURE__ */ new Set();
6861
- /**
6862
- * Static-dict descriptors keyed by dictionary name. Backs the
6863
- * read-path label resolver (resolve from the in-memory table) and the
6864
- * query-seam `resolveDictSource` snapshot. Last writer wins when the same
6865
- * name is registered by multiple collections (identical-across-vaults by
6866
- * construction, so the tables match).
6867
- */
7707
+ /** Static-dict descriptors by dictionary name — backs the read-path label resolver and the `resolveDictSource` snapshot. Last writer wins across collections (tables match by construction). */
6868
7708
  staticByName = /* @__PURE__ */ new Map();
6869
- /**
6870
- * Per-collection map of field name → StaticDictDescriptor. Used by
6871
- * `enforceStaticDictOnPut` to validate stored codes against `desc.keys`.
6872
- */
7709
+ /** Per-collection field name → StaticDictDescriptor, validated by `enforceStaticDictOnPut`. */
6873
7710
  staticDescriptorByField = /* @__PURE__ */ new Map();
6874
- /**
6875
- * Registry of i18nText fields declared across all collections. Keyed
6876
- * by collection name → field name → I18nTextDescriptor. Used by
6877
- * `applyI18nLocale` on reads and by `validateI18nTextValue` on puts.
6878
- *
6879
- * Populated by `collection()` when the `i18nFields` option is passed.
6880
- */
7711
+ /** i18nText fields: collection name → field name → descriptor. Used by `applyI18nLocale`/`validateI18nTextValue`; populated by `collection()` from `i18nFields`. */
6881
7712
  i18nFieldRegistry = /* @__PURE__ */ new Map();
6882
7713
  /** Cache of DictionaryHandle instances, one per dictionary name. */
6883
7714
  dictionaryCache = /* @__PURE__ */ new Map();
7715
+ /** #650 Task 4 (#647) — declared reserved-lookup collection name → dimension name, for dimensions
7716
+ * with `backing:'reserved'` (dict()/dictKey()). The explicit sync-pull registry: NOT a blanket
7717
+ * underscore-glob — populated at `collection()`-declare time and at `dictionary()`-call time. */
7718
+ reservedLookupCollections = /* @__PURE__ */ new Map();
6884
7719
  /** Registered link specs, keyed by link name; set by `vault.link()`. */
6885
7720
  linkRegistry = /* @__PURE__ */ new Map();
6886
7721
  /** Cache of link-set handles, one per link name (lazy -- see links()). */
@@ -6957,6 +7792,7 @@ var Vault = class {
6957
7792
  this.i18nStrategy = opts.i18nStrategy ?? NO_I18N;
6958
7793
  this.syncStrategy = opts.syncStrategy ?? NO_SYNC;
6959
7794
  this.classifiedStrategy = opts.classifiedStrategy ?? NO_CLASSIFIED;
7795
+ this.graph = new ViaGraph();
6960
7796
  void opts.guardStrategies;
6961
7797
  this.historyConfig = opts.historyConfig ?? { enabled: true };
6962
7798
  this.reloadKeyring = opts.reloadKeyring;
@@ -7083,6 +7919,7 @@ var Vault = class {
7083
7919
  }, collectionName, { ...options, satelliteOf: options.satelliteOf }, this.satelliteRegistry);
7084
7920
  }
7085
7921
  let coll = this.collectionCache.get(collectionName);
7922
+ const reconcilePlan = coll && (options?.computed || options?.classifiedFields) ? validateReconcileGraphEdges(this.graph, collectionName, options) : void 0;
7086
7923
  if (coll && options?.moneyFields) {
7087
7924
  coll._applyMoneyFields(options.moneyFields);
7088
7925
  }
@@ -7098,12 +7935,17 @@ var Vault = class {
7098
7935
  if (coll && options?.classifiedFields) {
7099
7936
  coll._applyClassifiedFields(options.classifiedFields);
7100
7937
  }
7938
+ if (reconcilePlan) {
7939
+ commitReconcileGraphEdges(this.graph, collectionName, reconcilePlan);
7940
+ applyTaintOverlay(coll, this.graph, collectionName);
7941
+ }
7101
7942
  if (!coll) {
7943
+ const effectiveViaFields = mergeViaFields({ moneyFields: options?.moneyFields, i18nFields: options?.i18nFields, dictKeyFields: options?.dictKeyFields, lookupFields: options?.lookupFields, viaFields: options?.viaFields });
7102
7944
  if (options?.refs) {
7103
7945
  this.refRegistry.register(collectionName, options.refs);
7104
7946
  }
7105
- if (options?.i18nFields) {
7106
- this.i18nFieldRegistry.set(collectionName, options.i18nFields);
7947
+ if (effectiveViaFields.i18nFields) {
7948
+ this.i18nFieldRegistry.set(collectionName, effectiveViaFields.i18nFields);
7107
7949
  }
7108
7950
  if (options?.blobFields) {
7109
7951
  this.blobFieldsRegistry.set(collectionName, options.blobFields);
@@ -7114,10 +7956,10 @@ var Vault = class {
7114
7956
  if (options?.attestation !== void 0) {
7115
7957
  this.attestation.register(collectionName, options.attestation);
7116
7958
  }
7117
- if (options?.dictKeyFields) {
7959
+ if (effectiveViaFields.dictKeyFields || effectiveViaFields.lookupFields) {
7118
7960
  const dictFieldMap = {};
7119
7961
  const staticFieldMap = {};
7120
- for (const [field, desc] of Object.entries(options.dictKeyFields)) {
7962
+ for (const [field, desc] of Object.entries(effectiveViaFields.dictKeyFields ?? {})) {
7121
7963
  if (isStaticDictDescriptor(desc)) {
7122
7964
  staticFieldMap[field] = desc;
7123
7965
  this.staticDictNames.add(desc.name);
@@ -7126,8 +7968,18 @@ var Vault = class {
7126
7968
  dictFieldMap[field] = desc.name;
7127
7969
  }
7128
7970
  }
7971
+ const lookupCompat = collectLookupDictCompat(effectiveViaFields.lookupFields);
7972
+ Object.assign(dictFieldMap, lookupCompat.dictFieldMap);
7973
+ for (const [field, desc] of lookupCompat.staticEntries) {
7974
+ staticFieldMap[field] = desc;
7975
+ this.staticDictNames.add(desc.name);
7976
+ this.staticByName.set(desc.name, desc);
7977
+ }
7129
7978
  if (Object.keys(dictFieldMap).length > 0) {
7130
7979
  this.dictKeyFieldRegistry.set(collectionName, dictFieldMap);
7980
+ for (const dictName of new Set(Object.values(dictFieldMap))) {
7981
+ this.reservedLookupCollections.set(dictCollectionName(dictName), dictName);
7982
+ }
7131
7983
  }
7132
7984
  if (Object.keys(staticFieldMap).length > 0) {
7133
7985
  this.staticDescriptorByField.set(collectionName, staticFieldMap);
@@ -7142,7 +7994,7 @@ var Vault = class {
7142
7994
  const strategies = options.schemaUpdate ?? [];
7143
7995
  const work = (async () => {
7144
7996
  const dek = await this.getDEK(collectionName);
7145
- const { persistSchemaIfNeeded } = await import("./register-3D6R2MKN.js");
7997
+ const { persistSchemaIfNeeded } = await import("./register-OVJKW7BS.js");
7146
7998
  const result = await persistSchemaIfNeeded({
7147
7999
  store: this.adapter,
7148
8000
  vault: this.name,
@@ -7207,6 +8059,7 @@ var Vault = class {
7207
8059
  defaultLocale: this.locale,
7208
8060
  onRegisterConflictResolver: this.onRegisterConflictResolver,
7209
8061
  onAccess: (op, id) => this._logConsent(op, collectionName, id),
8062
+ graphDispatch: { collect: (collection, id) => this._collectGraphTouch(collection, id) },
7210
8063
  // Derivation source is only wired when the corresponding registry
7211
8064
  // has been initialised. Guard source was removed in Track A slice 3b
7212
8065
  // — guards now run via the gate bus in Noydb.#registerGuardGate.
@@ -7277,9 +8130,10 @@ var Vault = class {
7277
8130
  if (options?.warmIndexOnOpen !== void 0) collOpts.warmIndexOnOpen = options.warmIndexOnOpen;
7278
8131
  if (options?.textIndexPersist !== void 0) collOpts.textIndexPersist = options.textIndexPersist;
7279
8132
  if (options?.moneyFields !== void 0) collOpts.moneyFields = options.moneyFields;
8133
+ if (options?.viaFields !== void 0) collOpts.viaFields = options.viaFields;
7280
8134
  if (options?.computed !== void 0) collOpts.computed = options.computed;
7281
8135
  if (options?.classifiedFields !== void 0) collOpts.classifiedFields = options.classifiedFields;
7282
- if (options?.dictKeyFields !== void 0) {
8136
+ if (effectiveViaFields.dictKeyFields !== void 0 || effectiveViaFields.lookupFields !== void 0) {
7283
8137
  collOpts.dictLabelResolver = async (dictName, key, locale, fallback) => {
7284
8138
  const stat = this.staticByName.get(dictName);
7285
8139
  if (stat) {
@@ -7290,15 +8144,23 @@ var Vault = class {
7290
8144
  return handle.resolveLabel(key, locale, fallback);
7291
8145
  };
7292
8146
  collOpts.getDictionary = async (name) => this.dictionary(name);
7293
- collOpts.dictKeyFields = options.dictKeyFields;
8147
+ collOpts.dictKeyFields = options?.dictKeyFields;
7294
8148
  }
7295
- if (options?.i18nFields !== void 0 || options?.dictKeyFields !== void 0) {
8149
+ if (effectiveViaFields.lookupFields !== void 0) {
8150
+ collOpts.lookupLabelResolver = collOpts.dictLabelResolver;
8151
+ collOpts.getLookupBacking = (dimension) => async (key) => await this.collection(dimension).get(key) ?? void 0;
8152
+ collOpts.lookupFields = options?.lookupFields;
8153
+ collOpts.membership = (field, key) => checkLookupMembership(effectiveViaFields.lookupFields[field], key, (n) => this.dictionary(n), (n) => this.collection(n));
8154
+ collOpts.getAltIndex = (d) => buildLookupAltIndex(d, (n) => this.dictionary(n), (n) => this.collection(n));
8155
+ collOpts.snapshotFor = (d) => buildLookupSnapshotRows(d, (n) => this.reservedLookupCollections.has(dictCollectionName(n)), (n) => this.dictionary(n), (n) => this.collection(n));
8156
+ }
8157
+ if (effectiveViaFields.i18nFields !== void 0 || effectiveViaFields.dictKeyFields !== void 0) {
7296
8158
  collOpts.i18nPutValidator = (record) => {
7297
8159
  this.enforceI18nOnPut(collectionName, record);
7298
8160
  this.enforceStaticDictOnPut(collectionName, record);
7299
8161
  };
7300
8162
  }
7301
- if (options?.i18nFields !== void 0 && this.translateText) {
8163
+ if (effectiveViaFields.i18nFields !== void 0 && this.translateText) {
7302
8164
  collOpts.autoTranslateHook = this.translateText;
7303
8165
  }
7304
8166
  if (options?.fieldMeta !== void 0) {
@@ -7312,6 +8174,9 @@ var Vault = class {
7312
8174
  }
7313
8175
  coll = new Collection(collOpts);
7314
8176
  this.collectionCache.set(collectionName, coll);
8177
+ registerCollectionGraphSources(this.graph, collectionName, collOpts);
8178
+ registerLookupRefEdges(this.graph, collectionName, effectiveViaFields.lookupFields);
8179
+ applyTaintOverlay(coll, this.graph, collectionName);
7315
8180
  if (options?.warmIndexOnOpen === true && options.prefetch !== false) {
7316
8181
  void coll.warmIndex().catch(() => {
7317
8182
  });
@@ -7321,7 +8186,7 @@ var Vault = class {
7321
8186
  const work = (async () => {
7322
8187
  try {
7323
8188
  const dek = await this.getDEK(collectionName);
7324
- await (await import("./register-3D6R2MKN.js")).persistSchemaIfNeeded({
8189
+ await (await import("./register-OVJKW7BS.js")).persistSchemaIfNeeded({
7325
8190
  store: this.adapter,
7326
8191
  vault: this.name,
7327
8192
  collectionName,
@@ -7384,7 +8249,9 @@ var Vault = class {
7384
8249
  async #runCutoverTransform(collectionName, transform) {
7385
8250
  const coll = this.collectionCache.get(collectionName);
7386
8251
  if (!coll) return;
8252
+ this._beginGraphBatch();
7387
8253
  await coll._applyCutoverTransform(transform);
8254
+ await this._flushGraphBatch();
7388
8255
  }
7389
8256
  /**
7390
8257
  * Refresh a loaded collection's view of one document from a peer
@@ -7396,12 +8263,45 @@ var Vault = class {
7396
8263
  if (!coll) return;
7397
8264
  await coll._applyRemoteChange(docId, action);
7398
8265
  }
7399
- /** @internal #598: refresh cache entries a sync-applied write rewrote underneath us. No-op if not loaded this session. */
8266
+ /** @internal #598: refresh cache entries a sync-applied write rewrote underneath us. No-op if not loaded this session.
8267
+ * #650 Task 4 (#647): a reserved-lookup collection has no `Collection` instance — instead, refresh
8268
+ * its already-warmed `LookupHandle._syncCache` (membership/altIndex/snapshot reads never see a
8269
+ * stale verdict for a pulled vocabulary edit) and collect the touch into any open graph batch
8270
+ * (the sync-apply wave-reachability seam Task 5's ref edges will use). No-op if never warmed. */
7400
8271
  async _invalidateSyncApplied(collection, id) {
7401
8272
  const coll = this.collectionCache.get(collection);
7402
- if (!coll) return;
7403
- coll._invalidateCekCacheEntry(id);
7404
- await coll._invalidateCacheEntry(id);
8273
+ if (coll) {
8274
+ await coll._onRecordMutated(id, "put", "sync-apply");
8275
+ return;
8276
+ }
8277
+ const dimName = this.reservedLookupCollections.get(collection);
8278
+ if (dimName === void 0) return;
8279
+ await this.dictionaryCache.get(dimName)?._refreshSyncCache(id);
8280
+ this._collectGraphTouch(collection, id);
8281
+ }
8282
+ /** @internal #650 Task 4 (#647) — declared reserved-lookup collection names, for `SyncEngine.pull()`'s explicit prefix registry. */
8283
+ _reservedLookupCollectionNames() {
8284
+ return [...this.reservedLookupCollections.keys()];
8285
+ }
8286
+ /** @internal #638 Task 4 — open a graph-dispatch touch batch (sync pull()/push()/cutover). */
8287
+ _beginGraphBatch() {
8288
+ this._graphBatch = /* @__PURE__ */ new Map();
8289
+ }
8290
+ /** @internal #638 Task 4 — `Collection._onRecordMutated`'s sync-apply/cutover/restore hook; a
8291
+ * no-op when no batch is open (`_beginGraphBatch()` was never called for this call chain). */
8292
+ _collectGraphTouch(collection, id) {
8293
+ if (!this._graphBatch) return;
8294
+ this._graphBatch.set(collection, (this._graphBatch.get(collection) ?? /* @__PURE__ */ new Set()).add(id));
8295
+ }
8296
+ /** @internal #638 Task 4 — close the open batch and run ONE dispatch wave over it. */
8297
+ async _flushGraphBatch() {
8298
+ const batch = this._graphBatch;
8299
+ this._graphBatch = void 0;
8300
+ if (batch && batch.size > 0) await runGraphDispatchWave(this, batch);
8301
+ }
8302
+ /** @internal #638 Task 4 — cached-collection lookup for `runGraphDispatchWave` (`VaultLike`); never constructs. */
8303
+ _getCollection(name) {
8304
+ return this.collectionCache.get(name);
7405
8305
  }
7406
8306
  /**
7407
8307
  * @internal #589 → #604. Physically remove delete markers with `_ts` strictly
@@ -7543,8 +8443,12 @@ var Vault = class {
7543
8443
  * Validate i18nText fields on a `put()`. Called by Collection just
7544
8444
  * before the adapter write, after schema validation. Throws
7545
8445
  * `MissingTranslationError` when a required translation is absent.
8446
+ *
8447
+ * Delegates through the Via registry (#623) — a no-op only when i18n was
8448
+ * never declared (mirrors the registry check below).
7546
8449
  */
7547
8450
  enforceI18nOnPut(collectionName, record) {
8451
+ if (!isViaInstalled("i18n")) return;
7548
8452
  const i18nFields = this.i18nFieldRegistry.get(collectionName);
7549
8453
  if (!i18nFields || Object.keys(i18nFields).length === 0) return;
7550
8454
  if (!record || typeof record !== "object") return;
@@ -7561,79 +8465,15 @@ var Vault = class {
7561
8465
  * Validate staticDict codes on a `put()`. For each `staticDict()`
7562
8466
  * field, every stored code must be a declared key of the descriptor's
7563
8467
  * table, else `UnknownDictCodeError`. Opt out per descriptor with
7564
- * `{ validateCodes: false }`. Supports scalar, dotted, and `[].`-wildcard
7565
- * field paths via `getAtPath` (same path support as i18n validation).
7566
- */
7567
- enforceStaticDictOnPut(collectionName, record) {
7568
- const staticFields = this.staticDescriptorByField.get(collectionName);
7569
- if (!staticFields || Object.keys(staticFields).length === 0) return;
7570
- if (!record || typeof record !== "object") return;
7571
- const obj = record;
7572
- for (const [field, desc] of Object.entries(staticFields)) {
7573
- if (desc.validateCodes === false) continue;
7574
- const known = new Set(desc.keys);
7575
- const values = getAtPath(obj, field);
7576
- for (const value of values) {
7577
- if (value === void 0 || value === null) continue;
7578
- const codes = Array.isArray(value) ? value : [value];
7579
- for (const code of codes) {
7580
- if (typeof code !== "string") continue;
7581
- if (!known.has(code)) {
7582
- throw new UnknownDictCodeError(desc.name, field, code);
7583
- }
7584
- }
7585
- }
7586
- }
7587
- }
7588
- /**
7589
- * Apply locale resolution to a record for the given collection.
8468
+ * `{ validateCodes: false }`.
7590
8469
  *
7591
- * Called by Collection after decryption when locale options are present.
7592
- * Returns a new object (never mutates the cached record).
8470
+ * Delegates through the Via registry see {@link enforceI18nOnPut}. The
8471
+ * per-field validation itself lives in `shape/via-lookup/registry.ts`
8472
+ * (#650 Task 1 extraction), reached via `port/with/lookup-strategy.ts`.
7593
8473
  */
7594
- async applyLocale(collectionName, record, localeOpts) {
7595
- const locale = localeOpts.locale ?? this.locale;
7596
- const staticFields = this.staticDescriptorByField.get(collectionName);
7597
- const hasStaticDisplay = staticFields !== void 0 && Object.values(staticFields).some((d) => d.displayLocale !== void 0);
7598
- if (!locale && !hasStaticDisplay) return record;
7599
- let result = record;
7600
- if (locale) {
7601
- const i18nFields = this.i18nFieldRegistry.get(collectionName);
7602
- if (i18nFields && Object.keys(i18nFields).length > 0) {
7603
- result = this.i18nStrategy.applyI18nLocale(result, i18nFields, locale, localeOpts.fallback);
7604
- }
7605
- }
7606
- const dictFields = this.dictKeyFieldRegistry.get(collectionName);
7607
- if (locale && dictFields && Object.keys(dictFields).length > 0 && locale !== "raw") {
7608
- const withLabels = { ...result };
7609
- for (const [field, dictName] of Object.entries(dictFields)) {
7610
- const key = result[field];
7611
- if (typeof key !== "string") continue;
7612
- const handle = this.dictionary(dictName);
7613
- const label = await handle.resolveLabel(key, locale, localeOpts.fallback);
7614
- if (label !== void 0) {
7615
- withLabels[`${field}Label`] = label;
7616
- }
7617
- }
7618
- result = withLabels;
7619
- }
7620
- if (staticFields && Object.keys(staticFields).length > 0 && locale !== "raw") {
7621
- const withLabels = { ...result };
7622
- for (const [field, desc] of Object.entries(staticFields)) {
7623
- const effLocale = locale ?? desc.displayLocale;
7624
- if (!effLocale) continue;
7625
- const key = result[field];
7626
- if (typeof key !== "string") continue;
7627
- const labels = desc.table[key];
7628
- if (!labels) continue;
7629
- const label = resolveLabelFromMap(labels, effLocale, localeOpts.fallback ?? desc.substitute);
7630
- if (label !== void 0) {
7631
- withLabels[`${field}Label`] = label;
7632
- }
7633
- }
7634
- result = withLabels;
7635
- }
7636
- return result;
8474
+ enforceStaticDictOnPut(collectionName, record) {
8475
+ if (!isViaInstalled("i18n")) return;
8476
+ enforceStaticDictOnPut(this.staticDescriptorByField.get(collectionName), record);
7637
8477
  }
7638
8478
  /**
7639
8479
  * Open a dictionary by name. Returns a `DictionaryHandle` for CRUD
@@ -7657,6 +8497,7 @@ var Vault = class {
7657
8497
  if (this.staticDictNames.has(name)) {
7658
8498
  throw new StaticDictReadonlyError(name);
7659
8499
  }
8500
+ this.reservedLookupCollections.set(dictCollectionName(name), name);
7660
8501
  let handle = this.dictionaryCache.get(name);
7661
8502
  if (!handle) {
7662
8503
  handle = this.i18nStrategy.buildDictionaryHandle({
@@ -7664,37 +8505,36 @@ var Vault = class {
7664
8505
  compartmentName: this.name,
7665
8506
  dictionaryName: name,
7666
8507
  keyring: this.keyring,
7667
- getDEK: this.getDEK,
8508
+ reservedEnvelopes: makeReservedEnvelopes((collection) => this.getDEK(collection), ["_dict_"])("_dict_"),
7668
8509
  encrypted: this.encrypted,
7669
8510
  ledger: this.getLedgerOrNull() ?? void 0,
7670
8511
  options,
7671
8512
  // findAndUpdateReferences: rewrite dictKey fields in all
7672
- // registered collections when rename() is called
8513
+ // registered collections when rename() is called. Body extracted to
8514
+ // shape/via-lookup/registry.ts (#650 Task 1) — reached via the
8515
+ // port/with/lookup-strategy.ts seam.
7673
8516
  findAndUpdateReferences: async (dictionaryName, oldKey, newKey) => {
7674
- for (const [collectionName, dictFields] of this.dictKeyFieldRegistry) {
7675
- const fields = Object.entries(dictFields).filter(([, dn]) => dn === dictionaryName).map(([field]) => field);
7676
- if (fields.length === 0) continue;
7677
- const coll = this.collection(collectionName);
7678
- const records = await coll.list();
7679
- for (const record of records) {
7680
- let changed = false;
7681
- const updated = { ...record };
7682
- for (const field of fields) {
7683
- if (updated[field] === oldKey) {
7684
- updated[field] = newKey;
7685
- changed = true;
7686
- }
7687
- }
7688
- if (changed) {
7689
- const id = record["id"];
7690
- if (id !== void 0) {
7691
- await coll.put(id, updated);
7692
- }
7693
- }
7694
- }
7695
- }
8517
+ await updateReferencingRecords(
8518
+ this.dictKeyFieldRegistry,
8519
+ (collectionName) => this.collection(collectionName),
8520
+ dictionaryName,
8521
+ oldKey,
8522
+ newKey
8523
+ );
8524
+ },
8525
+ emitter: this.emitter,
8526
+ buildDeleteMarker,
8527
+ // #647 fix wave 1 — real kernel/enclave builder; LookupHandle can't import it directly
8528
+ // #650 Task 4 (#647) — choke-point participation for LOCAL writes: dirty-log tracking + a
8529
+ // one-shot graph-dispatch wave (local-write's `graphDispatch.collect`-equivalent; Task 5's ref edges give it dependents).
8530
+ onDirty: this.onDirty,
8531
+ onRecordMutated: async (collection, id) => {
8532
+ this._beginGraphBatch();
8533
+ this._collectGraphTouch(collection, id);
8534
+ await this._flushGraphBatch();
7696
8535
  },
7697
- emitter: this.emitter
8536
+ checkReferencesOnDelete: (key) => this.linksEnforcer.enforceLookupRefsOnDelete(this.graph, name, dictCollectionName(name), key)
8537
+ // #650 Task 5
7698
8538
  });
7699
8539
  this.dictionaryCache.set(name, handle);
7700
8540
  }
@@ -7763,65 +8603,24 @@ var Vault = class {
7763
8603
  return handle;
7764
8604
  }
7765
8605
  /**
7766
- * Build a `JoinableSource` for a dictKey field, for use in dict joins
7767
- *. Returns a source whose snapshot contains `{ key, ...labels }`
7768
- * records — one per dictionary entry — keyed by the stable key.
7769
- *
7770
- * Returns `null` when `field` is not a dictKey in `leftCollection`.
7771
- *
7772
- * The snapshot is built synchronously from whatever the dictionary
7773
- * handle has in its cached state. For empty dictionaries this returns
7774
- * an empty snapshot rather than `null`.
7775
- */
7776
- /**
7777
- * Build a `JoinableSource` for a dictKey field, for use in dict joins
7778
- *. Returns a source whose snapshot contains
7779
- * `{ key, labels, ...labels }` records — one per dictionary entry —
7780
- * keyed by the stable key.
8606
+ * Build a `JoinableSource` for a dictKey field, for use in dict joins.
8607
+ * Returns a source whose snapshot contains `{ key, labels, ...labels }`
8608
+ * records — one per dictionary entry — keyed by the stable key. Returns
8609
+ * `null` when `field` is not a dictKey in `leftCollection`.
7781
8610
  *
7782
- * The snapshot is built synchronously from the DictionaryHandle's
7783
- * write-through cache, which is populated on every `put()`, `rename()`,
7784
- * `delete()`, and `list()` call. For pre-existing data not yet touched
7785
- * this session, call `await vault.dictionary(name).list()` first
7786
- * to warm the cache.
7787
- *
7788
- * Returns `null` when `field` is not a dictKey in `leftCollection`.
8611
+ * Body extracted to `shape/via-lookup/registry.ts` (#650 Task 1)
8612
+ * reached via the `port/with/lookup-strategy.ts` seam. See that
8613
+ * function's doc comment for the static-vs-dynamic-dict split and the
8614
+ * cache-warming caveat.
7789
8615
  */
7790
8616
  resolveDictSource(leftCollection, field) {
7791
- const staticFields = this.staticDescriptorByField.get(leftCollection);
7792
- if (staticFields && field in staticFields) {
7793
- const desc = staticFields[field];
7794
- const rows = Object.entries(desc.table).map(
7795
- ([key, labels]) => ({ key, labels, ...labels })
7796
- );
7797
- const source = {
7798
- snapshot() {
7799
- return rows;
7800
- },
7801
- lookupById(id) {
7802
- return rows.find((e) => e["key"] === id);
7803
- }
7804
- };
7805
- if (desc.displayLocale !== void 0) {
7806
- ;
7807
- source.displayLocale = desc.displayLocale;
7808
- }
7809
- return source;
7810
- }
7811
- const dictFields = this.dictKeyFieldRegistry.get(leftCollection);
7812
- if (!dictFields || !(field in dictFields)) return null;
7813
- const dictName = dictFields[field];
7814
- if (!dictName) return null;
7815
- const handle = this.dictionary(dictName);
7816
- return {
7817
- snapshot() {
7818
- return handle.snapshotEntries();
7819
- },
7820
- lookupById(id) {
7821
- const entries = handle.snapshotEntries();
7822
- return entries.find((e) => e["key"] === id);
7823
- }
7824
- };
8617
+ return resolveDictSource(
8618
+ leftCollection,
8619
+ field,
8620
+ this.staticDescriptorByField,
8621
+ this.dictKeyFieldRegistry,
8622
+ (name) => this.dictionary(name)
8623
+ );
7825
8624
  }
7826
8625
  /**
7827
8626
  * Set or update the vault-default locale at runtime.
@@ -8025,7 +8824,9 @@ var Vault = class {
8025
8824
  listRecordIds: (c) => this.adapter.list(this.name, c),
8026
8825
  getRecord: async (c, id) => await this.collection(c).get(id, { locale: "raw" }),
8027
8826
  getEnvelope: (c, id) => this.adapter.get(this.name, c, id),
8028
- removeFromPrimary: (c, id) => this.collection(c)._internalDelete(id),
8827
+ removeFromPrimary: async (c, id) => {
8828
+ await this.collection(c)._internalDelete(id);
8829
+ },
8029
8830
  restoreToPrimary: async (c, id, env) => {
8030
8831
  await this.adapter.put(this.name, c, id, env);
8031
8832
  await this.collection(c)._invalidateCacheEntry(id);
@@ -8112,7 +8913,7 @@ var Vault = class {
8112
8913
  * cascade guard. See `with-shape/links/vault-facade.ts`.
8113
8914
  */
8114
8915
  async enforceRefsOnDelete(collectionName, id) {
8115
- return this.linksEnforcer.enforceRefsOnDelete(collectionName, id);
8916
+ return this.linksEnforcer.enforceRefsOnDelete(this.graph, collectionName, id);
8116
8917
  }
8117
8918
  // ─── Join resolver) ────────────────────
8118
8919
  /**
@@ -8216,23 +9017,19 @@ var Vault = class {
8216
9017
  );
8217
9018
  }
8218
9019
  /**
8219
- * GDPR crypto-shred of a data subject. Consults the encrypted subject
8220
- * index and, per matching record:
8221
- * - rewrites the LIVE envelope to a tombstone (drops `_iv`/`_data`/`_cek`/`_det`),
8222
- * - tombstones every `_history` version of the record,
8223
- * so the body and all prior versions become permanently undecryptable while
8224
- * the collection DEK and every OTHER record stay intact. Then appends ONE
8225
- * `op:'forget'` ledger entry whose `payloadHash` is `sha256Hex(subjectId)` —
8226
- * the chain still `verify()`s, PROVING the subject existed and was erased
8227
- * without retaining any plaintext.
8228
- *
8229
- * Reports but does not silently swallowtwo completeness gaps:
8230
- * - `unmigratedRecords`: a record whose body was NOT yet migrated to a
8231
- * per-record CEK (legacy body still under the shared collection DEK). It
8232
- * is still tombstoned, but its pre-shred ciphertext (if leaked to a
8233
- * backup before migration) stays decryptable. Migrate, then re-forget.
8234
- * - `blobResidueCollections`: a shredded record still has blob attachments,
8235
- * which are keyed off a separate `_blob` DEK and are out of scope here.
9020
+ * GDPR crypto-shred of a data subject. Consults the encrypted subject index and, per matching
9021
+ * record: rewrites the LIVE envelope to a tombstone (drops `_iv`/`_data`/`_cek`/`_det`), tombstones
9022
+ * every `_history` version, and fans out to derived residue via the graph (#622) — record-grain
9023
+ * artifacts (MV rows, derivation copies) ERASED, aggregate-grain rollups RECOMPUTED without the
9024
+ * forgotten contribution (skip+audit in a frozen period). The body/history become permanently
9025
+ * undecryptable while the collection DEK and every OTHER record stay intact. Then appends ONE
9026
+ * `op:'forget'` ledger entry whose `payloadHash` is `sha256Hex(subjectId)` — the chain still
9027
+ * `verify()`s, PROVING the subject existed and was erased without retaining any plaintext.
9028
+ *
9029
+ * Reports — but does not silently swallow — two completeness gaps: `unmigratedRecords` (a record
9030
+ * whose body was NOT yet migrated to a per-record CEK still tombstoned, but pre-shred backups
9031
+ * stay decryptable; migrate, then re-forget) and `blobResidueCollections` (blob attachments, keyed
9032
+ * off a separate `_blob` DEK, out of scope here).
8236
9033
  *
8237
9034
  * @throws ForgetStrategyNotConfiguredError when no `withForgetCascade` was set.
8238
9035
  */
@@ -8257,23 +9054,29 @@ var Vault = class {
8257
9054
  const indexResidue = [];
8258
9055
  const blobsEnabled = this.blobStrategy !== void 0;
8259
9056
  const actor = this.keyring.userId;
9057
+ const fanoutStats = { recordsErased: 0, aggregatesRecomputed: 0, residueFrozen: [], lookupReferencesCascaded: 0, lookupReferencesNullified: 0, lookupReferencesResidue: [] };
8260
9058
  for (const ref2 of allRefs) {
8261
9059
  const coll = this.collection(ref2.collection);
8262
9060
  const satelliteOf = ref2.satelliteOf;
8263
9061
  const perRecordKeys = this.forgetStrategy.subjects[satelliteOf ?? ref2.collection] !== void 0;
9062
+ const lookupCompareKeys = await this.linksEnforcer.checkLookupRefsRestrict(this.graph, ref2.collection, ref2.collection, ref2.id);
8264
9063
  const live = await this.adapter.get(this.name, ref2.collection, ref2.id);
8265
9064
  if (perRecordKeys && live && live._data && !hasPerRecordKey(live)) {
8266
9065
  unmigratedRecords.push(`${ref2.collection}:${ref2.id}`);
8267
9066
  }
8268
9067
  if (live?._sealed !== void 0) {
8269
9068
  try {
8270
- const cls = await coll._classifySealedShred(live);
8271
- for (const slot of cls.slots) {
8272
- if (slot.class === "shreddable" || slot.class === "live-shreddable+dekResidue-in-backups") {
8273
- sealedFieldsShredded += 1;
9069
+ const viaReport = await coll._onViaErase(ref2.id, live);
9070
+ if (viaReport) {
9071
+ sealedFieldsShredded += viaReport.shredded;
9072
+ for (const entry of viaReport.residue) {
9073
+ if (entry.kind === "classified-sealed-dek-residue" && entry.field !== void 0) sealedResidue.push(`${ref2.collection}:${ref2.id}:${entry.field}`);
8274
9074
  }
8275
- if (slot.class === "dekResidue" || slot.class === "live-shreddable+dekResidue-in-backups") {
8276
- sealedResidue.push(`${ref2.collection}:${ref2.id}:${slot.field}`);
9075
+ } else {
9076
+ const cls = await coll._classifySealedShred(live);
9077
+ for (const slot of cls.slots) {
9078
+ if (slot.class === "shreddable" || slot.class === "live-shreddable+dekResidue-in-backups") sealedFieldsShredded += 1;
9079
+ if (slot.class === "dekResidue" || slot.class === "live-shreddable+dekResidue-in-backups") sealedResidue.push(`${ref2.collection}:${ref2.id}:${slot.field}`);
8277
9080
  }
8278
9081
  }
8279
9082
  } catch {
@@ -8306,6 +9109,7 @@ var Vault = class {
8306
9109
  if (shred !== null) {
8307
9110
  recordsShredded++;
8308
9111
  collections.add(ref2.collection);
9112
+ await forgetDerivedFanout(this, { collection: ref2.collection, id: ref2.id }, live, fanoutStats, lookupCompareKeys);
8309
9113
  }
8310
9114
  historyVersionsShredded += await this.historyStrategy.tombstoneHistory(
8311
9115
  this.adapter,
@@ -8389,7 +9193,14 @@ var Vault = class {
8389
9193
  sealedCekEnvelopesPurged,
8390
9194
  sealedCekResidue,
8391
9195
  sealedResidue,
8392
- ledgerEntry
9196
+ ledgerEntry,
9197
+ derivedRecordsErased: fanoutStats.recordsErased,
9198
+ derivedAggregatesRecomputed: fanoutStats.aggregatesRecomputed,
9199
+ derivedResidueFrozen: fanoutStats.residueFrozen,
9200
+ lookupReferencesCascaded: fanoutStats.lookupReferencesCascaded,
9201
+ lookupReferencesNullified: fanoutStats.lookupReferencesNullified,
9202
+ lookupReferencesResidue: fanoutStats.lookupReferencesResidue
9203
+ // #650 Task 5 (+ review Important fix: residue)
8393
9204
  };
8394
9205
  }
8395
9206
  // ─── Record-scoped CEK sealing ─────────────────────────────────────
@@ -8485,7 +9296,7 @@ var Vault = class {
8485
9296
  if (handles.length === 0) return;
8486
9297
  const [{ GuardRegistry }, { ReadOnlyVaultFacade }] = await Promise.all([
8487
9298
  import("./registry-VW6P6A3J.js"),
8488
- import("./read-only-facade-5ADRJVTM.js")
9299
+ import("./read-only-facade-UCYNRE56.js")
8489
9300
  ]);
8490
9301
  const registry = new GuardRegistry();
8491
9302
  for (const h of handles) registry.register(h.spec);
@@ -8512,14 +9323,15 @@ var Vault = class {
8512
9323
  async _initDerivations(handles) {
8513
9324
  if (handles.length === 0) return;
8514
9325
  const [{ DerivationRegistry }, { ReadOnlyVaultFacade }] = await Promise.all([
8515
- import("./registry-WIX6QCV5.js"),
8516
- import("./read-only-facade-5ADRJVTM.js")
9326
+ import("./registry-DQRNTWRL.js"),
9327
+ import("./read-only-facade-UCYNRE56.js")
8517
9328
  ]);
8518
9329
  const registry = new DerivationRegistry();
8519
9330
  for (const h of handles) {
8520
9331
  await registry.register(h.spec);
8521
9332
  }
8522
- registry.validate();
9333
+ for (const edge of registry.edges()) this.graph.registerDerived(edge.target, edge.sources, edge.kind, edge.grain);
9334
+ registry.validate(this.graph);
8523
9335
  this.derivationRegistry = registry;
8524
9336
  if (this.derivationFacade === null) {
8525
9337
  this.derivationFacade = new ReadOnlyVaultFacade(this, "derivation");
@@ -8543,14 +9355,15 @@ var Vault = class {
8543
9355
  */
8544
9356
  async _initMaterializedViews(handles) {
8545
9357
  if (handles.length === 0) return;
8546
- const { MaterializedViewRegistry } = await import("./registry-AI4E46PD.js");
9358
+ const { MaterializedViewRegistry } = await import("./registry-LTGLERS7.js");
8547
9359
  const registry = new MaterializedViewRegistry();
8548
9360
  this.materializedViewRegistry = registry;
8549
9361
  const db = this;
8550
9362
  for (const h of handles) {
8551
9363
  await registry.register(h.spec, db);
8552
9364
  }
8553
- registry.validate(this.derivationRegistry);
9365
+ for (const edge of registry.edges()) this.graph.registerDerived(edge.target, edge.sources, edge.kind, edge.grain);
9366
+ registry.validate(this.graph);
8554
9367
  }
8555
9368
  /**
8556
9369
  * @internal — consumed by `Collection.put` at write-time. Returns
@@ -8567,7 +9380,7 @@ var Vault = class {
8567
9380
  */
8568
9381
  async _initOverlayedViews(handles) {
8569
9382
  if (handles.length === 0) return;
8570
- const { OverlayedViewRegistry } = await import("./registry-HSNQ4NBP.js");
9383
+ const { OverlayedViewRegistry } = await import("./registry-5RJSK5AJ.js");
8571
9384
  const registry = new OverlayedViewRegistry();
8572
9385
  const mvRegistry = this.materializedViewRegistry;
8573
9386
  const overlayNames = /* @__PURE__ */ new Set();
@@ -8584,6 +9397,7 @@ var Vault = class {
8584
9397
  isOverlayName: (n) => overlayNames.has(n) && n !== h.spec.name,
8585
9398
  isMVOutput
8586
9399
  });
9400
+ this.graph.registerDerived({ collection: h.spec.name, field: "*" }, [{ collection: h.spec.base, field: "*" }], "overlay", "record");
8587
9401
  }
8588
9402
  this.overlayedViewRegistry = registry;
8589
9403
  }
@@ -8594,16 +9408,15 @@ var Vault = class {
8594
9408
  _getOverlayedViewRegistry() {
8595
9409
  return this.overlayedViewRegistry;
8596
9410
  }
9411
+ /** @internal — ctx for `putDerivedOutput`'s frozen-period skip+audit (#638 Task 5). */
9412
+ _dispatchCtx(source) {
9413
+ return { emit: (e, p) => this.emitter.emit(e, p), source, audit: ledgerAuditHook(this.getLedgerOrNull() ?? void 0, this.keyring.userId) };
9414
+ }
8597
9415
  /**
8598
- * Manual re-materialize for a single registered MV. Useful
8599
- * for `refresh: 'manual'` MVs (whose consumer drives refreshes
8600
- * externally), for stale-bit recovery on vault re-open, and as the
8601
- * explicit bulk-recompute escape hatch after a strategy change.
8602
- *
8603
- * Returns `{ written, deleted, failed }`. `deleted` is always 0
8604
- * when tombstoning is not enabled.
8605
- *
8606
- * Throws if `name` is not a registered MV.
9416
+ * Manual re-materialize for a single registered MV (`refresh: 'manual'` consumers,
9417
+ * stale-bit recovery on vault reopen, bulk-recompute escape hatch after a strategy
9418
+ * change). Returns `{ written, deleted, failed }` (`deleted` always 0 without
9419
+ * tombstoning). Throws if `name` is not a registered MV.
8607
9420
  */
8608
9421
  async refreshView(name) {
8609
9422
  const registry = this.materializedViewRegistry;
@@ -8614,42 +9427,42 @@ var Vault = class {
8614
9427
  if (!reg) {
8615
9428
  throw new Error(`refreshView: no MV registered with name "${name}"`);
8616
9429
  }
8617
- const { MaterializedViewExecutor } = await import("./executor-MHUM2VZK.js");
9430
+ const { MaterializedViewExecutor } = await import("./executor-IL7SRNUZ.js");
8618
9431
  const result = await MaterializedViewExecutor.refresh(reg, {
8619
9432
  getCollection: (n) => this.collection(n),
8620
9433
  getActiveTxContext: () => this.noydb._activeTxContextOrNull,
8621
- getQueryContext: () => this
9434
+ getQueryContext: () => this,
9435
+ dispatchCtx: this._dispatchCtx({ collection: name, id: "refreshView" })
8622
9436
  });
8623
- const { clearMVStale } = await import("./stale-77LP2EEF.js");
9437
+ const { clearMVStale } = await import("./stale-IYYXEH3U.js");
8624
9438
  clearMVStale(registry, name);
8625
9439
  return result;
8626
9440
  }
8627
9441
  /**
8628
- * Re-derive every record in the named source collection. Useful
8629
- * after a strategy change to bring previously-derived records
8630
- * up-to-date.
8631
- *
8632
- * Sequential in v1; parallelisation deferred to v2.
9442
+ * Re-derive every record in the named source collection (useful after a strategy
9443
+ * change to bring previously-derived records up-to-date). Sequential in v1.
8633
9444
  */
8634
9445
  async deriveAll(sourceCollection) {
8635
9446
  const registry = this._getDerivationRegistry();
8636
- if (registry === null) return { derived: 0, failed: 0 };
9447
+ if (registry === null) return { derived: 0, failed: 0, skippedFrozen: 0 };
8637
9448
  const strategies = registry.strategiesForSource(sourceCollection);
8638
- if (strategies.length === 0) return { derived: 0, failed: 0 };
8639
- const { DerivationExecutor } = await import("./executor-EUPYHCLU.js");
9449
+ if (strategies.length === 0) return { derived: 0, failed: 0, skippedFrozen: 0 };
9450
+ const { DerivationExecutor } = await import("./executor-WAEI6PDV.js");
8640
9451
  const sourceColl = this.collection(sourceCollection);
8641
9452
  const records = await sourceColl.list();
8642
- const ctx = { vault: this.derivationFacade ?? new (await import("./read-only-facade-5ADRJVTM.js")).ReadOnlyVaultFacade(this, "derivation") };
9453
+ const ctx = { vault: this.derivationFacade ?? new (await import("./read-only-facade-UCYNRE56.js")).ReadOnlyVaultFacade(this, "derivation") };
8643
9454
  let derived = 0;
8644
9455
  let failed = 0;
9456
+ let skippedFrozen = 0;
8645
9457
  for (const record of records) {
8646
9458
  if (typeof record !== "object" || record === null) continue;
8647
9459
  const id = record.id;
8648
9460
  if (typeof id !== "string") continue;
9461
+ const dispatchCtx = this._dispatchCtx({ collection: sourceCollection, id });
8649
9462
  for (const { spec, strategyHash } of strategies) {
8650
9463
  const sourceWithId = { ...record, id };
8651
9464
  const result = await DerivationExecutor.run(spec, sourceWithId, 0, strategyHash, ctx);
8652
- let anyFailed = false;
9465
+ let anyFailed = false, anyFrozenSkip = false;
8653
9466
  for (const key of Object.keys(spec.outputs)) {
8654
9467
  const out = result.outputs[key];
8655
9468
  if (!out) continue;
@@ -8661,7 +9474,7 @@ var Vault = class {
8661
9474
  if (!outSpec) continue;
8662
9475
  const outputColl = this.collection(outSpec.collection);
8663
9476
  if (out.kind === "array") {
8664
- const { loadFanoutSidecar, saveFanoutSidecar } = await import("./fanout-sidecar-NBDKKCHT.js");
9477
+ const { loadFanoutSidecar, saveFanoutSidecar } = await import("./fanout-sidecar-CI5BCMQM.js");
8665
9478
  const prior = await loadFanoutSidecar(this.adapter, this.name, spec.source, id, key, this.getDEK, this.encrypted);
8666
9479
  const prevKeys = new Set(prior?.keys ?? []);
8667
9480
  const newKeysList = out.entries.map((e) => e.key);
@@ -8671,7 +9484,7 @@ var Vault = class {
8671
9484
  await outputColl._internalDelete(k);
8672
9485
  }
8673
9486
  for (const entry of out.entries) {
8674
- await outputColl.put(entry.key, entry.value);
9487
+ if (await putDerivedOutput(outputColl, entry.key, entry.value, dispatchCtx) === "skipped-frozen") anyFrozenSkip = true;
8675
9488
  }
8676
9489
  await saveFanoutSidecar(this.adapter, this.name, {
8677
9490
  source: spec.source,
@@ -8686,13 +9499,14 @@ var Vault = class {
8686
9499
  await outputColl._internalDelete(id);
8687
9500
  continue;
8688
9501
  }
8689
- await outputColl.put(id, out.value);
9502
+ if (await putDerivedOutput(outputColl, id, out.value, dispatchCtx) === "skipped-frozen") anyFrozenSkip = true;
8690
9503
  }
8691
9504
  if (anyFailed) failed++;
9505
+ else if (anyFrozenSkip) skippedFrozen++;
8692
9506
  else derived++;
8693
9507
  }
8694
9508
  }
8695
- return { derived, failed };
9509
+ return { derived, failed, skippedFrozen };
8696
9510
  }
8697
9511
  /**
8698
9512
  * @internal — exposed for `runTransaction({ amendment: true })` so
@@ -8883,7 +9697,7 @@ var Vault = class {
8883
9697
  * collection.
8884
9698
  */
8885
9699
  async delegate(opts) {
8886
- const { issueDelegation, DELEGATIONS_COLLECTION } = await import("./delegation-IHXOASHV.js");
9700
+ const { issueDelegation, DELEGATIONS_COLLECTION } = await import("./delegation-ZKII5UBI.js");
8887
9701
  if (!this.keyring.kek) {
8888
9702
  throw new ValidationError(
8889
9703
  "issueDelegation: keyring.kek is null \u2014 issuing a delegation requires a tier-1 unlock. Re-authenticate at tier 1 (passphrase) first."
@@ -8905,7 +9719,7 @@ var Vault = class {
8905
9719
  * if the id does not exist.
8906
9720
  */
8907
9721
  async revokeDelegation(id) {
8908
- const { revokeDelegation, DELEGATIONS_COLLECTION } = await import("./delegation-IHXOASHV.js");
9722
+ const { revokeDelegation, DELEGATIONS_COLLECTION } = await import("./delegation-ZKII5UBI.js");
8909
9723
  await revokeDelegation(this.adapter, this.name, id);
8910
9724
  void DELEGATIONS_COLLECTION;
8911
9725
  }
@@ -9153,7 +9967,7 @@ var Vault = class {
9153
9967
  * @see docs/superpowers/specs/2026-05-22-schema-dump-design.md
9154
9968
  */
9155
9969
  async dumpSchema(opts = {}) {
9156
- const { dumpVaultSchema } = await import("./walk-SMAE4IEI.js");
9970
+ const { dumpVaultSchema } = await import("./walk-76UCFXYV.js");
9157
9971
  return dumpVaultSchema(this, opts);
9158
9972
  }
9159
9973
  /**
@@ -9288,7 +10102,7 @@ var Vault = class {
9288
10102
  * @see https://github.com/vLannaAi/noy-db-docs/blob/main/content/docs/services/public-envelope.md
9289
10103
  */
9290
10104
  async getPublicEnvelope(opts = {}) {
9291
- const { readPublicEnvelope } = await import("./public-envelope-NC7SPNMV.js");
10105
+ const { readPublicEnvelope } = await import("./public-envelope-J5PLQOW2.js");
9292
10106
  return readPublicEnvelope(this.adapter, this.name, opts);
9293
10107
  }
9294
10108
  /**
@@ -9303,7 +10117,7 @@ var Vault = class {
9303
10117
  * both modes round-trip cleanly.
9304
10118
  */
9305
10119
  async dump() {
9306
- const { dumpVault } = await import("./backup-WRGNTGJF.js");
10120
+ const { dumpVault } = await import("./backup-ODXO7OGA.js");
9307
10121
  return dumpVault(this.backupContext());
9308
10122
  }
9309
10123
  /**
@@ -9355,7 +10169,7 @@ var Vault = class {
9355
10169
  * — there's no chain to verify against.
9356
10170
  */
9357
10171
  async load(backupJson) {
9358
- const { loadVault } = await import("./backup-WRGNTGJF.js");
10172
+ const { loadVault } = await import("./backup-ODXO7OGA.js");
9359
10173
  return loadVault(this.backupContext(), backupJson);
9360
10174
  }
9361
10175
  /**
@@ -9387,7 +10201,7 @@ var Vault = class {
9387
10201
  * way to detect tampering of an in-place vault.
9388
10202
  */
9389
10203
  async verifyBackupIntegrity() {
9390
- const { verifyBackupIntegrity } = await import("./backup-WRGNTGJF.js");
10204
+ const { verifyBackupIntegrity } = await import("./backup-ODXO7OGA.js");
9391
10205
  return verifyBackupIntegrity(this.backupContext());
9392
10206
  }
9393
10207
  /**
@@ -9493,7 +10307,7 @@ var Vault = class {
9493
10307
  const records = [];
9494
10308
  for (const id of ids) {
9495
10309
  const record = await coll.get(id, localeOpts);
9496
- if (record !== null) records.push(record);
10310
+ if (record !== null) records.push(exportRedact(coll, record));
9497
10311
  }
9498
10312
  const chunk = {
9499
10313
  collection: collectionName,
@@ -9512,7 +10326,7 @@ var Vault = class {
9512
10326
  collection: collectionName,
9513
10327
  schema,
9514
10328
  refs,
9515
- records: [record],
10329
+ records: [exportRedact(coll, record)],
9516
10330
  ...dictionaries !== void 0 ? { dictionaries } : {},
9517
10331
  ...ledgerHead ? { ledgerHead } : {}
9518
10332
  };
@@ -9605,7 +10419,7 @@ var Vault = class {
9605
10419
  * is the live validator object, not a serialization of it).
9606
10420
  */
9607
10421
  async exportJSON(opts = {}) {
9608
- const { exportVaultJSON } = await import("./backup-WRGNTGJF.js");
10422
+ const { exportVaultJSON } = await import("./backup-ODXO7OGA.js");
9609
10423
  return exportVaultJSON(this.backupContext(), opts);
9610
10424
  }
9611
10425
  };
@@ -10319,17 +11133,17 @@ function memoryStore() {
10319
11133
  }
10320
11134
 
10321
11135
  // src/with-party/session/strategy.ts
10322
- function notEnabled4(op) {
11136
+ function notEnabled3(op) {
10323
11137
  return new Error(
10324
11138
  `${op} requires the session strategy. Import \`{ withSession }\` from "@noy-db/hub/session" and pass it to \`createNoydb({ sessionStrategy: withSession() })\`.`
10325
11139
  );
10326
11140
  }
10327
11141
  var NO_SESSION = {
10328
11142
  validateSessionPolicy() {
10329
- throw notEnabled4("sessionPolicy");
11143
+ throw notEnabled3("sessionPolicy");
10330
11144
  },
10331
11145
  createEnforcer() {
10332
- throw notEnabled4("session policy enforcement");
11146
+ throw notEnabled3("session policy enforcement");
10333
11147
  },
10334
11148
  revokeAllSessions() {
10335
11149
  }
@@ -11478,7 +12292,7 @@ var Noydb = class {
11478
12292
  if (!facade) return;
11479
12293
  const ctx = { existing, vault: facade, userId: e.userId, role: e.role };
11480
12294
  await registry.runChecks(e.collection, incoming, ctx);
11481
- const { GuardExecutor } = await import("./executor-KZ4B3PRT.js");
12295
+ const { GuardExecutor } = await import("./executor-OUA2Y6YC.js");
11482
12296
  for (const g of guards) {
11483
12297
  await GuardExecutor.checkFrozenFields(g, e.docId, existing, incoming, e.computedFieldNames);
11484
12298
  }
@@ -11699,6 +12513,8 @@ var Noydb = class {
11699
12513
  });
11700
12514
  this._forEachSyncEngine(name, (engine) => {
11701
12515
  engine.setCacheInvalidator((collection, id) => comp._invalidateSyncApplied(collection, id));
12516
+ engine.setGraphBatchController({ begin: () => comp._beginGraphBatch(), flush: () => comp._flushGraphBatch() });
12517
+ engine.setReservedLookupSource({ collections: () => comp._reservedLookupCollectionNames() });
11702
12518
  });
11703
12519
  await comp._initGuards(this.options.guardStrategies ?? []);
11704
12520
  await comp._initDerivations(this.options.derivationStrategies ?? []);
@@ -12743,7 +13559,7 @@ var Noydb = class {
12743
13559
  "setPublicEnvelope: the public-envelope feature is not enabled. Pass `publicEnvelope: true` (or a schema object) to `createNoydb`."
12744
13560
  );
12745
13561
  }
12746
- const { loadPublicEnvelope, savePublicEnvelope, resolveSchema, validatePublicEnvelopeInput } = await import("./public-envelope-NC7SPNMV.js");
13562
+ const { loadPublicEnvelope, savePublicEnvelope, resolveSchema, validatePublicEnvelopeInput } = await import("./public-envelope-J5PLQOW2.js");
12747
13563
  const schema = resolveSchema(this.options.publicEnvelope);
12748
13564
  validatePublicEnvelopeInput(input, schema);
12749
13565
  const now = (/* @__PURE__ */ new Date()).toISOString();
@@ -12772,7 +13588,7 @@ var Noydb = class {
12772
13588
  * regardless).
12773
13589
  */
12774
13590
  async getPublicEnvelope(vault, opts = {}) {
12775
- const { readPublicEnvelope } = await import("./public-envelope-NC7SPNMV.js");
13591
+ const { readPublicEnvelope } = await import("./public-envelope-J5PLQOW2.js");
12776
13592
  return readPublicEnvelope(this.options.store, vault, opts);
12777
13593
  }
12778
13594
  // ─── Auth introspection ─────────────────────────────────────────
@@ -12963,9 +13779,9 @@ async function createNoydb(options) {
12963
13779
  throw new ValidationError("A secret (passphrase) or getKeyring callback is required when encryption is enabled");
12964
13780
  }
12965
13781
  if (!options.coordinationStrategy) options = { ...options, coordinationStrategy: await createDefaultCoordinationProvider(options.store) };
12966
- if (!options.userApiFactory) options = { ...options, userApiFactory: (await import("./api-JDWL67WL.js")).createUserApi };
13782
+ if (!options.userApiFactory) options = { ...options, userApiFactory: (await import("./api-HV4PDUWF.js")).createUserApi };
12967
13783
  if (!options.policyFactory) {
12968
- const policyModule = await import("./policy-Y7HL2KGA.js");
13784
+ const policyModule = await import("./policy-SDGRWVIL.js");
12969
13785
  options = { ...options, policyFactory: policyModule.createNoydbPolicy, policyCheckGateFn: policyModule.checkGate };
12970
13786
  }
12971
13787
  return new Noydb(options);
@@ -12982,6 +13798,9 @@ function normalizeSyncTargets(sync) {
12982
13798
  export {
12983
13799
  memoryStore,
12984
13800
  withArchive,
13801
+ buildLookupSnapshot,
13802
+ via,
13803
+ isViaFieldSpec,
12985
13804
  Collection,
12986
13805
  ELEVATION_AUDIT_COLLECTION,
12987
13806
  ElevatedHandle,
@@ -13000,4 +13819,4 @@ export {
13000
13819
  Noydb,
13001
13820
  createNoydb
13002
13821
  };
13003
- //# sourceMappingURL=chunk-56JB2BGI.js.map
13822
+ //# sourceMappingURL=chunk-4C4WBS4V.js.map