@noy-db/hub 0.2.0-pre.4 → 0.2.0-pre.6

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 (282) hide show
  1. package/dist/aggregate/index.cjs.map +1 -1
  2. package/dist/aggregate/index.js +4 -4
  3. package/dist/attestation/index.cjs.map +1 -1
  4. package/dist/attestation/index.d.cts +4 -4
  5. package/dist/attestation/index.d.ts +4 -4
  6. package/dist/attestation/index.js +6 -6
  7. package/dist/blobs/index.cjs.map +1 -1
  8. package/dist/blobs/index.d.cts +5 -5
  9. package/dist/blobs/index.d.ts +5 -5
  10. package/dist/blobs/index.js +5 -5
  11. package/dist/bundle/index.cjs +496 -344
  12. package/dist/bundle/index.cjs.map +1 -1
  13. package/dist/bundle/index.d.cts +17 -17
  14. package/dist/bundle/index.d.ts +17 -17
  15. package/dist/bundle/index.js +10 -10
  16. package/dist/bundle/index.js.map +1 -1
  17. package/dist/{chunk-YL2DR3HY.js → chunk-25WFLKOH.js} +2 -2
  18. package/dist/chunk-25WFLKOH.js.map +1 -0
  19. package/dist/{chunk-EMEX37ZN.js → chunk-2GMRNNI3.js} +3 -3
  20. package/dist/chunk-2GMRNNI3.js.map +1 -0
  21. package/dist/{chunk-NGSPBLLE.js → chunk-34XGYMQT.js} +3 -3
  22. package/dist/chunk-34XGYMQT.js.map +1 -0
  23. package/dist/{chunk-FXQYZNOW.js → chunk-5OVIFUQE.js} +1 -1
  24. package/dist/chunk-5OVIFUQE.js.map +1 -0
  25. package/dist/{chunk-P6256WTJ.js → chunk-5QPF2MJ5.js} +3 -3
  26. package/dist/chunk-5QPF2MJ5.js.map +1 -0
  27. package/dist/{chunk-5ZGZ6HIZ.js → chunk-5VMTAX4Y.js} +2 -2
  28. package/dist/{chunk-74JEQFMT.js → chunk-6A4AMQ2H.js} +5 -5
  29. package/dist/chunk-6A4AMQ2H.js.map +1 -0
  30. package/dist/{chunk-YDLAFP36.js → chunk-6HJ2ZALB.js} +1 -1
  31. package/dist/chunk-6HJ2ZALB.js.map +1 -0
  32. package/dist/{chunk-GDTCGIPX.js → chunk-7TX7HN42.js} +2 -2
  33. package/dist/chunk-7TX7HN42.js.map +1 -0
  34. package/dist/{chunk-EPK6A3WJ.js → chunk-A3JMGXPG.js} +2 -2
  35. package/dist/chunk-A3JMGXPG.js.map +1 -0
  36. package/dist/{chunk-75QDHSE4.js → chunk-A4JNVBPF.js} +5 -5
  37. package/dist/{chunk-IS5HWQO7.js → chunk-ARZAHCCF.js} +3 -3
  38. package/dist/{chunk-4OQWR46B.js → chunk-CCC25PA7.js} +5 -5
  39. package/dist/{chunk-NSLTPGEN.js → chunk-CGJFCT3X.js} +2 -2
  40. package/dist/{chunk-YK72A4IT.js → chunk-CKH247ZR.js} +4 -4
  41. package/dist/{chunk-HGZ7DC5H.js → chunk-DFCINPB5.js} +2 -2
  42. package/dist/chunk-DFCINPB5.js.map +1 -0
  43. package/dist/{chunk-4X2S7PBF.js → chunk-E225X5CQ.js} +3 -3
  44. package/dist/chunk-E225X5CQ.js.map +1 -0
  45. package/dist/{chunk-5YHWBPOT.js → chunk-ED3E3OLO.js} +2 -2
  46. package/dist/{chunk-UOF74WQY.js → chunk-EKTOYEZ3.js} +2 -2
  47. package/dist/{chunk-SAVQ6E2O.js → chunk-G26QAQNI.js} +2 -2
  48. package/dist/{chunk-YMYK7US4.js → chunk-HIELMTUK.js} +2 -2
  49. package/dist/{chunk-MRIBLZL3.js → chunk-ICH4AIGL.js} +1 -1
  50. package/dist/chunk-ICH4AIGL.js.map +1 -0
  51. package/dist/{chunk-KMI2NBBF.js → chunk-JICBEFBT.js} +181 -6
  52. package/dist/chunk-JICBEFBT.js.map +1 -0
  53. package/dist/{chunk-LOL725S4.js → chunk-JSYTGEX4.js} +3 -3
  54. package/dist/{chunk-FBMXWVGP.js → chunk-KGFV72WK.js} +5 -5
  55. package/dist/{chunk-GVXBHCZ2.js → chunk-LJO6Q3X6.js} +5 -5
  56. package/dist/chunk-LJO6Q3X6.js.map +1 -0
  57. package/dist/{chunk-ZC2AAE6J.js → chunk-LWFQYT4N.js} +2 -2
  58. package/dist/chunk-LWFQYT4N.js.map +1 -0
  59. package/dist/{chunk-K5PVGKE4.js → chunk-MDIC4FAU.js} +2 -2
  60. package/dist/{chunk-ZUMGGHRB.js → chunk-OPD3PZOG.js} +4 -4
  61. package/dist/{chunk-LS3JLEIB.js → chunk-PS5G6A3Y.js} +4 -4
  62. package/dist/{chunk-KYKMKLJ6.js → chunk-PX3MJ6RB.js} +3 -3
  63. package/dist/{chunk-FCDO7UAO.js → chunk-R4LTCI6O.js} +2 -2
  64. package/dist/{chunk-BFI3RS42.js → chunk-R7JTYCRX.js} +2 -2
  65. package/dist/chunk-R7JTYCRX.js.map +1 -0
  66. package/dist/{chunk-WRLHNG6H.js → chunk-RIHZBSWJ.js} +4 -4
  67. package/dist/chunk-RIHZBSWJ.js.map +1 -0
  68. package/dist/{chunk-UVPGJXVO.js → chunk-SGSHQ4PH.js} +5 -5
  69. package/dist/{chunk-TLFUDXVV.js → chunk-T6MTNGBM.js} +5 -5
  70. package/dist/chunk-T6MTNGBM.js.map +1 -0
  71. package/dist/{chunk-6S3LLAQ5.js → chunk-TNBIWSQ7.js} +2 -2
  72. package/dist/{chunk-GD3BGKAR.js → chunk-UGVDIOY7.js} +2 -2
  73. package/dist/{chunk-T6HQMVML.js → chunk-W277AG6N.js} +411 -308
  74. package/dist/chunk-W277AG6N.js.map +1 -0
  75. package/dist/{chunk-FS7A4XNF.js → chunk-WEA4TDTJ.js} +3 -3
  76. package/dist/{chunk-4UBOTYP5.js → chunk-XDW37COG.js} +5 -5
  77. package/dist/chunk-XDW37COG.js.map +1 -0
  78. package/dist/{chunk-QAU5HM6Q.js → chunk-XVJFFGTG.js} +3 -3
  79. package/dist/{chunk-2EYC3WDT.js → chunk-Y3P5DEMZ.js} +6 -6
  80. package/dist/chunk-Y3P5DEMZ.js.map +1 -0
  81. package/dist/{chunk-G7PAZ3TD.js → chunk-YEHUEUNP.js} +4 -4
  82. package/dist/chunk-YEHUEUNP.js.map +1 -0
  83. package/dist/{chunk-2XLVPKXG.js → chunk-YJ46RFCD.js} +2 -2
  84. package/dist/{chunk-NCO2JGKK.js → chunk-Z6FNBOTC.js} +1 -1
  85. package/dist/chunk-Z6FNBOTC.js.map +1 -0
  86. package/dist/{chunk-GAUBWHAF.js → chunk-ZQMYB56Z.js} +4 -4
  87. package/dist/consent/index.cjs.map +1 -1
  88. package/dist/consent/index.d.cts +5 -5
  89. package/dist/consent/index.d.ts +5 -5
  90. package/dist/consent/index.js +3 -3
  91. package/dist/{crypto-H2Y3DDFW.js → crypto-5UDZZL26.js} +3 -3
  92. package/dist/{delegation-QSC7G5QC.js → delegation-42LO4WFO.js} +5 -5
  93. package/dist/derivations/index.cjs +1 -1
  94. package/dist/derivations/index.cjs.map +1 -1
  95. package/dist/derivations/index.d.cts +8 -8
  96. package/dist/derivations/index.d.ts +8 -8
  97. package/dist/derivations/index.js +4 -4
  98. package/dist/{dev-unlock-Cf2B7Kih.d.ts → dev-unlock-Cvo-xCQC.d.ts} +1 -1
  99. package/dist/{dev-unlock-De3mjQWv.d.cts → dev-unlock-Dy1qVpkL.d.cts} +1 -1
  100. package/dist/executor-AWCHQ2KN.js +8 -0
  101. package/dist/executor-RWICJI7J.js +11 -0
  102. package/dist/executor-SOLEQVUB.js +8 -0
  103. package/dist/{fanout-sidecar-NRBWSLRK.js → fanout-sidecar-EVICRM46.js} +2 -2
  104. package/dist/fanout-sidecar-EVICRM46.js.map +1 -0
  105. package/dist/guards/index.cjs +1 -1
  106. package/dist/guards/index.cjs.map +1 -1
  107. package/dist/guards/index.d.cts +6 -6
  108. package/dist/guards/index.d.ts +6 -6
  109. package/dist/guards/index.js +4 -4
  110. package/dist/{hash-gVn_uKhp.d.ts → hash-BAlWR4WD.d.ts} +1 -1
  111. package/dist/{hash-vBCB0-Ps.d.cts → hash-BgEQklQc.d.cts} +1 -1
  112. package/dist/history/index.cjs.map +1 -1
  113. package/dist/history/index.d.cts +6 -6
  114. package/dist/history/index.d.ts +6 -6
  115. package/dist/history/index.js +6 -6
  116. package/dist/i18n/index.cjs +75 -10
  117. package/dist/i18n/index.cjs.map +1 -1
  118. package/dist/i18n/index.d.cts +5 -5
  119. package/dist/i18n/index.d.ts +5 -5
  120. package/dist/i18n/index.js +16 -14
  121. package/dist/{index-DVkvrgpm.d.cts → index-5I0MZ0jQ.d.cts} +12 -12
  122. package/dist/{index-BF1B2HB9.d.ts → index-fIPPh5dg.d.ts} +12 -12
  123. package/dist/index.cjs +538 -378
  124. package/dist/index.cjs.map +1 -1
  125. package/dist/index.d.cts +20 -22
  126. package/dist/index.d.ts +20 -22
  127. package/dist/index.js +50 -52
  128. package/dist/index.js.map +1 -1
  129. package/dist/indexing/index.cjs +1 -1
  130. package/dist/indexing/index.cjs.map +1 -1
  131. package/dist/indexing/index.d.cts +3 -3
  132. package/dist/indexing/index.d.ts +3 -3
  133. package/dist/indexing/index.js +4 -4
  134. package/dist/issue-IODMTPME.js +12 -0
  135. package/dist/{lazy-builder-Rpd-V3jP.d.ts → lazy-builder-D1MyR1qH.d.ts} +2 -2
  136. package/dist/{lazy-builder-C-rPfWG0.d.cts → lazy-builder-DXlSCNCJ.d.cts} +2 -2
  137. package/dist/{ledger-WOEJUYTP.js → ledger-UX4QIHWI.js} +6 -6
  138. package/dist/materialized-views/index.cjs.map +1 -1
  139. package/dist/materialized-views/index.d.cts +18 -18
  140. package/dist/materialized-views/index.d.ts +18 -18
  141. package/dist/materialized-views/index.js +7 -7
  142. package/dist/noydb-FY2666NY.js +34 -0
  143. package/dist/overlay-views/index.cjs +1 -1
  144. package/dist/overlay-views/index.cjs.map +1 -1
  145. package/dist/overlay-views/index.d.cts +8 -8
  146. package/dist/overlay-views/index.d.ts +8 -8
  147. package/dist/overlay-views/index.js +4 -4
  148. package/dist/periods/index.cjs.map +1 -1
  149. package/dist/periods/index.d.cts +5 -5
  150. package/dist/periods/index.d.ts +5 -5
  151. package/dist/periods/index.js +6 -6
  152. package/dist/{predicate-Dnu81tsS.d.cts → predicate-B0IKeBXx.d.cts} +1 -1
  153. package/dist/{predicate-Dnu81tsS.d.ts → predicate-B0IKeBXx.d.ts} +1 -1
  154. package/dist/{public-envelope-OHQ5UZFM.js → public-envelope-YKHKP74C.js} +4 -4
  155. package/dist/query/index.cjs +2 -2
  156. package/dist/query/index.cjs.map +1 -1
  157. package/dist/query/index.d.cts +2 -2
  158. package/dist/query/index.d.ts +2 -2
  159. package/dist/query/index.js +6 -6
  160. package/dist/registry-446I2NMN.js +8 -0
  161. package/dist/{registry-CDHASH73.js → registry-4NEW7LQY.js} +3 -3
  162. package/dist/registry-524KJZG4.js +8 -0
  163. package/dist/registry-DKEXOJVO.js +7 -0
  164. package/dist/{revoke-7JOVLZFD.js → revoke-R5NIQ74J.js} +6 -6
  165. package/dist/session/index.cjs.map +1 -1
  166. package/dist/session/index.d.cts +6 -6
  167. package/dist/session/index.d.ts +6 -6
  168. package/dist/session/index.js +3 -3
  169. package/dist/shadow/index.cjs.map +1 -1
  170. package/dist/shadow/index.d.cts +5 -5
  171. package/dist/shadow/index.d.ts +5 -5
  172. package/dist/shadow/index.js +2 -2
  173. package/dist/{signer-M4K5HBLD.js → signer-WGDJNWSU.js} +5 -5
  174. package/dist/{stale-PAGCS4K5.js → stale-74WGLVZ2.js} +2 -2
  175. package/dist/store/index.cjs.map +1 -1
  176. package/dist/store/index.d.cts +5 -5
  177. package/dist/store/index.d.ts +5 -5
  178. package/dist/store/index.js +2 -2
  179. package/dist/sync/index.cjs.map +1 -1
  180. package/dist/sync/index.d.cts +4 -4
  181. package/dist/sync/index.d.ts +4 -4
  182. package/dist/sync/index.js +4 -4
  183. package/dist/team/index.cjs +1 -1
  184. package/dist/team/index.cjs.map +1 -1
  185. package/dist/team/index.d.cts +5 -5
  186. package/dist/team/index.d.ts +5 -5
  187. package/dist/team/index.js +8 -8
  188. package/dist/tx/index.cjs +2 -2
  189. package/dist/tx/index.cjs.map +1 -1
  190. package/dist/tx/index.d.cts +5 -5
  191. package/dist/tx/index.d.ts +5 -5
  192. package/dist/tx/index.js +3 -3
  193. package/dist/tx/index.js.map +1 -1
  194. package/dist/{types-CSLcfytP.d.cts → types-DVlvNn2c.d.cts} +362 -307
  195. package/dist/{types-D9eB0Rvh.d.ts → types-DlnZh1_i.d.ts} +362 -307
  196. package/dist/{ulid-CiM2OAeM.d.ts → ulid-CzPONlhG.d.ts} +19 -19
  197. package/dist/{ulid-CG2YvAbg.d.cts → ulid-r98nkjVd.d.cts} +19 -19
  198. package/dist/util/index.cjs.map +1 -1
  199. package/dist/util/index.js +1 -1
  200. package/dist/{with-derivation-Bzpj6UTv.d.ts → with-derivation-B98shCV8.d.ts} +1 -1
  201. package/dist/{with-derivation-DWajFh4K.d.cts → with-derivation-BMQ9pIHe.d.cts} +1 -1
  202. package/dist/{with-guard-DF_Ul3DT.d.cts → with-guard-DUnC3JDN.d.cts} +1 -1
  203. package/dist/{with-guard-DR7U-l4v.d.ts → with-guard-DmT50nVG.d.ts} +1 -1
  204. package/dist/{with-materialized-view-qtoJ3xKJ.d.ts → with-materialized-view-Bp_M3sNG.d.ts} +2 -2
  205. package/dist/{with-materialized-view-_piodoIz.d.cts → with-materialized-view-eMTZ65_J.d.cts} +2 -2
  206. package/dist/{with-overlayed-view-DFaRfgMr.d.ts → with-overlayed-view-BoY6PB3n.d.cts} +2 -2
  207. package/dist/{with-overlayed-view-DwzCKxn2.d.cts → with-overlayed-view-zzSnRQmS.d.ts} +2 -2
  208. package/package.json +3 -3
  209. package/dist/chunk-2EYC3WDT.js.map +0 -1
  210. package/dist/chunk-4UBOTYP5.js.map +0 -1
  211. package/dist/chunk-4X2S7PBF.js.map +0 -1
  212. package/dist/chunk-74JEQFMT.js.map +0 -1
  213. package/dist/chunk-A6SWRXUQ.js +0 -118
  214. package/dist/chunk-A6SWRXUQ.js.map +0 -1
  215. package/dist/chunk-BFI3RS42.js.map +0 -1
  216. package/dist/chunk-EMEX37ZN.js.map +0 -1
  217. package/dist/chunk-EPK6A3WJ.js.map +0 -1
  218. package/dist/chunk-FXQYZNOW.js.map +0 -1
  219. package/dist/chunk-G7PAZ3TD.js.map +0 -1
  220. package/dist/chunk-GDTCGIPX.js.map +0 -1
  221. package/dist/chunk-GVXBHCZ2.js.map +0 -1
  222. package/dist/chunk-HGZ7DC5H.js.map +0 -1
  223. package/dist/chunk-KMI2NBBF.js.map +0 -1
  224. package/dist/chunk-MRIBLZL3.js.map +0 -1
  225. package/dist/chunk-NCO2JGKK.js.map +0 -1
  226. package/dist/chunk-NGSPBLLE.js.map +0 -1
  227. package/dist/chunk-P6256WTJ.js.map +0 -1
  228. package/dist/chunk-T6HQMVML.js.map +0 -1
  229. package/dist/chunk-TLFUDXVV.js.map +0 -1
  230. package/dist/chunk-WRLHNG6H.js.map +0 -1
  231. package/dist/chunk-YDLAFP36.js.map +0 -1
  232. package/dist/chunk-YL2DR3HY.js.map +0 -1
  233. package/dist/chunk-ZC2AAE6J.js.map +0 -1
  234. package/dist/executor-BZKFZVRC.js +0 -8
  235. package/dist/executor-GFZFDQXV.js +0 -8
  236. package/dist/executor-KT2IOZVP.js +0 -11
  237. package/dist/fanout-sidecar-NRBWSLRK.js.map +0 -1
  238. package/dist/issue-BAJ7ZB4S.js +0 -12
  239. package/dist/noydb-XNQSKXGO.js +0 -34
  240. package/dist/registry-2IEARCGT.js +0 -7
  241. package/dist/registry-EMGLZGR6.js +0 -8
  242. package/dist/registry-NQALYR77.js +0 -8
  243. /package/dist/{chunk-5ZGZ6HIZ.js.map → chunk-5VMTAX4Y.js.map} +0 -0
  244. /package/dist/{chunk-75QDHSE4.js.map → chunk-A4JNVBPF.js.map} +0 -0
  245. /package/dist/{chunk-IS5HWQO7.js.map → chunk-ARZAHCCF.js.map} +0 -0
  246. /package/dist/{chunk-4OQWR46B.js.map → chunk-CCC25PA7.js.map} +0 -0
  247. /package/dist/{chunk-NSLTPGEN.js.map → chunk-CGJFCT3X.js.map} +0 -0
  248. /package/dist/{chunk-YK72A4IT.js.map → chunk-CKH247ZR.js.map} +0 -0
  249. /package/dist/{chunk-5YHWBPOT.js.map → chunk-ED3E3OLO.js.map} +0 -0
  250. /package/dist/{chunk-UOF74WQY.js.map → chunk-EKTOYEZ3.js.map} +0 -0
  251. /package/dist/{chunk-SAVQ6E2O.js.map → chunk-G26QAQNI.js.map} +0 -0
  252. /package/dist/{chunk-YMYK7US4.js.map → chunk-HIELMTUK.js.map} +0 -0
  253. /package/dist/{chunk-LOL725S4.js.map → chunk-JSYTGEX4.js.map} +0 -0
  254. /package/dist/{chunk-FBMXWVGP.js.map → chunk-KGFV72WK.js.map} +0 -0
  255. /package/dist/{chunk-K5PVGKE4.js.map → chunk-MDIC4FAU.js.map} +0 -0
  256. /package/dist/{chunk-ZUMGGHRB.js.map → chunk-OPD3PZOG.js.map} +0 -0
  257. /package/dist/{chunk-LS3JLEIB.js.map → chunk-PS5G6A3Y.js.map} +0 -0
  258. /package/dist/{chunk-KYKMKLJ6.js.map → chunk-PX3MJ6RB.js.map} +0 -0
  259. /package/dist/{chunk-FCDO7UAO.js.map → chunk-R4LTCI6O.js.map} +0 -0
  260. /package/dist/{chunk-UVPGJXVO.js.map → chunk-SGSHQ4PH.js.map} +0 -0
  261. /package/dist/{chunk-6S3LLAQ5.js.map → chunk-TNBIWSQ7.js.map} +0 -0
  262. /package/dist/{chunk-GD3BGKAR.js.map → chunk-UGVDIOY7.js.map} +0 -0
  263. /package/dist/{chunk-FS7A4XNF.js.map → chunk-WEA4TDTJ.js.map} +0 -0
  264. /package/dist/{chunk-QAU5HM6Q.js.map → chunk-XVJFFGTG.js.map} +0 -0
  265. /package/dist/{chunk-2XLVPKXG.js.map → chunk-YJ46RFCD.js.map} +0 -0
  266. /package/dist/{chunk-GAUBWHAF.js.map → chunk-ZQMYB56Z.js.map} +0 -0
  267. /package/dist/{crypto-H2Y3DDFW.js.map → crypto-5UDZZL26.js.map} +0 -0
  268. /package/dist/{delegation-QSC7G5QC.js.map → delegation-42LO4WFO.js.map} +0 -0
  269. /package/dist/{executor-BZKFZVRC.js.map → executor-AWCHQ2KN.js.map} +0 -0
  270. /package/dist/{executor-GFZFDQXV.js.map → executor-RWICJI7J.js.map} +0 -0
  271. /package/dist/{executor-KT2IOZVP.js.map → executor-SOLEQVUB.js.map} +0 -0
  272. /package/dist/{issue-BAJ7ZB4S.js.map → issue-IODMTPME.js.map} +0 -0
  273. /package/dist/{ledger-WOEJUYTP.js.map → ledger-UX4QIHWI.js.map} +0 -0
  274. /package/dist/{noydb-XNQSKXGO.js.map → noydb-FY2666NY.js.map} +0 -0
  275. /package/dist/{public-envelope-OHQ5UZFM.js.map → public-envelope-YKHKP74C.js.map} +0 -0
  276. /package/dist/{registry-2IEARCGT.js.map → registry-446I2NMN.js.map} +0 -0
  277. /package/dist/{registry-CDHASH73.js.map → registry-4NEW7LQY.js.map} +0 -0
  278. /package/dist/{registry-EMGLZGR6.js.map → registry-524KJZG4.js.map} +0 -0
  279. /package/dist/{registry-NQALYR77.js.map → registry-DKEXOJVO.js.map} +0 -0
  280. /package/dist/{revoke-7JOVLZFD.js.map → revoke-R5NIQ74J.js.map} +0 -0
  281. /package/dist/{signer-M4K5HBLD.js.map → signer-WGDJNWSU.js.map} +0 -0
  282. /package/dist/{stale-PAGCS4K5.js.map → stale-74WGLVZ2.js.map} +0 -0
