@noy-db/hub 0.2.0-pre.3 → 0.2.0-pre.5

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 (280) 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 +443 -338
  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-T6HQMVML.js → chunk-BT7544RM.js} +399 -301
  39. package/dist/chunk-BT7544RM.js.map +1 -0
  40. package/dist/{chunk-4OQWR46B.js → chunk-CCC25PA7.js} +5 -5
  41. package/dist/{chunk-NSLTPGEN.js → chunk-CGJFCT3X.js} +2 -2
  42. package/dist/{chunk-YK72A4IT.js → chunk-CKH247ZR.js} +4 -4
  43. package/dist/{chunk-HGZ7DC5H.js → chunk-DFCINPB5.js} +2 -2
  44. package/dist/chunk-DFCINPB5.js.map +1 -0
  45. package/dist/{chunk-4X2S7PBF.js → chunk-E225X5CQ.js} +3 -3
  46. package/dist/chunk-E225X5CQ.js.map +1 -0
  47. package/dist/{chunk-5YHWBPOT.js → chunk-ED3E3OLO.js} +2 -2
  48. package/dist/{chunk-UOF74WQY.js → chunk-EKTOYEZ3.js} +2 -2
  49. package/dist/{chunk-SAVQ6E2O.js → chunk-G26QAQNI.js} +2 -2
  50. package/dist/{chunk-YMYK7US4.js → chunk-HIELMTUK.js} +2 -2
  51. package/dist/{chunk-MRIBLZL3.js → chunk-ICH4AIGL.js} +1 -1
  52. package/dist/chunk-ICH4AIGL.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-A6SWRXUQ.js → chunk-NONMIU6C.js} +2 -2
  61. package/dist/{chunk-ZUMGGHRB.js → chunk-OPD3PZOG.js} +4 -4
  62. package/dist/{chunk-LS3JLEIB.js → chunk-PS5G6A3Y.js} +4 -4
  63. package/dist/{chunk-KYKMKLJ6.js → chunk-PX3MJ6RB.js} +3 -3
  64. package/dist/{chunk-FCDO7UAO.js → chunk-R4LTCI6O.js} +2 -2
  65. package/dist/{chunk-BFI3RS42.js → chunk-R7JTYCRX.js} +2 -2
  66. package/dist/chunk-R7JTYCRX.js.map +1 -0
  67. package/dist/{chunk-WRLHNG6H.js → chunk-RIHZBSWJ.js} +4 -4
  68. package/dist/chunk-RIHZBSWJ.js.map +1 -0
  69. package/dist/{chunk-UVPGJXVO.js → chunk-SGSHQ4PH.js} +5 -5
  70. package/dist/{chunk-TLFUDXVV.js → chunk-T6MTNGBM.js} +5 -5
  71. package/dist/chunk-T6MTNGBM.js.map +1 -0
  72. package/dist/{chunk-6S3LLAQ5.js → chunk-TNBIWSQ7.js} +2 -2
  73. package/dist/{chunk-GD3BGKAR.js → chunk-UGVDIOY7.js} +2 -2
  74. package/dist/{chunk-FS7A4XNF.js → chunk-WEA4TDTJ.js} +3 -3
  75. package/dist/{chunk-4UBOTYP5.js → chunk-XDW37COG.js} +5 -5
  76. package/dist/chunk-XDW37COG.js.map +1 -0
  77. package/dist/{chunk-QAU5HM6Q.js → chunk-XVJFFGTG.js} +3 -3
  78. package/dist/{chunk-2EYC3WDT.js → chunk-Y3P5DEMZ.js} +6 -6
  79. package/dist/chunk-Y3P5DEMZ.js.map +1 -0
  80. package/dist/{chunk-G7PAZ3TD.js → chunk-YEHUEUNP.js} +4 -4
  81. package/dist/chunk-YEHUEUNP.js.map +1 -0
  82. package/dist/{chunk-2XLVPKXG.js → chunk-YJ46RFCD.js} +2 -2
  83. package/dist/{chunk-KMI2NBBF.js → chunk-YZ6JETII.js} +6 -6
  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--ahUTrhc.d.ts} +1 -1
  99. package/dist/{dev-unlock-De3mjQWv.d.cts → dev-unlock-BIwt2V3p.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-vBCB0-Ps.d.cts → hash-BQVrGV-t.d.cts} +1 -1
  111. package/dist/{hash-gVn_uKhp.d.ts → hash-CJEFQxSD.d.ts} +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.map +1 -1
  117. package/dist/i18n/index.d.cts +5 -5
  118. package/dist/i18n/index.d.ts +5 -5
  119. package/dist/i18n/index.js +7 -7
  120. package/dist/{index-DVkvrgpm.d.cts → index-5I0MZ0jQ.d.cts} +12 -12
  121. package/dist/{index-BF1B2HB9.d.ts → index-fIPPh5dg.d.ts} +12 -12
  122. package/dist/index.cjs +362 -264
  123. package/dist/index.cjs.map +1 -1
  124. package/dist/index.d.cts +20 -22
  125. package/dist/index.d.ts +20 -22
  126. package/dist/index.js +45 -45
  127. package/dist/index.js.map +1 -1
  128. package/dist/indexing/index.cjs +1 -1
  129. package/dist/indexing/index.cjs.map +1 -1
  130. package/dist/indexing/index.d.cts +3 -3
  131. package/dist/indexing/index.d.ts +3 -3
  132. package/dist/indexing/index.js +4 -4
  133. package/dist/issue-IODMTPME.js +12 -0
  134. package/dist/{lazy-builder-Rpd-V3jP.d.ts → lazy-builder-D1MyR1qH.d.ts} +2 -2
  135. package/dist/{lazy-builder-C-rPfWG0.d.cts → lazy-builder-DXlSCNCJ.d.cts} +2 -2
  136. package/dist/{ledger-WOEJUYTP.js → ledger-UX4QIHWI.js} +6 -6
  137. package/dist/materialized-views/index.cjs.map +1 -1
  138. package/dist/materialized-views/index.d.cts +18 -18
  139. package/dist/materialized-views/index.d.ts +18 -18
  140. package/dist/materialized-views/index.js +7 -7
  141. package/dist/noydb-6TADQIYH.js +34 -0
  142. package/dist/overlay-views/index.cjs +1 -1
  143. package/dist/overlay-views/index.cjs.map +1 -1
  144. package/dist/overlay-views/index.d.cts +8 -8
  145. package/dist/overlay-views/index.d.ts +8 -8
  146. package/dist/overlay-views/index.js +4 -4
  147. package/dist/periods/index.cjs.map +1 -1
  148. package/dist/periods/index.d.cts +5 -5
  149. package/dist/periods/index.d.ts +5 -5
  150. package/dist/periods/index.js +6 -6
  151. package/dist/{predicate-Dnu81tsS.d.cts → predicate-B0IKeBXx.d.cts} +1 -1
  152. package/dist/{predicate-Dnu81tsS.d.ts → predicate-B0IKeBXx.d.ts} +1 -1
  153. package/dist/{public-envelope-OHQ5UZFM.js → public-envelope-YKHKP74C.js} +4 -4
  154. package/dist/query/index.cjs +2 -2
  155. package/dist/query/index.cjs.map +1 -1
  156. package/dist/query/index.d.cts +2 -2
  157. package/dist/query/index.d.ts +2 -2
  158. package/dist/query/index.js +6 -6
  159. package/dist/registry-446I2NMN.js +8 -0
  160. package/dist/{registry-CDHASH73.js → registry-4NEW7LQY.js} +3 -3
  161. package/dist/registry-524KJZG4.js +8 -0
  162. package/dist/registry-DKEXOJVO.js +7 -0
  163. package/dist/{revoke-7JOVLZFD.js → revoke-R5NIQ74J.js} +6 -6
  164. package/dist/session/index.cjs.map +1 -1
  165. package/dist/session/index.d.cts +6 -6
  166. package/dist/session/index.d.ts +6 -6
  167. package/dist/session/index.js +3 -3
  168. package/dist/shadow/index.cjs.map +1 -1
  169. package/dist/shadow/index.d.cts +5 -5
  170. package/dist/shadow/index.d.ts +5 -5
  171. package/dist/shadow/index.js +2 -2
  172. package/dist/{signer-M4K5HBLD.js → signer-WGDJNWSU.js} +5 -5
  173. package/dist/{stale-PAGCS4K5.js → stale-74WGLVZ2.js} +2 -2
  174. package/dist/store/index.cjs.map +1 -1
  175. package/dist/store/index.d.cts +5 -5
  176. package/dist/store/index.d.ts +5 -5
  177. package/dist/store/index.js +2 -2
  178. package/dist/sync/index.cjs.map +1 -1
  179. package/dist/sync/index.d.cts +4 -4
  180. package/dist/sync/index.d.ts +4 -4
  181. package/dist/sync/index.js +4 -4
  182. package/dist/team/index.cjs +1 -1
  183. package/dist/team/index.cjs.map +1 -1
  184. package/dist/team/index.d.cts +5 -5
  185. package/dist/team/index.d.ts +5 -5
  186. package/dist/team/index.js +8 -8
  187. package/dist/tx/index.cjs +2 -2
  188. package/dist/tx/index.cjs.map +1 -1
  189. package/dist/tx/index.d.cts +5 -5
  190. package/dist/tx/index.d.ts +5 -5
  191. package/dist/tx/index.js +3 -3
  192. package/dist/tx/index.js.map +1 -1
  193. package/dist/{types-D9eB0Rvh.d.ts → types-BV4AZKmx.d.ts} +340 -302
  194. package/dist/{types-CSLcfytP.d.cts → types-BeKi0hCx.d.cts} +340 -302
  195. package/dist/{ulid-CiM2OAeM.d.ts → ulid-CQc0eBxE.d.ts} +19 -19
  196. package/dist/{ulid-CG2YvAbg.d.cts → ulid-Cvljl7ZZ.d.cts} +19 -19
  197. package/dist/util/index.cjs.map +1 -1
  198. package/dist/util/index.js +1 -1
  199. package/dist/{with-derivation-Bzpj6UTv.d.ts → with-derivation-BWcwmevt.d.ts} +1 -1
  200. package/dist/{with-derivation-DWajFh4K.d.cts → with-derivation-BkOBDhsu.d.cts} +1 -1
  201. package/dist/{with-guard-DF_Ul3DT.d.cts → with-guard-BD4Hyu8s.d.cts} +1 -1
  202. package/dist/{with-guard-DR7U-l4v.d.ts → with-guard-Du54s3Ti.d.ts} +1 -1
  203. package/dist/{with-materialized-view-qtoJ3xKJ.d.ts → with-materialized-view-B5W4wFAC.d.ts} +2 -2
  204. package/dist/{with-materialized-view-_piodoIz.d.cts → with-materialized-view-BCPPZdjC.d.cts} +2 -2
  205. package/dist/{with-overlayed-view-DFaRfgMr.d.ts → with-overlayed-view-B8RrlLsG.d.cts} +2 -2
  206. package/dist/{with-overlayed-view-DwzCKxn2.d.cts → with-overlayed-view-Cw-h9p9N.d.ts} +2 -2
  207. package/package.json +3 -3
  208. package/dist/chunk-2EYC3WDT.js.map +0 -1
  209. package/dist/chunk-4UBOTYP5.js.map +0 -1
  210. package/dist/chunk-4X2S7PBF.js.map +0 -1
  211. package/dist/chunk-74JEQFMT.js.map +0 -1
  212. package/dist/chunk-BFI3RS42.js.map +0 -1
  213. package/dist/chunk-EMEX37ZN.js.map +0 -1
  214. package/dist/chunk-EPK6A3WJ.js.map +0 -1
  215. package/dist/chunk-FXQYZNOW.js.map +0 -1
  216. package/dist/chunk-G7PAZ3TD.js.map +0 -1
  217. package/dist/chunk-GDTCGIPX.js.map +0 -1
  218. package/dist/chunk-GVXBHCZ2.js.map +0 -1
  219. package/dist/chunk-HGZ7DC5H.js.map +0 -1
  220. package/dist/chunk-MRIBLZL3.js.map +0 -1
  221. package/dist/chunk-NCO2JGKK.js.map +0 -1
  222. package/dist/chunk-NGSPBLLE.js.map +0 -1
  223. package/dist/chunk-P6256WTJ.js.map +0 -1
  224. package/dist/chunk-T6HQMVML.js.map +0 -1
  225. package/dist/chunk-TLFUDXVV.js.map +0 -1
  226. package/dist/chunk-WRLHNG6H.js.map +0 -1
  227. package/dist/chunk-YDLAFP36.js.map +0 -1
  228. package/dist/chunk-YL2DR3HY.js.map +0 -1
  229. package/dist/chunk-ZC2AAE6J.js.map +0 -1
  230. package/dist/executor-BZKFZVRC.js +0 -8
  231. package/dist/executor-GFZFDQXV.js +0 -8
  232. package/dist/executor-KT2IOZVP.js +0 -11
  233. package/dist/fanout-sidecar-NRBWSLRK.js.map +0 -1
  234. package/dist/issue-BAJ7ZB4S.js +0 -12
  235. package/dist/noydb-XNQSKXGO.js +0 -34
  236. package/dist/registry-2IEARCGT.js +0 -7
  237. package/dist/registry-EMGLZGR6.js +0 -8
  238. package/dist/registry-NQALYR77.js +0 -8
  239. /package/dist/{chunk-5ZGZ6HIZ.js.map → chunk-5VMTAX4Y.js.map} +0 -0
  240. /package/dist/{chunk-75QDHSE4.js.map → chunk-A4JNVBPF.js.map} +0 -0
  241. /package/dist/{chunk-IS5HWQO7.js.map → chunk-ARZAHCCF.js.map} +0 -0
  242. /package/dist/{chunk-4OQWR46B.js.map → chunk-CCC25PA7.js.map} +0 -0
  243. /package/dist/{chunk-NSLTPGEN.js.map → chunk-CGJFCT3X.js.map} +0 -0
  244. /package/dist/{chunk-YK72A4IT.js.map → chunk-CKH247ZR.js.map} +0 -0
  245. /package/dist/{chunk-5YHWBPOT.js.map → chunk-ED3E3OLO.js.map} +0 -0
  246. /package/dist/{chunk-UOF74WQY.js.map → chunk-EKTOYEZ3.js.map} +0 -0
  247. /package/dist/{chunk-SAVQ6E2O.js.map → chunk-G26QAQNI.js.map} +0 -0
  248. /package/dist/{chunk-YMYK7US4.js.map → chunk-HIELMTUK.js.map} +0 -0
  249. /package/dist/{chunk-LOL725S4.js.map → chunk-JSYTGEX4.js.map} +0 -0
  250. /package/dist/{chunk-FBMXWVGP.js.map → chunk-KGFV72WK.js.map} +0 -0
  251. /package/dist/{chunk-K5PVGKE4.js.map → chunk-MDIC4FAU.js.map} +0 -0
  252. /package/dist/{chunk-A6SWRXUQ.js.map → chunk-NONMIU6C.js.map} +0 -0
  253. /package/dist/{chunk-ZUMGGHRB.js.map → chunk-OPD3PZOG.js.map} +0 -0
  254. /package/dist/{chunk-LS3JLEIB.js.map → chunk-PS5G6A3Y.js.map} +0 -0
  255. /package/dist/{chunk-KYKMKLJ6.js.map → chunk-PX3MJ6RB.js.map} +0 -0
  256. /package/dist/{chunk-FCDO7UAO.js.map → chunk-R4LTCI6O.js.map} +0 -0
  257. /package/dist/{chunk-UVPGJXVO.js.map → chunk-SGSHQ4PH.js.map} +0 -0
  258. /package/dist/{chunk-6S3LLAQ5.js.map → chunk-TNBIWSQ7.js.map} +0 -0
  259. /package/dist/{chunk-GD3BGKAR.js.map → chunk-UGVDIOY7.js.map} +0 -0
  260. /package/dist/{chunk-FS7A4XNF.js.map → chunk-WEA4TDTJ.js.map} +0 -0
  261. /package/dist/{chunk-QAU5HM6Q.js.map → chunk-XVJFFGTG.js.map} +0 -0
  262. /package/dist/{chunk-2XLVPKXG.js.map → chunk-YJ46RFCD.js.map} +0 -0
  263. /package/dist/{chunk-KMI2NBBF.js.map → chunk-YZ6JETII.js.map} +0 -0
  264. /package/dist/{chunk-GAUBWHAF.js.map → chunk-ZQMYB56Z.js.map} +0 -0
  265. /package/dist/{crypto-H2Y3DDFW.js.map → crypto-5UDZZL26.js.map} +0 -0
  266. /package/dist/{delegation-QSC7G5QC.js.map → delegation-42LO4WFO.js.map} +0 -0
  267. /package/dist/{executor-BZKFZVRC.js.map → executor-AWCHQ2KN.js.map} +0 -0
  268. /package/dist/{executor-GFZFDQXV.js.map → executor-RWICJI7J.js.map} +0 -0
  269. /package/dist/{executor-KT2IOZVP.js.map → executor-SOLEQVUB.js.map} +0 -0
  270. /package/dist/{issue-BAJ7ZB4S.js.map → issue-IODMTPME.js.map} +0 -0
  271. /package/dist/{ledger-WOEJUYTP.js.map → ledger-UX4QIHWI.js.map} +0 -0
  272. /package/dist/{noydb-XNQSKXGO.js.map → noydb-6TADQIYH.js.map} +0 -0
  273. /package/dist/{public-envelope-OHQ5UZFM.js.map → public-envelope-YKHKP74C.js.map} +0 -0
  274. /package/dist/{registry-2IEARCGT.js.map → registry-446I2NMN.js.map} +0 -0
  275. /package/dist/{registry-CDHASH73.js.map → registry-4NEW7LQY.js.map} +0 -0
  276. /package/dist/{registry-EMGLZGR6.js.map → registry-524KJZG4.js.map} +0 -0
  277. /package/dist/{registry-NQALYR77.js.map → registry-DKEXOJVO.js.map} +0 -0
  278. /package/dist/{revoke-7JOVLZFD.js.map → revoke-R5NIQ74J.js.map} +0 -0
  279. /package/dist/{signer-M4K5HBLD.js.map → signer-WGDJNWSU.js.map} +0 -0
  280. /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