package/dist/index.cjs CHANGED
@@ -3542,7 +3542,7 @@ var init_registry2 = __esm({
3542
3542
  guardsFor(collection) {
3543
3543
  return this._byCollection.get(collection) ?? [];
3544
3544
  }
3545
- /** Per-collection guard counts, for introspection (#229). */
3545
+ /** Per-collection guard counts, for introspection. */
3546
3546
  summary() {
3547
3547
  return [...this._byCollection.entries()].map(([collection, guards]) => ({
3548
3548
  collection,
@@ -8084,7 +8084,7 @@ async function changeSecret(adapter, vault, keyring, newPassphrase, passphraseOp
8084
8084
  // Tier-2 slots are NOT preserved through `changeSecret` —
8085
8085
  // each slot wraps the OLD KEK, so the new keyring has no
8086
8086
  // authenticator slots until the user re-enrolls. The higher-level
8087
- // `db.rotatePassphrase()` (#10) preserves slots by rewrapping the
8087
+ // `db.rotatePassphrase()` preserves slots by rewrapping the
8088
8088
  // KEK reference, not the KEK itself.
8089
8089
  authenticators: [],
8090
8090
  ...keyring.policy !== void 0 && { policy: keyring.policy }
@@ -8976,7 +8976,7 @@ var UserApi = class {
8976
8976
  * the envelope on first call. Optimistic-concurrency safe — a stale
8977
8977
  * `_v` (parallel writer on another device) throws `ConflictError`.
8978
8978
  *
8979
- * Patch semantics (#57):
8979
+ * Patch semantics:
8980
8980
  * - `undefined` (or omitted key) — skip; existing value preserved
8981
8981
  * - `null` — delete the field from the merged result
8982
8982
  * - any other value — overwrite (deep-merge for plain objects,
@@ -9032,7 +9032,7 @@ var UserApi = class {
9032
9032
  this.fireChange(this.writerKeyringId, written);
9033
9033
  return written;
9034
9034
  }
9035
- // ─── Visibility (#122) ───────────────────────────────────────────────
9035
+ // ─── Visibility ──────────────────────────────────────────────────────
9036
9036
  /**
9037
9037
  * Read the current user's visibility flag from
9038
9038
  * `_meta/visibility/<keyringId>`. Returns `{ hidden: false }` when no
@@ -9677,6 +9677,173 @@ var NO_CRDT = {
9677
9677
  }
9678
9678
  };
9679
9679
 
9680
+ // src/i18n/core.ts
9681
+ init_errors();
9682
+ function i18nText(options) {
9683
+ return { _noydbI18nText: true, options };
9684
+ }
9685
+ function isI18nTextDescriptor(x) {
9686
+ return typeof x === "object" && x !== null && x._noydbI18nText === true;
9687
+ }
9688
+ function validateI18nTextValue(value, field, descriptor) {
9689
+ const { options } = descriptor;
9690
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
9691
+ throw new MissingTranslationError(
9692
+ field,
9693
+ options.languages,
9694
+ `Field "${field}" must be a { [locale]: string } map, got ${typeof value}.`
9695
+ );
9696
+ }
9697
+ const map = value;
9698
+ for (const [locale, v] of Object.entries(map)) {
9699
+ if (typeof v !== "string") {
9700
+ throw new MissingTranslationError(
9701
+ field,
9702
+ [locale],
9703
+ `Field "${field}": locale "${locale}" must be a string, got ${typeof v}.`
9704
+ );
9705
+ }
9706
+ }
9707
+ const { required } = options;
9708
+ if (required === "all") {
9709
+ const missing = options.languages.filter(
9710
+ (lang) => !(lang in map) || map[lang] === ""
9711
+ );
9712
+ if (missing.length > 0) {
9713
+ throw new MissingTranslationError(
9714
+ field,
9715
+ missing,
9716
+ `Field "${field}" requires all declared languages. Missing: ${missing.join(", ")}.`
9717
+ );
9718
+ }
9719
+ } else if (required === "any") {
9720
+ const present = options.languages.some(
9721
+ (lang) => lang in map && map[lang] !== ""
9722
+ );
9723
+ if (!present) {
9724
+ throw new MissingTranslationError(
9725
+ field,
9726
+ options.languages,
9727
+ `Field "${field}" requires at least one declared language. None present.`
9728
+ );
9729
+ }
9730
+ } else {
9731
+ const requiredList = required;
9732
+ const missing = requiredList.filter(
9733
+ (lang) => !(lang in map) || map[lang] === ""
9734
+ );
9735
+ if (missing.length > 0) {
9736
+ throw new MissingTranslationError(
9737
+ field,
9738
+ missing,
9739
+ `Field "${field}" requires: ${requiredList.join(", ")}. Missing: ${missing.join(", ")}.`
9740
+ );
9741
+ }
9742
+ }
9743
+ }
9744
+ function resolveI18nText(value, locale, fallback, field) {
9745
+ if (locale === "raw") {
9746
+ return value;
9747
+ }
9748
+ if (!locale) {
9749
+ throw new LocaleNotSpecifiedError(field ?? "<unknown>");
9750
+ }
9751
+ if (value[locale] !== void 0 && value[locale] !== "") {
9752
+ return value[locale];
9753
+ }
9754
+ const chain = Array.isArray(fallback) ? fallback : fallback ? [fallback] : [];
9755
+ for (const fb of chain) {
9756
+ if (fb === "any") {
9757
+ const any = Object.values(value).find((v) => v !== "");
9758
+ if (any !== void 0) return any;
9759
+ } else if (value[fb] !== void 0 && value[fb] !== "") {
9760
+ return value[fb];
9761
+ }
9762
+ }
9763
+ throw new LocaleNotSpecifiedError(
9764
+ field ?? "<unknown>",
9765
+ `No translation available for locale "${locale}"` + (chain.length > 0 ? ` or fallback chain [${chain.join(", ")}]` : "") + "."
9766
+ );
9767
+ }
9768
+ function getAtPath(obj, path) {
9769
+ const arrayIdx = path.indexOf("[].");
9770
+ if (arrayIdx !== -1) {
9771
+ const arrayKey = path.slice(0, arrayIdx);
9772
+ const restPath = path.slice(arrayIdx + 3);
9773
+ const arr = obj[arrayKey];
9774
+ if (!Array.isArray(arr)) return [];
9775
+ return arr.flatMap((item) => {
9776
+ if (!item || typeof item !== "object" || Array.isArray(item)) return [];
9777
+ return getAtPath(item, restPath);
9778
+ });
9779
+ }
9780
+ const dotIdx = path.indexOf(".");
9781
+ if (dotIdx !== -1) {
9782
+ const head = path.slice(0, dotIdx);
9783
+ const rest = path.slice(dotIdx + 1);
9784
+ const nested = obj[head];
9785
+ if (!nested || typeof nested !== "object" || Array.isArray(nested)) return [];
9786
+ return getAtPath(nested, rest);
9787
+ }
9788
+ const val = obj[path];
9789
+ return val !== void 0 ? [val] : [];
9790
+ }
9791
+ function setAtPathInPlace(obj, path, value) {
9792
+ const dotIdx = path.indexOf(".");
9793
+ if (dotIdx !== -1) {
9794
+ const head = path.slice(0, dotIdx);
9795
+ const rest = path.slice(dotIdx + 1);
9796
+ const nested = obj[head];
9797
+ if (!nested || typeof nested !== "object" || Array.isArray(nested)) return;
9798
+ setAtPathInPlace(nested, rest, value);
9799
+ return;
9800
+ }
9801
+ obj[path] = value;
9802
+ }
9803
+ function applyAtPath(obj, path, locale, fallback) {
9804
+ const arrayIdx = path.indexOf("[].");
9805
+ if (arrayIdx !== -1) {
9806
+ const arrayKey = path.slice(0, arrayIdx);
9807
+ const restPath = path.slice(arrayIdx + 3);
9808
+ const arr = obj[arrayKey];
9809
+ if (!Array.isArray(arr)) return obj;
9810
+ return {
9811
+ ...obj,
9812
+ [arrayKey]: arr.map((item) => {
9813
+ if (!item || typeof item !== "object" || Array.isArray(item)) return item;
9814
+ return applyAtPath(item, restPath, locale, fallback);
9815
+ })
9816
+ };
9817
+ }
9818
+ const dotIdx = path.indexOf(".");
9819
+ if (dotIdx !== -1) {
9820
+ const head = path.slice(0, dotIdx);
9821
+ const rest = path.slice(dotIdx + 1);
9822
+ const nested = obj[head];
9823
+ if (!nested || typeof nested !== "object" || Array.isArray(nested)) return obj;
9824
+ return {
9825
+ ...obj,
9826
+ [head]: applyAtPath(nested, rest, locale, fallback)
9827
+ };
9828
+ }
9829
+ const raw = obj[path];
9830
+ if (raw === void 0 || raw === null) return obj;
9831
+ if (typeof raw !== "object" || Array.isArray(raw)) return obj;
9832
+ return {
9833
+ ...obj,
9834
+ [path]: resolveI18nText(raw, locale, fallback, path)
9835
+ };
9836
+ }
9837
+ function applyI18nLocale(record, i18nFields, locale, fallback) {
9838
+ const fieldNames = Object.keys(i18nFields);
9839
+ if (fieldNames.length === 0) return record;
9840
+ let result = record;
9841
+ for (const field of fieldNames) {
9842
+ result = applyAtPath(result, field, locale, fallback);
9843
+ }
9844
+ return result;
9845
+ }
9846
+
9680
9847
  // src/i18n/strategy.ts
9681
9848
  function notEnabled(op) {
9682
9849
  return new Error(
@@ -10028,7 +10195,7 @@ var Query = class _Query {
10028
10195
  /**
10029
10196
  * @internal — clone this Query with a declared-predicate map
10030
10197
  * attached. Used by the materialized-view registry to enable
10031
- * `.wherePredicate(name, ctx?)` for the MV's query callback (#153).
10198
+ * `.wherePredicate(name, ctx?)` for the MV's query callback.
10032
10199
  * Consumers don't call this directly.
10033
10200
  */
10034
10201
  _withPredicates(predicates) {
@@ -10041,7 +10208,7 @@ var Query = class _Query {
10041
10208
  );
10042
10209
  }
10043
10210
  /**
10044
- * Filter by a registered deterministic predicate (#153). Requires
10211
+ * Filter by a registered deterministic predicate. Requires
10045
10212
  * the Query to have been augmented with a predicates map (typically
10046
10213
  * via the materialized-view registry — bare Queries constructed
10047
10214
  * outside an MV throw on `.wherePredicate()`).
@@ -11695,7 +11862,7 @@ var NO_BLOBS = {
11695
11862
  init_errors();
11696
11863
  init_ulid();
11697
11864
  var TxContext = class {
11698
- /** Stable id for this transaction; shared by all writes it performs (#230). */
11865
+ /** Stable id for this transaction; shared by all writes it performs. */
11699
11866
  txId = generateULID();
11700
11867
  /** @internal */
11701
11868
  _ops = [];
@@ -11705,7 +11872,7 @@ var TxContext = class {
11705
11872
  * restore prior state via `revertExecuted`. Side-effect writes (e.g.
11706
11873
  * recursive derivation outputs fired inside `Collection.put`) are
11707
11874
  * appended here in execution order so they roll back alongside the
11708
- * main staged ops (#133).
11875
+ * main staged ops.
11709
11876
  */
11710
11877
  _executed = [];
11711
11878
  /** @internal */
@@ -12027,7 +12194,7 @@ async function resolveStaleOnRead(accessor, outputCollection, id) {
12027
12194
  }
12028
12195
  if (out.kind === "array") {
12029
12196
  console.warn(
12030
- `[derivation] unexpected array-shape output "${key}" in lazy resolve path; array-shape derivations require lifecycle: "eager" (#200 slice 1).`
12197
+ `[derivation] unexpected array-shape output "${key}" in lazy resolve path; array-shape derivations require lifecycle: "eager".`
12031
12198
  );
12032
12199
  continue;
12033
12200
  }
@@ -12065,6 +12232,7 @@ var Collection = class {
12065
12232
  schemaUpdateGate;
12066
12233
  schemaFence;
12067
12234
  writeHooks;
12235
+ subsystemBus;
12068
12236
  activeTxId;
12069
12237
  getDEK;
12070
12238
  onDirty;
@@ -12253,42 +12421,14 @@ var Collection = class {
12253
12421
  syncAdapter;
12254
12422
  /** — consent-audit hook, no-op when no scope is active. */
12255
12423
  onAccess;
12256
- /**
12257
- * accounting-period write guard. Called BEFORE any
12258
- * adapter write with:
12259
- * - `existing` — the prior envelope's `_ts` and decrypted record
12260
- * (or `null` if no prior envelope exists)
12261
- * - `incoming` — the record being written (or `null` for delete)
12262
- *
12263
- * Throws `PeriodClosedError` if either side falls inside a closed
12264
- * period. Installed by Vault; no-op when no period has been closed.
12265
- * Async so the Vault can lazy-load the period list from the
12266
- * adapter on first use.
12267
- */
12268
- periodGuard;
12269
- /**
12270
- * Optional back-reference to the owning vault's guard registry + a
12271
- * read-only vault facade. When present, `Collection.put` and
12272
- * `Collection.delete` consult the registry for guards declared
12273
- * against this collection and run their `check` + `frozenFields`
12274
- * before the adapter write. Absent in unit tests that construct
12275
- * a Collection directly; production code always sets it via
12276
- * `Vault.collection()`.
12277
- *
12278
- * Typed structurally rather than as `Vault` to avoid a circular
12279
- * import (mirrors the `refEnforcer` / `joinResolver` pattern).
12280
- */
12281
- guardSource;
12282
12424
  /**
12283
12425
  * Vault-internal hook for derivation dispatch. When set,
12284
12426
  * `Collection.put` consults the registry after the source-write
12285
12427
  * commits and writes derived outputs through `getCollection(name).put`.
12286
- * Same structural-interface pattern as `guardSource` to avoid a
12287
- * circular Vault import.
12288
12428
  */
12289
12429
  derivationSource;
12290
12430
  /**
12291
- * Vault-internal hook for materialized-view dispatch (#143/#150).
12431
+ * Vault-internal hook for materialized-view dispatch.
12292
12432
  * Parallel to `derivationSource` — when set, `Collection.put` fires
12293
12433
  * `MaterializedViewRegistry.onSourceWrite` after the source-write
12294
12434
  * commits + after `dispatchDerivations` has run.
@@ -12344,6 +12484,7 @@ var Collection = class {
12344
12484
  this.schemaUpdateGate = opts.schemaUpdateGate;
12345
12485
  this.schemaFence = opts.schemaFence;
12346
12486
  this.writeHooks = opts.writeHooks;
12487
+ this.subsystemBus = opts.subsystemBus;
12347
12488
  this.activeTxId = opts.activeTxId;
12348
12489
  this.blobStrategy = opts.blobStrategy ?? NO_BLOBS;
12349
12490
  this.aggregateStrategy = opts.aggregateStrategy ?? NO_AGGREGATE;
@@ -12368,8 +12509,6 @@ var Collection = class {
12368
12509
  this.crdtMode = opts.crdt;
12369
12510
  this.syncAdapter = opts.syncAdapter;
12370
12511
  this.onAccess = opts.onAccess;
12371
- this.periodGuard = opts.periodGuard;
12372
- this.guardSource = opts.guardSource;
12373
12512
  this.derivationSource = opts.derivationSource;
12374
12513
  this.materializedViewSource = opts.materializedViewSource;
12375
12514
  this.tiers = opts.tiers && opts.tiers.length > 0 ? new Set(opts.tiers) : null;
@@ -12571,21 +12710,23 @@ var Collection = class {
12571
12710
  }
12572
12711
  /**
12573
12712
  * Create or update a record. Runs inside the hub's write-queue tracker
12574
- * (#227) so `hub.writeQueue.pending` reflects this write.
12713
+ * so `hub.writeQueue.pending` reflects this write.
12575
12714
  *
12576
12715
  * @param id Record identifier.
12577
12716
  * @param record The record body (validated by the collection's schema
12578
12717
  * if one was attached at `vault.collection(...)` time).
12579
12718
  * @param options Optional metadata for audit + import workflows.
12580
12719
  * `reason` is stamped onto the resulting ledger entry
12581
- * (see #1) so audit consumers can filter via
12720
+ * so audit consumers can filter via
12582
12721
  * `entries.filter(e => e.reason?.startsWith('import:'))`.
12583
12722
  */
12584
12723
  async put(id, record, options) {
12585
12724
  await this.schemaUpdateGate?.assertWritable();
12586
12725
  await this.schemaFence?.assertWritable(this.name);
12726
+ const hooksActive = this.#hooksActive();
12727
+ const busAfterPut = (this.subsystemBus?.hasHandlers("afterPut") ?? false) && !(this.subsystemBus?.dispatching ?? false);
12587
12728
  let event;
12588
- if (this.#hooksActive()) {
12729
+ if (hooksActive || busAfterPut) {
12589
12730
  const prior = await this.#priorForHook(id);
12590
12731
  event = {
12591
12732
  op: prior.record === null ? "create" : "update",
@@ -12600,23 +12741,26 @@ var Collection = class {
12600
12741
  baseVersion: prior.version,
12601
12742
  version: prior.version + 1
12602
12743
  };
12603
- await this.writeHooks.runBefore(event);
12744
+ if (hooksActive) await this.writeHooks.runBefore(event);
12604
12745
  }
12605
12746
  if (this.writeQueue) await this.writeQueue.track(() => this.putInternal(id, record, options));
12606
12747
  else await this.putInternal(id, record, options);
12607
- if (event) await this.writeHooks.runAfter(event);
12748
+ if (event) {
12749
+ if (hooksActive) await this.writeHooks.runAfter(event);
12750
+ if (busAfterPut) await this.subsystemBus.dispatch("afterPut", event);
12751
+ }
12608
12752
  }
12609
- /** @internal #230 — true when hooks should fire for this write (handlers exist, not re-entrant). */
12753
+ /** @internal — true when hooks should fire for this write (handlers exist, not re-entrant). */
12610
12754
  #hooksActive() {
12611
12755
  return this.writeHooks !== void 0 && this.writeHooks.hasHandlers && !this.writeHooks.suppressed;
12612
12756
  }
12613
12757
  /**
12614
- * @internal #230/#228c — resolve the prior record for a hook's `before` and
12758
+ * @internal — resolve the prior record for a hook's `before` and
12615
12759
  * its version. Critically, this uses the SAME basis `putInternal` writes from
12616
12760
  * (the in-memory cache in eager mode; lru-then-adapter in lazy) — NOT a fresh
12617
12761
  * store read — so `baseVersion`/`version` match the version actually written.
12618
12762
  * A separate store read would diverge once another tab has advanced the shared
12619
- * store past this tab's cache, breaking #228c conflict detection.
12763
+ * store past this tab's cache, breaking cross-tab conflict detection.
12620
12764
  */
12621
12765
  async #priorForHook(id) {
12622
12766
  if (this.lazy && this.lru) {
@@ -12638,52 +12782,28 @@ var Collection = class {
12638
12782
  if (!hasWritePermission(this.keyring, this.name)) {
12639
12783
  throw new ReadOnlyError();
12640
12784
  }
12641
- if (this.guardSource) {
12642
- const registry = this.guardSource.registry();
12643
- const guards = registry.guardsFor(this.name);
12644
- if (guards.length > 0) {
12645
- const existingEnv = await this.adapter.get(this.vault, this.name, id);
12646
- let existingRecord = null;
12647
- if (existingEnv) {
12648
- try {
12649
- existingRecord = await this.decryptRecord(existingEnv, { skipValidation: true });
12650
- } catch {
12651
- existingRecord = null;
12652
- }
12653
- }
12654
- const incomingRecord = record;
12655
- const ctx = {
12656
- existing: existingRecord,
12657
- vault: this.guardSource.readOnlyVault(),
12658
- userId: this.keyring.userId,
12659
- role: this.keyring.role
12660
- };
12661
- if (registry.isAmendmentActive()) {
12662
- const vBefore = existingEnv?._v ?? 0;
12663
- registry.collectChange(this.name, id, existingRecord, incomingRecord, vBefore, vBefore + 1);
12664
- } else {
12665
- await registry.runChecks(this.name, incomingRecord, ctx);
12666
- const { GuardExecutor: GuardExecutor2 } = await Promise.resolve().then(() => (init_executor(), executor_exports));
12667
- for (const g of guards) {
12668
- await GuardExecutor2.checkFrozenFields(g, id, existingRecord, incomingRecord);
12669
- }
12670
- }
12671
- }
12672
- }
12673
- if (this.periodGuard !== void 0) {
12785
+ if (this.subsystemBus?.hasGateHandlers("beforePut")) {
12674
12786
  const existingEnv = await this.adapter.get(this.vault, this.name, id);
12675
- let priorRecord = null;
12787
+ let existingRecord = null;
12676
12788
  if (existingEnv) {
12677
12789
  try {
12678
- priorRecord = await this.decryptRecord(existingEnv, { skipValidation: true });
12790
+ existingRecord = await this.decryptRecord(existingEnv, { skipValidation: true });
12679
12791
  } catch {
12680
- priorRecord = null;
12792
+ existingRecord = null;
12681
12793
  }
12682
12794
  }
12683
- await this.periodGuard(
12684
- existingEnv ? { ts: existingEnv._ts, record: priorRecord } : null,
12685
- record
12686
- );
12795
+ await this.subsystemBus.dispatchGate("beforePut", {
12796
+ op: existingEnv ? "update" : "create",
12797
+ vault: this.vault,
12798
+ collection: this.name,
12799
+ docId: id,
12800
+ incoming: record,
12801
+ existing: existingRecord,
12802
+ existingVersion: existingEnv?._v ?? 0,
12803
+ existingTs: existingEnv?._ts,
12804
+ userId: this.keyring.userId,
12805
+ role: this.keyring.role
12806
+ });
12687
12807
  }
12688
12808
  if (this.schema !== void 0) {
12689
12809
  record = await validateSchemaInput(this.schema, record, `put(${id})`);
@@ -12692,7 +12812,9 @@ var Collection = class {
12692
12812
  const obj = record;
12693
12813
  for (const [field, descriptor] of Object.entries(this.i18nFields)) {
12694
12814
  if (!descriptor.options.autoTranslate) continue;
12695
- const value = obj[field];
12815
+ const leafValues = getAtPath(obj, field);
12816
+ if (leafValues.length !== 1) continue;
12817
+ const value = leafValues[0];
12696
12818
  if (!value || typeof value !== "object" || Array.isArray(value)) continue;
12697
12819
  const map = value;
12698
12820
  const { languages, required } = descriptor.options;
@@ -12716,8 +12838,7 @@ var Collection = class {
12716
12838
  this.name
12717
12839
  );
12718
12840
  }
12719
- ;
12720
- record[field] = translated;
12841
+ setAtPathInPlace(obj, field, translated);
12721
12842
  }
12722
12843
  }
12723
12844
  if (this.i18nPutValidator !== void 0) {
@@ -12869,7 +12990,7 @@ var Collection = class {
12869
12990
  * Fire registered MV strategies whose dependency set includes this
12870
12991
  * collection. Eager-mode MVs re-materialize inline via
12871
12992
  * `MaterializedViewExecutor.refresh`; lazy / manual modes are
12872
- * no-ops in the foundation (subtask #150) wired in #151.
12993
+ * no-ops in the foundation; wired in the lazy-mode implementation.
12873
12994
  *
12874
12995
  * Skips entirely when the record being written is itself an
12875
12996
  * MV-emitted row (carries `_materializedFrom`) — defensive guard
@@ -13013,13 +13134,15 @@ var Collection = class {
13013
13134
  }
13014
13135
  /**
13015
13136
  * Delete a record by ID. Runs inside the hub's write-queue tracker
13016
- * (#227) so `hub.writeQueue.pending` reflects this write.
13137
+ * so `hub.writeQueue.pending` reflects this write.
13017
13138
  */
13018
13139
  async delete(id) {
13019
13140
  await this.schemaUpdateGate?.assertWritable();
13020
13141
  await this.schemaFence?.assertWritable(this.name);
13142
+ const hooksActive = this.#hooksActive();
13143
+ const busAfterDelete = (this.subsystemBus?.hasHandlers("afterDelete") ?? false) && !(this.subsystemBus?.dispatching ?? false);
13021
13144
  let event;
13022
- if (this.#hooksActive()) {
13145
+ if (hooksActive || busAfterDelete) {
13023
13146
  const prior = await this.#priorForHook(id);
13024
13147
  event = {
13025
13148
  op: "delete",
@@ -13034,14 +13157,17 @@ var Collection = class {
13034
13157
  baseVersion: prior.version,
13035
13158
  version: prior.version + 1
13036
13159
  };
13037
- await this.writeHooks.runBefore(event);
13160
+ if (hooksActive) await this.writeHooks.runBefore(event);
13038
13161
  }
13039
13162
  if (this.writeQueue) await this.writeQueue.track(() => this.deleteInternal(id));
13040
13163
  else await this.deleteInternal(id);
13041
- if (event) await this.writeHooks.runAfter(event);
13164
+ if (event) {
13165
+ if (hooksActive) await this.writeHooks.runAfter(event);
13166
+ if (busAfterDelete) await this.subsystemBus.dispatch("afterDelete", event);
13167
+ }
13042
13168
  }
13043
13169
  /**
13044
- * @internal #232 — bulk-rewrite every record through a cutover transform.
13170
+ * @internal — bulk-rewrite every record through a cutover transform.
13045
13171
  * Raw adapter path (bypasses the write gate + guards — the transform is
13046
13172
  * trusted and runs only during the `migrating` phase). Bumps each
13047
13173
  * record's `_v` and appends a ledger `op:'migration'` entry.
@@ -13080,8 +13206,7 @@ var Collection = class {
13080
13206
  }
13081
13207
  /**
13082
13208
  * @internal — system-internal delete that bypasses user-facing
13083
- * delete hooks (`onDelete`, accounting-period guard, FK ref
13084
- * enforcer). Used by derivation tombstones (#144) and MV refresh
13209
+ * delete hooks (`onDelete`, FK ref enforcer). Used by derivation tombstones and MV refresh
13085
13210
  * (Dim 14 v2) — system housekeeping shouldn't trip user invariants
13086
13211
  * registered against the output collection. The ledger entry and
13087
13212
  * history snapshot still fire so backup integrity and time-travel
@@ -13093,7 +13218,7 @@ var Collection = class {
13093
13218
  *
13094
13219
  * When a `txCtx` is supplied, the prior envelope is captured and
13095
13220
  * pushed onto `txCtx._executed` BEFORE the delete fires — mirrors
13096
- * the #133 rollback hardening for puts. Callers outside a
13221
+ * the rollback hardening for puts. Callers outside a
13097
13222
  * multi-record transaction pass `null` and skip the tracking.
13098
13223
  *
13099
13224
  * Amendment composition: if `_internalDelete` runs while a vault's
@@ -13136,58 +13261,27 @@ var Collection = class {
13136
13261
  if (!hasWritePermission(this.keyring, this.name)) {
13137
13262
  throw new ReadOnlyError();
13138
13263
  }
13139
- if (this.guardSource) {
13140
- const registry = this.guardSource.registry();
13141
- const guards = registry.guardsFor(this.name);
13142
- if (guards.length > 0) {
13143
- const existingEnv = await this.adapter.get(this.vault, this.name, id);
13144
- if (existingEnv) {
13145
- let existingRecord = null;
13146
- try {
13147
- existingRecord = await this.decryptRecord(existingEnv, { skipValidation: true });
13148
- } catch {
13149
- existingRecord = null;
13150
- }
13151
- if (registry.isAmendmentActive()) {
13152
- const vBefore = existingEnv._v;
13153
- registry.collectChange(
13154
- this.name,
13155
- id,
13156
- existingRecord,
13157
- null,
13158
- vBefore,
13159
- vBefore
13160
- );
13161
- } else if (!internal) {
13162
- const ctx = {
13163
- existing: existingRecord,
13164
- vault: this.guardSource.readOnlyVault(),
13165
- userId: this.keyring.userId,
13166
- role: this.keyring.role
13167
- };
13168
- await registry.runOnDelete(
13169
- this.name,
13170
- existingRecord ?? {},
13171
- ctx
13172
- );
13173
- }
13174
- }
13175
- }
13176
- }
13177
- if (!internal && this.periodGuard !== void 0) {
13264
+ if (this.subsystemBus?.hasGateHandlers("beforeDelete")) {
13178
13265
  const existingEnv = await this.adapter.get(this.vault, this.name, id);
13179
- let priorRecord = null;
13180
13266
  if (existingEnv) {
13267
+ let existingRecord = null;
13181
13268
  try {
13182
- priorRecord = await this.decryptRecord(existingEnv, { skipValidation: true });
13269
+ existingRecord = await this.decryptRecord(existingEnv, { skipValidation: true });
13183
13270
  } catch {
13184
- priorRecord = null;
13271
+ existingRecord = null;
13185
13272
  }
13273
+ await this.subsystemBus.dispatchGate("beforeDelete", {
13274
+ vault: this.vault,
13275
+ collection: this.name,
13276
+ docId: id,
13277
+ existing: existingRecord,
13278
+ existingVersion: existingEnv._v,
13279
+ existingTs: existingEnv._ts,
13280
+ internal,
13281
+ userId: this.keyring.userId,
13282
+ role: this.keyring.role
13283
+ });
13186
13284
  }
13187
- await this.periodGuard(
13188
- existingEnv ? { ts: existingEnv._ts, record: priorRecord } : null,
13189
- null
13190
- );
13191
13285
  }
13192
13286
  if (!internal && this.refEnforcer !== void 0) {
13193
13287
  await this.refEnforcer.enforceRefsOnDelete(this.name, id);
@@ -13248,7 +13342,7 @@ var Collection = class {
13248
13342
  }
13249
13343
  /**
13250
13344
  * Cascade deletes of array-shape derived rows when a source row is
13251
- * deleted (#200). Reads each registered strategy's fanout sidecar
13345
+ * deleted. Reads each registered strategy's fanout sidecar
13252
13346
  * for this source id, deletes every listed derived row, then
13253
13347
  * deletes the sidecar itself.
13254
13348
  *
@@ -13287,8 +13381,8 @@ var Collection = class {
13287
13381
  }
13288
13382
  }
13289
13383
  /**
13290
- * Mirror of {@link dispatchMaterializedViews} for the delete path
13291
- * (#181). No record content is available (it's gone), so the
13384
+ * Mirror of {@link dispatchMaterializedViews} for the delete path.
13385
+ * No record content is available (it's gone), so the
13292
13386
  * `_materializedFrom` skip used by the put-side dispatch doesn't
13293
13387
  * apply here — instead, the recursion guard is the `internal` gate
13294
13388
  * at the `_doDelete` call site above.
@@ -13818,7 +13912,7 @@ var Collection = class {
13818
13912
  * .aggregate({ total: sum('amount'), n: count() })
13819
13913
  * ```
13820
13914
  *
13821
- * **Lazy-MV gap (#157):** `scan()` is synchronous-build and does
13915
+ * **Lazy-MV gap:** `scan()` is synchronous-build and does
13822
13916
  * NOT trigger lazy materialized-view resolve-on-read. For lazy
13823
13917
  * MVs, call `list()` (which DOES resolve) or `vault.refreshView(name)`
13824
13918
  * before scanning. Same shape as the `query()` limitation.
@@ -13899,7 +13993,7 @@ var Collection = class {
13899
13993
  this.indexes?.upsert(id, record, previous ? previous.record : null);
13900
13994
  }
13901
13995
  /**
13902
- * #228b — apply a peer tab's committed write to THIS tab's in-memory view:
13996
+ * Apply a peer tab's committed write to THIS tab's in-memory view:
13903
13997
  * re-read the (already-persisted) envelope from the shared store + refresh
13904
13998
  * cache/indexes, then emit a `change` event so reactive consumers re-render.
13905
13999
  * Never writes to the store and never fires write hooks, so it cannot loop.
@@ -13908,7 +14002,7 @@ var Collection = class {
13908
14002
  await this._invalidateCacheEntry(id);
13909
14003
  this.emitter.emit("change", { vault: this.vault, collection: this.name, id, action });
13910
14004
  }
13911
- /** @internal #228c — the current in-memory record without a store read (for conflict capture). */
14005
+ /** @internal — the current in-memory record without a store read (for conflict capture). */
13912
14006
  _peekCached(id) {
13913
14007
  const entry = this.lazy && this.lru ? this.lru.get(id) : this.cache.get(id);
13914
14008
  return entry ? entry.record : null;
@@ -14959,7 +15053,7 @@ var OverlayedCollection = class {
14959
15053
  // error pointing at the relevant issue — so consumers don't hit a
14960
15054
  // cryptic `undefined is not a function` runtime crash.
14961
15055
  //
14962
- // Closes niwat-review of PR #160.
15056
+ // Throw-stubs so consumers get actionable errors rather than cryptic crashes.
14963
15057
  /** @throws — chainable Query<T> over a virtual collection is deferred. */
14964
15058
  query() {
14965
15059
  throw new Error(
@@ -16270,23 +16364,23 @@ var Vault = class {
16270
16364
  * `null` for vaults that never register any guard strategy. The
16271
16365
  * runtime class is dynamic-imported on demand so consumers that
16272
16366
  * never use guards don't pull `GuardRegistry`/`GuardExecutor` into
16273
- * their bundle (#130).
16367
+ * their bundle.
16274
16368
  */
16275
16369
  guardRegistry = null;
16276
16370
  /**
16277
16371
  * Per-vault derivation registry. Same lazy-load contract as
16278
16372
  * `guardRegistry` — `null` until `_initDerivations()` runs with at
16279
- * least one strategy handle. See #130 for the bundle motivation.
16373
+ * least one strategy handle.
16280
16374
  */
16281
16375
  derivationRegistry = null;
16282
16376
  /**
16283
- * Per-vault materialized-view registry (#143/#150). Same lazy-load
16377
+ * Per-vault materialized-view registry. Same lazy-load
16284
16378
  * contract as `derivationRegistry` — `null` until
16285
16379
  * `_initMaterializedViews()` runs with at least one MV handle.
16286
16380
  */
16287
16381
  materializedViewRegistry = null;
16288
16382
  /**
16289
- * Per-vault overlay registry (#154). Same lazy-load contract as
16383
+ * Per-vault overlay registry. Same lazy-load contract as
16290
16384
  * `materializedViewRegistry` — `null` until `_initOverlayedViews()`
16291
16385
  * runs with at least one handle.
16292
16386
  */
@@ -16307,7 +16401,7 @@ var Vault = class {
16307
16401
  * target this vault session's keyringId. There is no method to write
16308
16402
  * another principal's envelope (own-only write rule, structural).
16309
16403
  * - Read-anyone: `get(keyringId)`, `list()` — read other principals'
16310
- * envelopes, subject to the `view-team-profiles` policy gate (#22).
16404
+ * envelopes, subject to the `view-team-profiles` policy gate.
16311
16405
  * - Reactive: `subscribe(id, cb)`, `live(id)` — fire on local writes.
16312
16406
  *
16313
16407
  * @see docs/superpowers/specs/2026-05-05-user-envelope-design.md
@@ -16327,12 +16421,12 @@ var Vault = class {
16327
16421
  */
16328
16422
  reloadKeyring;
16329
16423
  collectionCache = /* @__PURE__ */ new Map();
16330
- /** #232 — vault-level schema cutover fence/controller. */
16424
+ /** Vault-level schema cutover fence/controller. */
16331
16425
  schemaFence;
16332
- /** #232 — per-client heartbeat/watcher; started lazily on cutover registration. */
16426
+ /** Per-client heartbeat/watcher; started lazily on cutover registration. */
16333
16427
  #fenceWatcher;
16334
16428
  #fenceCoordinationStarted = false;
16335
- /** #229 — per-collection registered schema-update strategy names. */
16429
+ /** Per-collection registered schema-update strategy names. */
16336
16430
  #schemaUpdateNames = /* @__PURE__ */ new Map();
16337
16431
  /**
16338
16432
  * per-collection `blobFields` retention/TTL config.
@@ -16406,8 +16500,7 @@ var Vault = class {
16406
16500
  * Cache of closed/opened accounting periods.
16407
16501
  * Populated on first `closePeriod` / `openPeriod` / `listPeriods` /
16408
16502
  * per-collection write call. Kept in memory as an ordered list (by
16409
- * `closedAt`) so the `periodGuard` hook runs synchronously against
16410
- * each collection's put/delete path.
16503
+ * `closedAt`) so period checks run fast when the gate bus fires.
16411
16504
  *
16412
16505
  * Sentinel `null` means "not yet loaded" — the first consumer
16413
16506
  * triggers a one-time `loadPeriods()` pass. Every subsequent
@@ -16602,6 +16695,7 @@ var Vault = class {
16602
16695
  emitter: this.emitter,
16603
16696
  writeQueue: this.noydb._writeQueueTracker,
16604
16697
  writeHooks: this.noydb._writeHooks,
16698
+ subsystemBus: this.noydb._subsystemBus,
16605
16699
  activeTxId: () => this.noydb._activeTxContextOrNull?.txId ?? null,
16606
16700
  schemaUpdateGate,
16607
16701
  schemaFence: this.schemaFence,
@@ -16624,18 +16718,12 @@ var Vault = class {
16624
16718
  defaultLocale: this.locale,
16625
16719
  onRegisterConflictResolver: this.onRegisterConflictResolver,
16626
16720
  onAccess: (op, id) => this._logConsent(op, collectionName, id),
16627
- periodGuard: (existing, incoming) => this._assertTsWritable(existing, incoming),
16628
- // Guard / derivation sources are only wired when the
16629
- // corresponding registry has been initialised. Vaults without
16630
- // guards/derivations skip this entirely so `Collection.put`'s
16631
- // `if (this.guardSource)` / `if (this.derivationSource)`
16632
- // branches no-op without ever touching the subsystem code.
16633
- ...this.guardRegistry !== null ? {
16634
- guardSource: {
16635
- registry: () => this.guardRegistry,
16636
- readOnlyVault: () => this._ensureReadOnlyFacade()
16637
- }
16638
- } : {},
16721
+ // Derivation source is only wired when the corresponding registry
16722
+ // has been initialised. Guard source was removed in Track A slice 3b
16723
+ // guards now run via the gate bus in Noydb.#registerGuardGate.
16724
+ // Vaults without derivations skip this so `Collection.put`'s
16725
+ // `if (this.derivationSource)` branch no-ops without touching the
16726
+ // derivation subsystem code.
16639
16727
  ...this.derivationRegistry !== null ? {
16640
16728
  derivationSource: {
16641
16729
  registry: () => this.derivationRegistry,
@@ -16725,7 +16813,7 @@ var Vault = class {
16725
16813
  await Promise.allSettled(pending);
16726
16814
  }
16727
16815
  /**
16728
- * Run a coordinated schema cutover (#232). Drains pending writes, waits
16816
+ * Run a coordinated schema cutover. Drains pending writes, waits
16729
16817
  * for the active client set to quiesce (the ack-barrier), applies every
16730
16818
  * pending collection transform in bulk, bumps the vault schema generation,
16731
16819
  * and clears the fence. Returns the count of collections migrated.
@@ -16743,9 +16831,9 @@ var Vault = class {
16743
16831
  await coll._applyCutoverTransform(transform);
16744
16832
  }
16745
16833
  /**
16746
- * #228b — refresh a loaded collection's view of one document from a peer
16834
+ * Refresh a loaded collection's view of one document from a peer
16747
16835
  * tab's broadcast. No-op when the collection isn't loaded in this tab
16748
- * (it will read fresh on next open). Mirrors #runCutoverTransform's guard.
16836
+ * (it will read fresh on next open). Mirrors `#runCutoverTransform`'s guard.
16749
16837
  */
16750
16838
  async _applyRemoteWrite(collectionName, docId, action) {
16751
16839
  const coll = this.collectionCache.get(collectionName);
@@ -16753,9 +16841,9 @@ var Vault = class {
16753
16841
  await coll._applyRemoteChange(docId, action);
16754
16842
  }
16755
16843
  /**
16756
- * #228c — for a detected conflict: capture this tab's clobbered record,
16844
+ * For a detected conflict: capture this tab's clobbered record,
16757
16845
  * read the common ancestor from history, converge the cache to the store's
16758
- * authoritative value (the (b) re-read), and return all three for the
16846
+ * authoritative value (the re-read), and return all three for the
16759
16847
  * WriteConflict payload. Returns null when the collection isn't loaded.
16760
16848
  */
16761
16849
  async _captureAndConverge(collectionName, docId, action, baseV) {
@@ -16772,15 +16860,15 @@ var Vault = class {
16772
16860
  const remote = await coll.get(docId);
16773
16861
  return { local, remote, base };
16774
16862
  }
16775
- /** Recover a stuck cutover fence (#232) — reset to normal without bumping. */
16863
+ /** Recover a stuck cutover fence — reset to normal without bumping. */
16776
16864
  async abortSchemaCutover() {
16777
16865
  await this.schemaFence.abort();
16778
16866
  }
16779
- /** Current schema-cutover fence state for this vault (#232/#233). Thin live read. */
16867
+ /** Current schema-cutover fence state for this vault. Thin live read. */
16780
16868
  async schemaFenceState() {
16781
16869
  return loadFence(this.adapter, this.name);
16782
16870
  }
16783
- /** @internal Start the per-client heartbeat + fence watcher once a cutover is registered (#232). */
16871
+ /** @internal Start the per-client heartbeat + fence watcher once a cutover is registered. */
16784
16872
  _ensureFenceCoordination() {
16785
16873
  if (this.#fenceCoordinationStarted) return;
16786
16874
  this.#fenceCoordinationStarted = true;
@@ -16816,9 +16904,11 @@ var Vault = class {
16816
16904
  if (!record || typeof record !== "object") return;
16817
16905
  const obj = record;
16818
16906
  for (const [field, descriptor] of Object.entries(i18nFields)) {
16819
- const value = obj[field];
16820
- if (value === void 0 || value === null) continue;
16821
- this.i18nStrategy.validateI18nTextValue(value, field, descriptor);
16907
+ const values = getAtPath(obj, field);
16908
+ for (const value of values) {
16909
+ if (value === void 0 || value === null) continue;
16910
+ this.i18nStrategy.validateI18nTextValue(value, field, descriptor);
16911
+ }
16822
16912
  }
16823
16913
  }
16824
16914
  /**
@@ -17453,7 +17543,7 @@ var Vault = class {
17453
17543
  * Dynamic-imports `GuardRegistry` + `ReadOnlyVaultFacade` and seeds
17454
17544
  * the registry with the supplied strategy handles. No-op when the
17455
17545
  * handles array is empty — keeps the guard subsystem out of the
17456
- * floor bundle for consumers that don't use guards (#130).
17546
+ * floor bundle for consumers that don't use guards.
17457
17547
  *
17458
17548
  * The read-only facade is eagerly instantiated here so the sync
17459
17549
  * accessor `_getReadOnlyFacade()` (called from the tx amendment
@@ -17471,10 +17561,9 @@ var Vault = class {
17471
17561
  this.readOnlyFacade = new ReadOnlyVaultFacade2(this);
17472
17562
  }
17473
17563
  /**
17474
- * @internal — Collection.put calls into this. Returns `null` for
17475
- * vaults that never registered any guard strategy. Callers MUST
17476
- * gate on null (the existing `if (this.guardSource)` branches in
17477
- * `Collection` already do this transitively).
17564
+ * @internal — The gate handler in Noydb.#registerGuardGate calls into
17565
+ * this. Returns `null` for vaults that never registered any guard
17566
+ * strategy. Callers MUST gate on null.
17478
17567
  */
17479
17568
  _getGuardRegistry() {
17480
17569
  return this.guardRegistry;
@@ -17485,7 +17574,7 @@ var Vault = class {
17485
17574
  * derivation strategies (async because `strategyHash` computation
17486
17575
  * goes through `crypto.subtle.digest`). No-op when the handles
17487
17576
  * array is empty — keeps the derivation subsystem out of the floor
17488
- * bundle for consumers that don't use derivations (#130). Throws
17577
+ * bundle for consumers that don't use derivations. Throws
17489
17578
  * `DerivationCycleError` if a cycle is detected after registration.
17490
17579
  */
17491
17580
  async _initDerivations(handles) {
@@ -17517,7 +17606,7 @@ var Vault = class {
17517
17606
  * MV spec (which invokes its `query()` once for dependency
17518
17607
  * analysis), then runs the unified cycle detection across the MV +
17519
17608
  * derivation graphs. No-op when the handles array is empty — keeps
17520
- * the MV subsystem out of the floor bundle (mirrors v1 #130).
17609
+ * the MV subsystem out of the floor bundle (mirrors the derivation lazy-import pattern).
17521
17610
  * Throws `MaterializedViewCycleError` if a cycle is detected.
17522
17611
  */
17523
17612
  async _initMaterializedViews(handles) {
@@ -17574,13 +17663,13 @@ var Vault = class {
17574
17663
  return this.overlayedViewRegistry;
17575
17664
  }
17576
17665
  /**
17577
- * Manual re-materialize for a single registered MV (#151). Useful
17666
+ * Manual re-materialize for a single registered MV. Useful
17578
17667
  * for `refresh: 'manual'` MVs (whose consumer drives refreshes
17579
17668
  * externally), for stale-bit recovery on vault re-open, and as the
17580
17669
  * explicit bulk-recompute escape hatch after a strategy change.
17581
17670
  *
17582
- * Returns `{ written, deleted, failed }`. `deleted` is always 0 in
17583
- * foundation + this sub-issue — tombstoning lands in #152.
17671
+ * Returns `{ written, deleted, failed }`. `deleted` is always 0
17672
+ * when tombstoning is not enabled.
17584
17673
  *
17585
17674
  * Throws if `name` is not a registered MV.
17586
17675
  */
@@ -17676,22 +17765,19 @@ var Vault = class {
17676
17765
  /**
17677
17766
  * @internal — exposed for `runTransaction({ amendment: true })` so
17678
17767
  * the amendment invariant runner can pass the SAME read-only vault
17679
- * facade that the per-record `Collection.put` guard hook uses
17680
- * (`guardSource.readOnlyVault()` above). Eagerly instantiated by
17681
- * `_initGuards()` so this accessor stays synchronous; returns
17682
- * `null` for vaults that never registered any guard (amendments
17683
- * require at least one guard, so the caller should never see null).
17768
+ * facade that the gate handler in Noydb.#registerGuardGate uses.
17769
+ * Eagerly instantiated by `_initGuards()` so this accessor stays
17770
+ * synchronous; returns `null` for vaults that never registered any
17771
+ * guard (amendments require at least one guard, so the caller should
17772
+ * never see null).
17684
17773
  */
17685
17774
  _getReadOnlyFacade() {
17686
17775
  return this.readOnlyFacade;
17687
17776
  }
17688
17777
  /**
17689
- * Internal lazy-allocator for the read-only facade. Used by the
17690
- * per-collection `guardSource.readOnlyVault` callback when guards
17691
- * ARE configured but `_initGuards()` raced with the first guard
17692
- * invocation (theoretically impossible — `Noydb.openVault` awaits
17693
- * `_initGuards` before returning — but we keep the defensive lazy
17694
- * path so the closure's contract stays "always returns a facade").
17778
+ * Internal lazy-allocator for the read-only facade. Used as a
17779
+ * defensive fallback; in practice `_initGuards()` eagerly
17780
+ * instantiates this, so the lazy path is a no-op.
17695
17781
  */
17696
17782
  _ensureReadOnlyFacade() {
17697
17783
  if (this.readOnlyFacade !== null) return this.readOnlyFacade;
@@ -18146,7 +18232,7 @@ var Vault = class {
18146
18232
  const all = await this._loadPeriodsCache();
18147
18233
  return all.find((p) => p.name === name) ?? null;
18148
18234
  }
18149
- /** @internal — periodGuard callback installed on every Collection. */
18235
+ /** @internal — called by the gate bus before put/delete. */
18150
18236
  async _assertTsWritable(existing, incoming) {
18151
18237
  if (existing === null && incoming === null) return;
18152
18238
  if (this.periodCache === null) {
@@ -18234,7 +18320,7 @@ var Vault = class {
18234
18320
  return dumpVaultSchema(this, opts);
18235
18321
  }
18236
18322
  /**
18237
- * Lightweight read of the vault's registered schema (#229): collections
18323
+ * Lightweight read of the vault's registered schema: collections
18238
18324
  * (+ doc counts), guards, materialized views, schema-update strategies,
18239
18325
  * and the unlocked user's grants. Cheap — one `adapter.list` per
18240
18326
  * collection, no decryption. For a full snapshot + stats use dumpSchema().
@@ -19021,6 +19107,110 @@ var WriteHookRegistry = class {
19021
19107
  }
19022
19108
  };
19023
19109
 
19110
+ // src/subsystem-bus.ts
19111
+ var SubsystemBus = class {
19112
+ #handlers = /* @__PURE__ */ new Map();
19113
+ #gateHandlers = /* @__PURE__ */ new Map();
19114
+ #depth = 0;
19115
+ /** Register a handler for an observe point. Returns an unsubscribe fn. */
19116
+ register(point, handler) {
19117
+ let arr = this.#handlers.get(point);
19118
+ if (!arr) {
19119
+ arr = [];
19120
+ this.#handlers.set(point, arr);
19121
+ }
19122
+ arr.push(handler);
19123
+ return () => {
19124
+ const a = this.#handlers.get(point);
19125
+ if (!a) return;
19126
+ const i = a.indexOf(handler);
19127
+ if (i >= 0) a.splice(i, 1);
19128
+ };
19129
+ }
19130
+ /** Cheap gate for the write path — true when any handler is registered for the point. */
19131
+ hasHandlers(point) {
19132
+ const a = this.#handlers.get(point);
19133
+ return a !== void 0 && a.length > 0;
19134
+ }
19135
+ /**
19136
+ * True while one or more dispatches are in flight. Backed by a depth counter
19137
+ * so that two concurrent async dispatches (`Promise.all([put('a'), put('b')])`
19138
+ * each captured `busAfterPut=true` at their respective put() tops while depth
19139
+ * was 0) both proceed independently — the counter stays > 0 until BOTH finish,
19140
+ * so any nested write attempted by a handler still sees `dispatching === true`
19141
+ * and is suppressed by the write-path gate in `collection.ts`
19142
+ * (`busAfterPut = hasHandlers('afterPut') && !dispatching`). Re-entrancy
19143
+ * suppression lives exclusively on that write-path gate; concurrent independent
19144
+ * dispatches must not drop each other's events.
19145
+ */
19146
+ get dispatching() {
19147
+ return this.#depth > 0;
19148
+ }
19149
+ /**
19150
+ * Dispatch in registration order, awaited. Per-handler errors are warned, not
19151
+ * thrown — an observe handler must never abort a completed write. A
19152
+ * re-entrancy guard suppresses nested firing so a handler that itself writes
19153
+ * cannot loop (same rationale as WriteHookRegistry.#suppressed).
19154
+ */
19155
+ async dispatch(point, event) {
19156
+ const a = this.#handlers.get(point);
19157
+ if (!a || a.length === 0) return;
19158
+ this.#depth++;
19159
+ try {
19160
+ for (const h of a.slice()) {
19161
+ try {
19162
+ await h(event);
19163
+ } catch (err) {
19164
+ console.warn(
19165
+ `[noy-db] subsystem observe handler failed at ${point}: ` + (err instanceof Error ? err.message : String(err))
19166
+ );
19167
+ }
19168
+ }
19169
+ } finally {
19170
+ this.#depth--;
19171
+ }
19172
+ }
19173
+ /** Register a write-gating handler. A throw from the handler ABORTS the write. Returns an unsubscribe fn. */
19174
+ registerGate(point, handler) {
19175
+ let arr = this.#gateHandlers.get(point);
19176
+ if (!arr) {
19177
+ arr = [];
19178
+ this.#gateHandlers.set(point, arr);
19179
+ }
19180
+ arr.push(handler);
19181
+ return () => {
19182
+ const a = this.#gateHandlers.get(point);
19183
+ if (!a) return;
19184
+ const i = a.indexOf(handler);
19185
+ if (i >= 0) a.splice(i, 1);
19186
+ };
19187
+ }
19188
+ /** Cheap gate for the write path — true when any gate handler is registered for the point. */
19189
+ hasGateHandlers(point) {
19190
+ const a = this.#gateHandlers.get(point);
19191
+ return a !== void 0 && a.length > 0;
19192
+ }
19193
+ /**
19194
+ * Run gate handlers in registration order, awaited. Unlike `dispatch`
19195
+ * (observe), a handler throw is NOT swallowed — it PROPAGATES, aborting the
19196
+ * write before it reaches the store. The first throw stops the remaining
19197
+ * handlers (fail-fast). This is the seam guards/periods migrate onto.
19198
+ *
19199
+ * Note: gate handlers are validators that read, not write. A gate handler
19200
+ * that writes back into the same collection would re-enter the write path
19201
+ * and re-dispatch this point; loop-suppression for that case is deferred to
19202
+ * the migration slice (contract: gate handlers must not perform writes that
19203
+ * re-trigger their own point).
19204
+ */
19205
+ async dispatchGate(point, event) {
19206
+ const a = this.#gateHandlers.get(point);
19207
+ if (!a || a.length === 0) return;
19208
+ for (const h of a.slice()) {
19209
+ await h(event);
19210
+ }
19211
+ }
19212
+ };
19213
+
19024
19214
  // src/tab-coordination.ts
19025
19215
  var TabCoordinator = class {
19026
19216
  tabId;
@@ -19336,9 +19526,9 @@ var PERSONAL_POLICY = Object.freeze({
19336
19526
  minTier: 1,
19337
19527
  enabled: true
19338
19528
  },
19339
- // rotate-recovery (#121): deliberate paper-sheet regeneration
19340
- // when the user remembers their passphrase. PERSONAL matches the
19341
- // pre-#121 low-level flow's bar — knowing the passphrase is enough.
19529
+ // rotate-recovery: deliberate paper-sheet regeneration
19530
+ // when the user remembers their passphrase. PERSONAL allows tier-1 —
19531
+ // knowing the passphrase is enough.
19342
19532
  "rotate-recovery": { minTier: 1 },
19343
19533
  "enroll-authenticator": { minTier: 1 },
19344
19534
  "remove-authenticator": { minTier: 1 },
@@ -19372,7 +19562,7 @@ var PERSONAL_POLICY = Object.freeze({
19372
19562
  minTier: 1,
19373
19563
  enabled: false
19374
19564
  },
19375
- // ─── User envelope gates (#22) ────────────────────────────────────
19565
+ // ─── User envelope gates ──────────────────────────────────────────
19376
19566
  // edit-own-profile: tier 3 floor — any active session can edit their
19377
19567
  // own profile/preferences. Tightening to require a TOTP for
19378
19568
  // profile changes is a one-line override.
@@ -19399,7 +19589,7 @@ var STRICT_POLICY = Object.freeze({
19399
19589
  minTier: 1,
19400
19590
  enabled: true
19401
19591
  },
19402
- // rotate-recovery (#121): STRICT requires an off-device factor —
19592
+ // rotate-recovery: STRICT requires an off-device factor —
19403
19593
  // rotating recovery is an off-site-trust event; a stolen unlocked
19404
19594
  // laptop must not be able to silently mint a new sheet for the
19405
19595
  // attacker. Matches the `peer-recover-user` STRICT default.
@@ -19472,7 +19662,7 @@ var STRICT_POLICY = Object.freeze({
19472
19662
  minTier: 1,
19473
19663
  enabled: false
19474
19664
  },
19475
- // ─── User envelope gates (#22) ────────────────────────────────────
19665
+ // ─── User envelope gates ──────────────────────────────────────────
19476
19666
  // STRICT: profile edits require a TOTP/email-OTP factor (typical
19477
19667
  // shared-workstation hardening — your name/avatar shouldn't change
19478
19668
  // without a fresh second-factor proof).
@@ -19583,13 +19773,14 @@ var Noydb = class {
19583
19773
  emitter = new NoydbEventEmitter();
19584
19774
  writeQueueTracker = new WriteQueueTracker();
19585
19775
  writeHooks = new WriteHookRegistry();
19776
+ subsystemBus = new SubsystemBus();
19586
19777
  clientId = generateULID();
19587
19778
  vaultCache = /* @__PURE__ */ new Map();
19588
19779
  keyringCache = /* @__PURE__ */ new Map();
19589
19780
  syncEngines = /* @__PURE__ */ new Map();
19590
19781
  /**
19591
19782
  * Per-vault active session tier — defaults to `1` after a passphrase
19592
- * unlock; tier-2 / tier-3 unlocks (issue #11) downgrade it. Used by
19783
+ * unlock; tier-2 / tier-3 unlocks downgrade it. Used by
19593
19784
  * {@link checkGate} to evaluate `gate.minTier`.
19594
19785
  */
19595
19786
  activeTier = /* @__PURE__ */ new Map();
@@ -19599,14 +19790,14 @@ var Noydb = class {
19599
19790
  */
19600
19791
  policyCache = /* @__PURE__ */ new Map();
19601
19792
  /**
19602
- * One-shot bypass for the managed-mode strong-recovery check (#195).
19793
+ * One-shot bypass for the managed-mode strong-recovery check.
19603
19794
  * Set true by {@link openVaultAndEnrollRecovery} for the duration of
19604
19795
  * the bootstrap window so the keyring can be created before the
19605
19796
  * strong recovery is enrolled. Always cleared (try/finally).
19606
19797
  * @internal
19607
19798
  */
19608
19799
  _skipNextManagedRecoveryCheck = false;
19609
- /** Per-vault tier-3 (PIN / quick-resume) state — issue #11. */
19800
+ /** Per-vault tier-3 (PIN / quick-resume) state. */
19610
19801
  quickUnlock = new QuickUnlockStore();
19611
19802
  /**
19612
19803
  * Resolved public-envelope schema. Lazily computed once from
@@ -19616,9 +19807,9 @@ var Noydb = class {
19616
19807
  publicEnvelopeSchema;
19617
19808
  closed = false;
19618
19809
  sessionTimer = null;
19619
- /** Same-device multi-tab coordinator (#228); created on `enableTabCoordination()`. */
19810
+ /** Same-device multi-tab coordinator; created on `enableTabCoordination()`. */
19620
19811
  tabCoordinator;
19621
- /** Cross-tab write relay (#228b); created on `enableTabCoordination()`. */
19812
+ /** Cross-tab write relay; created on `enableTabCoordination()`. */
19622
19813
  writeRelay;
19623
19814
  /** Per-vault policy enforcers. */
19624
19815
  policyEnforcers = /* @__PURE__ */ new Map();
@@ -19631,8 +19822,8 @@ var Noydb = class {
19631
19822
  * the same function's `finally` block. Side-effect writes triggered
19632
19823
  * during a staged op's `Collection.put` (today: eager derivation
19633
19824
  * outputs) register their pre-write envelope on `_executed` here so
19634
- * a mid-batch failure rolls them back alongside the main staged ops
19635
- * (#133). `null` outside of Phase 2.
19825
+ * a mid-batch failure rolls them back alongside the main staged ops.
19826
+ * `null` outside of Phase 2.
19636
19827
  * @internal
19637
19828
  */
19638
19829
  _activeTxContext = null;
@@ -19653,8 +19844,95 @@ var Noydb = class {
19653
19844
  if (options.sessionPolicy) {
19654
19845
  this.sessionStrategy.validateSessionPolicy(options.sessionPolicy);
19655
19846
  }
19847
+ this.#registerGuardGate();
19848
+ this.#registerPeriodGate();
19656
19849
  this.resetSessionTimer();
19657
19850
  }
19851
+ // Track A — guards migration. Registers record-lock / field-freeze / onDelete
19852
+ // / amendment-collect as gate-bus handlers (only when guards are opted in, so
19853
+ // the write path is zero-cost otherwise). Resolves the live vault's
19854
+ // GuardRegistry per dispatch. Registered BEFORE the period gate so guard
19855
+ // checks run first. The amendment branch is a side-effect (collectChange),
19856
+ // NOT a throw — and runs even for internal deletes (an amendment invariant
19857
+ // must see system housekeeping tombstones); onDelete/checks run only for
19858
+ // user (non-internal) operations.
19859
+ #registerGuardGate() {
19860
+ if (this.options.guardStrategies === void 0) return;
19861
+ this.subsystemBus.registerGate("beforePut", async (e) => {
19862
+ const v = this.vaultCache.get(e.vault);
19863
+ if (!v) return;
19864
+ const registry = v._getGuardRegistry();
19865
+ if (!registry) return;
19866
+ const guards = registry.guardsFor(e.collection);
19867
+ if (guards.length === 0) return;
19868
+ const existing = e.existing ?? null;
19869
+ const incoming = e.incoming;
19870
+ if (registry.isAmendmentActive()) {
19871
+ registry.collectChange(e.collection, e.docId, existing, incoming, e.existingVersion, e.existingVersion + 1);
19872
+ return;
19873
+ }
19874
+ const facade = v._getReadOnlyFacade();
19875
+ if (!facade) return;
19876
+ const ctx = { existing, vault: facade, userId: e.userId, role: e.role };
19877
+ await registry.runChecks(e.collection, incoming, ctx);
19878
+ const { GuardExecutor: GuardExecutor2 } = await Promise.resolve().then(() => (init_executor(), executor_exports));
19879
+ for (const g of guards) {
19880
+ await GuardExecutor2.checkFrozenFields(g, e.docId, existing, incoming);
19881
+ }
19882
+ });
19883
+ this.subsystemBus.registerGate("beforeDelete", async (e) => {
19884
+ const v = this.vaultCache.get(e.vault);
19885
+ if (!v) return;
19886
+ const registry = v._getGuardRegistry();
19887
+ if (!registry) return;
19888
+ const guards = registry.guardsFor(e.collection);
19889
+ if (guards.length === 0) return;
19890
+ const existing = e.existing ?? null;
19891
+ if (registry.isAmendmentActive()) {
19892
+ registry.collectChange(e.collection, e.docId, existing, null, e.existingVersion, e.existingVersion);
19893
+ return;
19894
+ }
19895
+ if (e.internal) return;
19896
+ const facade = v._getReadOnlyFacade();
19897
+ if (!facade) return;
19898
+ const ctx = { existing, vault: facade, userId: e.userId, role: e.role };
19899
+ await registry.runOnDelete(e.collection, existing ?? {}, ctx);
19900
+ });
19901
+ }
19902
+ /**
19903
+ * Register closed-period write guards on the subsystem bus when a
19904
+ * periodsStrategy is configured. Handlers resolve the live Vault from
19905
+ * vaultCache so they always use the up-to-date period cache.
19906
+ */
19907
+ // Track A — periods migration. Registers the closed-period write guard as a
19908
+ // gate-bus handler (only when periods is opted in, so the write path is
19909
+ // zero-cost otherwise). Each handler resolves the LIVE vault from the cache
19910
+ // per dispatch and delegates to its `_assertTsWritable`, which owns all
19911
+ // period logic. Resolving the live vault makes eviction/re-creation
19912
+ // transparent. Semantics note: if a write reaches the gate through a retained
19913
+ // collection handle whose vault has been evicted from `vaultCache` (e.g. a
19914
+ // post-revocation write on a stale handle), the period check is skipped — the
19915
+ // guard binds to the live vault, not a captured instance. Periods is a
19916
+ // write-integrity guard, not a security boundary, and a re-open reloads the
19917
+ // period cache; the trade-off is intentional.
19918
+ #registerPeriodGate() {
19919
+ if (this.options.periodsStrategy === void 0) return;
19920
+ this.subsystemBus.registerGate("beforePut", async (e) => {
19921
+ const v = this.vaultCache.get(e.vault);
19922
+ if (!v) return;
19923
+ const existing = e.op === "create" ? null : { ts: e.existingTs ?? null, record: e.existing ?? null };
19924
+ await v._assertTsWritable(existing, e.incoming);
19925
+ });
19926
+ this.subsystemBus.registerGate("beforeDelete", async (e) => {
19927
+ if (e.internal) return;
19928
+ const v = this.vaultCache.get(e.vault);
19929
+ if (!v) return;
19930
+ await v._assertTsWritable(
19931
+ { ts: e.existingTs ?? null, record: e.existing ?? null },
19932
+ null
19933
+ );
19934
+ });
19935
+ }
19658
19936
  resetSessionTimer() {
19659
19937
  if (this.sessionTimer) clearTimeout(this.sessionTimer);
19660
19938
  const idleMs = this.options.sessionPolicy?.idleTimeoutMs ?? this.options.sessionTimeout;
@@ -19944,8 +20222,6 @@ var Noydb = class {
19944
20222
  * @throws `NoAccessError` when no keyring exists for the target.
19945
20223
  * @throws `PermissionDeniedError` when the role hierarchy rejects.
19946
20224
  * @throws `ValidationError` when no field is provided.
19947
- *
19948
- * @see #54
19949
20225
  */
19950
20226
  async updateUser(vault, options, factors) {
19951
20227
  await this.checkGate(vault, "update-user", factors);
@@ -20281,7 +20557,7 @@ var Noydb = class {
20281
20557
  * Phase 2. `Collection.dispatchDerivations` consults this so a
20282
20558
  * recursive derived-output write inside `Collection.put` can register
20283
20559
  * its envelope onto `ctx._executed` and roll back with the main
20284
- * staged ops on mid-batch failure (#133).
20560
+ * staged ops on mid-batch failure.
20285
20561
  *
20286
20562
  * @internal
20287
20563
  */
@@ -20310,7 +20586,7 @@ var Noydb = class {
20310
20586
  * `Collection.putManyAtomic` (via `derivationSource.createTxContext`)
20311
20587
  * to publish an active context for the duration of its bulk-atomic
20312
20588
  * Phase 2 loop, so recursive derivation-output writes register on
20313
- * `ctx._executed` and roll back together with the source ops (#133).
20589
+ * `ctx._executed` and roll back together with the source ops.
20314
20590
  *
20315
20591
  * @internal
20316
20592
  */
@@ -20381,26 +20657,26 @@ var Noydb = class {
20381
20657
  return this.writeQueueTracker;
20382
20658
  }
20383
20659
  /**
20384
- * Register a hook that runs before each write (#230). Awaited; a throw
20660
+ * Register a hook that runs before each write. Awaited; a throw
20385
20661
  * aborts the write. Returns an unsubscribe function.
20386
20662
  */
20387
20663
  onBeforeWrite(handler) {
20388
20664
  return this.writeHooks.onBeforeWrite(handler);
20389
20665
  }
20390
20666
  /**
20391
- * Register a hook that runs after each committed write (#230). Awaited;
20667
+ * Register a hook that runs after each committed write. Awaited;
20392
20668
  * a handler error is warned, never rolled back. Returns an unsubscribe fn.
20393
20669
  */
20394
20670
  onAfterWrite(handler) {
20395
20671
  return this.writeHooks.onAfterWrite(handler);
20396
20672
  }
20397
- /** Subscribe to cross-tab write conflicts (#228c). Returns an unsubscribe. */
20673
+ /** Subscribe to cross-tab write conflicts. Returns an unsubscribe. */
20398
20674
  onWriteConflict(fn) {
20399
20675
  this.on("write:conflict", fn);
20400
20676
  return () => this.off("write:conflict", fn);
20401
20677
  }
20402
20678
  /**
20403
- * Enable same-device multi-tab coordination (#228): primary/secondary
20679
+ * Enable same-device multi-tab coordination: primary/secondary
20404
20680
  * election + presence. Browser-only — a graceful no-op (role 'unknown')
20405
20681
  * when Web Locks / BroadcastChannel are unavailable and nothing is
20406
20682
  * injected. Idempotent; returns a disposer.
@@ -20483,7 +20759,11 @@ var Noydb = class {
20483
20759
  get _writeHooks() {
20484
20760
  return this.writeHooks;
20485
20761
  }
20486
- /** @internal Stable per-instance id for schema-cutover coordination (#232). */
20762
+ /** @internal The observe bus, threaded into every Collection. */
20763
+ get _subsystemBus() {
20764
+ return this.subsystemBus;
20765
+ }
20766
+ /** @internal Stable per-instance id for schema-cutover coordination. */
20487
20767
  get _clientId() {
20488
20768
  return this.clientId;
20489
20769
  }
@@ -20503,10 +20783,6 @@ var Noydb = class {
20503
20783
  * survives lock; nothing about it changes when DEKs are scrubbed).
20504
20784
  *
20505
20785
  * No-op when `vault` is not currently in cache (idempotent).
20506
- *
20507
- * Unblocks vLannaAi/niwat#33.
20508
- *
20509
- * @see #17
20510
20786
  */
20511
20787
  lockVault(vault) {
20512
20788
  this.syncEngines.get(vault)?.stopAutoSync();
@@ -20621,7 +20897,7 @@ var Noydb = class {
20621
20897
  return merged;
20622
20898
  }
20623
20899
  /**
20624
- * Read the current vault-level user-directory toggle (#122). Returns
20900
+ * Read the current vault-level user-directory toggle. Returns
20625
20901
  * the default-on shape (`{ enabled: true }`) when no `_meta/directory`
20626
20902
  * document has been persisted yet.
20627
20903
  *
@@ -20633,7 +20909,7 @@ var Noydb = class {
20633
20909
  return persisted?.enabled ?? true;
20634
20910
  }
20635
20911
  /**
20636
- * Toggle the vault's user-directory listing on or off (#122).
20912
+ * Toggle the vault's user-directory listing on or off.
20637
20913
  * Owner-only. When disabled, `listUsersWithEnvelopes()` throws
20638
20914
  * {@link import('./errors.js').DirectoryDisabledError} for callers
20639
20915
  * whose role is neither `owner` nor `admin`.
@@ -20693,7 +20969,7 @@ var Noydb = class {
20693
20969
  *
20694
20970
  * Two enforcement modes:
20695
20971
  *
20696
- * 1. **Managed-mode mandatory strong-recovery (#195).** When
20972
+ * 1. **Managed-mode mandatory strong-recovery.** When
20697
20973
  * `passphraseMode === 'managed'`, the vault MUST have at least
20698
20974
  * one **strong** recovery profile (Shamir today). Paper alone is
20699
20975
  * rejected because under managed mode the user has no memorized
@@ -20727,14 +21003,14 @@ var Noydb = class {
20727
21003
  throw new RecoveryNotEnrolledError();
20728
21004
  }
20729
21005
  /**
20730
- * Internal accessor used by tier-2/tier-3 unlock paths (issue #11)
21006
+ * Internal accessor used by tier-2/tier-3 unlock paths
20731
21007
  * to mark the active session tier.
20732
21008
  * @internal
20733
21009
  */
20734
21010
  _setActiveTier(vault, tier) {
20735
21011
  this.activeTier.set(vault, tier);
20736
21012
  }
20737
- // ─── Tier-2 enroll / remove (issue #11) ────────────────────────
21013
+ // ─── Tier-2 enroll / remove ─────────────────────────────────────
20738
21014
  /**
20739
21015
  * Add a tier-2 authenticator slot to the calling user's keyring.
20740
21016
  * Each slot independently wraps the SAME KEK under a method-specific
@@ -20764,7 +21040,7 @@ var Noydb = class {
20764
21040
  const next = await removeAuthenticator(this.options.store, vault, keyring, slotId);
20765
21041
  this.keyringCache.set(vault, next);
20766
21042
  }
20767
- /** Read the slot list for a vault. Internal — `describeAuthConfig` (#13) consumes this. */
21043
+ /** Read the slot list for a vault. Internal — `describeAuthConfig` consumes this. */
20768
21044
  async listAuthenticators(vault) {
20769
21045
  const keyring = await this.getKeyringInternal(vault);
20770
21046
  return keyring.authenticators;
@@ -20776,7 +21052,7 @@ var Noydb = class {
20776
21052
  * are immutable through this method. Anti-slot-swap is structural,
20777
21053
  * not gate-driven.
20778
21054
  *
20779
- * `meta` patch semantics (#57-aligned):
21055
+ * `meta` patch semantics (top-level merge):
20780
21056
  * - Top-level merge — absent keys preserved
20781
21057
  * - `null` value — delete that meta key
20782
21058
  * - Other values — replace verbatim
@@ -20794,8 +21070,6 @@ var Noydb = class {
20794
21070
  *
20795
21071
  * @throws `NoAccessError` when no slot with the given id exists.
20796
21072
  * @throws `ValidationError` when no patch field is provided.
20797
- *
20798
- * @see #55
20799
21073
  */
20800
21074
  async updateAuthenticator(vault, slotId, options, factors) {
20801
21075
  await this.checkGate(vault, "update-authenticator", factors);
@@ -20804,7 +21078,7 @@ var Noydb = class {
20804
21078
  this.keyringCache.set(vault, next);
20805
21079
  }
20806
21080
  /**
20807
- * Native WebAuthn enrollment using the **real** internal keyring (#16).
21081
+ * Native WebAuthn enrollment using the **real** internal keyring.
20808
21082
  *
20809
21083
  * Why this exists: when a consumer is using `createNoydb({ secret })`,
20810
21084
  * they cannot reach the live `UnlockedKeyring` to feed it to
@@ -20847,8 +21121,6 @@ var Noydb = class {
20847
21121
  * a server-side allowlist).
20848
21122
  *
20849
21123
  * Gated by `enroll-authenticator` like `enrollAuthenticator()` itself.
20850
- *
20851
- * @see #16
20852
21124
  */
20853
21125
  async enrollWebAuthn(vault, ceremony, factors) {
20854
21126
  await this.checkGate(vault, "enroll-authenticator", factors);
@@ -20875,8 +21147,6 @@ var Noydb = class {
20875
21147
  * deciding when a new device prompt should appear. Identity is
20876
21148
  * `id` + `enrolled_at`; the `meta.credentialId` (base64) is used by
20877
21149
  * `allowCredentials` at unlock time.
20878
- *
20879
- * @see #16
20880
21150
  */
20881
21151
  async listWebAuthnSlots(vault) {
20882
21152
  const keyring = await this.getKeyringInternal(vault);
@@ -20958,7 +21228,7 @@ var Noydb = class {
20958
21228
  async getPublicEnvelope(vault, opts = {}) {
20959
21229
  return readPublicEnvelope(this.options.store, vault, opts);
20960
21230
  }
20961
- // ─── Auth introspection (issue #13) ────────────────────────────
21231
+ // ─── Auth introspection ─────────────────────────────────────────
20962
21232
  /** English summary of the configured auth model. */
20963
21233
  async describeAuthConfig(vault) {
20964
21234
  return describeAuthConfig(this.options.store, vault);
@@ -20981,7 +21251,7 @@ var Noydb = class {
20981
21251
  await this.checkGate(vault, "view-user-auth", factors);
20982
21252
  return describeAllUsersAuth(this.options.store, vault);
20983
21253
  }
20984
- // ─── Tier-1 change flows (issue #10) ───────────────────────────
21254
+ // ─── Tier-1 change flows ────────────────────────────────────────
20985
21255
  /**
20986
21256
  * Rotate the user's passphrase (user remembers old). Validates the
20987
21257
  * new phrase against the configured `passphrase` policy, runs the
@@ -20989,8 +21259,7 @@ var Noydb = class {
20989
21259
  *
20990
21260
  * Tier-2 authenticator slots are dropped — each slot wraps the old
20991
21261
  * KEK and would need its derivation key to be re-presented. Re-enrol
20992
- * via `db.enrollAuthenticator` after rotation. Tracked as a
20993
- * v0.1.0-pre.5 limitation.
21262
+ * via `db.enrollAuthenticator` after rotation.
20994
21263
  *
20995
21264
  * @throws `WeakPassphraseError` on a weak new phrase.
20996
21265
  * @throws `PolicyDeniedError` when the gate denies (missing factor, …).
@@ -21012,8 +21281,8 @@ var Noydb = class {
21012
21281
  }
21013
21282
  /**
21014
21283
  * Reset the passphrase using a recovery proof (user forgot the old).
21015
- * v0.1.0-pre.5 supports the `'paper'` profile end-to-end; the
21016
- * other three profiles throw {@link RecoveryProfileNotImplementedError}.
21284
+ * Currently supports the `'paper'` profile end-to-end; the
21285
+ * other profiles throw {@link RecoveryProfileNotImplementedError}.
21017
21286
  *
21018
21287
  * Burns the used recovery entry on success.
21019
21288
  */
@@ -21042,7 +21311,7 @@ var Noydb = class {
21042
21311
  return { newCodes: codes };
21043
21312
  }
21044
21313
  /**
21045
- * Deliberate paper-recovery-code regeneration (#121). User knows their
21314
+ * Deliberate paper-recovery-code regeneration. User knows their
21046
21315
  * passphrase but wants a fresh sheet — they lost the printout or
21047
21316
  * suspect compromise of the off-site copy.
21048
21317
  *
@@ -21052,7 +21321,7 @@ var Noydb = class {
21052
21321
  *
21053
21322
  * Gated by the `rotate-recovery` policy gate:
21054
21323
  * - PERSONAL_POLICY: `{ minTier: 1 }` — knowing the passphrase
21055
- * suffices, matching the pre-#121 low-level flow's bar.
21324
+ * suffices, matching the lower-level flow's bar.
21056
21325
  * - STRICT_POLICY: `{ minTier: 1, factors: [{ anyOf: ['totp',
21057
21326
  * 'email-otp', 'webauthn-roaming'] }] }` — rotation is an
21058
21327
  * off-site-trust event; require an off-device factor so a
@@ -21157,7 +21426,7 @@ var Noydb = class {
21157
21426
  return { newShares: shareStrings, entryId: targetEntryId };
21158
21427
  }
21159
21428
  /**
21160
- * **Atomic create-and-enroll for managed-mode vaults (#195).**
21429
+ * **Atomic create-and-enroll for managed-mode vaults.**
21161
21430
  *
21162
21431
  * Bootstraps a managed-mode vault and enrolls strong recovery in
21163
21432
  * a single ceremony. Under `passphraseMode: 'managed'`, every
@@ -21228,7 +21497,7 @@ var Noydb = class {
21228
21497
  return { vault: vaultHandle, recoveryEnrollments };
21229
21498
  }
21230
21499
  /**
21231
- * **Recovery flow under managed-passphrase mode (#195).**
21500
+ * **Recovery flow under managed-passphrase mode.**
21232
21501
  *
21233
21502
  * Replaces the sealed passphrase of a managed-mode vault with a
21234
21503
  * fresh 256-bit random, sealed under the configured
@@ -21245,7 +21514,7 @@ var Noydb = class {
21245
21514
  * 5. Drop the keyring cache so the next operation re-derives.
21246
21515
  *
21247
21516
  * The vault's strong-recovery enrollment is preserved across
21248
- * recovery (Shamir entries are not burned on use — see #196).
21517
+ * recovery (Shamir entries are not burned on use).
21249
21518
  *
21250
21519
  * @throws ValidationError if the Noydb instance is not in managed mode.
21251
21520
  */
@@ -21293,7 +21562,7 @@ var Noydb = class {
21293
21562
  }
21294
21563
  /**
21295
21564
  * Atomic peer-recovery — re-wraps an EXISTING user's keyring under
21296
- * a fresh temp passphrase in a single store write. Closes #34's
21565
+ * a fresh temp passphrase in a single store write. Closes the
21297
21566
  * partial-failure window (the previous compose-from-primitives
21298
21567
  * pattern was `db.revoke + db.grant`, two writes — if the issuer
21299
21568
  * cancelled between them the target was locked out entirely).
@@ -21303,7 +21572,7 @@ var Noydb = class {
21303
21572
  * - Same `userId`, role, permissions, capabilities preserved.
21304
21573
  * - DEKs unchanged → every other principal in the vault keeps
21305
21574
  * access. No key rotation.
21306
- * - Allows owner→owner natively (#33). The existing
21575
+ * - Allows owner→owner natively. The existing
21307
21576
  * `db.revoke` retains its block — peer-recovery is a separate,
21308
21577
  * intentionally-named operation.
21309
21578
  * - Tier-2 slots dropped (they wrap the old KEK).
@@ -21332,7 +21601,6 @@ var Noydb = class {
21332
21601
  * @throws `PrivilegeEscalationError` when the caller lacks a DEK
21333
21602
  * the target previously had access to.
21334
21603
  *
21335
- * @see #33 #34 — the issues this method closes.
21336
21604
  */
21337
21605
  async recoverUser(vault, options, factors) {
21338
21606
  await this.checkGate(vault, "peer-recover-user", factors);
@@ -21343,7 +21611,7 @@ var Noydb = class {
21343
21611
  }
21344
21612
  }
21345
21613
  /**
21346
- * Persist a recovery enrollment. v0.1.0-pre.5 accepts the `'paper'`
21614
+ * Persist a recovery enrollment. Accepts the `'paper'`
21347
21615
  * profile.
21348
21616
  *
21349
21617
  * The hub wraps the user's DEK set (not the KEK) under a code-derived
@@ -21363,7 +21631,7 @@ var Noydb = class {
21363
21631
  * showCodesToUser(codes)
21364
21632
  * ```
21365
21633
  *
21366
- * As of pre.8, `@noy-db/on-recovery`'s `generateRecoveryCodeSet`
21634
+ * `@noy-db/on-recovery`'s `generateRecoveryCodeSet`
21367
21635
  * delegates to `mintPaperRecoveryEntry` internally — its output is
21368
21636
  * fed directly to this API. Pick whichever fits your code-gen layer:
21369
21637
  *
@@ -21403,13 +21671,13 @@ var Noydb = class {
21403
21671
  "#196"
21404
21672
  );
21405
21673
  }
21406
- /** Read the persisted recovery entries (paper + Shamir). Used by `describeAuthConfig` (#13). */
21674
+ /** Read the persisted recovery entries (paper + Shamir). Used by `describeAuthConfig`. */
21407
21675
  async listRecoveryEntries(vault) {
21408
21676
  const paper = await loadPaperRecoveryEntries(this.options.store, vault);
21409
21677
  const shamir = await loadShamirRecoveryEntries(this.options.store, vault);
21410
21678
  return { paper, shamir };
21411
21679
  }
21412
- // ─── Tier-3 enroll / unlock (issue #11) ────────────────────────
21680
+ // ─── Tier-3 enroll / unlock ─────────────────────────────────────
21413
21681
  /**
21414
21682
  * Register a tier-3 quick-unlock state for the vault. The state is
21415
21683
  * an opaque blob produced by `@noy-db/on-pin/enrollPin` (or any
@@ -21445,11 +21713,11 @@ var Noydb = class {
21445
21713
  this.quickUnlock.delete(vault);
21446
21714
  }
21447
21715
  /**
21448
- * Public accessor for the unlocked keyring of a vault — issue #28.
21716
+ * Public accessor for the unlocked keyring of a vault.
21449
21717
  *
21450
21718
  * Returns a **defensive shallow copy** so consumers can read the DEK
21451
21719
  * map and authenticator list without the risk of mutating the hub's
21452
- * internal cache (#88). Internal hub code paths use a live reference
21720
+ * internal cache. Internal hub code paths use a live reference
21453
21721
  * via `getKeyringInternal`; ceremonies and external consumers always
21454
21722
  * get a snapshot.
21455
21723
  *
@@ -22388,7 +22656,7 @@ function withDerivation(spec) {
22388
22656
  if (outputSpec.shape === "array") {
22389
22657
  if (lifecycleMode !== "eager") {
22390
22658
  throw new ValidationError(
22391
- `withDerivation: shape 'array' supports lifecycle 'eager' only in this release (#200 slice 1). Output "${outputKey}" declared lifecycle '${lifecycleMode}'. Switch to \`lifecycle: "eager"\` or use shape: "record".`
22659
+ `withDerivation: shape 'array' supports lifecycle 'eager' only in this release Output "${outputKey}" declared lifecycle '${lifecycleMode}'. Switch to \`lifecycle: "eager"\` or use shape: "record".`
22392
22660
  );
22393
22661
  }
22394
22662
  if (typeof outputSpec.key !== "function") {
@@ -22524,114 +22792,6 @@ function withOverlayedView(spec) {
22524
22792
  // src/index.ts
22525
22793
  init_errors();
22526
22794
  init_errors();
22527
-
22528
- // src/i18n/core.ts
22529
- init_errors();
22530
- function i18nText(options) {
22531
- return { _noydbI18nText: true, options };
22532
- }
22533
- function isI18nTextDescriptor(x) {
22534
- return typeof x === "object" && x !== null && x._noydbI18nText === true;
22535
- }
22536
- function validateI18nTextValue(value, field, descriptor) {
22537
- const { options } = descriptor;
22538
- if (typeof value !== "object" || value === null || Array.isArray(value)) {
22539
- throw new MissingTranslationError(
22540
- field,
22541
- options.languages,
22542
- `Field "${field}" must be a { [locale]: string } map, got ${typeof value}.`
22543
- );
22544
- }
22545
- const map = value;
22546
- for (const [locale, v] of Object.entries(map)) {
22547
- if (typeof v !== "string") {
22548
- throw new MissingTranslationError(
22549
- field,
22550
- [locale],
22551
- `Field "${field}": locale "${locale}" must be a string, got ${typeof v}.`
22552
- );
22553
- }
22554
- }
22555
- const { required } = options;
22556
- if (required === "all") {
22557
- const missing = options.languages.filter(
22558
- (lang) => !(lang in map) || map[lang] === ""
22559
- );
22560
- if (missing.length > 0) {
22561
- throw new MissingTranslationError(
22562
- field,
22563
- missing,
22564
- `Field "${field}" requires all declared languages. Missing: ${missing.join(", ")}.`
22565
- );
22566
- }
22567
- } else if (required === "any") {
22568
- const present = options.languages.some(
22569
- (lang) => lang in map && map[lang] !== ""
22570
- );
22571
- if (!present) {
22572
- throw new MissingTranslationError(
22573
- field,
22574
- options.languages,
22575
- `Field "${field}" requires at least one declared language. None present.`
22576
- );
22577
- }
22578
- } else {
22579
- const requiredList = required;
22580
- const missing = requiredList.filter(
22581
- (lang) => !(lang in map) || map[lang] === ""
22582
- );
22583
- if (missing.length > 0) {
22584
- throw new MissingTranslationError(
22585
- field,
22586
- missing,
22587
- `Field "${field}" requires: ${requiredList.join(", ")}. Missing: ${missing.join(", ")}.`
22588
- );
22589
- }
22590
- }
22591
- }
22592
- function resolveI18nText(value, locale, fallback, field) {
22593
- if (locale === "raw") {
22594
- return value;
22595
- }
22596
- if (!locale) {
22597
- throw new LocaleNotSpecifiedError(field ?? "<unknown>");
22598
- }
22599
- if (value[locale] !== void 0 && value[locale] !== "") {
22600
- return value[locale];
22601
- }
22602
- const chain = Array.isArray(fallback) ? fallback : fallback ? [fallback] : [];
22603
- for (const fb of chain) {
22604
- if (fb === "any") {
22605
- const any = Object.values(value).find((v) => v !== "");
22606
- if (any !== void 0) return any;
22607
- } else if (value[fb] !== void 0 && value[fb] !== "") {
22608
- return value[fb];
22609
- }
22610
- }
22611
- throw new LocaleNotSpecifiedError(
22612
- field ?? "<unknown>",
22613
- `No translation available for locale "${locale}"` + (chain.length > 0 ? ` or fallback chain [${chain.join(", ")}]` : "") + "."
22614
- );
22615
- }
22616
- function applyI18nLocale(record, i18nFields, locale, fallback) {
22617
- const fieldNames = Object.keys(i18nFields);
22618
- if (fieldNames.length === 0) return record;
22619
- const result = { ...record };
22620
- for (const field of fieldNames) {
22621
- const raw = result[field];
22622
- if (raw === void 0 || raw === null) continue;
22623
- if (typeof raw !== "object" || Array.isArray(raw)) continue;
22624
- result[field] = resolveI18nText(
22625
- raw,
22626
- locale,
22627
- fallback,
22628
- field
22629
- );
22630
- }
22631
- return result;
22632
- }
22633
-
22634
- // src/index.ts
22635
22795
  init_errors();
22636
22796
 
22637
22797
  // src/team/sync-credentials.ts