@@ -10028,7 +10028,7 @@ var Query = class _Query {
10028
10028
  /**
10029
10029
  * @internal — clone this Query with a declared-predicate map
10030
10030
  * attached. Used by the materialized-view registry to enable
10031
- * `.wherePredicate(name, ctx?)` for the MV's query callback (#153).
10031
+ * `.wherePredicate(name, ctx?)` for the MV's query callback.
10032
10032
  * Consumers don't call this directly.
10033
10033
  */
10034
10034
  _withPredicates(predicates) {
@@ -10041,7 +10041,7 @@ var Query = class _Query {
10041
10041
  );
10042
10042
  }
10043
10043
  /**
10044
- * Filter by a registered deterministic predicate (#153). Requires
10044
+ * Filter by a registered deterministic predicate. Requires
10045
10045
  * the Query to have been augmented with a predicates map (typically
10046
10046
  * via the materialized-view registry — bare Queries constructed
10047
10047
  * outside an MV throw on `.wherePredicate()`).
@@ -11695,7 +11695,7 @@ var NO_BLOBS = {
11695
11695
  init_errors();
11696
11696
  init_ulid();
11697
11697
  var TxContext = class {
11698
- /** Stable id for this transaction; shared by all writes it performs (#230). */
11698
+ /** Stable id for this transaction; shared by all writes it performs. */
11699
11699
  txId = generateULID();
11700
11700
  /** @internal */
11701
11701
  _ops = [];
@@ -11705,7 +11705,7 @@ var TxContext = class {
11705
11705
  * restore prior state via `revertExecuted`. Side-effect writes (e.g.
11706
11706
  * recursive derivation outputs fired inside `Collection.put`) are
11707
11707
  * appended here in execution order so they roll back alongside the
11708
- * main staged ops (#133).
11708
+ * main staged ops.
11709
11709
  */
11710
11710
  _executed = [];
11711
11711
  /** @internal */
@@ -12027,7 +12027,7 @@ async function resolveStaleOnRead(accessor, outputCollection, id) {
12027
12027
  }
12028
12028
  if (out.kind === "array") {
12029
12029
  console.warn(
12030
- `[derivation] unexpected array-shape output "${key}" in lazy resolve path; array-shape derivations require lifecycle: "eager" (#200 slice 1).`
12030
+ `[derivation] unexpected array-shape output "${key}" in lazy resolve path; array-shape derivations require lifecycle: "eager".`
12031
12031
  );
12032
12032
  continue;
12033
12033
  }
@@ -12065,6 +12065,7 @@ var Collection = class {
12065
12065
  schemaUpdateGate;
12066
12066
  schemaFence;
12067
12067
  writeHooks;
12068
+ subsystemBus;
12068
12069
  activeTxId;
12069
12070
  getDEK;
12070
12071
  onDirty;
@@ -12253,42 +12254,14 @@ var Collection = class {
12253
12254
  syncAdapter;
12254
12255
  /** — consent-audit hook, no-op when no scope is active. */
12255
12256
  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
12257
  /**
12283
12258
  * Vault-internal hook for derivation dispatch. When set,
12284
12259
  * `Collection.put` consults the registry after the source-write
12285
12260
  * commits and writes derived outputs through `getCollection(name).put`.
12286
- * Same structural-interface pattern as `guardSource` to avoid a
12287
- * circular Vault import.
12288
12261
  */
12289
12262
  derivationSource;
12290
12263
  /**
12291
- * Vault-internal hook for materialized-view dispatch (#143/#150).
12264
+ * Vault-internal hook for materialized-view dispatch.
12292
12265
  * Parallel to `derivationSource` — when set, `Collection.put` fires
12293
12266
  * `MaterializedViewRegistry.onSourceWrite` after the source-write
12294
12267
  * commits + after `dispatchDerivations` has run.
@@ -12344,6 +12317,7 @@ var Collection = class {
12344
12317
  this.schemaUpdateGate = opts.schemaUpdateGate;
12345
12318
  this.schemaFence = opts.schemaFence;
12346
12319
  this.writeHooks = opts.writeHooks;
12320
+ this.subsystemBus = opts.subsystemBus;
12347
12321
  this.activeTxId = opts.activeTxId;
12348
12322
  this.blobStrategy = opts.blobStrategy ?? NO_BLOBS;
12349
12323
  this.aggregateStrategy = opts.aggregateStrategy ?? NO_AGGREGATE;
@@ -12368,8 +12342,6 @@ var Collection = class {
12368
12342
  this.crdtMode = opts.crdt;
12369
12343
  this.syncAdapter = opts.syncAdapter;
12370
12344
  this.onAccess = opts.onAccess;
12371
- this.periodGuard = opts.periodGuard;
12372
- this.guardSource = opts.guardSource;
12373
12345
  this.derivationSource = opts.derivationSource;
12374
12346
  this.materializedViewSource = opts.materializedViewSource;
12375
12347
  this.tiers = opts.tiers && opts.tiers.length > 0 ? new Set(opts.tiers) : null;
@@ -12571,21 +12543,23 @@ var Collection = class {
12571
12543
  }
12572
12544
  /**
12573
12545
  * Create or update a record. Runs inside the hub's write-queue tracker
12574
- * (#227) so `hub.writeQueue.pending` reflects this write.
12546
+ * so `hub.writeQueue.pending` reflects this write.
12575
12547
  *
12576
12548
  * @param id Record identifier.
12577
12549
  * @param record The record body (validated by the collection's schema
12578
12550
  * if one was attached at `vault.collection(...)` time).
12579
12551
  * @param options Optional metadata for audit + import workflows.
12580
12552
  * `reason` is stamped onto the resulting ledger entry
12581
- * (see #1) so audit consumers can filter via
12553
+ * so audit consumers can filter via
12582
12554
  * `entries.filter(e => e.reason?.startsWith('import:'))`.
12583
12555
  */
12584
12556
  async put(id, record, options) {
12585
12557
  await this.schemaUpdateGate?.assertWritable();
12586
12558
  await this.schemaFence?.assertWritable(this.name);
12559
+ const hooksActive = this.#hooksActive();
12560
+ const busAfterPut = (this.subsystemBus?.hasHandlers("afterPut") ?? false) && !(this.subsystemBus?.dispatching ?? false);
12587
12561
  let event;
12588
- if (this.#hooksActive()) {
12562
+ if (hooksActive || busAfterPut) {
12589
12563
  const prior = await this.#priorForHook(id);
12590
12564
  event = {
12591
12565
  op: prior.record === null ? "create" : "update",
@@ -12600,23 +12574,26 @@ var Collection = class {
12600
12574
  baseVersion: prior.version,
12601
12575
  version: prior.version + 1
12602
12576
  };
12603
- await this.writeHooks.runBefore(event);
12577
+ if (hooksActive) await this.writeHooks.runBefore(event);
12604
12578
  }
12605
12579
  if (this.writeQueue) await this.writeQueue.track(() => this.putInternal(id, record, options));
12606
12580
  else await this.putInternal(id, record, options);
12607
- if (event) await this.writeHooks.runAfter(event);
12581
+ if (event) {
12582
+ if (hooksActive) await this.writeHooks.runAfter(event);
12583
+ if (busAfterPut) await this.subsystemBus.dispatch("afterPut", event);
12584
+ }
12608
12585
  }
12609
- /** @internal #230 — true when hooks should fire for this write (handlers exist, not re-entrant). */
12586
+ /** @internal — true when hooks should fire for this write (handlers exist, not re-entrant). */
12610
12587
  #hooksActive() {
12611
12588
  return this.writeHooks !== void 0 && this.writeHooks.hasHandlers && !this.writeHooks.suppressed;
12612
12589
  }
12613
12590
  /**
12614
- * @internal #230/#228c — resolve the prior record for a hook's `before` and
12591
+ * @internal — resolve the prior record for a hook's `before` and
12615
12592
  * its version. Critically, this uses the SAME basis `putInternal` writes from
12616
12593
  * (the in-memory cache in eager mode; lru-then-adapter in lazy) — NOT a fresh
12617
12594
  * store read — so `baseVersion`/`version` match the version actually written.
12618
12595
  * A separate store read would diverge once another tab has advanced the shared
12619
- * store past this tab's cache, breaking #228c conflict detection.
12596
+ * store past this tab's cache, breaking cross-tab conflict detection.
12620
12597
  */
12621
12598
  async #priorForHook(id) {
12622
12599
  if (this.lazy && this.lru) {
@@ -12638,52 +12615,28 @@ var Collection = class {
12638
12615
  if (!hasWritePermission(this.keyring, this.name)) {
12639
12616
  throw new ReadOnlyError();
12640
12617
  }
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) {
12618
+ if (this.subsystemBus?.hasGateHandlers("beforePut")) {
12674
12619
  const existingEnv = await this.adapter.get(this.vault, this.name, id);
12675
- let priorRecord = null;
12620
+ let existingRecord = null;
12676
12621
  if (existingEnv) {
12677
12622
  try {
12678
- priorRecord = await this.decryptRecord(existingEnv, { skipValidation: true });
12623
+ existingRecord = await this.decryptRecord(existingEnv, { skipValidation: true });
12679
12624
  } catch {
12680
- priorRecord = null;
12625
+ existingRecord = null;
12681
12626
  }
12682
12627
  }
12683
- await this.periodGuard(
12684
- existingEnv ? { ts: existingEnv._ts, record: priorRecord } : null,
12685
- record
12686
- );
12628
+ await this.subsystemBus.dispatchGate("beforePut", {
12629
+ op: existingEnv ? "update" : "create",
12630
+ vault: this.vault,
12631
+ collection: this.name,
12632
+ docId: id,
12633
+ incoming: record,
12634
+ existing: existingRecord,
12635
+ existingVersion: existingEnv?._v ?? 0,
12636
+ existingTs: existingEnv?._ts,
12637
+ userId: this.keyring.userId,
12638
+ role: this.keyring.role
12639
+ });
12687
12640
  }
12688
12641
  if (this.schema !== void 0) {
12689
12642
  record = await validateSchemaInput(this.schema, record, `put(${id})`);
@@ -12869,7 +12822,7 @@ var Collection = class {
12869
12822
  * Fire registered MV strategies whose dependency set includes this
12870
12823
  * collection. Eager-mode MVs re-materialize inline via
12871
12824
  * `MaterializedViewExecutor.refresh`; lazy / manual modes are
12872
- * no-ops in the foundation (subtask #150) wired in #151.
12825
+ * no-ops in the foundation; wired in the lazy-mode implementation.
12873
12826
  *
12874
12827
  * Skips entirely when the record being written is itself an
12875
12828
  * MV-emitted row (carries `_materializedFrom`) — defensive guard
@@ -13013,13 +12966,15 @@ var Collection = class {
13013
12966
  }
13014
12967
  /**
13015
12968
  * Delete a record by ID. Runs inside the hub's write-queue tracker
13016
- * (#227) so `hub.writeQueue.pending` reflects this write.
12969
+ * so `hub.writeQueue.pending` reflects this write.
13017
12970
  */
13018
12971
  async delete(id) {
13019
12972
  await this.schemaUpdateGate?.assertWritable();
13020
12973
  await this.schemaFence?.assertWritable(this.name);
12974
+ const hooksActive = this.#hooksActive();
12975
+ const busAfterDelete = (this.subsystemBus?.hasHandlers("afterDelete") ?? false) && !(this.subsystemBus?.dispatching ?? false);
13021
12976
  let event;
13022
- if (this.#hooksActive()) {
12977
+ if (hooksActive || busAfterDelete) {
13023
12978
  const prior = await this.#priorForHook(id);
13024
12979
  event = {
13025
12980
  op: "delete",
@@ -13034,14 +12989,17 @@ var Collection = class {
13034
12989
  baseVersion: prior.version,
13035
12990
  version: prior.version + 1
13036
12991
  };
13037
- await this.writeHooks.runBefore(event);
12992
+ if (hooksActive) await this.writeHooks.runBefore(event);
13038
12993
  }
13039
12994
  if (this.writeQueue) await this.writeQueue.track(() => this.deleteInternal(id));
13040
12995
  else await this.deleteInternal(id);
13041
- if (event) await this.writeHooks.runAfter(event);
12996
+ if (event) {
12997
+ if (hooksActive) await this.writeHooks.runAfter(event);
12998
+ if (busAfterDelete) await this.subsystemBus.dispatch("afterDelete", event);
12999
+ }
13042
13000
  }
13043
13001
  /**
13044
- * @internal #232 — bulk-rewrite every record through a cutover transform.
13002
+ * @internal — bulk-rewrite every record through a cutover transform.
13045
13003
  * Raw adapter path (bypasses the write gate + guards — the transform is
13046
13004
  * trusted and runs only during the `migrating` phase). Bumps each
13047
13005
  * record's `_v` and appends a ledger `op:'migration'` entry.
@@ -13080,8 +13038,7 @@ var Collection = class {
13080
13038
  }
13081
13039
  /**
13082
13040
  * @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
13041
+ * delete hooks (`onDelete`, FK ref enforcer). Used by derivation tombstones and MV refresh
13085
13042
  * (Dim 14 v2) — system housekeeping shouldn't trip user invariants
13086
13043
  * registered against the output collection. The ledger entry and
13087
13044
  * history snapshot still fire so backup integrity and time-travel
@@ -13093,7 +13050,7 @@ var Collection = class {
13093
13050
  *
13094
13051
  * When a `txCtx` is supplied, the prior envelope is captured and
13095
13052
  * pushed onto `txCtx._executed` BEFORE the delete fires — mirrors
13096
- * the #133 rollback hardening for puts. Callers outside a
13053
+ * the rollback hardening for puts. Callers outside a
13097
13054
  * multi-record transaction pass `null` and skip the tracking.
13098
13055
  *
13099
13056
  * Amendment composition: if `_internalDelete` runs while a vault's
@@ -13136,58 +13093,27 @@ var Collection = class {
13136
13093
  if (!hasWritePermission(this.keyring, this.name)) {
13137
13094
  throw new ReadOnlyError();
13138
13095
  }
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) {
13096
+ if (this.subsystemBus?.hasGateHandlers("beforeDelete")) {
13178
13097
  const existingEnv = await this.adapter.get(this.vault, this.name, id);
13179
- let priorRecord = null;
13180
13098
  if (existingEnv) {
13099
+ let existingRecord = null;
13181
13100
  try {
13182
- priorRecord = await this.decryptRecord(existingEnv, { skipValidation: true });
13101
+ existingRecord = await this.decryptRecord(existingEnv, { skipValidation: true });
13183
13102
  } catch {
13184
- priorRecord = null;
13103
+ existingRecord = null;
13185
13104
  }
13105
+ await this.subsystemBus.dispatchGate("beforeDelete", {
13106
+ vault: this.vault,
13107
+ collection: this.name,
13108
+ docId: id,
13109
+ existing: existingRecord,
13110
+ existingVersion: existingEnv._v,
13111
+ existingTs: existingEnv._ts,
13112
+ internal,
13113
+ userId: this.keyring.userId,
13114
+ role: this.keyring.role
13115
+ });
13186
13116
  }
13187
- await this.periodGuard(
13188
- existingEnv ? { ts: existingEnv._ts, record: priorRecord } : null,
13189
- null
13190
- );
13191
13117
  }
13192
13118
  if (!internal && this.refEnforcer !== void 0) {
13193
13119
  await this.refEnforcer.enforceRefsOnDelete(this.name, id);
@@ -13248,7 +13174,7 @@ var Collection = class {
13248
13174
  }
13249
13175
  /**
13250
13176
  * Cascade deletes of array-shape derived rows when a source row is
13251
- * deleted (#200). Reads each registered strategy's fanout sidecar
13177
+ * deleted. Reads each registered strategy's fanout sidecar
13252
13178
  * for this source id, deletes every listed derived row, then
13253
13179
  * deletes the sidecar itself.
13254
13180
  *
@@ -13287,8 +13213,8 @@ var Collection = class {
13287
13213
  }
13288
13214
  }
13289
13215
  /**
13290
- * Mirror of {@link dispatchMaterializedViews} for the delete path
13291
- * (#181). No record content is available (it's gone), so the
13216
+ * Mirror of {@link dispatchMaterializedViews} for the delete path.
13217
+ * No record content is available (it's gone), so the
13292
13218
  * `_materializedFrom` skip used by the put-side dispatch doesn't
13293
13219
  * apply here — instead, the recursion guard is the `internal` gate
13294
13220
  * at the `_doDelete` call site above.
@@ -13818,7 +13744,7 @@ var Collection = class {
13818
13744
  * .aggregate({ total: sum('amount'), n: count() })
13819
13745
  * ```
13820
13746
  *
13821
- * **Lazy-MV gap (#157):** `scan()` is synchronous-build and does
13747
+ * **Lazy-MV gap:** `scan()` is synchronous-build and does
13822
13748
  * NOT trigger lazy materialized-view resolve-on-read. For lazy
13823
13749
  * MVs, call `list()` (which DOES resolve) or `vault.refreshView(name)`
13824
13750
  * before scanning. Same shape as the `query()` limitation.
@@ -13899,7 +13825,7 @@ var Collection = class {
13899
13825
  this.indexes?.upsert(id, record, previous ? previous.record : null);
13900
13826
  }
13901
13827
  /**
13902
- * #228b — apply a peer tab's committed write to THIS tab's in-memory view:
13828
+ * Apply a peer tab's committed write to THIS tab's in-memory view:
13903
13829
  * re-read the (already-persisted) envelope from the shared store + refresh
13904
13830
  * cache/indexes, then emit a `change` event so reactive consumers re-render.
13905
13831
  * Never writes to the store and never fires write hooks, so it cannot loop.
@@ -13908,7 +13834,7 @@ var Collection = class {
13908
13834
  await this._invalidateCacheEntry(id);
13909
13835
  this.emitter.emit("change", { vault: this.vault, collection: this.name, id, action });
13910
13836
  }
13911
- /** @internal #228c — the current in-memory record without a store read (for conflict capture). */
13837
+ /** @internal — the current in-memory record without a store read (for conflict capture). */
13912
13838
  _peekCached(id) {
13913
13839
  const entry = this.lazy && this.lru ? this.lru.get(id) : this.cache.get(id);
13914
13840
  return entry ? entry.record : null;
@@ -14959,7 +14885,7 @@ var OverlayedCollection = class {
14959
14885
  // error pointing at the relevant issue — so consumers don't hit a
14960
14886
  // cryptic `undefined is not a function` runtime crash.
14961
14887
  //
14962
- // Closes niwat-review of PR #160.
14888
+ // Throw-stubs so consumers get actionable errors rather than cryptic crashes.
14963
14889
  /** @throws — chainable Query<T> over a virtual collection is deferred. */
14964
14890
  query() {
14965
14891
  throw new Error(
@@ -16270,23 +16196,23 @@ var Vault = class {
16270
16196
  * `null` for vaults that never register any guard strategy. The
16271
16197
  * runtime class is dynamic-imported on demand so consumers that
16272
16198
  * never use guards don't pull `GuardRegistry`/`GuardExecutor` into
16273
- * their bundle (#130).
16199
+ * their bundle.
16274
16200
  */
16275
16201
  guardRegistry = null;
16276
16202
  /**
16277
16203
  * Per-vault derivation registry. Same lazy-load contract as
16278
16204
  * `guardRegistry` — `null` until `_initDerivations()` runs with at
16279
- * least one strategy handle. See #130 for the bundle motivation.
16205
+ * least one strategy handle.
16280
16206
  */
16281
16207
  derivationRegistry = null;
16282
16208
  /**
16283
- * Per-vault materialized-view registry (#143/#150). Same lazy-load
16209
+ * Per-vault materialized-view registry. Same lazy-load
16284
16210
  * contract as `derivationRegistry` — `null` until
16285
16211
  * `_initMaterializedViews()` runs with at least one MV handle.
16286
16212
  */
16287
16213
  materializedViewRegistry = null;
16288
16214
  /**
16289
- * Per-vault overlay registry (#154). Same lazy-load contract as
16215
+ * Per-vault overlay registry. Same lazy-load contract as
16290
16216
  * `materializedViewRegistry` — `null` until `_initOverlayedViews()`
16291
16217
  * runs with at least one handle.
16292
16218
  */
@@ -16307,7 +16233,7 @@ var Vault = class {
16307
16233
  * target this vault session's keyringId. There is no method to write
16308
16234
  * another principal's envelope (own-only write rule, structural).
16309
16235
  * - Read-anyone: `get(keyringId)`, `list()` — read other principals'
16310
- * envelopes, subject to the `view-team-profiles` policy gate (#22).
16236
+ * envelopes, subject to the `view-team-profiles` policy gate.
16311
16237
  * - Reactive: `subscribe(id, cb)`, `live(id)` — fire on local writes.
16312
16238
  *
16313
16239
  * @see docs/superpowers/specs/2026-05-05-user-envelope-design.md
@@ -16327,12 +16253,12 @@ var Vault = class {
16327
16253
  */
16328
16254
  reloadKeyring;
16329
16255
  collectionCache = /* @__PURE__ */ new Map();
16330
- /** #232 — vault-level schema cutover fence/controller. */
16256
+ /** Vault-level schema cutover fence/controller. */
16331
16257
  schemaFence;
16332
- /** #232 — per-client heartbeat/watcher; started lazily on cutover registration. */
16258
+ /** Per-client heartbeat/watcher; started lazily on cutover registration. */
16333
16259
  #fenceWatcher;
16334
16260
  #fenceCoordinationStarted = false;
16335
- /** #229 — per-collection registered schema-update strategy names. */
16261
+ /** Per-collection registered schema-update strategy names. */
16336
16262
  #schemaUpdateNames = /* @__PURE__ */ new Map();
16337
16263
  /**
16338
16264
  * per-collection `blobFields` retention/TTL config.
@@ -16406,8 +16332,7 @@ var Vault = class {
16406
16332
  * Cache of closed/opened accounting periods.
16407
16333
  * Populated on first `closePeriod` / `openPeriod` / `listPeriods` /
16408
16334
  * 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.
16335
+ * `closedAt`) so period checks run fast when the gate bus fires.
16411
16336
  *
16412
16337
  * Sentinel `null` means "not yet loaded" — the first consumer
16413
16338
  * triggers a one-time `loadPeriods()` pass. Every subsequent
@@ -16602,6 +16527,7 @@ var Vault = class {
16602
16527
  emitter: this.emitter,
16603
16528
  writeQueue: this.noydb._writeQueueTracker,
16604
16529
  writeHooks: this.noydb._writeHooks,
16530
+ subsystemBus: this.noydb._subsystemBus,
16605
16531
  activeTxId: () => this.noydb._activeTxContextOrNull?.txId ?? null,
16606
16532
  schemaUpdateGate,
16607
16533
  schemaFence: this.schemaFence,
@@ -16624,18 +16550,12 @@ var Vault = class {
16624
16550
  defaultLocale: this.locale,
16625
16551
  onRegisterConflictResolver: this.onRegisterConflictResolver,
16626
16552
  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
- } : {},
16553
+ // Derivation source is only wired when the corresponding registry
16554
+ // has been initialised. Guard source was removed in Track A slice 3b
16555
+ // guards now run via the gate bus in Noydb.#registerGuardGate.
16556
+ // Vaults without derivations skip this so `Collection.put`'s
16557
+ // `if (this.derivationSource)` branch no-ops without touching the
16558
+ // derivation subsystem code.
16639
16559
  ...this.derivationRegistry !== null ? {
16640
16560
  derivationSource: {
16641
16561
  registry: () => this.derivationRegistry,
@@ -16725,7 +16645,7 @@ var Vault = class {
16725
16645
  await Promise.allSettled(pending);
16726
16646
  }
16727
16647
  /**
16728
- * Run a coordinated schema cutover (#232). Drains pending writes, waits
16648
+ * Run a coordinated schema cutover. Drains pending writes, waits
16729
16649
  * for the active client set to quiesce (the ack-barrier), applies every
16730
16650
  * pending collection transform in bulk, bumps the vault schema generation,
16731
16651
  * and clears the fence. Returns the count of collections migrated.
@@ -16743,9 +16663,9 @@ var Vault = class {
16743
16663
  await coll._applyCutoverTransform(transform);
16744
16664
  }
16745
16665
  /**
16746
- * #228b — refresh a loaded collection's view of one document from a peer
16666
+ * Refresh a loaded collection's view of one document from a peer
16747
16667
  * 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.
16668
+ * (it will read fresh on next open). Mirrors `#runCutoverTransform`'s guard.
16749
16669
  */
16750
16670
  async _applyRemoteWrite(collectionName, docId, action) {
16751
16671
  const coll = this.collectionCache.get(collectionName);
@@ -16753,9 +16673,9 @@ var Vault = class {
16753
16673
  await coll._applyRemoteChange(docId, action);
16754
16674
  }
16755
16675
  /**
16756
- * #228c — for a detected conflict: capture this tab's clobbered record,
16676
+ * For a detected conflict: capture this tab's clobbered record,
16757
16677
  * 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
16678
+ * authoritative value (the re-read), and return all three for the
16759
16679
  * WriteConflict payload. Returns null when the collection isn't loaded.
16760
16680
  */
16761
16681
  async _captureAndConverge(collectionName, docId, action, baseV) {
@@ -16772,15 +16692,15 @@ var Vault = class {
16772
16692
  const remote = await coll.get(docId);
16773
16693
  return { local, remote, base };
16774
16694
  }
16775
- /** Recover a stuck cutover fence (#232) — reset to normal without bumping. */
16695
+ /** Recover a stuck cutover fence — reset to normal without bumping. */
16776
16696
  async abortSchemaCutover() {
16777
16697
  await this.schemaFence.abort();
16778
16698
  }
16779
- /** Current schema-cutover fence state for this vault (#232/#233). Thin live read. */
16699
+ /** Current schema-cutover fence state for this vault. Thin live read. */
16780
16700
  async schemaFenceState() {
16781
16701
  return loadFence(this.adapter, this.name);
16782
16702
  }
16783
- /** @internal Start the per-client heartbeat + fence watcher once a cutover is registered (#232). */
16703
+ /** @internal Start the per-client heartbeat + fence watcher once a cutover is registered. */
16784
16704
  _ensureFenceCoordination() {
16785
16705
  if (this.#fenceCoordinationStarted) return;
16786
16706
  this.#fenceCoordinationStarted = true;
@@ -17453,7 +17373,7 @@ var Vault = class {
17453
17373
  * Dynamic-imports `GuardRegistry` + `ReadOnlyVaultFacade` and seeds
17454
17374
  * the registry with the supplied strategy handles. No-op when the
17455
17375
  * handles array is empty — keeps the guard subsystem out of the
17456
- * floor bundle for consumers that don't use guards (#130).
17376
+ * floor bundle for consumers that don't use guards.
17457
17377
  *
17458
17378
  * The read-only facade is eagerly instantiated here so the sync
17459
17379
  * accessor `_getReadOnlyFacade()` (called from the tx amendment
@@ -17471,10 +17391,9 @@ var Vault = class {
17471
17391
  this.readOnlyFacade = new ReadOnlyVaultFacade2(this);
17472
17392
  }
17473
17393
  /**
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).
17394
+ * @internal — The gate handler in Noydb.#registerGuardGate calls into
17395
+ * this. Returns `null` for vaults that never registered any guard
17396
+ * strategy. Callers MUST gate on null.
17478
17397
  */
17479
17398
  _getGuardRegistry() {
17480
17399
  return this.guardRegistry;
@@ -17485,7 +17404,7 @@ var Vault = class {
17485
17404
  * derivation strategies (async because `strategyHash` computation
17486
17405
  * goes through `crypto.subtle.digest`). No-op when the handles
17487
17406
  * array is empty — keeps the derivation subsystem out of the floor
17488
- * bundle for consumers that don't use derivations (#130). Throws
17407
+ * bundle for consumers that don't use derivations. Throws
17489
17408
  * `DerivationCycleError` if a cycle is detected after registration.
17490
17409
  */
17491
17410
  async _initDerivations(handles) {
@@ -17517,7 +17436,7 @@ var Vault = class {
17517
17436
  * MV spec (which invokes its `query()` once for dependency
17518
17437
  * analysis), then runs the unified cycle detection across the MV +
17519
17438
  * derivation graphs. No-op when the handles array is empty — keeps
17520
- * the MV subsystem out of the floor bundle (mirrors v1 #130).
17439
+ * the MV subsystem out of the floor bundle (mirrors the derivation lazy-import pattern).
17521
17440
  * Throws `MaterializedViewCycleError` if a cycle is detected.
17522
17441
  */
17523
17442
  async _initMaterializedViews(handles) {
@@ -17574,13 +17493,13 @@ var Vault = class {
17574
17493
  return this.overlayedViewRegistry;
17575
17494
  }
17576
17495
  /**
17577
- * Manual re-materialize for a single registered MV (#151). Useful
17496
+ * Manual re-materialize for a single registered MV. Useful
17578
17497
  * for `refresh: 'manual'` MVs (whose consumer drives refreshes
17579
17498
  * externally), for stale-bit recovery on vault re-open, and as the
17580
17499
  * explicit bulk-recompute escape hatch after a strategy change.
17581
17500
  *
17582
- * Returns `{ written, deleted, failed }`. `deleted` is always 0 in
17583
- * foundation + this sub-issue — tombstoning lands in #152.
17501
+ * Returns `{ written, deleted, failed }`. `deleted` is always 0
17502
+ * when tombstoning is not enabled.
17584
17503
  *
17585
17504
  * Throws if `name` is not a registered MV.
17586
17505
  */
@@ -17676,22 +17595,19 @@ var Vault = class {
17676
17595
  /**
17677
17596
  * @internal — exposed for `runTransaction({ amendment: true })` so
17678
17597
  * 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).
17598
+ * facade that the gate handler in Noydb.#registerGuardGate uses.
17599
+ * Eagerly instantiated by `_initGuards()` so this accessor stays
17600
+ * synchronous; returns `null` for vaults that never registered any
17601
+ * guard (amendments require at least one guard, so the caller should
17602
+ * never see null).
17684
17603
  */
17685
17604
  _getReadOnlyFacade() {
17686
17605
  return this.readOnlyFacade;
17687
17606
  }
17688
17607
  /**
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").
17608
+ * Internal lazy-allocator for the read-only facade. Used as a
17609
+ * defensive fallback; in practice `_initGuards()` eagerly
17610
+ * instantiates this, so the lazy path is a no-op.
17695
17611
  */
17696
17612
  _ensureReadOnlyFacade() {
17697
17613
  if (this.readOnlyFacade !== null) return this.readOnlyFacade;
@@ -18146,7 +18062,7 @@ var Vault = class {
18146
18062
  const all = await this._loadPeriodsCache();
18147
18063
  return all.find((p) => p.name === name) ?? null;
18148
18064
  }
18149
- /** @internal — periodGuard callback installed on every Collection. */
18065
+ /** @internal — called by the gate bus before put/delete. */
18150
18066
  async _assertTsWritable(existing, incoming) {
18151
18067
  if (existing === null && incoming === null) return;
18152
18068
  if (this.periodCache === null) {
@@ -18234,7 +18150,7 @@ var Vault = class {
18234
18150
  return dumpVaultSchema(this, opts);
18235
18151
  }
18236
18152
  /**
18237
- * Lightweight read of the vault's registered schema (#229): collections
18153
+ * Lightweight read of the vault's registered schema: collections
18238
18154
  * (+ doc counts), guards, materialized views, schema-update strategies,
18239
18155
  * and the unlocked user's grants. Cheap — one `adapter.list` per
18240
18156
  * collection, no decryption. For a full snapshot + stats use dumpSchema().
@@ -19021,6 +18937,110 @@ var WriteHookRegistry = class {
19021
18937
  }
19022
18938
  };
19023
18939
 
18940
+ // src/subsystem-bus.ts
18941
+ var SubsystemBus = class {
18942
+ #handlers = /* @__PURE__ */ new Map();
18943
+ #gateHandlers = /* @__PURE__ */ new Map();
18944
+ #depth = 0;
18945
+ /** Register a handler for an observe point. Returns an unsubscribe fn. */
18946
+ register(point, handler) {
18947
+ let arr = this.#handlers.get(point);
18948
+ if (!arr) {
18949
+ arr = [];
18950
+ this.#handlers.set(point, arr);
18951
+ }
18952
+ arr.push(handler);
18953
+ return () => {
18954
+ const a = this.#handlers.get(point);
18955
+ if (!a) return;
18956
+ const i = a.indexOf(handler);
18957
+ if (i >= 0) a.splice(i, 1);
18958
+ };
18959
+ }
18960
+ /** Cheap gate for the write path — true when any handler is registered for the point. */
18961
+ hasHandlers(point) {
18962
+ const a = this.#handlers.get(point);
18963
+ return a !== void 0 && a.length > 0;
18964
+ }
18965
+ /**
18966
+ * True while one or more dispatches are in flight. Backed by a depth counter
18967
+ * so that two concurrent async dispatches (`Promise.all([put('a'), put('b')])`
18968
+ * each captured `busAfterPut=true` at their respective put() tops while depth
18969
+ * was 0) both proceed independently — the counter stays > 0 until BOTH finish,
18970
+ * so any nested write attempted by a handler still sees `dispatching === true`
18971
+ * and is suppressed by the write-path gate in `collection.ts`
18972
+ * (`busAfterPut = hasHandlers('afterPut') && !dispatching`). Re-entrancy
18973
+ * suppression lives exclusively on that write-path gate; concurrent independent
18974
+ * dispatches must not drop each other's events.
18975
+ */
18976
+ get dispatching() {
18977
+ return this.#depth > 0;
18978
+ }
18979
+ /**
18980
+ * Dispatch in registration order, awaited. Per-handler errors are warned, not
18981
+ * thrown — an observe handler must never abort a completed write. A
18982
+ * re-entrancy guard suppresses nested firing so a handler that itself writes
18983
+ * cannot loop (same rationale as WriteHookRegistry.#suppressed).
18984
+ */
18985
+ async dispatch(point, event) {
18986
+ const a = this.#handlers.get(point);
18987
+ if (!a || a.length === 0) return;
18988
+ this.#depth++;
18989
+ try {
18990
+ for (const h of a.slice()) {
18991
+ try {
18992
+ await h(event);
18993
+ } catch (err) {
18994
+ console.warn(
18995
+ `[noy-db] subsystem observe handler failed at ${point}: ` + (err instanceof Error ? err.message : String(err))
18996
+ );
18997
+ }
18998
+ }
18999
+ } finally {
19000
+ this.#depth--;
19001
+ }
19002
+ }
19003
+ /** Register a write-gating handler. A throw from the handler ABORTS the write. Returns an unsubscribe fn. */
19004
+ registerGate(point, handler) {
19005
+ let arr = this.#gateHandlers.get(point);
19006
+ if (!arr) {
19007
+ arr = [];
19008
+ this.#gateHandlers.set(point, arr);
19009
+ }
19010
+ arr.push(handler);
19011
+ return () => {
19012
+ const a = this.#gateHandlers.get(point);
19013
+ if (!a) return;
19014
+ const i = a.indexOf(handler);
19015
+ if (i >= 0) a.splice(i, 1);
19016
+ };
19017
+ }
19018
+ /** Cheap gate for the write path — true when any gate handler is registered for the point. */
19019
+ hasGateHandlers(point) {
19020
+ const a = this.#gateHandlers.get(point);
19021
+ return a !== void 0 && a.length > 0;
19022
+ }
19023
+ /**
19024
+ * Run gate handlers in registration order, awaited. Unlike `dispatch`
19025
+ * (observe), a handler throw is NOT swallowed — it PROPAGATES, aborting the
19026
+ * write before it reaches the store. The first throw stops the remaining
19027
+ * handlers (fail-fast). This is the seam guards/periods migrate onto.
19028
+ *
19029
+ * Note: gate handlers are validators that read, not write. A gate handler
19030
+ * that writes back into the same collection would re-enter the write path
19031
+ * and re-dispatch this point; loop-suppression for that case is deferred to
19032
+ * the migration slice (contract: gate handlers must not perform writes that
19033
+ * re-trigger their own point).
19034
+ */
19035
+ async dispatchGate(point, event) {
19036
+ const a = this.#gateHandlers.get(point);
19037
+ if (!a || a.length === 0) return;
19038
+ for (const h of a.slice()) {
19039
+ await h(event);
19040
+ }
19041
+ }
19042
+ };
19043
+
19024
19044
  // src/tab-coordination.ts
19025
19045
  var TabCoordinator = class {
19026
19046
  tabId;
@@ -19336,9 +19356,9 @@ var PERSONAL_POLICY = Object.freeze({
19336
19356
  minTier: 1,
19337
19357
  enabled: true
19338
19358
  },
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.
19359
+ // rotate-recovery: deliberate paper-sheet regeneration
19360
+ // when the user remembers their passphrase. PERSONAL allows tier-1 —
19361
+ // knowing the passphrase is enough.
19342
19362
  "rotate-recovery": { minTier: 1 },
19343
19363
  "enroll-authenticator": { minTier: 1 },
19344
19364
  "remove-authenticator": { minTier: 1 },
@@ -19372,7 +19392,7 @@ var PERSONAL_POLICY = Object.freeze({
19372
19392
  minTier: 1,
19373
19393
  enabled: false
19374
19394
  },
19375
- // ─── User envelope gates (#22) ────────────────────────────────────
19395
+ // ─── User envelope gates ──────────────────────────────────────────
19376
19396
  // edit-own-profile: tier 3 floor — any active session can edit their
19377
19397
  // own profile/preferences. Tightening to require a TOTP for
19378
19398
  // profile changes is a one-line override.
@@ -19399,7 +19419,7 @@ var STRICT_POLICY = Object.freeze({
19399
19419
  minTier: 1,
19400
19420
  enabled: true
19401
19421
  },
19402
- // rotate-recovery (#121): STRICT requires an off-device factor —
19422
+ // rotate-recovery: STRICT requires an off-device factor —
19403
19423
  // rotating recovery is an off-site-trust event; a stolen unlocked
19404
19424
  // laptop must not be able to silently mint a new sheet for the
19405
19425
  // attacker. Matches the `peer-recover-user` STRICT default.
@@ -19472,7 +19492,7 @@ var STRICT_POLICY = Object.freeze({
19472
19492
  minTier: 1,
19473
19493
  enabled: false
19474
19494
  },
19475
- // ─── User envelope gates (#22) ────────────────────────────────────
19495
+ // ─── User envelope gates ──────────────────────────────────────────
19476
19496
  // STRICT: profile edits require a TOTP/email-OTP factor (typical
19477
19497
  // shared-workstation hardening — your name/avatar shouldn't change
19478
19498
  // without a fresh second-factor proof).
@@ -19583,13 +19603,14 @@ var Noydb = class {
19583
19603
  emitter = new NoydbEventEmitter();
19584
19604
  writeQueueTracker = new WriteQueueTracker();
19585
19605
  writeHooks = new WriteHookRegistry();
19606
+ subsystemBus = new SubsystemBus();
19586
19607
  clientId = generateULID();
19587
19608
  vaultCache = /* @__PURE__ */ new Map();
19588
19609
  keyringCache = /* @__PURE__ */ new Map();
19589
19610
  syncEngines = /* @__PURE__ */ new Map();
19590
19611
  /**
19591
19612
  * Per-vault active session tier — defaults to `1` after a passphrase
19592
- * unlock; tier-2 / tier-3 unlocks (issue #11) downgrade it. Used by
19613
+ * unlock; tier-2 / tier-3 unlocks downgrade it. Used by
19593
19614
  * {@link checkGate} to evaluate `gate.minTier`.
19594
19615
  */
19595
19616
  activeTier = /* @__PURE__ */ new Map();
@@ -19599,14 +19620,14 @@ var Noydb = class {
19599
19620
  */
19600
19621
  policyCache = /* @__PURE__ */ new Map();
19601
19622
  /**
19602
- * One-shot bypass for the managed-mode strong-recovery check (#195).
19623
+ * One-shot bypass for the managed-mode strong-recovery check.
19603
19624
  * Set true by {@link openVaultAndEnrollRecovery} for the duration of
19604
19625
  * the bootstrap window so the keyring can be created before the
19605
19626
  * strong recovery is enrolled. Always cleared (try/finally).
19606
19627
  * @internal
19607
19628
  */
19608
19629
  _skipNextManagedRecoveryCheck = false;
19609
- /** Per-vault tier-3 (PIN / quick-resume) state — issue #11. */
19630
+ /** Per-vault tier-3 (PIN / quick-resume) state. */
19610
19631
  quickUnlock = new QuickUnlockStore();
19611
19632
  /**
19612
19633
  * Resolved public-envelope schema. Lazily computed once from
@@ -19616,9 +19637,9 @@ var Noydb = class {
19616
19637
  publicEnvelopeSchema;
19617
19638
  closed = false;
19618
19639
  sessionTimer = null;
19619
- /** Same-device multi-tab coordinator (#228); created on `enableTabCoordination()`. */
19640
+ /** Same-device multi-tab coordinator; created on `enableTabCoordination()`. */
19620
19641
  tabCoordinator;
19621
- /** Cross-tab write relay (#228b); created on `enableTabCoordination()`. */
19642
+ /** Cross-tab write relay; created on `enableTabCoordination()`. */
19622
19643
  writeRelay;
19623
19644
  /** Per-vault policy enforcers. */
19624
19645
  policyEnforcers = /* @__PURE__ */ new Map();
@@ -19631,8 +19652,8 @@ var Noydb = class {
19631
19652
  * the same function's `finally` block. Side-effect writes triggered
19632
19653
  * during a staged op's `Collection.put` (today: eager derivation
19633
19654
  * 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.
19655
+ * a mid-batch failure rolls them back alongside the main staged ops.
19656
+ * `null` outside of Phase 2.
19636
19657
  * @internal
19637
19658
  */
19638
19659
  _activeTxContext = null;
@@ -19653,8 +19674,95 @@ var Noydb = class {
19653
19674
  if (options.sessionPolicy) {
19654
19675
  this.sessionStrategy.validateSessionPolicy(options.sessionPolicy);
19655
19676
  }
19677
+ this.#registerGuardGate();
19678
+ this.#registerPeriodGate();
19656
19679
  this.resetSessionTimer();
19657
19680
  }
19681
+ // Track A — guards migration. Registers record-lock / field-freeze / onDelete
19682
+ // / amendment-collect as gate-bus handlers (only when guards are opted in, so
19683
+ // the write path is zero-cost otherwise). Resolves the live vault's
19684
+ // GuardRegistry per dispatch. Registered BEFORE the period gate so guard
19685
+ // checks run first. The amendment branch is a side-effect (collectChange),
19686
+ // NOT a throw — and runs even for internal deletes (an amendment invariant
19687
+ // must see system housekeeping tombstones); onDelete/checks run only for
19688
+ // user (non-internal) operations.
19689
+ #registerGuardGate() {
19690
+ if (this.options.guardStrategies === void 0) return;
19691
+ this.subsystemBus.registerGate("beforePut", async (e) => {
19692
+ const v = this.vaultCache.get(e.vault);
19693
+ if (!v) return;
19694
+ const registry = v._getGuardRegistry();
19695
+ if (!registry) return;
19696
+ const guards = registry.guardsFor(e.collection);
19697
+ if (guards.length === 0) return;
19698
+ const existing = e.existing ?? null;
19699
+ const incoming = e.incoming;
19700
+ if (registry.isAmendmentActive()) {
19701
+ registry.collectChange(e.collection, e.docId, existing, incoming, e.existingVersion, e.existingVersion + 1);
19702
+ return;
19703
+ }
19704
+ const facade = v._getReadOnlyFacade();
19705
+ if (!facade) return;
19706
+ const ctx = { existing, vault: facade, userId: e.userId, role: e.role };
19707
+ await registry.runChecks(e.collection, incoming, ctx);
19708
+ const { GuardExecutor: GuardExecutor2 } = await Promise.resolve().then(() => (init_executor(), executor_exports));
19709
+ for (const g of guards) {
19710
+ await GuardExecutor2.checkFrozenFields(g, e.docId, existing, incoming);
19711
+ }
19712
+ });
19713
+ this.subsystemBus.registerGate("beforeDelete", async (e) => {
19714
+ const v = this.vaultCache.get(e.vault);
19715
+ if (!v) return;
19716
+ const registry = v._getGuardRegistry();
19717
+ if (!registry) return;
19718
+ const guards = registry.guardsFor(e.collection);
19719
+ if (guards.length === 0) return;
19720
+ const existing = e.existing ?? null;
19721
+ if (registry.isAmendmentActive()) {
19722
+ registry.collectChange(e.collection, e.docId, existing, null, e.existingVersion, e.existingVersion);
19723
+ return;
19724
+ }
19725
+ if (e.internal) return;
19726
+ const facade = v._getReadOnlyFacade();
19727
+ if (!facade) return;
19728
+ const ctx = { existing, vault: facade, userId: e.userId, role: e.role };
19729
+ await registry.runOnDelete(e.collection, existing ?? {}, ctx);
19730
+ });
19731
+ }
19732
+ /**
19733
+ * Register closed-period write guards on the subsystem bus when a
19734
+ * periodsStrategy is configured. Handlers resolve the live Vault from
19735
+ * vaultCache so they always use the up-to-date period cache.
19736
+ */
19737
+ // Track A — periods migration. Registers the closed-period write guard as a
19738
+ // gate-bus handler (only when periods is opted in, so the write path is
19739
+ // zero-cost otherwise). Each handler resolves the LIVE vault from the cache
19740
+ // per dispatch and delegates to its `_assertTsWritable`, which owns all
19741
+ // period logic. Resolving the live vault makes eviction/re-creation
19742
+ // transparent. Semantics note: if a write reaches the gate through a retained
19743
+ // collection handle whose vault has been evicted from `vaultCache` (e.g. a
19744
+ // post-revocation write on a stale handle), the period check is skipped — the
19745
+ // guard binds to the live vault, not a captured instance. Periods is a
19746
+ // write-integrity guard, not a security boundary, and a re-open reloads the
19747
+ // period cache; the trade-off is intentional.
19748
+ #registerPeriodGate() {
19749
+ if (this.options.periodsStrategy === void 0) return;
19750
+ this.subsystemBus.registerGate("beforePut", async (e) => {
19751
+ const v = this.vaultCache.get(e.vault);
19752
+ if (!v) return;
19753
+ const existing = e.op === "create" ? null : { ts: e.existingTs ?? null, record: e.existing ?? null };
19754
+ await v._assertTsWritable(existing, e.incoming);
19755
+ });
19756
+ this.subsystemBus.registerGate("beforeDelete", async (e) => {
19757
+ if (e.internal) return;
19758
+ const v = this.vaultCache.get(e.vault);
19759
+ if (!v) return;
19760
+ await v._assertTsWritable(
19761
+ { ts: e.existingTs ?? null, record: e.existing ?? null },
19762
+ null
19763
+ );
19764
+ });
19765
+ }
19658
19766
  resetSessionTimer() {
19659
19767
  if (this.sessionTimer) clearTimeout(this.sessionTimer);
19660
19768
  const idleMs = this.options.sessionPolicy?.idleTimeoutMs ?? this.options.sessionTimeout;
@@ -19944,8 +20052,6 @@ var Noydb = class {
19944
20052
  * @throws `NoAccessError` when no keyring exists for the target.
19945
20053
  * @throws `PermissionDeniedError` when the role hierarchy rejects.
19946
20054
  * @throws `ValidationError` when no field is provided.
19947
- *
19948
- * @see #54
19949
20055
  */
19950
20056
  async updateUser(vault, options, factors) {
19951
20057
  await this.checkGate(vault, "update-user", factors);
@@ -20281,7 +20387,7 @@ var Noydb = class {
20281
20387
  * Phase 2. `Collection.dispatchDerivations` consults this so a
20282
20388
  * recursive derived-output write inside `Collection.put` can register
20283
20389
  * its envelope onto `ctx._executed` and roll back with the main
20284
- * staged ops on mid-batch failure (#133).
20390
+ * staged ops on mid-batch failure.
20285
20391
  *
20286
20392
  * @internal
20287
20393
  */
@@ -20310,7 +20416,7 @@ var Noydb = class {
20310
20416
  * `Collection.putManyAtomic` (via `derivationSource.createTxContext`)
20311
20417
  * to publish an active context for the duration of its bulk-atomic
20312
20418
  * Phase 2 loop, so recursive derivation-output writes register on
20313
- * `ctx._executed` and roll back together with the source ops (#133).
20419
+ * `ctx._executed` and roll back together with the source ops.
20314
20420
  *
20315
20421
  * @internal
20316
20422
  */
@@ -20381,26 +20487,26 @@ var Noydb = class {
20381
20487
  return this.writeQueueTracker;
20382
20488
  }
20383
20489
  /**
20384
- * Register a hook that runs before each write (#230). Awaited; a throw
20490
+ * Register a hook that runs before each write. Awaited; a throw
20385
20491
  * aborts the write. Returns an unsubscribe function.
20386
20492
  */
20387
20493
  onBeforeWrite(handler) {
20388
20494
  return this.writeHooks.onBeforeWrite(handler);
20389
20495
  }
20390
20496
  /**
20391
- * Register a hook that runs after each committed write (#230). Awaited;
20497
+ * Register a hook that runs after each committed write. Awaited;
20392
20498
  * a handler error is warned, never rolled back. Returns an unsubscribe fn.
20393
20499
  */
20394
20500
  onAfterWrite(handler) {
20395
20501
  return this.writeHooks.onAfterWrite(handler);
20396
20502
  }
20397
- /** Subscribe to cross-tab write conflicts (#228c). Returns an unsubscribe. */
20503
+ /** Subscribe to cross-tab write conflicts. Returns an unsubscribe. */
20398
20504
  onWriteConflict(fn) {
20399
20505
  this.on("write:conflict", fn);
20400
20506
  return () => this.off("write:conflict", fn);
20401
20507
  }
20402
20508
  /**
20403
- * Enable same-device multi-tab coordination (#228): primary/secondary
20509
+ * Enable same-device multi-tab coordination: primary/secondary
20404
20510
  * election + presence. Browser-only — a graceful no-op (role 'unknown')
20405
20511
  * when Web Locks / BroadcastChannel are unavailable and nothing is
20406
20512
  * injected. Idempotent; returns a disposer.
@@ -20483,7 +20589,11 @@ var Noydb = class {
20483
20589
  get _writeHooks() {
20484
20590
  return this.writeHooks;
20485
20591
  }
20486
- /** @internal Stable per-instance id for schema-cutover coordination (#232). */
20592
+ /** @internal The observe bus, threaded into every Collection. */
20593
+ get _subsystemBus() {
20594
+ return this.subsystemBus;
20595
+ }
20596
+ /** @internal Stable per-instance id for schema-cutover coordination. */
20487
20597
  get _clientId() {
20488
20598
  return this.clientId;
20489
20599
  }
@@ -20503,10 +20613,6 @@ var Noydb = class {
20503
20613
  * survives lock; nothing about it changes when DEKs are scrubbed).
20504
20614
  *
20505
20615
  * No-op when `vault` is not currently in cache (idempotent).
20506
- *
20507
- * Unblocks vLannaAi/niwat#33.
20508
- *
20509
- * @see #17
20510
20616
  */
20511
20617
  lockVault(vault) {
20512
20618
  this.syncEngines.get(vault)?.stopAutoSync();
@@ -20621,7 +20727,7 @@ var Noydb = class {
20621
20727
  return merged;
20622
20728
  }
20623
20729
  /**
20624
- * Read the current vault-level user-directory toggle (#122). Returns
20730
+ * Read the current vault-level user-directory toggle. Returns
20625
20731
  * the default-on shape (`{ enabled: true }`) when no `_meta/directory`
20626
20732
  * document has been persisted yet.
20627
20733
  *
@@ -20633,7 +20739,7 @@ var Noydb = class {
20633
20739
  return persisted?.enabled ?? true;
20634
20740
  }
20635
20741
  /**
20636
- * Toggle the vault's user-directory listing on or off (#122).
20742
+ * Toggle the vault's user-directory listing on or off.
20637
20743
  * Owner-only. When disabled, `listUsersWithEnvelopes()` throws
20638
20744
  * {@link import('./errors.js').DirectoryDisabledError} for callers
20639
20745
  * whose role is neither `owner` nor `admin`.
@@ -20693,7 +20799,7 @@ var Noydb = class {
20693
20799
  *
20694
20800
  * Two enforcement modes:
20695
20801
  *
20696
- * 1. **Managed-mode mandatory strong-recovery (#195).** When
20802
+ * 1. **Managed-mode mandatory strong-recovery.** When
20697
20803
  * `passphraseMode === 'managed'`, the vault MUST have at least
20698
20804
  * one **strong** recovery profile (Shamir today). Paper alone is
20699
20805
  * rejected because under managed mode the user has no memorized
@@ -20727,14 +20833,14 @@ var Noydb = class {
20727
20833
  throw new RecoveryNotEnrolledError();
20728
20834
  }
20729
20835
  /**
20730
- * Internal accessor used by tier-2/tier-3 unlock paths (issue #11)
20836
+ * Internal accessor used by tier-2/tier-3 unlock paths
20731
20837
  * to mark the active session tier.
20732
20838
  * @internal
20733
20839
  */
20734
20840
  _setActiveTier(vault, tier) {
20735
20841
  this.activeTier.set(vault, tier);
20736
20842
  }
20737
- // ─── Tier-2 enroll / remove (issue #11) ────────────────────────
20843
+ // ─── Tier-2 enroll / remove ─────────────────────────────────────
20738
20844
  /**
20739
20845
  * Add a tier-2 authenticator slot to the calling user's keyring.
20740
20846
  * Each slot independently wraps the SAME KEK under a method-specific
@@ -20764,7 +20870,7 @@ var Noydb = class {
20764
20870
  const next = await removeAuthenticator(this.options.store, vault, keyring, slotId);
20765
20871
  this.keyringCache.set(vault, next);
20766
20872
  }
20767
- /** Read the slot list for a vault. Internal — `describeAuthConfig` (#13) consumes this. */
20873
+ /** Read the slot list for a vault. Internal — `describeAuthConfig` consumes this. */
20768
20874
  async listAuthenticators(vault) {
20769
20875
  const keyring = await this.getKeyringInternal(vault);
20770
20876
  return keyring.authenticators;
@@ -20776,7 +20882,7 @@ var Noydb = class {
20776
20882
  * are immutable through this method. Anti-slot-swap is structural,
20777
20883
  * not gate-driven.
20778
20884
  *
20779
- * `meta` patch semantics (#57-aligned):
20885
+ * `meta` patch semantics (top-level merge):
20780
20886
  * - Top-level merge — absent keys preserved
20781
20887
  * - `null` value — delete that meta key
20782
20888
  * - Other values — replace verbatim
@@ -20794,8 +20900,6 @@ var Noydb = class {
20794
20900
  *
20795
20901
  * @throws `NoAccessError` when no slot with the given id exists.
20796
20902
  * @throws `ValidationError` when no patch field is provided.
20797
- *
20798
- * @see #55
20799
20903
  */
20800
20904
  async updateAuthenticator(vault, slotId, options, factors) {
20801
20905
  await this.checkGate(vault, "update-authenticator", factors);
@@ -20804,7 +20908,7 @@ var Noydb = class {
20804
20908
  this.keyringCache.set(vault, next);
20805
20909
  }
20806
20910
  /**
20807
- * Native WebAuthn enrollment using the **real** internal keyring (#16).
20911
+ * Native WebAuthn enrollment using the **real** internal keyring.
20808
20912
  *
20809
20913
  * Why this exists: when a consumer is using `createNoydb({ secret })`,
20810
20914
  * they cannot reach the live `UnlockedKeyring` to feed it to
@@ -20847,8 +20951,6 @@ var Noydb = class {
20847
20951
  * a server-side allowlist).
20848
20952
  *
20849
20953
  * Gated by `enroll-authenticator` like `enrollAuthenticator()` itself.
20850
- *
20851
- * @see #16
20852
20954
  */
20853
20955
  async enrollWebAuthn(vault, ceremony, factors) {
20854
20956
  await this.checkGate(vault, "enroll-authenticator", factors);
@@ -20875,8 +20977,6 @@ var Noydb = class {
20875
20977
  * deciding when a new device prompt should appear. Identity is
20876
20978
  * `id` + `enrolled_at`; the `meta.credentialId` (base64) is used by
20877
20979
  * `allowCredentials` at unlock time.
20878
- *
20879
- * @see #16
20880
20980
  */
20881
20981
  async listWebAuthnSlots(vault) {
20882
20982
  const keyring = await this.getKeyringInternal(vault);
@@ -20958,7 +21058,7 @@ var Noydb = class {
20958
21058
  async getPublicEnvelope(vault, opts = {}) {
20959
21059
  return readPublicEnvelope(this.options.store, vault, opts);
20960
21060
  }
20961
- // ─── Auth introspection (issue #13) ────────────────────────────
21061
+ // ─── Auth introspection ─────────────────────────────────────────
20962
21062
  /** English summary of the configured auth model. */
20963
21063
  async describeAuthConfig(vault) {
20964
21064
  return describeAuthConfig(this.options.store, vault);
@@ -20981,7 +21081,7 @@ var Noydb = class {
20981
21081
  await this.checkGate(vault, "view-user-auth", factors);
20982
21082
  return describeAllUsersAuth(this.options.store, vault);
20983
21083
  }
20984
- // ─── Tier-1 change flows (issue #10) ───────────────────────────
21084
+ // ─── Tier-1 change flows ────────────────────────────────────────
20985
21085
  /**
20986
21086
  * Rotate the user's passphrase (user remembers old). Validates the
20987
21087
  * new phrase against the configured `passphrase` policy, runs the
@@ -20989,8 +21089,7 @@ var Noydb = class {
20989
21089
  *
20990
21090
  * Tier-2 authenticator slots are dropped — each slot wraps the old
20991
21091
  * 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.
21092
+ * via `db.enrollAuthenticator` after rotation.
20994
21093
  *
20995
21094
  * @throws `WeakPassphraseError` on a weak new phrase.
20996
21095
  * @throws `PolicyDeniedError` when the gate denies (missing factor, …).
@@ -21012,8 +21111,8 @@ var Noydb = class {
21012
21111
  }
21013
21112
  /**
21014
21113
  * 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}.
21114
+ * Currently supports the `'paper'` profile end-to-end; the
21115
+ * other profiles throw {@link RecoveryProfileNotImplementedError}.
21017
21116
  *
21018
21117
  * Burns the used recovery entry on success.
21019
21118
  */
@@ -21042,7 +21141,7 @@ var Noydb = class {
21042
21141
  return { newCodes: codes };
21043
21142
  }
21044
21143
  /**
21045
- * Deliberate paper-recovery-code regeneration (#121). User knows their
21144
+ * Deliberate paper-recovery-code regeneration. User knows their
21046
21145
  * passphrase but wants a fresh sheet — they lost the printout or
21047
21146
  * suspect compromise of the off-site copy.
21048
21147
  *
@@ -21052,7 +21151,7 @@ var Noydb = class {
21052
21151
  *
21053
21152
  * Gated by the `rotate-recovery` policy gate:
21054
21153
  * - PERSONAL_POLICY: `{ minTier: 1 }` — knowing the passphrase
21055
- * suffices, matching the pre-#121 low-level flow's bar.
21154
+ * suffices, matching the lower-level flow's bar.
21056
21155
  * - STRICT_POLICY: `{ minTier: 1, factors: [{ anyOf: ['totp',
21057
21156
  * 'email-otp', 'webauthn-roaming'] }] }` — rotation is an
21058
21157
  * off-site-trust event; require an off-device factor so a
@@ -21157,7 +21256,7 @@ var Noydb = class {
21157
21256
  return { newShares: shareStrings, entryId: targetEntryId };
21158
21257
  }
21159
21258
  /**
21160
- * **Atomic create-and-enroll for managed-mode vaults (#195).**
21259
+ * **Atomic create-and-enroll for managed-mode vaults.**
21161
21260
  *
21162
21261
  * Bootstraps a managed-mode vault and enrolls strong recovery in
21163
21262
  * a single ceremony. Under `passphraseMode: 'managed'`, every
@@ -21228,7 +21327,7 @@ var Noydb = class {
21228
21327
  return { vault: vaultHandle, recoveryEnrollments };
21229
21328
  }
21230
21329
  /**
21231
- * **Recovery flow under managed-passphrase mode (#195).**
21330
+ * **Recovery flow under managed-passphrase mode.**
21232
21331
  *
21233
21332
  * Replaces the sealed passphrase of a managed-mode vault with a
21234
21333
  * fresh 256-bit random, sealed under the configured
@@ -21245,7 +21344,7 @@ var Noydb = class {
21245
21344
  * 5. Drop the keyring cache so the next operation re-derives.
21246
21345
  *
21247
21346
  * The vault's strong-recovery enrollment is preserved across
21248
- * recovery (Shamir entries are not burned on use — see #196).
21347
+ * recovery (Shamir entries are not burned on use).
21249
21348
  *
21250
21349
  * @throws ValidationError if the Noydb instance is not in managed mode.
21251
21350
  */
@@ -21293,7 +21392,7 @@ var Noydb = class {
21293
21392
  }
21294
21393
  /**
21295
21394
  * Atomic peer-recovery — re-wraps an EXISTING user's keyring under
21296
- * a fresh temp passphrase in a single store write. Closes #34's
21395
+ * a fresh temp passphrase in a single store write. Closes the
21297
21396
  * partial-failure window (the previous compose-from-primitives
21298
21397
  * pattern was `db.revoke + db.grant`, two writes — if the issuer
21299
21398
  * cancelled between them the target was locked out entirely).
@@ -21303,7 +21402,7 @@ var Noydb = class {
21303
21402
  * - Same `userId`, role, permissions, capabilities preserved.
21304
21403
  * - DEKs unchanged → every other principal in the vault keeps
21305
21404
  * access. No key rotation.
21306
- * - Allows owner→owner natively (#33). The existing
21405
+ * - Allows owner→owner natively. The existing
21307
21406
  * `db.revoke` retains its block — peer-recovery is a separate,
21308
21407
  * intentionally-named operation.
21309
21408
  * - Tier-2 slots dropped (they wrap the old KEK).
@@ -21332,7 +21431,6 @@ var Noydb = class {
21332
21431
  * @throws `PrivilegeEscalationError` when the caller lacks a DEK
21333
21432
  * the target previously had access to.
21334
21433
  *
21335
- * @see #33 #34 — the issues this method closes.
21336
21434
  */
21337
21435
  async recoverUser(vault, options, factors) {
21338
21436
  await this.checkGate(vault, "peer-recover-user", factors);
@@ -21343,7 +21441,7 @@ var Noydb = class {
21343
21441
  }
21344
21442
  }
21345
21443
  /**
21346
- * Persist a recovery enrollment. v0.1.0-pre.5 accepts the `'paper'`
21444
+ * Persist a recovery enrollment. Accepts the `'paper'`
21347
21445
  * profile.
21348
21446
  *
21349
21447
  * The hub wraps the user's DEK set (not the KEK) under a code-derived
@@ -21363,7 +21461,7 @@ var Noydb = class {
21363
21461
  * showCodesToUser(codes)
21364
21462
  * ```
21365
21463
  *
21366
- * As of pre.8, `@noy-db/on-recovery`'s `generateRecoveryCodeSet`
21464
+ * `@noy-db/on-recovery`'s `generateRecoveryCodeSet`
21367
21465
  * delegates to `mintPaperRecoveryEntry` internally — its output is
21368
21466
  * fed directly to this API. Pick whichever fits your code-gen layer:
21369
21467
  *
@@ -21403,13 +21501,13 @@ var Noydb = class {
21403
21501
  "#196"
21404
21502
  );
21405
21503
  }
21406
- /** Read the persisted recovery entries (paper + Shamir). Used by `describeAuthConfig` (#13). */
21504
+ /** Read the persisted recovery entries (paper + Shamir). Used by `describeAuthConfig`. */
21407
21505
  async listRecoveryEntries(vault) {
21408
21506
  const paper = await loadPaperRecoveryEntries(this.options.store, vault);
21409
21507
  const shamir = await loadShamirRecoveryEntries(this.options.store, vault);
21410
21508
  return { paper, shamir };
21411
21509
  }
21412
- // ─── Tier-3 enroll / unlock (issue #11) ────────────────────────
21510
+ // ─── Tier-3 enroll / unlock ─────────────────────────────────────
21413
21511
  /**
21414
21512
  * Register a tier-3 quick-unlock state for the vault. The state is
21415
21513
  * an opaque blob produced by `@noy-db/on-pin/enrollPin` (or any
@@ -21445,11 +21543,11 @@ var Noydb = class {
21445
21543
  this.quickUnlock.delete(vault);
21446
21544
  }
21447
21545
  /**
21448
- * Public accessor for the unlocked keyring of a vault — issue #28.
21546
+ * Public accessor for the unlocked keyring of a vault.
21449
21547
  *
21450
21548
  * Returns a **defensive shallow copy** so consumers can read the DEK
21451
21549
  * map and authenticator list without the risk of mutating the hub's
21452
- * internal cache (#88). Internal hub code paths use a live reference
21550
+ * internal cache. Internal hub code paths use a live reference
21453
21551
  * via `getKeyringInternal`; ceremonies and external consumers always
21454
21552
  * get a snapshot.
21455
21553
  *
@@ -22388,7 +22486,7 @@ function withDerivation(spec) {
22388
22486
  if (outputSpec.shape === "array") {
22389
22487
  if (lifecycleMode !== "eager") {
22390
22488
  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".`
22489
+ `withDerivation: shape 'array' supports lifecycle 'eager' only in this release Output "${outputKey}" declared lifecycle '${lifecycleMode}'. Switch to \`lifecycle: "eager"\` or use shape: "record".`
22392
22490
  );
22393
22491
  }
22394
22492
  if (typeof outputSpec.key !== "function") {