@noy-db/hub 0.2.0-pre.1 → 0.2.0-pre.2

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 (237) hide show
  1. package/dist/aggregate/index.cjs.map +1 -1
  2. package/dist/aggregate/index.js +2 -2
  3. package/dist/attestation/index.cjs +305 -0
  4. package/dist/attestation/index.cjs.map +1 -0
  5. package/dist/attestation/index.d.cts +52 -0
  6. package/dist/attestation/index.d.ts +52 -0
  7. package/dist/attestation/index.js +36 -0
  8. package/dist/attestation/index.js.map +1 -0
  9. package/dist/blobs/index.cjs.map +1 -1
  10. package/dist/blobs/index.d.cts +4 -3
  11. package/dist/blobs/index.d.ts +4 -3
  12. package/dist/blobs/index.js +9 -7
  13. package/dist/blobs/index.js.map +1 -1
  14. package/dist/bundle/index.cjs +16701 -129
  15. package/dist/bundle/index.cjs.map +1 -1
  16. package/dist/bundle/index.d.cts +172 -3
  17. package/dist/bundle/index.d.ts +172 -3
  18. package/dist/bundle/index.js +533 -5
  19. package/dist/bundle/index.js.map +1 -1
  20. package/dist/{chunk-5SCJ5UEF.js → chunk-243PNUA6.js} +2 -2
  21. package/dist/{chunk-WCA2NROQ.js → chunk-2PAQNPE3.js} +2 -2
  22. package/dist/chunk-3QAKZ37R.js +83 -0
  23. package/dist/chunk-3QAKZ37R.js.map +1 -0
  24. package/dist/chunk-3S4BJX25.js +36 -0
  25. package/dist/chunk-3S4BJX25.js.map +1 -0
  26. package/dist/chunk-3XHOCQK4.js +118 -0
  27. package/dist/chunk-3XHOCQK4.js.map +1 -0
  28. package/dist/{chunk-4TFSM22V.js → chunk-3Y53S2SA.js} +3 -3
  29. package/dist/{chunk-6HPZY4ON.js → chunk-3Z2TPHC4.js} +3 -3
  30. package/dist/chunk-4HIL6AHQ.js +57 -0
  31. package/dist/chunk-4HIL6AHQ.js.map +1 -0
  32. package/dist/{chunk-DYECX3IX.js → chunk-7BRE6EUA.js} +2 -2
  33. package/dist/{chunk-DYBQG5PQ.js → chunk-7BUTTVMR.js} +2 -2
  34. package/dist/{chunk-KESP7GOK.js → chunk-7Q5PLD5C.js} +3 -3
  35. package/dist/{chunk-UA4RI7OT.js → chunk-7Z23ZFLV.js} +4 -4
  36. package/dist/chunk-AHPFONIL.js +59 -0
  37. package/dist/chunk-AHPFONIL.js.map +1 -0
  38. package/dist/{chunk-EGQYGYIU.js → chunk-CXSCDO5T.js} +2 -2
  39. package/dist/chunk-E535SAN4.js +8834 -0
  40. package/dist/chunk-E535SAN4.js.map +1 -0
  41. package/dist/{chunk-CBAHB2BF.js → chunk-EUYOGYGV.js} +6 -69
  42. package/dist/chunk-EUYOGYGV.js.map +1 -0
  43. package/dist/{chunk-OMLIZL2P.js → chunk-FAQVNJD4.js} +2 -2
  44. package/dist/{chunk-I6MX32UC.js → chunk-G6FRSBKK.js} +4 -4
  45. package/dist/{chunk-FCXOFQAJ.js → chunk-GIV6DWBG.js} +2 -2
  46. package/dist/{chunk-34YSDCDP.js → chunk-HXJXPZRE.js} +2 -2
  47. package/dist/{chunk-23TTQXVO.js → chunk-J4KLMEUL.js} +2 -2
  48. package/dist/{chunk-VMIO4IXG.js → chunk-JYQTXEIO.js} +5 -228
  49. package/dist/chunk-JYQTXEIO.js.map +1 -0
  50. package/dist/{chunk-NIOHFJPJ.js → chunk-LRAZDV5X.js} +6 -118
  51. package/dist/chunk-LRAZDV5X.js.map +1 -0
  52. package/dist/{chunk-P7EQ2S5O.js → chunk-MUWOSVEP.js} +2 -2
  53. package/dist/chunk-NWZ3I6R6.js +79 -0
  54. package/dist/chunk-NWZ3I6R6.js.map +1 -0
  55. package/dist/{chunk-HB3Z2GCR.js → chunk-OVZDFEOR.js} +2 -2
  56. package/dist/chunk-PFSNOPBQ.js +233 -0
  57. package/dist/chunk-PFSNOPBQ.js.map +1 -0
  58. package/dist/{chunk-UZXLQCHP.js → chunk-PLI5TV7N.js} +2 -2
  59. package/dist/{chunk-PA6R5ZCI.js → chunk-Q6W2CMEJ.js} +3 -3
  60. package/dist/{chunk-537VFZTR.js → chunk-QPEXPHJR.js} +4 -4
  61. package/dist/{chunk-ZNOEIM6Y.js → chunk-QXQRKXCU.js} +2 -2
  62. package/dist/{chunk-RD5LYKD6.js → chunk-RTZVQAJ7.js} +2 -2
  63. package/dist/{chunk-DPMFBCV6.js → chunk-TBKOGSYR.js} +2 -2
  64. package/dist/{chunk-DPMFBCV6.js.map → chunk-TBKOGSYR.js.map} +1 -1
  65. package/dist/chunk-UND4XIB6.js +251 -0
  66. package/dist/chunk-UND4XIB6.js.map +1 -0
  67. package/dist/{chunk-7H6DOO3E.js → chunk-VCGTOS2A.js} +211 -36
  68. package/dist/chunk-VCGTOS2A.js.map +1 -0
  69. package/dist/{chunk-MKSA2V7A.js → chunk-VE6YVP32.js} +2 -2
  70. package/dist/{chunk-5DWL3JBF.js → chunk-VK5EER6C.js} +2 -2
  71. package/dist/{chunk-MIQHZESA.js → chunk-VPSUZLOJ.js} +4 -4
  72. package/dist/{chunk-MIQHZESA.js.map → chunk-VPSUZLOJ.js.map} +1 -1
  73. package/dist/{chunk-XGSOTWYX.js → chunk-VRBCTEKQ.js} +2 -2
  74. package/dist/{chunk-ADQ5MQ54.js → chunk-W3XXT26A.js} +29 -1
  75. package/dist/{chunk-ADQ5MQ54.js.map → chunk-W3XXT26A.js.map} +1 -1
  76. package/dist/{chunk-2AXFIYHT.js → chunk-XG3PTSCD.js} +1 -1
  77. package/dist/chunk-XG3PTSCD.js.map +1 -0
  78. package/dist/{chunk-SIZWEV2Y.js → chunk-Y2RKOPNC.js} +4 -4
  79. package/dist/{chunk-SIZWEV2Y.js.map → chunk-Y2RKOPNC.js.map} +1 -1
  80. package/dist/{chunk-Z72JH4KG.js → chunk-YTXSFG3C.js} +4 -34
  81. package/dist/chunk-YTXSFG3C.js.map +1 -0
  82. package/dist/consent/index.cjs.map +1 -1
  83. package/dist/consent/index.d.cts +4 -3
  84. package/dist/consent/index.d.ts +4 -3
  85. package/dist/consent/index.js +3 -3
  86. package/dist/{crypto-A7FRXYHC.js → crypto-5ZDIY3NG.js} +3 -3
  87. package/dist/{delegation-YBA4X4JN.js → delegation-QYXZW25W.js} +5 -5
  88. package/dist/derivations/index.cjs.map +1 -1
  89. package/dist/derivations/index.d.cts +5 -4
  90. package/dist/derivations/index.d.ts +5 -4
  91. package/dist/derivations/index.js +4 -4
  92. package/dist/{dev-unlock-DRwVSy2S.d.cts → dev-unlock-DQCNDfFp.d.cts} +1 -1
  93. package/dist/{dev-unlock-D9s-loPr.d.ts → dev-unlock-utkybTKb.d.ts} +1 -1
  94. package/dist/executor-AS2IDHKZ.js +11 -0
  95. package/dist/executor-HLXFXNFM.js +8 -0
  96. package/dist/executor-HN6YBHZ5.js +8 -0
  97. package/dist/guards/index.cjs.map +1 -1
  98. package/dist/guards/index.d.cts +5 -4
  99. package/dist/guards/index.d.ts +5 -4
  100. package/dist/guards/index.js +3 -3
  101. package/dist/{hash-DXXXusyk.d.ts → hash-DcoYWfJ_.d.ts} +1 -1
  102. package/dist/{hash-DtRih9MQ.d.cts → hash-jDowCrK2.d.cts} +1 -1
  103. package/dist/history/index.cjs +1 -1
  104. package/dist/history/index.cjs.map +1 -1
  105. package/dist/history/index.d.cts +5 -4
  106. package/dist/history/index.d.ts +5 -4
  107. package/dist/history/index.js +5 -5
  108. package/dist/i18n/index.cjs.map +1 -1
  109. package/dist/i18n/index.d.cts +4 -3
  110. package/dist/i18n/index.d.ts +4 -3
  111. package/dist/i18n/index.js +13 -11
  112. package/dist/i18n/index.js.map +1 -1
  113. package/dist/{index-CNwA-B6-.d.ts → index-BCKdioeh.d.ts} +29 -1
  114. package/dist/{index-CmVgTkqk.d.cts → index-BMjrzNZr.d.cts} +29 -1
  115. package/dist/index.cjs +507 -37
  116. package/dist/index.cjs.map +1 -1
  117. package/dist/index.d.cts +12 -11
  118. package/dist/index.d.ts +12 -11
  119. package/dist/index.js +106 -8817
  120. package/dist/index.js.map +1 -1
  121. package/dist/indexing/index.cjs.map +1 -1
  122. package/dist/indexing/index.js +2 -2
  123. package/dist/issue-ORP37MVW.js +12 -0
  124. package/dist/{ledger-3TXNP47J.js → ledger-3IU5GMXA.js} +5 -5
  125. package/dist/materialized-views/index.cjs.map +1 -1
  126. package/dist/materialized-views/index.d.cts +6 -5
  127. package/dist/materialized-views/index.d.ts +6 -5
  128. package/dist/materialized-views/index.js +6 -6
  129. package/dist/noydb-5H3C24GG.js +34 -0
  130. package/dist/overlay-views/index.cjs.map +1 -1
  131. package/dist/overlay-views/index.d.cts +5 -4
  132. package/dist/overlay-views/index.d.ts +5 -4
  133. package/dist/overlay-views/index.js +6 -4
  134. package/dist/periods/index.cjs.map +1 -1
  135. package/dist/periods/index.d.cts +4 -3
  136. package/dist/periods/index.d.ts +4 -3
  137. package/dist/periods/index.js +5 -5
  138. package/dist/{public-envelope-PY6NKFLI.js → public-envelope-U3CMEOMV.js} +3 -3
  139. package/dist/query/index.cjs.map +1 -1
  140. package/dist/query/index.d.cts +1 -1
  141. package/dist/query/index.d.ts +1 -1
  142. package/dist/query/index.js +3 -3
  143. package/dist/{registry-3L3N3PTG.js → registry-3ALP62P6.js} +3 -3
  144. package/dist/registry-7HE6VJGC.js +8 -0
  145. package/dist/registry-PSIPG2QR.js +8 -0
  146. package/dist/registry-PSIPG2QR.js.map +1 -0
  147. package/dist/revoke-KY2GB4KP.js +17 -0
  148. package/dist/revoke-KY2GB4KP.js.map +1 -0
  149. package/dist/session/index.cjs.map +1 -1
  150. package/dist/session/index.d.cts +5 -4
  151. package/dist/session/index.d.ts +5 -4
  152. package/dist/session/index.js +3 -3
  153. package/dist/shadow/index.cjs.map +1 -1
  154. package/dist/shadow/index.d.cts +4 -3
  155. package/dist/shadow/index.d.ts +4 -3
  156. package/dist/shadow/index.js +2 -2
  157. package/dist/signer-GRI5TZKH.js +18 -0
  158. package/dist/signer-GRI5TZKH.js.map +1 -0
  159. package/dist/{stale-HSC5YO2O.js → stale-OTOF3FH7.js} +2 -2
  160. package/dist/stale-OTOF3FH7.js.map +1 -0
  161. package/dist/store/index.cjs.map +1 -1
  162. package/dist/store/index.d.cts +4 -3
  163. package/dist/store/index.d.ts +4 -3
  164. package/dist/store/index.js +2 -2
  165. package/dist/sync/index.cjs.map +1 -1
  166. package/dist/sync/index.d.cts +3 -2
  167. package/dist/sync/index.d.ts +3 -2
  168. package/dist/sync/index.js +3 -3
  169. package/dist/team/index.cjs.map +1 -1
  170. package/dist/team/index.d.cts +4 -3
  171. package/dist/team/index.d.ts +4 -3
  172. package/dist/team/index.js +12 -10
  173. package/dist/tx/index.cjs.map +1 -1
  174. package/dist/tx/index.d.cts +4 -3
  175. package/dist/tx/index.d.ts +4 -3
  176. package/dist/tx/index.js +2 -2
  177. package/dist/{types-DW9RGSSs.d.ts → types-BoFFiskX.d.ts} +119 -3
  178. package/dist/{types-C4lwMKKF.d.cts → types-DJG8HG6F.d.cts} +119 -3
  179. package/dist/{index-hdFvZkBP.d.cts → ulid-BmBgooGm.d.ts} +51 -33
  180. package/dist/{index-4agOpzqd.d.ts → ulid-C7ms9oli.d.cts} +51 -33
  181. package/dist/util/index.cjs.map +1 -1
  182. package/dist/util/index.js +1 -1
  183. package/dist/{with-derivation-g-pGoMzL.d.ts → with-derivation-BKXXa8Vt.d.ts} +1 -1
  184. package/dist/{with-derivation-C8LDlV7t.d.cts → with-derivation-BjQ7q4NE.d.cts} +1 -1
  185. package/dist/{with-guard-DWOCK4Ca.d.ts → with-guard-C25yNjzd.d.ts} +1 -1
  186. package/dist/{with-guard-jI1x9Z3k.d.cts → with-guard-DQme5DKE.d.cts} +1 -1
  187. package/dist/{with-materialized-view-DcTx4H3j.d.cts → with-materialized-view-BbEPFIIJ.d.cts} +1 -1
  188. package/dist/{with-materialized-view-DaKR-N6J.d.ts → with-materialized-view-CqnRwI2S.d.ts} +1 -1
  189. package/dist/{with-overlayed-view-N7jYuNOS.d.ts → with-overlayed-view-Ct1fSJt-.d.ts} +1 -1
  190. package/dist/{with-overlayed-view-D-6oWAgM.d.cts → with-overlayed-view-bwlmmFjx.d.cts} +1 -1
  191. package/package.json +15 -3
  192. package/dist/chunk-2AXFIYHT.js.map +0 -1
  193. package/dist/chunk-7H6DOO3E.js.map +0 -1
  194. package/dist/chunk-CBAHB2BF.js.map +0 -1
  195. package/dist/chunk-NIOHFJPJ.js.map +0 -1
  196. package/dist/chunk-VMIO4IXG.js.map +0 -1
  197. package/dist/chunk-Z72JH4KG.js.map +0 -1
  198. package/dist/executor-7E3VFGW7.js +0 -11
  199. package/dist/executor-CEWX2FQI.js +0 -8
  200. package/dist/executor-X4SQ3ZLC.js +0 -8
  201. package/dist/registry-O47PUPSY.js +0 -8
  202. package/dist/registry-WLLMODKN.js +0 -8
  203. /package/dist/{chunk-5SCJ5UEF.js.map → chunk-243PNUA6.js.map} +0 -0
  204. /package/dist/{chunk-WCA2NROQ.js.map → chunk-2PAQNPE3.js.map} +0 -0
  205. /package/dist/{chunk-4TFSM22V.js.map → chunk-3Y53S2SA.js.map} +0 -0
  206. /package/dist/{chunk-6HPZY4ON.js.map → chunk-3Z2TPHC4.js.map} +0 -0
  207. /package/dist/{chunk-DYECX3IX.js.map → chunk-7BRE6EUA.js.map} +0 -0
  208. /package/dist/{chunk-DYBQG5PQ.js.map → chunk-7BUTTVMR.js.map} +0 -0
  209. /package/dist/{chunk-KESP7GOK.js.map → chunk-7Q5PLD5C.js.map} +0 -0
  210. /package/dist/{chunk-UA4RI7OT.js.map → chunk-7Z23ZFLV.js.map} +0 -0
  211. /package/dist/{chunk-EGQYGYIU.js.map → chunk-CXSCDO5T.js.map} +0 -0
  212. /package/dist/{chunk-OMLIZL2P.js.map → chunk-FAQVNJD4.js.map} +0 -0
  213. /package/dist/{chunk-I6MX32UC.js.map → chunk-G6FRSBKK.js.map} +0 -0
  214. /package/dist/{chunk-FCXOFQAJ.js.map → chunk-GIV6DWBG.js.map} +0 -0
  215. /package/dist/{chunk-34YSDCDP.js.map → chunk-HXJXPZRE.js.map} +0 -0
  216. /package/dist/{chunk-23TTQXVO.js.map → chunk-J4KLMEUL.js.map} +0 -0
  217. /package/dist/{chunk-P7EQ2S5O.js.map → chunk-MUWOSVEP.js.map} +0 -0
  218. /package/dist/{chunk-HB3Z2GCR.js.map → chunk-OVZDFEOR.js.map} +0 -0
  219. /package/dist/{chunk-UZXLQCHP.js.map → chunk-PLI5TV7N.js.map} +0 -0
  220. /package/dist/{chunk-PA6R5ZCI.js.map → chunk-Q6W2CMEJ.js.map} +0 -0
  221. /package/dist/{chunk-537VFZTR.js.map → chunk-QPEXPHJR.js.map} +0 -0
  222. /package/dist/{chunk-ZNOEIM6Y.js.map → chunk-QXQRKXCU.js.map} +0 -0
  223. /package/dist/{chunk-RD5LYKD6.js.map → chunk-RTZVQAJ7.js.map} +0 -0
  224. /package/dist/{chunk-MKSA2V7A.js.map → chunk-VE6YVP32.js.map} +0 -0
  225. /package/dist/{chunk-5DWL3JBF.js.map → chunk-VK5EER6C.js.map} +0 -0
  226. /package/dist/{chunk-XGSOTWYX.js.map → chunk-VRBCTEKQ.js.map} +0 -0
  227. /package/dist/{crypto-A7FRXYHC.js.map → crypto-5ZDIY3NG.js.map} +0 -0
  228. /package/dist/{delegation-YBA4X4JN.js.map → delegation-QYXZW25W.js.map} +0 -0
  229. /package/dist/{executor-7E3VFGW7.js.map → executor-AS2IDHKZ.js.map} +0 -0
  230. /package/dist/{executor-CEWX2FQI.js.map → executor-HLXFXNFM.js.map} +0 -0
  231. /package/dist/{executor-X4SQ3ZLC.js.map → executor-HN6YBHZ5.js.map} +0 -0
  232. /package/dist/{ledger-3TXNP47J.js.map → issue-ORP37MVW.js.map} +0 -0
  233. /package/dist/{public-envelope-PY6NKFLI.js.map → ledger-3IU5GMXA.js.map} +0 -0
  234. /package/dist/{registry-3L3N3PTG.js.map → noydb-5H3C24GG.js.map} +0 -0
  235. /package/dist/{registry-O47PUPSY.js.map → public-envelope-U3CMEOMV.js.map} +0 -0
  236. /package/dist/{registry-WLLMODKN.js.map → registry-3ALP62P6.js.map} +0 -0
  237. /package/dist/{stale-HSC5YO2O.js.map → registry-7HE6VJGC.js.map} +0 -0
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/bundle/index.ts","../../src/bundle/format.ts","../../src/errors.ts","../../src/bundle/bundle.ts","../../src/bundle/ulid.ts"],"sourcesContent":["/**\n * @noy-db/hub/bundle — opt-in .noydb container format subsystem.\n *\n * @category capability\n *\n * The `.noydb` binary wrapper around `vault.dump()` for safe\n * cloud-storage drops: 10-byte magic prefix + JSON header +\n * compressed body. Consumers that don't export/import bundles can\n * omit this subpath and save ~805 LOC of format code + Brotli/gzip\n * wiring.\n */\n\nexport {\n writeNoydbBundle,\n readNoydbBundle,\n readNoydbBundleHeader,\n resetBrotliSupportCache,\n} from './bundle.js'\nexport type {\n WriteNoydbBundleOptions,\n ReadNoydbBundleOptions,\n NoydbBundleReadResult,\n} from './bundle.js'\n\nexport {\n NOYDB_BUNDLE_MAGIC,\n NOYDB_BUNDLE_PREFIX_BYTES,\n NOYDB_BUNDLE_FORMAT_VERSION,\n FLAG_COMPRESSED,\n FLAG_HAS_INTEGRITY_HASH,\n COMPRESSION_NONE,\n COMPRESSION_GZIP,\n COMPRESSION_BROTLI,\n validateBundleHeader,\n encodeBundleHeader,\n} from './format.js'\nexport type {\n CompressionAlgo,\n NoydbBundleHeader,\n} from './format.js'\n\nexport { generateULID, isULID } from './ulid.js'\n\n// ─── Bundle / backup errors ─────────────────────────────\n// Re-exported from the central errors module so subpath consumers can\n// `instanceof BundleIntegrityError` without falling back to the root barrel.\nexport {\n BundleIntegrityError,\n BundleSealMismatchError,\n BundleVersionConflictError,\n BackupLedgerError,\n BackupCorruptedError,\n} from '../errors.js'\n","/**\n * `.noydb` container format — byte layout, header schema, validators.\n *\n *. Wraps a `vault.dump()` JSON string in a thin\n * binary container with a magic-byte prefix, a minimum-disclosure\n * unencrypted header, and a compressed body.\n *\n * **Byte layout** (read in order from offset 0):\n *\n * ```\n * +--------+--------+--------+--------+\n * | N=78 | D=68 | B=66 | 1=49 | Magic 'NDB1' (4 bytes)\n * +--------+--------+--------+--------+\n * | flags | compr | header_length (uint32 BE) |\n * +--------+--------+--------+--------+--------+--------+--------+\n * | header_length bytes of UTF-8 JSON header ...\n * +--------+--------+\n * | compressed body bytes ...\n * ```\n *\n * Total fixed prefix before the header JSON is **10 bytes**:\n * - 4 bytes magic\n * - 1 byte flags\n * - 1 byte compression algorithm\n * - 4 bytes header length (uint32 big-endian)\n *\n * **Why a binary container** at all? `vault.dump()` already\n * produces a JSON string with encrypted records inside. Wrapping it\n * again seems redundant — but the wrap is what makes the file safe\n * to drop into cloud storage (Drive, Dropbox, iCloud) without\n * leaking the vault name and exporter identity through the\n * cloud's metadata API. The minimum-disclosure header is the only\n * thing visible without downloading and decompressing the body.\n * The dump JSON inside the body still contains the original\n * metadata, but that's only readable by someone who already has the\n * file bytes — the same person who could read the encrypted records\n * with the right passphrase.\n *\n * **Why minimum disclosure** in the header? Because consumers will\n * inevitably store these in services where the filename, file size,\n * and any unencrypted metadata are indexed for search. A field like\n * `vault: \"Acme Corp\"` would let an attacker (or a curious\n * cloud admin) enumerate which compartments exist and who exported\n * them, even with zero access to the encrypted body. The header\n * carries only what's needed to identify the file as a NOYDB\n * bundle and verify its integrity — nothing about the contents.\n */\n\nimport type { PublicEnvelope } from '../meta/public-envelope/types.js'\n\n/** Magic bytes 'NDB1' (ASCII), identifying a NOYDB bundle. */\nexport const NOYDB_BUNDLE_MAGIC = new Uint8Array([0x4e, 0x44, 0x42, 0x31])\n\n/** Total fixed prefix before the header JSON: 4+1+1+4 bytes. */\nexport const NOYDB_BUNDLE_PREFIX_BYTES = 10\n\n/** Current bundle format version. Bumped on layout changes. */\nexport const NOYDB_BUNDLE_FORMAT_VERSION = 1\n\n/**\n * Bitfield interpretation of the flags byte.\n *\n * Bit 0 — body is compressed (0 = raw, 1 = compressed)\n * Bit 1 — header carries an integrity hash over the body bytes\n * Bits 2-7 — reserved, must be 0 in\n */\nexport const FLAG_COMPRESSED = 0b0000_0001\nexport const FLAG_HAS_INTEGRITY_HASH = 0b0000_0010\n\n/**\n * Compression algorithm encoding for the byte at offset 5.\n *\n * `none` is admitted for round-trip testing and for callers that\n * want to bundle without compression (e.g. when piping into a\n * separately compressed transport). `gzip` is the universally\n * available baseline (Node 18+, all modern browsers). `brotli` is\n * preferred when the runtime supports it — typically 30-50% smaller\n * for JSON payloads — but Node 22+ / Chrome 124+ / Firefox 122+\n * are required, so the writer feature-detects at runtime and falls\n * back to gzip. The reader must handle all three.\n */\nexport const COMPRESSION_NONE = 0\nexport const COMPRESSION_GZIP = 1\nexport const COMPRESSION_BROTLI = 2\n\nexport type CompressionAlgo = 0 | 1 | 2\n\n/**\n * The unencrypted header carried in every `.noydb` bundle.\n *\n * **Minimum-disclosure rules:** these are the ONLY allowed keys.\n * Any other key in a parsed header causes\n * `validateBundleHeader` to throw. The set is kept short to\n * minimize attack surface from cloud-storage metadata indexing —\n * see the file-level doc comment for the rationale.\n *\n * Forbidden in particular:\n * - `vault` / `_compartment` — would leak the tenant name\n * - `exporter` / `_exported_by` — would leak user identity\n * - `timestamp` / `_exported_at` — would leak activity timing\n * - `kdfParams` / salt fields — would leak crypto config that\n * could narrow brute-force search space\n * - any field starting with `_` (reserved by the dump format)\n */\nexport interface NoydbBundleHeader {\n /** Bundle format version — bumped on layout changes. */\n readonly formatVersion: number\n /**\n * Opaque ULID identifier — generated once per vault and\n * stable across re-exports of the same vault. Does not\n * leak any information about contents (the timestamp prefix is\n * just monotonicity for sortability, not exporter activity —\n * see `bundle/ulid.ts` for the design notes).\n */\n readonly handle: string\n /** Compressed body length in bytes. Lets readers verify completeness without decompressing. */\n readonly bodyBytes: number\n /** SHA-256 of the compressed body bytes (lowercase hex). Lets readers verify integrity without decompressing. */\n readonly bodySha256: string\n /**\n * Owner-curated public envelope (`docs/subsystems/public-envelope.md`).\n * Optional — present only when the source vault has a\n * `_meta/public-envelope` document AND the writer's hub is opted\n * into the feature. Treat as **untrusted hint**; the body's\n * encrypted contents remain the source of truth.\n *\n * The envelope deliberately widens the minimum-disclosure rule\n * for explicit, owner-curated label fields (name, icon, …). Every\n * other unknown header key still rejects at parse time.\n */\n readonly publicEnvelope?: PublicEnvelope\n /**\n * Auto-unlock material indicator (#197). When present, the bundle\n * body wraps the dump JSON in a structure carrying per-user\n * passphrases — either plaintext (`'unsealed'`, public-by-design)\n * or sealed under a `SealingKeyProvider` (`'sealed'`, requires\n * matching provider on the recipient side).\n *\n * Visible pre-decompression so cloud listing UIs can warn before\n * download: \"this bundle opens itself for anyone holding the file\"\n * (unsealed) or \"this bundle is sealed for a specific provider\"\n * (sealed).\n *\n * Absent → the body is a raw `vault.dump()` JSON string (the\n * pre-#197 shape; back-compatible).\n */\n readonly autoUnlock?: 'unsealed' | 'sealed'\n}\n\n/**\n * Allowlist of header keys. Any key not in this set is forbidden\n * and causes `validateBundleHeader` to throw. Kept as a Set for\n * O(1) lookup; the validator iterates over the parsed header and\n * checks each key against this set.\n */\nconst ALLOWED_HEADER_KEYS: ReadonlySet<string> = new Set([\n 'formatVersion',\n 'handle',\n 'bodyBytes',\n 'bodySha256',\n 'publicEnvelope',\n 'autoUnlock',\n])\n\n/**\n * Validate a parsed bundle header. Throws on any deviation from\n * the minimum-disclosure schema:\n *\n * - Missing required field\n * - Wrong type for any field\n * - Any extra key not in `ALLOWED_HEADER_KEYS`\n * - Unsupported `formatVersion`\n * - Negative or non-integer `bodyBytes`\n * - Malformed `handle` (must be 26-char Crockford base32)\n * - Malformed `bodySha256` (must be 64-char lowercase hex)\n *\n * The error messages name the offending field so consumers can\n * fix the producer rather than the reader.\n */\nexport function validateBundleHeader(\n parsed: unknown,\n): asserts parsed is NoydbBundleHeader {\n if (parsed === null || typeof parsed !== 'object') {\n throw new Error(\n `.noydb bundle header must be a JSON object, got ${parsed === null ? 'null' : typeof parsed}`,\n )\n }\n // Disallow any unknown key — minimum disclosure means we reject\n // forward-compat extension keys at the format layer; new fields\n // require a format version bump and a new validator.\n for (const key of Object.keys(parsed)) {\n if (!ALLOWED_HEADER_KEYS.has(key)) {\n throw new Error(\n `.noydb bundle header contains forbidden key \"${key}\". ` +\n `Only minimum-disclosure fields are allowed: ` +\n `${[...ALLOWED_HEADER_KEYS].join(', ')}.`,\n )\n }\n }\n const h = parsed as Record<string, unknown>\n if (typeof h['formatVersion'] !== 'number' || h['formatVersion'] !== NOYDB_BUNDLE_FORMAT_VERSION) {\n throw new Error(\n `.noydb bundle header.formatVersion must be ${NOYDB_BUNDLE_FORMAT_VERSION}, ` +\n `got ${String(h['formatVersion'])}. The reader does not support ` +\n `forward-compat versions; upgrade the reader to handle newer bundles.`,\n )\n }\n if (typeof h['handle'] !== 'string' || !/^[0-9A-HJKMNP-TV-Z]{26}$/.test(h['handle'])) {\n throw new Error(\n `.noydb bundle header.handle must be a 26-character Crockford base32 ULID, ` +\n `got ${typeof h['handle'] === 'string' ? `\"${h['handle']}\"` : String(h['handle'])}.`,\n )\n }\n if (typeof h['bodyBytes'] !== 'number' || !Number.isInteger(h['bodyBytes']) || h['bodyBytes'] < 0) {\n throw new Error(\n `.noydb bundle header.bodyBytes must be a non-negative integer, ` +\n `got ${String(h['bodyBytes'])}.`,\n )\n }\n if (typeof h['bodySha256'] !== 'string' || !/^[0-9a-f]{64}$/.test(h['bodySha256'])) {\n throw new Error(\n `.noydb bundle header.bodySha256 must be a 64-character lowercase hex string, ` +\n `got ${typeof h['bodySha256'] === 'string' ? `\"${h['bodySha256']}\"` : String(h['bodySha256'])}.`,\n )\n }\n if (h['publicEnvelope'] !== undefined) {\n const env = h['publicEnvelope']\n if (env === null || typeof env !== 'object' || Array.isArray(env)) {\n throw new Error(\n `.noydb bundle header.publicEnvelope must be a JSON object when present, got ${typeof env}.`,\n )\n }\n const e = env as Record<string, unknown>\n if (e['_noydb_public'] !== 1) {\n throw new Error(\n `.noydb bundle header.publicEnvelope._noydb_public must be 1, got ${String(e['_noydb_public'])}.`,\n )\n }\n if (typeof e['version'] !== 'number' || !Number.isInteger(e['version']) || e['version'] < 1) {\n throw new Error(\n `.noydb bundle header.publicEnvelope.version must be a positive integer, got ${String(e['version'])}.`,\n )\n }\n }\n if (h['autoUnlock'] !== undefined) {\n if (h['autoUnlock'] !== 'unsealed' && h['autoUnlock'] !== 'sealed') {\n const got = typeof h['autoUnlock'] === 'string' ? `\"${h['autoUnlock']}\"` : typeof h['autoUnlock']\n throw new Error(\n `.noydb bundle header.autoUnlock must be 'unsealed' or 'sealed' when present, got ${got}.`,\n )\n }\n }\n}\n\n/**\n * Encode a header object to UTF-8 JSON bytes after validating\n * minimum disclosure. Used by the writer to serialize the header\n * region of the container.\n */\nexport function encodeBundleHeader(header: NoydbBundleHeader): Uint8Array {\n validateBundleHeader(header)\n // Stable key ordering — JSON.stringify with no replacer uses\n // insertion order, which is fine here because we control the\n // object construction. Stable ordering means two bundles with\n // identical contents produce byte-identical headers.\n const json = JSON.stringify({\n formatVersion: header.formatVersion,\n handle: header.handle,\n bodyBytes: header.bodyBytes,\n bodySha256: header.bodySha256,\n ...(header.publicEnvelope !== undefined ? { publicEnvelope: header.publicEnvelope } : {}),\n ...(header.autoUnlock !== undefined ? { autoUnlock: header.autoUnlock } : {}),\n })\n return new TextEncoder().encode(json)\n}\n\n/**\n * Parse a bundle header from its UTF-8 JSON bytes. Throws on\n * invalid JSON or any minimum-disclosure violation.\n */\nexport function decodeBundleHeader(bytes: Uint8Array): NoydbBundleHeader {\n const json = new TextDecoder('utf-8', { fatal: true }).decode(bytes)\n let parsed: unknown\n try {\n parsed = JSON.parse(json)\n } catch (err) {\n throw new Error(\n `.noydb bundle header is not valid JSON: ${(err as Error).message}`,\n )\n }\n validateBundleHeader(parsed)\n return parsed\n}\n\n/**\n * Read a uint32 from `bytes` at `offset` in big-endian byte order.\n * No bounds check — callers must guarantee `offset + 4 <= bytes.length`.\n * Used to decode the header length field; kept inline so the parser\n * doesn't depend on DataView allocation per call.\n */\nexport function readUint32BE(bytes: Uint8Array, offset: number): number {\n return (\n (bytes[offset]! << 24 >>> 0) +\n (bytes[offset + 1]! << 16) +\n (bytes[offset + 2]! << 8) +\n bytes[offset + 3]!\n )\n}\n\n/**\n * Write a uint32 to `bytes` at `offset` in big-endian byte order.\n * No bounds check — callers must guarantee `offset + 4 <= bytes.length`.\n */\nexport function writeUint32BE(bytes: Uint8Array, offset: number, value: number): void {\n bytes[offset] = (value >>> 24) & 0xff\n bytes[offset + 1] = (value >>> 16) & 0xff\n bytes[offset + 2] = (value >>> 8) & 0xff\n bytes[offset + 3] = value & 0xff\n}\n\n/**\n * Verify the magic prefix of a bundle. Returns true if the first\n * 4 bytes match `NDB1`. Used by readers as a fast file-type check\n * before any further parsing.\n */\nexport function hasNoydbBundleMagic(bytes: Uint8Array): boolean {\n if (bytes.length < NOYDB_BUNDLE_MAGIC.length) return false\n for (let i = 0; i < NOYDB_BUNDLE_MAGIC.length; i++) {\n if (bytes[i] !== NOYDB_BUNDLE_MAGIC[i]) return false\n }\n return true\n}\n","/**\n * All NOYDB error classes — a single import surface for `catch` blocks and\n * `instanceof` checks.\n *\n * ## Class hierarchy\n *\n * ```\n * Error\n * └─ NoydbError (code: string)\n * ├─ Crypto errors\n * │ ├─ DecryptionError — AES-GCM tag failure\n * │ ├─ TamperedError — ciphertext modified after write\n * │ └─ InvalidKeyError — wrong passphrase / corrupt keyring\n * ├─ Access errors\n * │ ├─ NoAccessError — no DEK for this collection\n * │ ├─ ReadOnlyError — ro permission, write attempted\n * │ ├─ PermissionDeniedError — role too low for operation\n * │ ├─ PrivilegeEscalationError — grant wider than grantor holds\n * │ └─ StoreCapabilityError — optional store method missing\n * ├─ Sync errors\n * │ ├─ ConflictError — optimistic-lock version mismatch\n * │ ├─ BundleVersionConflictError — bundle push rejected by remote\n * │ └─ NetworkError — push/pull network failure\n * ├─ Data errors\n * │ ├─ NotFoundError — get(id) on missing record\n * │ ├─ ValidationError — application-level guard failed\n * │ └─ SchemaValidationError — Standard Schema v1 rejection\n * ├─ Query errors\n * │ ├─ JoinTooLargeError — join row ceiling exceeded\n * │ ├─ DanglingReferenceError — strict ref() points at nothing\n * │ ├─ GroupCardinalityError — groupBy bucket cap exceeded\n * │ ├─ IndexRequiredError — lazy-mode query touches unindexed field\n * │ └─ IndexWriteFailureError — index side-car put/delete failed post-main\n * ├─ i18n / Dictionary errors\n * │ ├─ ReservedCollectionNameError\n * │ ├─ DictKeyMissingError\n * │ ├─ DictKeyInUseError\n * │ ├─ MissingTranslationError\n * │ ├─ LocaleNotSpecifiedError\n * │ └─ TranslatorNotConfiguredError\n * ├─ Backup errors\n * │ ├─ BackupLedgerError — hash-chain verification failed\n * │ └─ BackupCorruptedError — envelope hash mismatch in dump\n * ├─ Bundle errors\n * │ └─ BundleIntegrityError — .noydb body sha256 mismatch\n * └─ Session errors\n * ├─ SessionExpiredError\n * ├─ SessionNotFoundError\n * └─ SessionPolicyError\n * ```\n *\n * ## Catching all NOYDB errors\n *\n * ```ts\n * import { NoydbError, InvalidKeyError, ConflictError } from '@noy-db/hub'\n *\n * try {\n * await vault.unlock(passphrase)\n * } catch (e) {\n * if (e instanceof InvalidKeyError) { showBadPassphraseUI(); return }\n * if (e instanceof NoydbError) { logToSentry(e.code, e); return }\n * throw e // unexpected — re-throw\n * }\n * ```\n *\n * @module\n */\n\n/**\n * Base class for all NOYDB errors.\n *\n * Every error thrown by `@noy-db/hub` extends this class, so consumers can\n * catch all NOYDB errors in a single `catch (e) { if (e instanceof NoydbError) ... }`\n * block. The `code` field is a machine-readable string (e.g. `'DECRYPTION_FAILED'`)\n * suitable for `switch` statements and logging pipelines.\n */\nexport class NoydbError extends Error {\n /** Machine-readable error code. Stable across library versions. */\n readonly code: string\n\n constructor(code: string, message: string) {\n super(message)\n this.name = 'NoydbError'\n this.code = code\n }\n}\n\n// ─── Crypto Errors ─────────────────────────────────────────────────────\n\n/**\n * Thrown when AES-GCM decryption fails.\n *\n * The most common cause is a wrong passphrase or a corrupted ciphertext.\n * A `DecryptionError` at the wrong passphrase level is caught internally\n * and re-thrown as `InvalidKeyError` — so in practice this surfaces for\n * per-record corruption rather than authentication failures.\n */\nexport class DecryptionError extends NoydbError {\n constructor(message = 'Decryption failed') {\n super('DECRYPTION_FAILED', message)\n this.name = 'DecryptionError'\n }\n}\n\n/**\n * Thrown when GCM tag verification fails, indicating the ciphertext was\n * modified after encryption.\n *\n * AES-256-GCM is authenticated encryption — the tag over the ciphertext\n * is checked on every decrypt. If any byte was flipped (accidental\n * corruption or deliberate tampering), decryption throws this error.\n * Treat it as a security alert: the stored bytes are not what NOYDB wrote.\n */\nexport class TamperedError extends NoydbError {\n constructor(message = 'Data integrity check failed — record may have been tampered with') {\n super('TAMPERED', message)\n this.name = 'TamperedError'\n }\n}\n\n/**\n * Thrown when key unwrapping fails, typically because the passphrase is wrong\n * or the keyring file is corrupted.\n *\n * NOYDB uses AES-KW (RFC 3394) to wrap DEKs with the KEK. If AES-KW\n * unwrapping fails, it means either the KEK was derived from the wrong\n * passphrase (PBKDF2 with 600K iterations) or the keyring bytes are\n * corrupted. This is the error shown to the user on a failed unlock attempt.\n */\nexport class InvalidKeyError extends NoydbError {\n constructor(message = 'Invalid key — wrong passphrase or corrupted keyring') {\n super('INVALID_KEY', message)\n this.name = 'InvalidKeyError'\n }\n}\n\n/**\n * Thrown when a keyring's wrapped-DEK set unwraps partially — at least\n * one DEK succeeds (proving the KEK is correct) but at least one fails.\n * The passphrase is right; the failed entries are corrupted.\n *\n * This is distinct from {@link InvalidKeyError} so that\n * `NoydbOptions.onInvalidKey: 'reset'` does NOT fire — resetting on\n * partial corruption would destroy the still-valid DEKs and the data\n * they protect, which is silent data loss in response to a feature\n * designed for stale-credential recovery.\n */\nexport class KeyringCorruptError extends NoydbError {\n readonly failedCollections: readonly string[]\n readonly intactCount: number\n constructor(opts: { failedCollections: readonly string[]; intactCount: number; message?: string }) {\n super(\n 'KEYRING_CORRUPT',\n opts.message ??\n `Keyring has ${opts.failedCollections.length} corrupted wrapped DEK(s) ` +\n `(${opts.failedCollections.join(', ')}); ${opts.intactCount} other DEK(s) ` +\n `unwrapped successfully — the passphrase is correct, the entries are damaged. ` +\n `Do NOT use onInvalidKey: 'reset' here — that would destroy the intact DEKs.`,\n )\n this.name = 'KeyringCorruptError'\n this.failedCollections = opts.failedCollections\n this.intactCount = opts.intactCount\n }\n}\n\n// ─── Access Errors ─────────────────────────────────────────────────────\n\n/**\n * Thrown when the authenticated user does not have a DEK for the requested\n * collection — i.e. the collection is not in their keyring at all.\n *\n * This is the \"no key for this door\" error. It is different from\n * `ReadOnlyError` (user has a key but it only grants ro) and from\n * `PermissionDeniedError` (user's role doesn't allow the operation).\n */\nexport class NoAccessError extends NoydbError {\n constructor(message = 'No access — user does not have a key for this collection') {\n super('NO_ACCESS', message)\n this.name = 'NoAccessError'\n }\n}\n\n/**\n * Thrown when a user with read-only (`ro`) permission attempts a write\n * operation (`put` or `delete`) on a collection.\n *\n * The user has a DEK for the collection (they can decrypt and read), but\n * their keyring grants only `ro`. To fix: re-grant the user with `rw`\n * permission, or do not attempt writes as a viewer/client role.\n */\nexport class ReadOnlyError extends NoydbError {\n constructor(message = 'Read-only — user has ro permission on this collection') {\n super('READ_ONLY', message)\n this.name = 'ReadOnlyError'\n }\n}\n\n/**\n * Thrown when a write is attempted against a historical view produced\n * by `vault.at(timestamp)`. Time-machine views are read-only by\n * contract — mutating the past would require either the shadow-vault\n * mechanism or a ledger-history rewrite (which breaks\n * the tamper-evidence guarantee).\n *\n * Distinct from {@link ReadOnlyError} (keyring-level) and\n * {@link PermissionDeniedError} (role-level): this error is about the\n * *view* being historical, independent of the caller's permissions.\n */\nexport class ReadOnlyAtInstantError extends NoydbError {\n constructor(operation: string, timestamp: string) {\n super(\n 'READ_ONLY_AT_INSTANT',\n `Cannot ${operation}() on a vault view anchored at ${timestamp} — time-machine views are read-only`,\n )\n this.name = 'ReadOnlyAtInstantError'\n }\n}\n\n/**\n * Thrown when a write is attempted against a shadow-vault frame\n * produced by `vault.frame()`. Frames are read-only by contract —\n * the use case is screen-sharing / demos / compliance review where\n * the operator wants to prevent accidental edits.\n *\n * Behavioural enforcement only — the underlying keyring still holds\n * write-capable DEKs. See {@link VaultFrame} for the full caveat.\n */\nexport class ReadOnlyFrameError extends NoydbError {\n constructor(operation: string) {\n super(\n 'READ_ONLY_FRAME',\n `Cannot ${operation}() on a vault frame — frames are read-only presentations of the current vault`,\n )\n this.name = 'ReadOnlyFrameError'\n }\n}\n\n/**\n * Thrown when the authenticated user's role does not permit the requested\n * operation — e.g. a `viewer` calling `grantAccess()`, or an `operator`\n * calling `rotateKeys()`.\n *\n * This is a role-level check (what the user's role allows), distinct from\n * `NoAccessError` (collection not in keyring) and `ReadOnlyError` (in\n * keyring, but write not allowed).\n */\nexport class PermissionDeniedError extends NoydbError {\n constructor(message = 'Permission denied — insufficient role for this operation') {\n super('PERMISSION_DENIED', message)\n this.name = 'PermissionDeniedError'\n }\n}\n\n/**\n * Thrown when an `@noy-db/as-*` export is attempted without the\n * required capability bit on the invoking keyring.\n *\n * Two sub-cases discriminated by the `tier` field:\n *\n * - `tier: 'plaintext'` — a plaintext-tier export (`as-xlsx`,\n * `as-csv`, `as-blob`, `as-zip`, …) was attempted but the\n * keyring's `exportCapability.plaintext` does not include the\n * requested `format` (nor the `'*'` wildcard). Default for every\n * role is `plaintext: []` — the owner must positively grant.\n * - `tier: 'bundle'` — an encrypted `as-noydb` bundle export was\n * attempted but the keyring's `exportCapability.bundle` is\n * `false`. Default for `owner`/`admin` is `true`; for\n * `operator`/`viewer`/`client` it is `false`.\n *\n * Distinct from `PermissionDeniedError` (role-level check) and\n * `NoAccessError` (collection not readable). Surfaces separately so\n * UI layers can show a \"request the export capability from your\n * admin\" flow rather than a generic permission error.\n */\nexport class ExportCapabilityError extends NoydbError {\n readonly tier: 'plaintext' | 'bundle'\n readonly format?: string\n readonly userId: string\n\n constructor(opts: {\n tier: 'plaintext' | 'bundle'\n userId: string\n format?: string\n message?: string\n }) {\n const msg =\n opts.message ??\n (opts.tier === 'plaintext'\n ? `Export capability denied — keyring \"${opts.userId}\" is not granted plaintext-export capability for format \"${opts.format ?? '<unknown>'}\". Ask a vault owner or admin to grant it via vault.grant({ exportCapability: { plaintext: ['${opts.format ?? '<format>'}'] } }).`\n : `Export capability denied — keyring \"${opts.userId}\" is not granted encrypted-bundle export capability. Ask a vault owner or admin to grant it via vault.grant({ exportCapability: { bundle: true } }).`)\n super('EXPORT_CAPABILITY', msg)\n this.name = 'ExportCapabilityError'\n this.tier = opts.tier\n this.userId = opts.userId\n if (opts.format !== undefined) this.format = opts.format\n }\n}\n\n/**\n * Thrown when a keyring file's `expires_at` cutoff has passed.\n * Surfaced by `loadKeyring` before any DEK unwrap is attempted —\n * past the cutoff the slot refuses to open even with the right\n * passphrase. Distinct from PBKDF2 / unwrap errors so consumer code\n * can show a precise \"this bundle slot has expired\" message instead\n * of the generic decryption-failure UX.\n *\n * Used predominantly on `BundleRecipient` slots produced by\n * `writeNoydbBundle({ recipients: [...] })` to time-box audit access.\n */\nexport class KeyringExpiredError extends NoydbError {\n readonly userId: string\n readonly expiresAt: string\n constructor(opts: { userId: string; expiresAt: string }) {\n super(\n 'KEYRING_EXPIRED',\n `Keyring \"${opts.userId}\" expired at ${opts.expiresAt}. ` +\n 'The slot refuses to unlock past its expiry timestamp.',\n )\n this.name = 'KeyringExpiredError'\n this.userId = opts.userId\n this.expiresAt = opts.expiresAt\n }\n}\n\n/**\n * Thrown when an `@noy-db/as-*` import is attempted but the invoking\n * keyring lacks the required import-capability bit.\n *\n * - `tier: 'plaintext'` — a plaintext-tier import (`as-csv`, `as-json`,\n * `as-ndjson`, `as-zip`, …) was attempted but the keyring's\n * `importCapability.plaintext` does not include the requested\n * `format` (nor the `'*'` wildcard).\n * - `tier: 'bundle'` — a `.noydb` bundle import was attempted but the\n * keyring's `importCapability.bundle` is not `true`.\n *\n * Default for every role on every dimension is closed — owners and\n * admins must positively grant the capability. Distinct from\n * `PermissionDeniedError` and `NoAccessError` so UI layers can show a\n * specific \"request the import capability\" flow.\n */\nexport class ImportCapabilityError extends NoydbError {\n readonly tier: 'plaintext' | 'bundle'\n readonly format?: string\n readonly userId: string\n\n constructor(opts: {\n tier: 'plaintext' | 'bundle'\n userId: string\n format?: string\n message?: string\n }) {\n const msg =\n opts.message ??\n (opts.tier === 'plaintext'\n ? `Import capability denied — keyring \"${opts.userId}\" is not granted plaintext-import capability for format \"${opts.format ?? '<unknown>'}\". Ask a vault owner or admin to grant it via vault.grant({ importCapability: { plaintext: ['${opts.format ?? '<format>'}'] } }).`\n : `Import capability denied — keyring \"${opts.userId}\" is not granted encrypted-bundle import capability. Ask a vault owner or admin to grant it via vault.grant({ importCapability: { bundle: true } }).`)\n super('IMPORT_CAPABILITY', msg)\n this.name = 'ImportCapabilityError'\n this.tier = opts.tier\n this.userId = opts.userId\n if (opts.format !== undefined) this.format = opts.format\n }\n}\n\n/**\n * Thrown when a grant would give the grantee a permission the grantor\n * does not themselves hold — the \"admin cannot grant what admin cannot\n * do\" rule from the admin-delegation work.\n *\n * Distinct from `PermissionDeniedError` so callers can tell the two\n * cases apart in logs and tests:\n *\n * - `PermissionDeniedError` — \"you are not allowed to perform this\n * operation at all\" (wrong role).\n * - `PrivilegeEscalationError` — \"you are allowed to grant, but not\n * with these specific permissions\" (widening attempt).\n *\n * Under the admin model the grantee of an admin-grants-admin call\n * inherits the caller's entire DEK set by construction, so this error\n * is structurally unreachable in typical flows. The check and error\n * class exist so that future per-collection admin scoping cannot\n * accidentally bypass the subset rule — the guard is already wired in.\n *\n * `offendingCollection` carries the first collection name that failed\n * the subset check, to make the violation actionable in error output.\n */\n/**\n * Thrown when a caller invokes an API that requires an optional\n * store capability the active store does not implement.\n *\n * Today the only call site is `Noydb.listAccessibleVaults()`,\n * which depends on the optional `NoydbStore.listVaults()`\n * method. The error message names the missing method and the calling\n * API so consumers know exactly which combination is unsupported,\n * and the `capability` field is machine-readable so library code can\n * pattern-match in catch blocks (e.g. fall back to a candidate-list\n * shape).\n *\n * The class lives in `errors.ts` rather than as a generic\n * `ValidationError` because the diagnostic shape is different: a\n * `ValidationError` says \"the inputs you passed are wrong\"; this\n * error says \"the inputs are fine, but the store you wired up\n * doesn't support what you're asking for.\" Different fix, different\n * documentation.\n */\nexport class StoreCapabilityError extends NoydbError {\n /** The store method/capability that was missing. */\n readonly capability: string\n\n constructor(capability: string, callerApi: string, storeName?: string) {\n super(\n 'STORE_CAPABILITY',\n `${callerApi} requires the optional store capability \"${capability}\" ` +\n `but the active store${storeName ? ` (${storeName})` : ''} does not implement it. ` +\n `Use a store that supports \"${capability}\" (store-memory, store-file) or pass an explicit ` +\n `vault list to bypass enumeration.`,\n )\n this.name = 'StoreCapabilityError'\n this.capability = capability\n }\n}\n\nexport class PrivilegeEscalationError extends NoydbError {\n readonly offendingCollection: string\n\n constructor(offendingCollection: string, message?: string) {\n super(\n 'PRIVILEGE_ESCALATION',\n message ??\n `Privilege escalation: grantor has no DEK for collection \"${offendingCollection}\" and cannot grant access to it.`,\n )\n this.name = 'PrivilegeEscalationError'\n this.offendingCollection = offendingCollection\n }\n}\n\n/**\n * Thrown by `Collection.put` / `.delete` when the target record's\n * envelope `_ts` falls within a closed accounting period.\n *\n * Distinct from `ReadOnlyError` (keyring-level), `ReadOnlyAtInstantError`\n * (historical view), and `ReadOnlyFrameError` (shadow vault): this\n * error is about the STORED RECORD being sealed by an operator call\n * to `vault.closePeriod()`, independent of caller permissions or\n * view type. The `periodName` and `endDate` fields name the sealing\n * period so audit UIs can surface a \"this record is locked in\n * FY2026-Q1 (closed 2026-03-31)\" message without parsing the error\n * string.\n *\n * To apply a correction after close, book a compensating entry in a\n * new period rather than unlocking the old one. Re-opening a closed\n * period is deliberately unsupported.\n */\nexport class PeriodClosedError extends NoydbError {\n readonly periodName: string\n readonly endDate: string\n readonly recordTs: string\n\n constructor(periodName: string, endDate: string, recordTs: string) {\n super(\n 'PERIOD_CLOSED',\n `Cannot modify record (last written ${recordTs}) — sealed by closed period ` +\n `\"${periodName}\" (endDate: ${endDate}). Post a compensating entry in a ` +\n `new period instead.`,\n )\n this.name = 'PeriodClosedError'\n this.periodName = periodName\n this.endDate = endDate\n this.recordTs = recordTs\n }\n}\n\n/**\n * Thrown when a `put()` or `delete()` is rejected by a guard's `check`\n * function. The `reason` is the message the guard supplied — typically a\n * short business description (e.g. \"invoice is issued\"). The full\n * collection + id are surfaced so audit UIs can link back to the record.\n */\nexport class RecordLockedError extends NoydbError {\n readonly collection: string\n readonly id: string\n readonly reason: string\n\n constructor(collection: string, id: string, reason: string) {\n super(\n 'RECORD_LOCKED',\n `Cannot modify ${collection}/${id} — locked by guard: ${reason}. ` +\n `Use withTransactions({ amendment: true, reason }) with admin/owner role to override.`,\n )\n this.name = 'RecordLockedError'\n this.collection = collection\n this.id = id\n this.reason = reason\n }\n}\n\n/**\n * Thrown when a `put()` changes one or more fields that are frozen by a\n * `frozenFields` guard. The `fields` list contains the specific paths\n * that were detected as changed.\n */\nexport class FieldFrozenError extends NoydbError {\n readonly collection: string\n readonly id: string\n readonly fields: readonly string[]\n\n constructor(collection: string, id: string, fields: readonly string[]) {\n super(\n 'FIELD_FROZEN',\n `Cannot change frozen field(s) on ${collection}/${id}: ${fields.join(', ')}. ` +\n `Use withTransactions({ amendment: true, reason }) with admin/owner role to override.`,\n )\n this.name = 'FieldFrozenError'\n this.collection = collection\n this.id = id\n this.fields = fields\n }\n}\n\n/**\n * Thrown by an amendment invariant when the proposed change-set violates\n * the declared business rule (e.g. disbursement total not preserved).\n * Triggers a full transaction rollback via the existing revert pass.\n */\nexport class InvariantError extends NoydbError {\n constructor(message: string) {\n super('INVARIANT_VIOLATED', message)\n this.name = 'InvariantError'\n }\n}\n\n/**\n * Thrown at `withTransactions({ amendment: true })` open if the caller's\n * role is not in the guard's allowed amendment roles. Fail-fast: thrown\n * before any writes are attempted.\n */\nexport class AmendmentForbiddenError extends NoydbError {\n readonly userId: string\n readonly role: string\n\n constructor(userId: string, role: string) {\n super(\n 'AMENDMENT_FORBIDDEN',\n `User \"${userId}\" with role \"${role}\" cannot open an amendment transaction. ` +\n `Amendments require admin or owner role.`,\n )\n this.name = 'AmendmentForbiddenError'\n this.userId = userId\n this.role = role\n }\n}\n\n/**\n * Thrown by `listUsersWithEnvelopes` when the vault's user directory\n * has been disabled (via `db.setDirectoryEnabled(vault, false)`) and\n * the caller's role is neither `owner` nor `admin`. Owner/admin can\n * still enumerate users — the toggle is a UX privacy switch, not a\n * security boundary.\n *\n * Honest caveat: this is a UX flag, not a privacy guarantee. The\n * envelope ciphertext is still in the store, the keyring file is\n * still listed at `_keyring/*`, and anyone with direct store read\n * access can count keyrings without going through the hub. See\n * `docs/subsystems/user-envelope.md` → \"Directory visibility\".\n */\nexport class DirectoryDisabledError extends NoydbError {\n readonly vault: string\n\n constructor(vault: string) {\n super(\n 'DIRECTORY_DISABLED',\n `Vault \"${vault}\" has its user directory disabled. ` +\n `Only owners and admins can call listUsersWithEnvelopes() here. ` +\n `Use db.setDirectoryEnabled(vault, true) to re-enable.`,\n )\n this.name = 'DirectoryDisabledError'\n this.vault = vault\n }\n}\n\n// ─── Hierarchical Access Errors ─────────────────────\n\n/**\n * Thrown when a user tries to act at a tier they are not cleared for.\n *\n * This is the umbrella error for tier write refusals:\n * - `put({ tier: N })` when the user's keyring lacks tier-N DEK.\n * - `elevate(id, N)` when the caller cannot reach tier N.\n *\n * Distinct from `TierAccessDeniedError` which covers *read* refusals on\n * the invisibility/ghost path.\n */\nexport class TierNotGrantedError extends NoydbError {\n readonly tier: number\n readonly collection: string\n\n constructor(collection: string, tier: number) {\n super(\n 'TIER_NOT_GRANTED',\n `User has no DEK for tier ${tier} in collection \"${collection}\"`,\n )\n this.name = 'TierNotGrantedError'\n this.collection = collection\n this.tier = tier\n }\n}\n\n/**\n * Thrown when an elevated-handle operation runs after the elevation's\n * TTL expired. Reads continue at the original tier; only writes\n * through the scoped handle flip to throwing once expired.\n */\nexport class ElevationExpiredError extends NoydbError {\n readonly tier: number\n readonly expiresAt: number\n\n constructor(opts: { tier: number; expiresAt: number }) {\n super(\n 'ELEVATION_EXPIRED',\n `Elevation to tier ${opts.tier} expired at ${new Date(opts.expiresAt).toISOString()}`,\n )\n this.name = 'ElevationExpiredError'\n this.tier = opts.tier\n this.expiresAt = opts.expiresAt\n }\n}\n\n/**\n * Thrown by `vault.elevate(...)` when an elevation is already active\n * on the vault. Adopters must `release()` the existing handle before\n * starting a new elevation.\n */\nexport class AlreadyElevatedError extends NoydbError {\n readonly activeTier: number\n\n constructor(activeTier: number) {\n super(\n 'ALREADY_ELEVATED',\n `Vault is already elevated to tier ${activeTier}; release the existing handle first`,\n )\n this.name = 'AlreadyElevatedError'\n this.activeTier = activeTier\n }\n}\n\n/**\n * Thrown when `demote()` is called by someone who is not the original\n * elevator and not an owner.\n */\nexport class TierDemoteDeniedError extends NoydbError {\n constructor(id: string, tier: number) {\n super(\n 'TIER_DEMOTE_DENIED',\n `Only the original elevator or an owner can demote record \"${id}\" from tier ${tier}`,\n )\n this.name = 'TierDemoteDeniedError'\n }\n}\n\n/**\n * Thrown when `db.delegate()` is called against a user that has no\n * keyring in the target vault — the delegation token cannot be\n * constructed without the target user's KEK wrap.\n */\nexport class DelegationTargetMissingError extends NoydbError {\n readonly toUser: string\n\n constructor(toUser: string) {\n super(\n 'DELEGATION_TARGET_MISSING',\n `Delegation target user \"${toUser}\" has no keyring in this vault`,\n )\n this.name = 'DelegationTargetMissingError'\n this.toUser = toUser\n }\n}\n\n// ─── Sync Errors ───────────────────────────────────────────────────────\n\n/**\n * Thrown when a `put()` detects an optimistic concurrency conflict.\n *\n * NOYDB uses version numbers (`_v`) for optimistic locking. If a `put()`\n * is called with `expectedVersion: N` but the stored record is at version\n * `M ≠ N`, the write is rejected and the caller must re-read, re-apply their\n * change, and retry. The `version` field carries the actual stored version\n * so callers can decide whether to retry or surface the conflict to the user.\n */\nexport class ConflictError extends NoydbError {\n /** The actual stored version at the time of conflict. */\n readonly version: number\n\n constructor(version: number, message = 'Version conflict') {\n super('CONFLICT', message)\n this.name = 'ConflictError'\n this.version = version\n }\n}\n\n/**\n * Thrown by `LedgerStore.append()` after exhausting its CAS retry\n * budget under multi-writer contention. Two browser tabs, a\n * web app + an offline mobile peer, or a server worker pool all\n * producing ledger entries against the same vault can race on the\n * \"read head, write head+1\" cycle; the optimistic-CAS retry loop\n * resolves the race for `casAtomic: true` stores, but pathological\n * contention (or a buggy peer) can still exhaust the budget. When\n * that happens, the chain is intact — the failed writer simply\n * couldn't claim a slot. Caller's choice whether to retry, queue,\n * or surface the failure to the user.\n */\nexport class LedgerContentionError extends NoydbError {\n readonly attempts: number\n\n constructor(attempts: number) {\n super(\n 'LEDGER_CONTENTION',\n `LedgerStore.append: failed to claim a chain slot after ${attempts} optimistic-CAS retries`,\n )\n this.name = 'LedgerContentionError'\n this.attempts = attempts\n }\n}\n\n/**\n * Thrown when a bundle push is rejected because the remote has been updated\n * since the local bundle was last pulled.\n *\n * Unlike `ConflictError` (per-record), this is a whole-bundle conflict —\n * the remote's bundle handle has changed. The caller must pull the new\n * bundle, merge, and re-push. `remoteVersion` is the handle of the newer\n * remote bundle for use in diagnostics.\n */\nexport class BundleVersionConflictError extends NoydbError {\n /** The bundle handle of the newer remote version that rejected the push. */\n readonly remoteVersion: string\n\n constructor(remoteVersion: string, message = 'Bundle version conflict — remote has been updated') {\n super('BUNDLE_VERSION_CONFLICT', message)\n this.name = 'BundleVersionConflictError'\n this.remoteVersion = remoteVersion\n }\n}\n\n/**\n * Thrown when a sync operation (push or pull) fails due to a network error.\n *\n * NOYDB's offline-first design means network errors are expected during sync.\n * Callers should catch `NetworkError`, surface connectivity status in the UI,\n * and rely on the `SyncScheduler` to retry when connectivity is restored.\n */\nexport class NetworkError extends NoydbError {\n constructor(message = 'Network error') {\n super('NETWORK_ERROR', message)\n this.name = 'NetworkError'\n }\n}\n\n// ─── Data Errors ───────────────────────────────────────────────────────\n\n/**\n * Thrown when `collection.get(id)` is called with an ID that does not exist.\n *\n * NOYDB collections are memory-first, so this error is synchronous and cheap —\n * it does not make a network round-trip. Callers that expect the record to be\n * absent should use `collection.getOrNull(id)` instead.\n */\nexport class NotFoundError extends NoydbError {\n constructor(message = 'Record not found') {\n super('NOT_FOUND', message)\n this.name = 'NotFoundError'\n }\n}\n\n/**\n * Thrown when application-level validation fails before encryption.\n *\n * Distinct from `SchemaValidationError` (Standard Schema v1 validator)\n * and `MissingTranslationError` (i18nText). `ValidationError` is the\n * general-purpose validation base — use it for custom guards in `put()`\n * hooks or store middleware.\n */\nexport class ValidationError extends NoydbError {\n constructor(message = 'Validation error') {\n super('VALIDATION_ERROR', message)\n this.name = 'ValidationError'\n }\n}\n\n/**\n * Thrown when a Standard Schema v1 validator rejects a record on\n * `put()` (input validation) or on read (output validation). Carries\n * the raw issue list so callers can render field-level errors.\n *\n * `direction` distinguishes the two cases:\n * - `'input'`: the user passed bad data into `put()`. This is a\n * normal error case that application code should handle — typically\n * by showing validation messages in the UI.\n * - `'output'`: stored data does not match the current schema. This\n * indicates a schema drift (the schema was changed without\n * migrating the existing records) and should be treated as a bug\n * — the application should not swallow it silently.\n *\n * The `issues` type is deliberately `readonly unknown[]` on this class\n * so that `errors.ts` doesn't need to import from `schema.ts` (and\n * create a dependency cycle). Callers who know they're holding a\n * `SchemaValidationError` can cast to the more precise\n * `readonly StandardSchemaV1Issue[]` from `schema.ts`.\n */\nexport class SchemaValidationError extends NoydbError {\n readonly issues: readonly unknown[]\n readonly direction: 'input' | 'output'\n\n constructor(\n message: string,\n issues: readonly unknown[],\n direction: 'input' | 'output',\n ) {\n super('SCHEMA_VALIDATION_FAILED', message)\n this.name = 'SchemaValidationError'\n this.issues = issues\n this.direction = direction\n }\n}\n\n// ─── Query DSL Errors ─────────────────────────────────────────────────\n\n/**\n * Thrown when `.groupBy().aggregate()` produces more than the hard\n * cardinality cap (default 100_000 groups)..\n *\n * The cap exists because `.groupBy()` materializes one bucket per\n * distinct key value in memory, and runaway cardinality — a groupBy\n * on a high-uniqueness field like `id` or `createdAt` — is almost\n * always a query mistake rather than legitimate use. A hard error is\n * better than silent OOM: the consumer sees an actionable message\n * naming the field and the observed cardinality, with guidance to\n * either narrow the query with `.where()` or accept the ceiling\n * override.\n *\n * A separate one-shot warning fires at 10% of the cap (10_000\n * groups) so consumers get a heads-up before the hard error — same\n * pattern as `JoinTooLargeError` and the `.join()` row ceiling.\n *\n * **Not overridable in.** The 100k cap is a fixed constant so\n * the failure mode is consistent across the codebase; a\n * `{ maxGroups }` override can be added later without a break if a\n * real consumer asks.\n */\nexport class GroupCardinalityError extends NoydbError {\n /** The field being grouped on. */\n readonly field: string\n /** Observed number of distinct groups at the moment the cap tripped. */\n readonly cardinality: number\n /** The cap that was exceeded. */\n readonly maxGroups: number\n\n constructor(field: string, cardinality: number, maxGroups: number) {\n super(\n 'GROUP_CARDINALITY',\n `.groupBy(\"${field}\") produced ${cardinality} distinct groups, ` +\n `exceeding the ${maxGroups}-group ceiling. This is almost always a ` +\n `query mistake — grouping on a high-uniqueness field like \"id\" or ` +\n `\"createdAt\" produces one bucket per record. Narrow the query with ` +\n `.where() before grouping, or group on a lower-cardinality field ` +\n `(status, category, clientId). If you genuinely need high-cardinality ` +\n `grouping, file an issue with your use case.`,\n )\n this.name = 'GroupCardinalityError'\n this.field = field\n this.cardinality = cardinality\n this.maxGroups = maxGroups\n }\n}\n\n/**\n * Thrown in lazy mode when a `.query()` / `.where()` / `.orderBy()` clause\n * references a field that does not have a declared index.\n *\n * Lazy-mode queries only work when every touched field is indexed.\n * This is deliberate — silent scan-fallback would hide the performance\n * cliff that lazy-mode indexes exist to prevent.\n *\n * Payload:\n * - `collection` — name of the collection queried\n * - `touchedFields` — every field referenced by the query (filter + order)\n * - `missingFields` — subset of `touchedFields` that have no declared index\n */\nexport class IndexRequiredError extends NoydbError {\n readonly collection: string\n readonly touchedFields: readonly string[]\n readonly missingFields: readonly string[]\n\n constructor(args: { collection: string; touchedFields: readonly string[]; missingFields: readonly string[] }) {\n super(\n 'INDEX_REQUIRED',\n `Collection \"${args.collection}\": query references unindexed fields in lazy mode ` +\n `(missing: ${args.missingFields.join(', ')}). ` +\n `Declare an index on each field, or use collection.scan() for non-indexed iteration.`,\n )\n this.name = 'IndexRequiredError'\n this.collection = args.collection\n this.touchedFields = [...args.touchedFields]\n this.missingFields = [...args.missingFields]\n }\n}\n\n/**\n * Thrown (or surfaced via the `index:write-partial` event) when one or more\n * per-indexed-field side-car writes fail after the main record write has\n * already succeeded.\n *\n * Not thrown out of `.put()` / `.delete()` directly — those succeed when the\n * main record succeeds. Instead, `IndexWriteFailureError` instances are collected\n * into the session-scoped reconcile queue and emitted on the Collection\n * emitter as `index:write-partial`.\n *\n * Payload:\n * - `recordId` — the id of the main record whose side-car writes failed\n * - `field` — the indexed field whose side-car write failed\n * - `op` — `'put'` or `'delete'`, indicating which mutation was in flight\n * - `cause` — the underlying error from the store\n */\nexport class IndexWriteFailureError extends NoydbError {\n readonly recordId: string\n readonly field: string\n readonly op: 'put' | 'delete'\n override readonly cause: unknown\n\n constructor(args: { recordId: string; field: string; op: 'put' | 'delete'; cause: unknown }) {\n super(\n 'INDEX_WRITE_FAILURE',\n `Index side-car ${args.op} failed for field \"${args.field}\" on record \"${args.recordId}\"`,\n )\n this.name = 'IndexWriteFailureError'\n this.recordId = args.recordId\n this.field = args.field\n this.op = args.op\n this.cause = args.cause\n }\n}\n\n// ─── Bundle Format Errors ─────────────────────────────────\n\n/**\n * Thrown by `readNoydbBundle()` when the body bytes don't match\n * the integrity hash declared in the bundle header — i.e. someone\n * modified the bytes between write and read.\n *\n * Distinct from a generic `Error` (which would be thrown for\n * format violations like a missing magic prefix or malformed\n * header JSON) so consumers can pattern-match the corruption case\n * and handle it differently from a producer bug. A\n * `BundleIntegrityError` indicates \"the bytes you got are not\n * what was written\"; a plain `Error` from `parsePrefixAndHeader`\n * indicates \"what was written wasn't a valid bundle in the first\n * place.\"\n *\n * Also thrown when decompression fails after the integrity hash\n * passed — that's a producer bug (the wrong algorithm byte was\n * written) but it surfaces with the same error class because the\n * end result is \"the body cannot be turned back into a dump.\"\n */\nexport class BundleIntegrityError extends NoydbError {\n constructor(message: string) {\n super('BUNDLE_INTEGRITY', `.noydb bundle integrity check failed: ${message}`)\n this.name = 'BundleIntegrityError'\n }\n}\n\n/**\n * Thrown by `readNoydbBundle` (#197) when the bundle carries\n * sealed per-user passphrases but no supplied `SealingKeyProvider`\n * has a `.id` (= `pid`) matching the sealed entry's `pid`.\n *\n * Carries the failing pid + the user id so the recipient can\n * surface an actionable prompt:\n *\n * ```\n * BundleSealMismatchError: bundle carries sealed passphrase for user \"alice\"\n * under provider \"macos-keychain:com.acme.app/alice@acme.example\",\n * but no registered provider matches that pid.\n * ```\n *\n * Three resolution paths the message names (per foundation §11.9.4):\n *\n * 1. Configure a provider matching the pid and retry import.\n * 2. Pass `attemptUnsealAcrossProviders: true` to try each\n * registered provider regardless of pid.\n * 3. Inspect without unsealing — pass no `sealingProviders` to\n * receive the sealed entries unmodified for offline analysis.\n */\nexport class BundleSealMismatchError extends NoydbError {\n readonly userId: string\n readonly pid: string\n constructor(userId: string, pid: string) {\n super(\n 'BUNDLE_SEAL_MISMATCH',\n `bundle carries sealed passphrase for user \"${userId}\" under provider `\n + `\"${pid}\", but no registered provider matches that pid.\\n\\n`\n + 'Resolutions:\\n'\n + ' 1. Configure a provider matching the pid and retry import.\\n'\n + ' 2. Pass `attemptUnsealAcrossProviders: true` to try each registered\\n'\n + ' provider regardless of pid (extra credential prompts may surface).\\n'\n + ' 3. Inspect the bundle without unsealing — pass no `sealingProviders`\\n'\n + ' to receive the sealed entries unmodified for offline analysis.',\n )\n this.name = 'BundleSealMismatchError'\n this.userId = userId\n this.pid = pid\n }\n}\n\n// ─── i18n / Dictionary Errors ──────────────────────────\n\n/**\n * Thrown when `vault.collection()` is called with a name that is\n * reserved for NOYDB internal use (any name starting with `_dict_`).\n *\n * Dictionary collections are accessed exclusively via\n * `vault.dictionary(name)` — attempting to open one as a regular\n * collection would bypass the dictionary invariants (ACL, rename\n * tracking, reserved-name policy).\n */\nexport class ReservedCollectionNameError extends NoydbError {\n /** The rejected collection name. */\n readonly collectionName: string\n\n constructor(collectionName: string) {\n super(\n 'RESERVED_COLLECTION_NAME',\n `\"${collectionName}\" is a reserved collection name. ` +\n `Use vault.dictionary(\"${collectionName.replace(/^_dict_/, '')}\") ` +\n `to access dictionary collections.`,\n )\n this.name = 'ReservedCollectionNameError'\n this.collectionName = collectionName\n }\n}\n\n/**\n * Thrown by `DictionaryHandle.get()` and `DictionaryHandle.delete()` when\n * the requested key does not exist in the dictionary.\n *\n * Distinct from `NotFoundError` (which is for data records) so callers\n * can distinguish \"data record missing\" from \"dictionary key missing\"\n * without inspecting error messages.\n */\nexport class DictKeyMissingError extends NoydbError {\n /** The dictionary name. */\n readonly dictionaryName: string\n /** The key that was not found. */\n readonly key: string\n\n constructor(dictionaryName: string, key: string) {\n super(\n 'DICT_KEY_MISSING',\n `Dictionary \"${dictionaryName}\" has no entry for key \"${key}\".`,\n )\n this.name = 'DictKeyMissingError'\n this.dictionaryName = dictionaryName\n this.key = key\n }\n}\n\n/**\n * Thrown by `DictionaryHandle.delete()` in strict mode when the key to\n * be deleted is still referenced by one or more records.\n *\n * The caller must either rename the key first (the only sanctioned\n * mass-mutation path) or pass `{ mode: 'warn' }` to skip the check\n * (development only).\n */\nexport class DictKeyInUseError extends NoydbError {\n /** The dictionary name. */\n readonly dictionaryName: string\n /** The key that is still referenced. */\n readonly key: string\n /** Name of the first collection found to reference this key. */\n readonly usedBy: string\n /** Number of records in `usedBy` that reference this key. */\n readonly count: number\n\n constructor(\n dictionaryName: string,\n key: string,\n usedBy: string,\n count: number,\n ) {\n super(\n 'DICT_KEY_IN_USE',\n `Cannot delete key \"${key}\" from dictionary \"${dictionaryName}\": ` +\n `${count} record(s) in \"${usedBy}\" still reference it. ` +\n `Use dictionary.rename(\"${key}\", newKey) to rewrite references first.`,\n )\n this.name = 'DictKeyInUseError'\n this.dictionaryName = dictionaryName\n this.key = key\n this.usedBy = usedBy\n this.count = count\n }\n}\n\n/**\n * Thrown by `Collection.put()` when an `i18nText` field is missing one\n * or more required translations.\n *\n * The `missing` array names each locale code that was absent from the\n * field value. The `field` property names the field so callers can\n * render a field-level error message without parsing the string.\n */\nexport class MissingTranslationError extends NoydbError {\n /** The field name whose translation(s) are missing. */\n readonly field: string\n /** Locale codes that were required but absent. */\n readonly missing: readonly string[]\n\n constructor(field: string, missing: readonly string[], message?: string) {\n super(\n 'MISSING_TRANSLATION',\n message ??\n `Field \"${field}\": missing required translation(s): ${missing.join(', ')}.`,\n )\n this.name = 'MissingTranslationError'\n this.field = field\n this.missing = missing\n }\n}\n\n/**\n * Thrown when reading an `i18nText` field without specifying a locale —\n * either at the call site (`get(id, { locale })`) or on the vault\n * (`openVault(name, { locale })`).\n *\n * Also thrown when `resolveI18nText()` exhausts the fallback chain and\n * no translation is available for the requested locale.\n *\n * The `field` property names the field that triggered the error so the\n * caller can surface it in the UI.\n */\nexport class LocaleNotSpecifiedError extends NoydbError {\n /** The field name that required a locale. */\n readonly field: string\n\n constructor(field: string, message?: string) {\n super(\n 'LOCALE_NOT_SPECIFIED',\n message ??\n `Cannot read i18nText field \"${field}\" without a locale. ` +\n `Pass { locale } to get()/list()/query() or set a default via ` +\n `openVault(name, { locale }).`,\n )\n this.name = 'LocaleNotSpecifiedError'\n this.field = field\n }\n}\n\n// ─── Translator Errors ─────────────────────────────────────\n\n/**\n * Thrown when a collection has an `i18nText` field with\n * `autoTranslate: true` but no `plaintextTranslator` was configured\n * on `createNoydb()`.\n *\n * The error is raised at `put()` time (not at schema construction) so\n * the mis-configuration is surfaced by the first write rather than\n * silently at startup.\n */\nexport class TranslatorNotConfiguredError extends NoydbError {\n /** The field that requested auto-translation. */\n readonly field: string\n /** The collection the put was targeting. */\n readonly collection: string\n\n constructor(field: string, collection: string) {\n super(\n 'TRANSLATOR_NOT_CONFIGURED',\n `Field \"${field}\" in collection \"${collection}\" has autoTranslate: true, ` +\n `but no plaintextTranslator was configured on createNoydb(). ` +\n `Either configure a plaintextTranslator or remove autoTranslate from the schema.`,\n )\n this.name = 'TranslatorNotConfiguredError'\n this.field = field\n this.collection = collection\n }\n}\n\n// ─── Backup Errors ─────────────────────────────────────────\n\n/**\n * Thrown when `Vault.load()` finds that a backup's hash chain\n * doesn't verify, or that its embedded `ledgerHead.hash` doesn't\n * match the chain head reconstructed from the loaded entries.\n *\n * Distinct from `BackupCorruptedError` so callers can choose to\n * recover from one but not the other (e.g., a corrupted JSON file is\n * unrecoverable; a chain mismatch might mean the backup is from an\n * incompatible noy-db version).\n */\nexport class BackupLedgerError extends NoydbError {\n /** First-broken-entry index, if known. */\n readonly divergedAt?: number\n\n constructor(message: string, divergedAt?: number) {\n super('BACKUP_LEDGER', message)\n this.name = 'BackupLedgerError'\n if (divergedAt !== undefined) this.divergedAt = divergedAt\n }\n}\n\n/**\n * Thrown when `Vault.load()` finds that the backup's data\n * collection content doesn't match the ledger's recorded\n * `payloadHash`es. This is the \"envelope was tampered with after\n * dump\" detection — the chain itself can be intact, but if any\n * encrypted record bytes were swapped, this check catches it.\n */\nexport class BackupCorruptedError extends NoydbError {\n /** The (collection, id) pair whose envelope failed the hash check. */\n readonly collection: string\n readonly id: string\n\n constructor(collection: string, id: string, message: string) {\n super('BACKUP_CORRUPTED', message)\n this.name = 'BackupCorruptedError'\n this.collection = collection\n this.id = id\n }\n}\n\n// ─── Session Errors ───────────────────────────────────────\n\n/**\n * Thrown by `resolveSession()` when the session token's `expiresAt`\n * timestamp is in the past. The session key is also removed from the\n * in-memory store when this is thrown, so retrying with the same sessionId\n * will produce `SessionNotFoundError`.\n *\n * Separate from `SessionNotFoundError` so callers can distinguish between\n * \"session is gone\" (key store cleared, tab reloaded) and \"session is\n * still in the store but has exceeded its lifetime\" (idle timeout, absolute\n * timeout, policy-driven expiry). The remediation differs: expired sessions\n * should prompt a fresh unlock; not-found sessions may indicate a bug or a\n * cross-tab scenario where the session was never established.\n */\nexport class SessionExpiredError extends NoydbError {\n readonly sessionId: string\n\n constructor(sessionId: string) {\n super('SESSION_EXPIRED', `Session \"${sessionId}\" has expired. Re-unlock to continue.`)\n this.name = 'SessionExpiredError'\n this.sessionId = sessionId\n }\n}\n\n/**\n * Thrown by `resolveSession()` when the session key cannot be found in\n * the module-level store. This happens when:\n * - The session was explicitly revoked via `revokeSession()`.\n * - The JS context was reloaded (tab navigation, page refresh, worker restart).\n * - `Noydb.close()` was called (which calls `revokeAllSessions()`).\n * - The sessionId is wrong or was generated by a different JS context.\n *\n * The session token (if the caller holds it) is permanently useless after\n * this error — the key is gone and cannot be recovered.\n */\nexport class SessionNotFoundError extends NoydbError {\n readonly sessionId: string\n\n constructor(sessionId: string) {\n super('SESSION_NOT_FOUND', `Session key for \"${sessionId}\" not found. The session may have been revoked or the page reloaded.`)\n this.name = 'SessionNotFoundError'\n this.sessionId = sessionId\n }\n}\n\n/**\n * Thrown when a session policy blocks an operation — for example,\n * `requireReAuthFor: ['export']` is set and the caller attempts to\n * call `exportStream()` without re-authenticating for this session.\n *\n * The `operation` field names the specific operation that was blocked\n * (e.g. `'export'`, `'grant'`, `'rotate'`) so the caller can surface\n * a targeted prompt (\"Please re-enter your passphrase to export data\").\n */\nexport class SessionPolicyError extends NoydbError {\n readonly operation: string\n\n constructor(operation: string, message?: string) {\n super(\n 'SESSION_POLICY',\n message ?? `Operation \"${operation}\" requires re-authentication per the active session policy.`,\n )\n this.name = 'SessionPolicyError'\n this.operation = operation\n }\n}\n\n// ─── Query / Join Errors ────────────────────────────────────\n\n/**\n * Thrown when a `.join()` would exceed its configured row ceiling on\n * either side. The ceiling defaults to 50,000 per side and can be\n * overridden via the `{ maxRows }` option on `.join()`.\n *\n * Carries both row counts so the error message can show which side\n * tripped the limit (e.g. \"left had 60,000 rows, right had 1,200,\n * max was 50,000\"). The `side` field is machine-readable so test\n * code and devtools can match on it without regex-parsing the\n * message.\n *\n * The row ceiling exists because joins are bounded in-memory\n * operations over materialized record sets. Consumers whose\n * collections genuinely exceed the ceiling should track \n * (streaming joins over `scan()`) or filter the left side further\n * with `where()` / `limit()` before joining.\n */\nexport class JoinTooLargeError extends NoydbError {\n readonly leftRows: number\n readonly rightRows: number\n readonly maxRows: number\n readonly side: 'left' | 'right'\n\n constructor(opts: {\n leftRows: number\n rightRows: number\n maxRows: number\n side: 'left' | 'right'\n message: string\n }) {\n super('JOIN_TOO_LARGE', opts.message)\n this.name = 'JoinTooLargeError'\n this.leftRows = opts.leftRows\n this.rightRows = opts.rightRows\n this.maxRows = opts.maxRows\n this.side = opts.side\n }\n}\n\n/**\n * Thrown by `.join()` in strict `ref()` mode when a left-side record\n * points at a right-side id that does not exist in the target\n * collection.\n *\n * Distinct from `RefIntegrityError` so test code can pattern-match\n * on the *read-time* dangling case without catching *write-time*\n * integrity violations. Both indicate \"ref points at nothing\" but\n * happen at different lifecycle phases and deserve different\n * remediation in documentation: a RefIntegrityError on `put()`\n * means the input is invalid; a DanglingReferenceError on `.join()`\n * means stored data has drifted and `vault.checkIntegrity()`\n * is the right tool to find the full set of orphans.\n */\nexport class DanglingReferenceError extends NoydbError {\n readonly field: string\n readonly target: string\n readonly refId: string\n\n constructor(opts: {\n field: string\n target: string\n refId: string\n message: string\n }) {\n super('DANGLING_REFERENCE', opts.message)\n this.name = 'DanglingReferenceError'\n this.field = opts.field\n this.target = opts.target\n this.refId = opts.refId\n }\n}\n\n/**\n * Thrown by {@link sanitizeFilename} when an input filename cannot be\n * made safe — NUL byte, empty after normalization, missing\n * `opaqueId` for the opaque profile, `..` segment, or a `maxBytes`\n * cap too small to hold a single code point.\n */\nexport class FilenameSanitizationError extends NoydbError {\n constructor(message: string) {\n super('FILENAME_SANITIZATION', message)\n this.name = 'FilenameSanitizationError'\n }\n}\n\n/**\n * Thrown when a write target resolves OUTSIDE the requested\n * directory after sanitization — the canonical Zip-Slip class. The\n * sanitizer's job is to strip path-traversal segments; this error\n * is the defense-in-depth fallback at the FS write site.\n */\nexport class PathEscapeError extends NoydbError {\n readonly attempted: string\n readonly targetDir: string\n\n constructor(opts: { attempted: string; targetDir: string }) {\n super(\n 'PATH_ESCAPE',\n `Sanitized filename \"${opts.attempted}\" resolves outside target dir \"${opts.targetDir}\"`,\n )\n this.name = 'PathEscapeError'\n this.attempted = opts.attempted\n this.targetDir = opts.targetDir\n }\n}\n\n// ─── Derivation Errors ──────────────────────────────\n\n/**\n * Thrown at vault open if the derivation graph contains a cycle.\n * `path` is the offending chain (e.g. `['a', 'b', 'c', 'a']`).\n */\nexport class DerivationCycleError extends NoydbError {\n readonly path: readonly string[]\n\n constructor(path: readonly string[]) {\n super(\n 'DERIVATION_CYCLE',\n `Derivation graph contains a cycle: ${path.join(' → ')}. ` +\n `Refusing to open vault — break the cycle before retrying.`,\n )\n this.name = 'DerivationCycleError'\n this.path = path\n }\n}\n\n/**\n * Thrown when a cascade of source → output → source → … exceeds the\n * configured `maxDepth` (default 5).\n */\nexport class DerivationDepthError extends NoydbError {\n readonly limit: number\n readonly attempted: number\n\n constructor(limit: number, attempted: number) {\n super(\n 'DERIVATION_DEPTH',\n `Derivation cascade exceeded max depth ${limit} (attempted ${attempted}). ` +\n `Pass lifecycle: { maxDepth: N } to raise the limit if intentional.`,\n )\n this.name = 'DerivationDepthError'\n this.limit = limit\n this.attempted = attempted\n }\n}\n\n/**\n * Thrown at registration if a `withDerivation` strategy references an\n * output `collection` that isn't otherwise declared (no schema, no use\n * elsewhere). Surfacing this early catches typos in collection names.\n */\nexport class DerivationOutputUnknownError extends NoydbError {\n readonly collection: string\n\n constructor(collection: string) {\n super(\n 'DERIVATION_OUTPUT_UNKNOWN',\n `Derivation output collection \"${collection}\" is not declared on the vault. ` +\n `Register the collection (e.g. via schema) before registering a derivation that writes to it.`,\n )\n this.name = 'DerivationOutputUnknownError'\n this.collection = collection\n }\n}\n\n/**\n * Thrown when the user's `derive` function returns a value that doesn't\n * match the declared output spec (e.g. wrong shape, wrong key set).\n */\nexport class DerivationOutputShapeError extends NoydbError {\n readonly outputKey: string\n\n constructor(outputKey: string, detail: string) {\n super(\n 'DERIVATION_OUTPUT_SHAPE',\n `Derivation output \"${outputKey}\" has invalid shape: ${detail}.`,\n )\n this.name = 'DerivationOutputShapeError'\n this.outputKey = outputKey\n }\n}\n\n/**\n * Thrown by array-shape derivations (#200) when the `derive` function\n * returns more rows than the output's `maxFanout` cap. The cap exists\n * to keep dispatch cost bounded — without it a single source-row\n * update could fan out to thousands of derived rows, dominating the\n * write path.\n *\n * Defaults to `maxFanout: 64`. Raise on the output spec for\n * carry-forward expansion cases (e.g. monthly rows across multi-year\n * contracts).\n */\nexport class DerivationCapExceededError extends NoydbError {\n readonly outputKey: string\n readonly returned: number\n readonly maxFanout: number\n\n constructor(outputKey: string, returned: number, maxFanout: number) {\n super(\n 'DERIVATION_CAP_EXCEEDED',\n `Derivation array output \"${outputKey}\" returned ${returned} rows, exceeding `\n + `maxFanout=${maxFanout}. Raise \\`maxFanout\\` on the OutputSpec if this fanout `\n + 'is intended (the cap exists to keep dispatch cost bounded).',\n )\n this.name = 'DerivationCapExceededError'\n this.outputKey = outputKey\n this.returned = returned\n this.maxFanout = maxFanout\n }\n}\n\n/**\n * Thrown at vault open if the materialized-view graph contains a\n * cycle. `path` is the offending chain (e.g. `['a-mv', 'b-mv', 'a-mv']`).\n * Detected by the same shared DFS that catches `DerivationCycleError`;\n * surfaces with a distinct error type so consumers can disambiguate.\n */\nexport class MaterializedViewCycleError extends NoydbError {\n readonly path: readonly string[]\n\n constructor(path: readonly string[]) {\n super(\n 'MATERIALIZED_VIEW_CYCLE',\n `Materialized-view graph contains a cycle: ${path.join(' → ')}. ` +\n `Refusing to open vault — break the cycle before retrying.`,\n )\n this.name = 'MaterializedViewCycleError'\n this.path = path\n }\n}\n\n/**\n * Thrown at MV registration if the query references a source\n * collection that isn't declared on the vault. Surfacing this early\n * catches typos in collection names.\n */\nexport class MaterializedViewSourceUnknownError extends NoydbError {\n readonly mvName: string\n readonly collection: string\n\n constructor(mvName: string, collection: string) {\n super(\n 'MATERIALIZED_VIEW_SOURCE_UNKNOWN',\n `Materialized view \"${mvName}\" references unknown source collection \"${collection}\". ` +\n `Declare the collection (e.g. via schema or by writing to it once) before registering the MV.`,\n )\n this.name = 'MaterializedViewSourceUnknownError'\n this.mvName = mvName\n this.collection = collection\n }\n}\n\n/**\n * Thrown by the MV executor when a refresh produces more rows than\n * the configured ceiling. Default ceiling is 100k rows; override\n * per-MV via `maxRows`. Mirrors `JoinTooLargeError` /\n * `GroupCardinalityError` from the query DSL — the explosion is\n * detected BEFORE writes hit the store, so the source-write\n * transaction can roll back cleanly via strict-mode.\n */\nexport class MaterializedViewTooLargeError extends NoydbError {\n readonly mvName: string\n readonly expected: number\n readonly limit: number\n\n constructor(mvName: string, expected: number, limit: number) {\n super(\n 'MATERIALIZED_VIEW_TOO_LARGE',\n `Materialized view \"${mvName}\" would emit ${expected} rows, exceeding the configured limit of ${limit}. ` +\n `Override via { maxRows: N } on the MV strategy if intentional, or tighten the query's filter/groupBy.`,\n )\n this.name = 'MaterializedViewTooLargeError'\n this.mvName = mvName\n this.expected = expected\n this.limit = limit\n }\n}\n\n/**\n * Thrown by `withMaterializedView()` at registration time when the\n * strategy is structurally malformed. Distinct from\n * `MaterializedViewSourceUnknownError` (the source list is well-formed\n * but names a collection the vault doesn't know) and\n * `MaterializedViewCycleError` (the source graph has a cycle): this\n * error fires before either check, at the moment the spec is being\n * normalized.\n *\n * Today the trigger cases are all about the `query` / `unionSources`\n * dichotomy introduced by #165:\n * - both `query` and `unionSources` were set (mutually exclusive),\n * - neither `query` nor `unionSources` was set,\n * - `unionSources` has fewer than 2 arms,\n * - two arms in `unionSources` reference the same `collection`.\n *\n * The error message is prefixed with `[noy-db] withMaterializedView:`\n * so it's grep-friendly in logs and looks consistent with the existing\n * `ValidationError` messages from the same factory.\n */\nexport class MaterializedViewConfigError extends NoydbError {\n constructor(message: string) {\n super(\n 'MATERIALIZED_VIEW_CONFIG',\n `[noy-db] withMaterializedView: ${message}`,\n )\n this.name = 'MaterializedViewConfigError'\n }\n}\n\n/**\n * Thrown at vault open when a `withOverlayedView` declaration uses\n * another virtual-overlay name as its `base`. Multi-overlay stacking\n * is a v2 non-goal — the shallow expansion in\n * `QueryDependencyAnalyzer` would truncate at the inner overlay\n * name, leaving downstream MVs silently stale.\n */\nexport class OverlayBaseIsVirtualError extends NoydbError {\n readonly overlayName: string\n readonly base: string\n\n constructor(overlayName: string, base: string) {\n super(\n 'OVERLAY_BASE_IS_VIRTUAL',\n `withOverlayedView \"${overlayName}\": base \"${base}\" is another overlay's virtual name. ` +\n `Multi-overlay stacking is a v3 feature; base must reference a concrete collection (a real source or an MV output).`,\n )\n this.name = 'OverlayBaseIsVirtualError'\n this.overlayName = overlayName\n this.base = base\n }\n}\n\n/**\n * Thrown at vault open when a `withOverlayedView`'s `overlay`\n * references an unknown collection or an MV-owned collection. The\n * overlay collection is user-writable; MV-owned collections aren't.\n */\nexport class OverlayCollectionUnavailableError extends NoydbError {\n readonly overlayName: string\n readonly overlay: string\n\n constructor(overlayName: string, overlay: string) {\n super(\n 'OVERLAY_COLLECTION_UNAVAILABLE',\n `withOverlayedView \"${overlayName}\": overlay collection \"${overlay}\" is unavailable. ` +\n `It must be a real vault-known collection that is NOT itself an MV output collection.`,\n )\n this.name = 'OverlayCollectionUnavailableError'\n this.overlayName = overlayName\n this.overlay = overlay\n }\n}\n\n/**\n * Thrown at vault open when a `withOverlayedView`'s virtual `name`\n * collides with an MV output or a concrete source collection.\n */\nexport class OverlayNameCollisionError extends NoydbError {\n readonly overlayName: string\n\n constructor(overlayName: string) {\n super(\n 'OVERLAY_NAME_COLLISION',\n `withOverlayedView \"${overlayName}\": virtual name collides with an MV output or a concrete source collection. ` +\n `Pick a unique name for the virtual collection.`,\n )\n this.name = 'OverlayNameCollisionError'\n this.overlayName = overlayName\n }\n}\n\n/**\n * Thrown by the virtual overlay's `put(id, record)` when the\n * consumer-supplied `id` doesn't match `rowKey(record)`. Catches\n * fat-finger separator typos that would otherwise silently produce\n * orphaned overlay rows. Direct writes to the underlying overlay\n * collection (bypass the virtual layer) skip this validation.\n */\nexport class OverlayIdMismatchError extends NoydbError {\n readonly actual: string\n readonly expected: string\n\n constructor(actual: string, expected: string) {\n super(\n 'OVERLAY_ID_MISMATCH',\n `Overlay put(id, record): id \"${actual}\" does not match the base MV's rowKey(record) → \"${expected}\". ` +\n `Pass the row directly via .put(record) to derive the id, or fix the id to match the base MV's rowKey output.`,\n )\n this.name = 'OverlayIdMismatchError'\n this.actual = actual\n this.expected = expected\n }\n}\n","/**\n * `.noydb` container primitives — write, read, header-only read.\n *\n *. Wraps a `vault.dump()` JSON string in the\n * binary container described in `format.ts`.\n *\n * **Three primitives:**\n *\n * - `writeNoydbBundle(vault, opts?)` — produces the\n * full container bytes ready to write to disk or upload\n * - `readNoydbBundleHeader(bytes)` — parses just the header\n * without decompressing the body, fast file-type and\n * metadata read for cloud listing UIs\n * - `readNoydbBundle(bytes)` — full read: validates magic,\n * header, integrity hash, and decompresses the body to\n * return the original `dump()` JSON string for use with\n * `vault.load()`\n *\n * **Compression strategy:** brotli when available (Node 22+,\n * Chrome 124+, Firefox 122+), gzip fallback elsewhere. The\n * algorithm choice is encoded in the format byte at offset 5,\n * so readers handle either transparently. Brotli wins ~30-50%\n * on JSON payloads with repeated keys (which vault dumps\n * are).\n *\n * **Why split read/load?** `readNoydbBundle` returns the\n * *unwrapped JSON string*, not a Vault object. The caller\n * is responsible for piping that JSON into\n * `vault.load(json, passphrase)`. Splitting the layers\n * keeps the bundle module free of any crypto/passphrase\n * concerns — it's purely a format layer. The same `readNoydbBundle`\n * call can also feed verification tools, format inspectors, or\n * archive utilities that don't care about decryption.\n */\n\nimport {\n COMPRESSION_BROTLI,\n COMPRESSION_GZIP,\n COMPRESSION_NONE,\n FLAG_COMPRESSED,\n FLAG_HAS_INTEGRITY_HASH,\n NOYDB_BUNDLE_FORMAT_VERSION,\n NOYDB_BUNDLE_MAGIC,\n NOYDB_BUNDLE_PREFIX_BYTES,\n decodeBundleHeader,\n encodeBundleHeader,\n hasNoydbBundleMagic,\n readUint32BE,\n writeUint32BE,\n type CompressionAlgo,\n type NoydbBundleHeader,\n} from './format.js'\nimport { BundleIntegrityError, BundleSealMismatchError, ValidationError } from '../errors.js'\nimport type { Vault } from '../vault.js'\nimport type { BundleRecipient } from '../team/keyring.js'\nimport { pickLocale } from '../meta/public-envelope/storage.js'\nimport type { PublicEnvelope } from '../meta/public-envelope/types.js'\nimport type { SealingKeyProvider } from '../team/managed-passphrase.js'\n\n// ─── #215 auto-credential types ───────────────────────────────────────────────\n\n/**\n * The credential kinds that can be bundled for auto-unlock.\n * WebAuthn is intentionally excluded — it is hardware-bound and\n * cannot be embedded as a portable credential.\n */\nexport type AutoCredentialKind = 'passphrase' | 'password' | 'pin'\n\n/**\n * A typed credential for auto-unlock. Carries the credential `kind`\n * alongside the plaintext `value`, so consumers can dispatch the\n * correct login/prefill path rather than treating all credentials\n * as passphrases.\n *\n * `bundle.ts` is a pure format layer — it carries the credential\n * without interpreting it. The consumer is responsible for\n * dispatching on `kind`.\n */\nexport interface AutoCredential {\n readonly kind: AutoCredentialKind\n readonly value: string\n}\n\n/**\n * Options accepted by `writeNoydbBundle`.\n *\n * - `compression: 'auto'` (default) — try brotli, fall back to gzip\n * - `compression: 'brotli'` — force brotli, throw if unsupported\n * - `compression: 'gzip'` — force gzip\n * - `compression: 'none'` — no compression (round-trip testing only)\n *\n * **Slice filtering** (added in ):\n * - `collections` — allowlist of collection names to include. Internal\n * collections (keyrings, ledger) and excluded user collections are\n * dropped from the bundle. Records inside included collections are\n * carried through verbatim.\n * - `since` — only records whose envelope `_ts` is on/after the given\n * instant survive. Operates on the unencrypted envelope timestamp,\n * so plaintext access to records is not required.\n *\n * Both filters intersect (AND). When neither is provided the bundle is\n * a whole-vault snapshot, identical to today's behaviour.\n */\nexport interface WriteNoydbBundleOptions {\n readonly compression?: 'auto' | 'brotli' | 'gzip' | 'none'\n /** Allowlist of user-collection names to include. */\n readonly collections?: readonly string[]\n /**\n * Drop records whose envelope `_ts` is strictly older than this\n * instant. Accepts a `Date` or any ISO-8601 string parseable by\n * `new Date()`.\n */\n readonly since?: Date | string\n /**\n * Plaintext-pipeline record predicate. Decrypts each record\n * with the vault's per-collection DEK, runs the predicate, and\n * keeps the original ciphertext for survivors (no re-encrypt —\n * preserves zero-knowledge cleanly). Records the predicate returns\n * `false` for are dropped from the bundle.\n *\n * Async predicates are supported. Mutating the record from inside\n * the predicate is undefined behaviour.\n */\n readonly where?: (\n record: unknown,\n ctx: { collection: string; id: string },\n ) => boolean | Promise<boolean>\n /**\n * Hierarchical-tier ceiling. Records whose envelope `_tier`\n * is strictly greater than this number are dropped. Operates on the\n * envelope `_tier` (no decryption needed) — vault.exportStream is\n * referenced in the issue body for symmetry, but the tier value\n * lives on the unencrypted envelope. Vault without tiers is a no-op.\n */\n readonly tierAtMost?: number\n /**\n * Single-recipient re-keying shorthand. When set, the\n * bundle's keyring is replaced with one freshly-derived entry sealed\n * with this passphrase. The recipient inherits the source keyring's\n * userId, role, and permissions. Mutually exclusive with `recipients`.\n */\n readonly exportPassphrase?: string\n /**\n * Multi-recipient re-keying. Replaces the bundle's keyring\n * map with one slot per recipient, each sealed with its own\n * passphrase. DEKs are unwrapped from the source keyring once and\n * re-wrapped per recipient — record ciphertext is unchanged.\n *\n * Mutually exclusive with `exportPassphrase`. When neither is set,\n * the bundle inherits the source keyring as-is (today's behaviour,\n * suited to personal backup-and-restore).\n */\n readonly recipients?: readonly BundleRecipient[]\n /**\n * Auto-unlock — unsealed per-user credentials (#215).\n *\n * Generalises `autoPassphrases` to support any bundleable credential\n * kind (`passphrase` | `password` | `pin`).\n *\n * Public-by-design: anyone holding the bundle bytes can read these\n * plaintext credentials. Use for demo data, sample vaults,\n * prospect onboarding.\n *\n * The `policy: 'public-by-design'` discriminant is mandatory. A\n * bare `{ perUser }` without it is rejected at write time — the\n * safety net against a careless call against a production vault.\n *\n * Mutually exclusive with `sealedCredentials`, `autoPassphrases`,\n * and `sealedPassphrases`.\n */\n readonly autoCredentials?: {\n readonly policy: 'public-by-design'\n readonly perUser: Record<string, AutoCredential>\n }\n /**\n * Auto-unlock — per-user credentials sealed under a\n * {@link SealingKeyProvider} (#215).\n *\n * Generalises `sealedPassphrases` to support any bundleable\n * credential kind (`passphrase` | `password` | `pin`).\n *\n * The hub seals each user's plaintext credential under `provider`\n * and embeds the resulting sealed envelopes in the bundle. The\n * recipient must hold a provider with a matching `pid` (i.e.,\n * `provider.id`) to auto-unseal on import.\n *\n * `mode: 'self-target'` is the only supported mode — sender and\n * recipient share the same provider identity (same iCloud Keychain\n * entry, same MDM-provisioned bundle id, same KMS account, etc.).\n *\n * Mutually exclusive with `autoCredentials`, `autoPassphrases`,\n * and `sealedPassphrases`.\n */\n readonly sealedCredentials?: {\n readonly mode: 'self-target'\n readonly provider: SealingKeyProvider\n readonly perUser: Record<string, AutoCredential>\n }\n /**\n * @deprecated Use `autoCredentials` instead (#215).\n *\n * Auto-unlock — unsealed per-user passphrases (#197 slice 1).\n *\n * Public-by-design: anyone holding the bundle bytes can read these\n * plaintext credentials. Use for demo data, sample vaults,\n * prospect onboarding.\n *\n * The `policy: 'public-by-design'` discriminant is mandatory. A\n * bare `{ perUser }` without it is rejected at write time — the\n * safety net against a careless call against a production vault.\n *\n * Mutually exclusive with `autoCredentials`, `sealedCredentials`,\n * and `sealedPassphrases`.\n */\n readonly autoPassphrases?: {\n readonly policy: 'public-by-design'\n readonly perUser: Record<string, string>\n }\n /**\n * @deprecated Use `sealedCredentials` instead (#215).\n *\n * Auto-unlock — per-user passphrases sealed under a\n * {@link SealingKeyProvider} (#197 slice 1, self-target only).\n *\n * The hub seals each user's plaintext passphrase under `provider`\n * and embeds the resulting sealed envelopes in the bundle. The\n * recipient must hold a provider with a matching `pid` (i.e.,\n * `provider.id`) to auto-unseal on import.\n *\n * `mode: 'self-target'` is the only mode in slice 1 — sender and\n * recipient share the same provider identity (same iCloud Keychain\n * entry, same MDM-provisioned bundle id, same KMS account, etc.).\n * Recipient-target sealing via the `RecipientSealer` interface\n * (foundation §11.4) is deferred to a follow-up slice.\n *\n * Mutually exclusive with `autoCredentials`, `sealedCredentials`,\n * and `autoPassphrases`.\n */\n readonly sealedPassphrases?: {\n readonly mode: 'self-target'\n readonly provider: SealingKeyProvider\n readonly perUser: Record<string, string>\n }\n}\n\n/**\n * Result returned by `readNoydbBundle`. The caller is expected to\n * pass `dumpJson` into `vault.load(json, passphrase)` to\n * actually restore a vault. Splitting the layers keeps the\n * bundle module free of crypto concerns — see file-level docs.\n */\nexport interface NoydbBundleReadResult {\n readonly header: NoydbBundleHeader\n readonly dumpJson: string\n /**\n * Auto-unlock material (#197, widened in #215). Present only when\n * the header's `autoUnlock` flag is set AND the body's wrapped\n * structure survived parsing. Values are typed credentials — either\n * delivered plain (`kind: 'unsealed'`) or unsealed at read time\n * using one of the supplied `sealingProviders` (`kind: 'sealed'`).\n *\n * Consumers dispatch on `cred.kind` to choose the correct login /\n * prefill path. Pre-0.2 bundles (bare string entries) are coerced\n * to `{ kind: 'passphrase', value }` on read for back-compat.\n *\n * For `kind: 'sealed'` bundles read without `sealingProviders`, the\n * `value` field is the raw base64 sealed bytes — opaque to the\n * consumer until unsealed elsewhere.\n */\n readonly autoUnlock?: {\n readonly kind: 'unsealed' | 'sealed'\n readonly perUser: Record<string, AutoCredential>\n }\n}\n\n/**\n * Sealed credential entry as it appears in the bundle body's\n * `_autoUnlock.perUser` map when the bundle was written with\n * `sealedCredentials` (or the deprecated `sealedPassphrases`).\n * Provider's sealed output is base64-encoded; the `pid` is the\n * dispatch key matched against recipient-supplied\n * `SealingKeyProvider.id`. The `kind` carries the plaintext-tier\n * metadata so the consumer can dispatch on credential type without\n * unsealing first.\n *\n * Back-compat: `kind` is absent in pre-0.2 bundles — readers must\n * default to `'passphrase'` when not present.\n */\ninterface SealedAutoUnlockEntry {\n readonly pid: string\n readonly sealed: string\n readonly alg: 'aes-256-gcm'\n readonly kind?: AutoCredentialKind\n readonly hint?: Record<string, unknown>\n}\n\n/**\n * Discriminated wrapper carried in the bundle body when the header's\n * `autoUnlock` flag is set. Without the flag, the body is the raw\n * `vault.dump()` JSON string (the pre-#197 shape).\n *\n * Back-compat: pre-0.2 bundles carry bare `string` values in the\n * unsealed `perUser` map. Readers must coerce those to\n * `{ kind: 'passphrase', value }`.\n */\ninterface AutoUnlockBody {\n readonly _noydb_bundle_body: 1\n readonly dump: string\n readonly _autoUnlock:\n | { readonly kind: 'unsealed'; readonly perUser: Record<string, AutoCredential | string> }\n | { readonly kind: 'sealed'; readonly perUser: Record<string, SealedAutoUnlockEntry> }\n}\n\n/**\n * Options accepted by {@link readNoydbBundle} for the #197\n * auto-unlock paths. Without these the reader behaves exactly as\n * pre-#197 (header parsed; body returned as `dumpJson`).\n */\nexport interface ReadNoydbBundleOptions {\n /**\n * Recipient-side sealing providers used to unseal entries from\n * `sealedPassphrases`. The reader picks the one whose `.id`\n * matches each entry's `pid`. Multiple providers may be supplied\n * (different users may seal under different identities).\n *\n * When unset and the bundle carries sealed envelopes, the\n * `autoUnlock.perUser` map remains the SEALED entries unmodified\n * — callers can inspect them or unseal elsewhere.\n */\n readonly sealingProviders?: readonly SealingKeyProvider[]\n /**\n * Opt-in trial mode for unsealing — when an entry's `pid` doesn't\n * match a registered provider, try each provider whose alg\n * matches. Default `false` (strict-pid dispatch per foundation\n * §11.9.2). Surfaces extra credential prompts; use deliberately.\n */\n readonly attemptUnsealAcrossProviders?: boolean\n}\n\n// ─── #197/#215 auto-unlock helpers ────────────────────────────────────────────\n\n/**\n * Internal normalized form of the auto-unlock options, computed once\n * from the four public-facing fields (autoCredentials, sealedCredentials,\n * autoPassphrases, sealedPassphrases). Callers work against this shape\n * so the build + validate paths share a single normalizer.\n */\ninterface NormalizedAutoUnlock {\n readonly mode: 'unsealed' | 'sealed'\n readonly provider?: SealingKeyProvider\n readonly perUser: Record<string, AutoCredential>\n}\n\n/**\n * Coerce a `Record<string, string>` (legacy passphrase-only map) into\n * a `Record<string, AutoCredential>` by tagging each entry as\n * `kind: 'passphrase'`. Used by the normalizer to promote the deprecated\n * `autoPassphrases`/`sealedPassphrases` sugar.\n */\nfunction toAutoCredentials(m: Record<string, string>): Record<string, AutoCredential> {\n return Object.fromEntries(\n Object.entries(m).map(([u, value]) => [u, { kind: 'passphrase' as const, value }]),\n )\n}\n\n/**\n * Normalize the four auto-unlock option fields into a single\n * `NormalizedAutoUnlock` (or `null` when none is set). Enforces mutual\n * exclusion — exactly one of the four may be present. Promotes the\n * deprecated sugar fields to `AutoCredential` shape.\n *\n * Does NOT validate field-level constraints (policy marker, perUser\n * length, mode, provider presence, kind allowlist) — those are checked\n * in `validateAutoUnlockOptions` after normalization.\n */\nfunction normalizeAutoUnlock(opts: WriteNoydbBundleOptions): NormalizedAutoUnlock | null {\n const set = [\n opts.autoCredentials,\n opts.sealedCredentials,\n opts.autoPassphrases,\n opts.sealedPassphrases,\n ].filter(v => v !== undefined).length\n if (set === 0) return null\n if (set > 1) {\n throw new ValidationError(\n 'writeNoydbBundle: only one of autoCredentials / sealedCredentials / '\n + 'autoPassphrases / sealedPassphrases may be set.',\n )\n }\n if (opts.autoCredentials !== undefined) {\n return { mode: 'unsealed', perUser: opts.autoCredentials.perUser }\n }\n if (opts.autoPassphrases !== undefined) {\n return { mode: 'unsealed', perUser: toAutoCredentials(opts.autoPassphrases.perUser) }\n }\n if (opts.sealedCredentials !== undefined) {\n return { mode: 'sealed', provider: opts.sealedCredentials.provider, perUser: opts.sealedCredentials.perUser }\n }\n // sealedPassphrases — only remaining option\n return {\n mode: 'sealed',\n provider: opts.sealedPassphrases!.provider,\n perUser: toAutoCredentials(opts.sealedPassphrases!.perUser),\n }\n}\n\n/**\n * Validate the auto-unlock options and return the resulting header\n * `autoUnlock` value (or null when no auto-unlock requested).\n *\n * Takes the pre-computed `NormalizedAutoUnlock` so the caller (i.e.\n * `writeNoydbBundle`) can pass the same object to `buildAutoUnlockWrapper`\n * without a second `normalizeAutoUnlock` call.\n *\n * Validation per spec (#197 + #215 §3):\n * - (mutual exclusion already enforced by normalizeAutoUnlock)\n * - unsealed path: `policy: 'public-by-design'` marker required\n * - non-empty `perUser` maps\n * - sealed path: `mode: 'self-target'` + provider present\n * - every AutoCredential.kind ∈ {passphrase, password, pin}\n * (WebAuthn is hardware-bound and cannot be bundled)\n *\n * Throws {@link ValidationError} on any violation.\n */\nfunction validateAutoUnlockOptions(\n opts: WriteNoydbBundleOptions,\n normalized: NormalizedAutoUnlock | null,\n): 'unsealed' | 'sealed' | null {\n if (normalized === null) return null\n\n const VALID_KINDS: ReadonlySet<string> = new Set(['passphrase', 'password', 'pin'])\n\n // Validate every credential kind before any further checks.\n for (const [userId, cred] of Object.entries(normalized.perUser)) {\n if (!VALID_KINDS.has(cred.kind)) {\n throw new ValidationError(\n `writeNoydbBundle: credential for user '${userId}' has unsupported kind '${cred.kind}'. `\n + 'auto-unlock supports passphrase/password/pin only; WebAuthn is hardware-bound '\n + 'and cannot be bundled.',\n )\n }\n }\n\n if (normalized.mode === 'unsealed') {\n // Read the policy marker from whichever active option carries it.\n const policy = opts.autoCredentials?.policy ?? opts.autoPassphrases?.policy\n if (policy !== 'public-by-design') {\n throw new ValidationError(\n 'writeNoydbBundle: `autoCredentials` (or `autoPassphrases`) requires '\n + '`policy: \"public-by-design\"`. '\n + 'This is an explicit opt-in marker — bundling plaintext credentials is '\n + 'safe only when those credentials are intended to be public (demo data, '\n + 'sample vaults). For production credentials, use `sealedCredentials` instead.',\n )\n }\n const userCount = Object.keys(normalized.perUser).length\n if (userCount === 0) {\n throw new ValidationError(\n 'writeNoydbBundle: `autoCredentials.perUser` (or `autoPassphrases.perUser`) '\n + 'must have at least one entry.',\n )\n }\n return 'unsealed'\n }\n\n // Sealed path.\n const mode = opts.sealedCredentials?.mode ?? opts.sealedPassphrases?.mode\n if (mode !== 'self-target') {\n throw new ValidationError(\n `writeNoydbBundle: \\`sealedCredentials.mode\\` (or \\`sealedPassphrases.mode\\`) must be `\n + `'self-target' in slice 1 (got '${String(mode)}'). Recipient-target sealing via the `\n + 'RecipientSealer interface is deferred per foundation §11.4.',\n )\n }\n if (normalized.provider === undefined) {\n throw new ValidationError(\n 'writeNoydbBundle: `sealedCredentials.provider` (or `sealedPassphrases.provider`) '\n + 'is required (a `SealingKeyProvider`).',\n )\n }\n const userCount = Object.keys(normalized.perUser).length\n if (userCount === 0) {\n throw new ValidationError(\n 'writeNoydbBundle: `sealedCredentials.perUser` (or `sealedPassphrases.perUser`) '\n + 'must have at least one entry.',\n )\n }\n return 'sealed'\n}\n\n/**\n * Build the body wrapper carrying the dump + `_autoUnlock` blob.\n * Takes the pre-computed `NormalizedAutoUnlock` so both validate and\n * build work off the same normalized form (no double-normalize).\n */\nasync function buildAutoUnlockWrapper(\n dumpJson: string,\n normalized: NormalizedAutoUnlock,\n): Promise<AutoUnlockBody> {\n if (normalized.mode === 'unsealed') {\n return {\n _noydb_bundle_body: 1,\n dump: dumpJson,\n _autoUnlock: {\n kind: 'unsealed',\n perUser: { ...normalized.perUser },\n },\n }\n }\n // Sealed path — seal each user's credential value under the provider.\n const provider = normalized.provider\n if (provider === undefined) {\n throw new Error('unreachable — validation should have caught this')\n }\n const sealedPerUser: Record<string, SealedAutoUnlockEntry> = {}\n const encoder = new TextEncoder()\n for (const [userId, cred] of Object.entries(normalized.perUser)) {\n const sealed = await provider.seal(encoder.encode(cred.value))\n sealedPerUser[userId] = {\n pid: provider.id,\n sealed: bytesToBase64(sealed),\n alg: 'aes-256-gcm',\n kind: cred.kind,\n }\n }\n return {\n _noydb_bundle_body: 1,\n dump: dumpJson,\n _autoUnlock: { kind: 'sealed', perUser: sealedPerUser },\n }\n}\n\n/**\n * Parse the body bytes when the header signaled an auto-unlock.\n * Returns the inner `dump` JSON string + the `_autoUnlock` blob;\n * throws if the wrapper structure is malformed.\n */\nfunction parseAutoUnlockBody(bodyString: string): { dump: string; blob: AutoUnlockBody['_autoUnlock'] } {\n let parsed: unknown\n try {\n parsed = JSON.parse(bodyString)\n } catch (err) {\n throw new BundleIntegrityError(\n 'header declared autoUnlock but body could not be parsed as JSON wrapper: '\n + (err instanceof Error ? err.message : String(err)),\n )\n }\n if (typeof parsed !== 'object' || parsed === null) {\n throw new BundleIntegrityError('autoUnlock body is not a JSON object')\n }\n const obj = parsed as Record<string, unknown>\n if (obj['_noydb_bundle_body'] !== 1) {\n throw new BundleIntegrityError(\n 'autoUnlock body missing `_noydb_bundle_body: 1` discriminator',\n )\n }\n if (typeof obj['dump'] !== 'string') {\n throw new BundleIntegrityError('autoUnlock body must carry a string `dump` field')\n }\n const blob = obj['_autoUnlock']\n if (typeof blob !== 'object' || blob === null) {\n throw new BundleIntegrityError('autoUnlock body missing `_autoUnlock` blob')\n }\n const blobObj = blob as Record<string, unknown>\n const kind = blobObj['kind']\n if (kind !== 'unsealed' && kind !== 'sealed') {\n throw new BundleIntegrityError(\n `autoUnlock blob has invalid kind ${String(kind)}; expected 'unsealed' or 'sealed'`,\n )\n }\n return {\n dump: obj['dump'],\n blob: blob as AutoUnlockBody['_autoUnlock'],\n }\n}\n\n/**\n * Coerce an unsealed perUser entry to `AutoCredential`. Pre-0.2 bundles\n * store bare strings; 0.2+ bundles store `{ kind, value }` objects.\n */\nfunction coerceUnsealed(entry: AutoCredential | string): AutoCredential {\n if (typeof entry === 'string') return { kind: 'passphrase', value: entry }\n return entry\n}\n\n/**\n * Resolve the `_autoUnlock` blob into a typed per-user credential map.\n *\n * - For `kind: 'unsealed'`: pass through, coercing pre-0.2 bare strings\n * to `{ kind: 'passphrase', value }`.\n * - For `kind: 'sealed'`: pick a `SealingKeyProvider` from\n * `opts.sealingProviders` whose `.id` matches each entry's `pid`;\n * unseal to `AutoCredential`. When no provider matches AND strict mode\n * (default), throw `BundleSealMismatchError`. With\n * `attemptUnsealAcrossProviders: true`, try each provider whose\n * `alg` matches the envelope.\n * - When `sealingProviders` is unset entirely on a `'sealed'` bundle,\n * pass through the SEALED entries as `{ kind, value: base64sealed }` —\n * the caller can inspect or unseal elsewhere.\n *\n * Pre-0.2 sealed entries missing `kind` default to `'passphrase'`.\n */\nasync function resolveAutoUnlock(\n blob: AutoUnlockBody['_autoUnlock'],\n opts: ReadNoydbBundleOptions,\n): Promise<{ kind: 'unsealed' | 'sealed'; perUser: Record<string, AutoCredential> }> {\n if (blob.kind === 'unsealed') {\n const resolved: Record<string, AutoCredential> = {}\n for (const [userId, entry] of Object.entries(blob.perUser)) {\n resolved[userId] = coerceUnsealed(entry)\n }\n return { kind: 'unsealed', perUser: resolved }\n }\n // Sealed path.\n if (opts.sealingProviders === undefined || opts.sealingProviders.length === 0) {\n // Inspection mode — pass the sealed payload through as a typed\n // credential whose `value` is the opaque base64 sealed bytes.\n // The caller is signalled by `kind: 'sealed'` on the outer result.\n const passthrough: Record<string, AutoCredential> = {}\n for (const [userId, entry] of Object.entries(blob.perUser)) {\n passthrough[userId] = { kind: entry.kind ?? 'passphrase', value: entry.sealed }\n }\n return { kind: 'sealed', perUser: passthrough }\n }\n const providersByPid = new Map<string, SealingKeyProvider>()\n for (const p of opts.sealingProviders) providersByPid.set(p.id, p)\n\n const decoder = new TextDecoder()\n const unsealedMap: Record<string, AutoCredential> = {}\n\n for (const [userId, entry] of Object.entries(blob.perUser)) {\n const credKind: AutoCredentialKind = entry.kind ?? 'passphrase'\n const provider = providersByPid.get(entry.pid)\n if (provider === undefined) {\n if (opts.attemptUnsealAcrossProviders === true) {\n // Try each provider; first that succeeds wins.\n let opened: string | null = null\n for (const candidate of opts.sealingProviders) {\n try {\n const plaintextBytes = await candidate.unseal(base64ToBytes(entry.sealed))\n opened = decoder.decode(plaintextBytes)\n break\n } catch {\n // try next\n }\n }\n if (opened === null) {\n throw new BundleSealMismatchError(userId, entry.pid)\n }\n unsealedMap[userId] = { kind: credKind, value: opened }\n continue\n }\n throw new BundleSealMismatchError(userId, entry.pid)\n }\n const plaintextBytes = await provider.unseal(base64ToBytes(entry.sealed))\n unsealedMap[userId] = { kind: credKind, value: decoder.decode(plaintextBytes) }\n }\n return { kind: 'sealed', perUser: unsealedMap }\n}\n\nfunction bytesToBase64(bytes: Uint8Array): string {\n let binary = ''\n for (let i = 0; i < bytes.length; i++) binary += String.fromCharCode(bytes[i]!)\n return btoa(binary)\n}\n\nfunction base64ToBytes(b64: string): Uint8Array {\n const binary = atob(b64)\n const out = new Uint8Array(binary.length)\n for (let i = 0; i < binary.length; i++) out[i] = binary.charCodeAt(i)\n return out\n}\n\n/**\n * Detect whether the runtime's `CompressionStream` supports brotli.\n *\n * Brotli requires Node 22+ / Chrome 124+ / Firefox 122+. The\n * detection runs the `CompressionStream` constructor in a\n * try/catch — unsupported formats throw `TypeError` synchronously,\n * making this a safe one-shot check that we cache for the\n * lifetime of the process.\n */\nlet cachedBrotliSupport: boolean | null = null\nfunction supportsBrotliCompression(): boolean {\n if (cachedBrotliSupport !== null) return cachedBrotliSupport\n try {\n new CompressionStream('br' as CompressionFormat)\n cachedBrotliSupport = true\n } catch {\n cachedBrotliSupport = false\n }\n return cachedBrotliSupport\n}\n\n/** Test-only: reset the brotli detection cache between tests. */\nexport function resetBrotliSupportCache(): void {\n cachedBrotliSupport = null\n}\n\n/**\n * Pick the compression algorithm and the corresponding format byte\n * from a user option. Throws if the user explicitly requests brotli\n * on a runtime that doesn't support it — a silent fallback would\n * make the produced bundle smaller-than-expected and confuse\n * size-bound tests.\n */\nfunction selectCompression(option: WriteNoydbBundleOptions['compression']): {\n format: CompressionAlgo\n streamFormat: CompressionFormat | null\n} {\n const choice = option ?? 'auto'\n if (choice === 'none') return { format: COMPRESSION_NONE, streamFormat: null }\n if (choice === 'gzip') return { format: COMPRESSION_GZIP, streamFormat: 'gzip' }\n if (choice === 'brotli') {\n if (!supportsBrotliCompression()) {\n throw new Error(\n `writeNoydbBundle({ compression: 'brotli' }) is not supported on this ` +\n `runtime. Brotli requires Node 22+, Chrome 124+, or Firefox 122+. ` +\n `Use { compression: 'auto' } to fall back to gzip silently, or ` +\n `{ compression: 'gzip' } to be explicit.`,\n )\n }\n return { format: COMPRESSION_BROTLI, streamFormat: 'br' as CompressionFormat }\n }\n // 'auto' — prefer brotli, fall back to gzip\n if (supportsBrotliCompression()) {\n return { format: COMPRESSION_BROTLI, streamFormat: 'br' as CompressionFormat }\n }\n return { format: COMPRESSION_GZIP, streamFormat: 'gzip' }\n}\n\n/**\n * Pump a Uint8Array through a CompressionStream / DecompressionStream\n * and collect the output. Both APIs are universally available in\n * Node 18+ and modern browsers; the only variance is which\n * formats they support, handled by `selectCompression` above.\n *\n * Implementation: build a single-chunk ReadableStream from the\n * input, pipe through the transform, then drain the resulting\n * ReadableStream into a single concatenated Uint8Array. This is\n * O(N) memory in the input + output sizes, which is fine for the\n * dump-sized payloads (typically <50MB) targets.\n */\nasync function pumpThroughStream(\n input: Uint8Array,\n stream: CompressionStream | DecompressionStream,\n): Promise<Uint8Array> {\n const readable = new Blob([input as BlobPart]).stream().pipeThrough(stream)\n const reader = readable.getReader()\n const chunks: Uint8Array[] = []\n let total = 0\n for (;;) {\n const { value, done } = await reader.read()\n if (done) break\n if (value) {\n chunks.push(value as Uint8Array)\n total += value.length\n }\n }\n const out = new Uint8Array(total)\n let offset = 0\n for (const chunk of chunks) {\n out.set(chunk, offset)\n offset += chunk.length\n }\n return out\n}\n\n/**\n * SHA-256 hex digest of `bytes`. Used for the bundle integrity\n * hash carried in the header. Web Crypto API only — no Node\n * crypto module, no third-party hash library.\n *\n * The output format is lowercase hex (64 chars for SHA-256). The\n * format validator pins this — uppercase or mixed-case digests\n * are rejected, so the writer and reader agree on canonicalization.\n */\nasync function sha256Hex(bytes: Uint8Array): Promise<string> {\n // Copy into a fresh ArrayBuffer-backed Uint8Array. The\n // underlying buffer of `bytes` may be SharedArrayBuffer (e.g.\n // from a worker), which `subtle.digest` rejects via TypeScript's\n // BufferSource type. Allocating a fresh ArrayBuffer-backed view\n // sidesteps the type narrowing and is portable across all\n // runtimes — the copy cost is O(N) but bundle bodies are\n // typically <50MB, well below the threshold where the copy\n // matters.\n const copy = new Uint8Array(bytes.length)\n copy.set(bytes)\n const digest = await crypto.subtle.digest('SHA-256', copy)\n const view = new Uint8Array(digest)\n let hex = ''\n for (let i = 0; i < view.length; i++) {\n hex += view[i]!.toString(16).padStart(2, '0')\n }\n return hex\n}\n\n/**\n * Concatenate any number of Uint8Arrays into a single new buffer.\n * Used to assemble the final bundle from its prefix + header +\n * body parts.\n */\nfunction concatBytes(parts: readonly Uint8Array[]): Uint8Array {\n let total = 0\n for (const p of parts) total += p.length\n const out = new Uint8Array(total)\n let offset = 0\n for (const p of parts) {\n out.set(p, offset)\n offset += p.length\n }\n return out\n}\n\n/**\n * Replace the bundle's keyrings with freshly built recipient slots,\n * one per supplied recipient. No-op when neither `exportPassphrase`\n * nor `recipients` is set — the source keyring is inherited as-is.\n *\n * The single-passphrase shorthand creates a one-recipient list whose\n * id, role, and permissions inherit from the source vault — useful\n * for \"back up to a different passphrase\" without changing role\n * semantics. The multi-recipient form wraps each slot independently\n * with its declared role + permissions.\n *\n * @internal\n */\nasync function applyRecipientRewrap(\n vault: Vault,\n dumpJson: string,\n opts: WriteNoydbBundleOptions,\n): Promise<string> {\n if (opts.exportPassphrase === undefined && opts.recipients === undefined) {\n return dumpJson\n }\n\n const recipients: readonly BundleRecipient[] =\n opts.recipients ?? [\n {\n id: vault.userId,\n passphrase: opts.exportPassphrase as string,\n role: vault.role,\n },\n ]\n\n const recipientKeyrings = await vault.buildBundleRecipientKeyrings(recipients)\n\n const backup = JSON.parse(dumpJson) as { keyrings: unknown; [k: string]: unknown }\n backup.keyrings = recipientKeyrings\n return JSON.stringify(backup)\n}\n\n/**\n * Apply opt-in slice filters to a vault dump JSON string. Filters that\n * narrow the bundle without crossing the encryption boundary — both\n * operate on metadata (collection name, envelope `_ts`) and never need\n * to decrypt records. When neither filter is set, the dump is returned\n * unchanged so the no-arg path stays a pure passthrough.\n *\n * Internal-collection filtering: when a `collections` allowlist is\n * provided, the bundle still carries `_internal` (ledger entries) and\n * the keyrings — they're necessary for the receiver to verify and\n * unlock the bundle. The allowlist applies to the user-collection\n * map only.\n *\n * @internal\n */\nfunction applySliceFilters(\n dumpJson: string,\n opts: WriteNoydbBundleOptions,\n): string {\n const collectionsFilter = opts.collections\n ? new Set(opts.collections)\n : null\n const sinceMs =\n opts.since !== undefined ? new Date(opts.since).getTime() : null\n if (collectionsFilter === null && sinceMs === null) return dumpJson\n\n // Parse, prune, re-serialize. The dump shape is stable\n // (VaultBackup) so this is a one-off allocation; for vaults beyond\n // the documented 1K–50K target a streaming variant would be a\n // follow-up, but the simple parse path keeps the slice path\n // type-safe and trivially auditable.\n const backup = JSON.parse(dumpJson) as {\n collections?: Record<string, Record<string, { _ts?: string }>>\n [k: string]: unknown\n }\n\n if (backup.collections && typeof backup.collections === 'object') {\n const next: Record<string, Record<string, unknown>> = {}\n for (const [name, records] of Object.entries(backup.collections)) {\n if (collectionsFilter && !collectionsFilter.has(name)) continue\n if (sinceMs === null) {\n next[name] = records\n continue\n }\n const kept: Record<string, unknown> = {}\n for (const [id, env] of Object.entries(records)) {\n const envTs = env._ts ? new Date(env._ts).getTime() : NaN\n if (Number.isFinite(envTs) && envTs >= sinceMs) {\n kept[id] = env\n }\n }\n next[name] = kept\n }\n backup.collections = next as typeof backup.collections\n }\n\n return JSON.stringify(backup)\n}\n\n/**\n * Apply opt-in plaintext-tier filters\n * to a vault dump. Operates BEFORE `applySliceFilters` so the metadata\n * pass sees the trimmed record set.\n *\n * The filter never re-encrypts: surviving records carry their original\n * envelope unchanged. Failing records are dropped from the\n * `collections` map. Internal collections (ledger, deltas) and the\n * keyrings map are untouched.\n *\n * @internal\n */\nasync function applyPlaintextFilters(\n vault: Vault,\n dumpJson: string,\n opts: WriteNoydbBundleOptions,\n): Promise<string> {\n if (opts.where === undefined && opts.tierAtMost === undefined) {\n return dumpJson\n }\n\n type Env = { _ts?: string; _tier?: number; _iv: string; _data: string }\n const backup = JSON.parse(dumpJson) as {\n collections?: Record<string, Record<string, Env>>\n [k: string]: unknown\n }\n if (!backup.collections || typeof backup.collections !== 'object') {\n return dumpJson\n }\n\n const tierCeiling = opts.tierAtMost\n const where = opts.where\n\n const next: Record<string, Record<string, Env>> = {}\n for (const [collName, records] of Object.entries(backup.collections)) {\n const kept: Record<string, Env> = {}\n for (const [id, env] of Object.entries(records)) {\n // Tier ceiling — runs FIRST so we don't waste a decrypt on\n // records about to be dropped anyway. Envelope tier defaults to\n // 0 when absent (matches Vault's tier-0 conventions).\n if (tierCeiling !== undefined) {\n const tier = env._tier ?? 0\n if (tier > tierCeiling) continue\n }\n // Plaintext predicate — decrypt, run, keep on truthy. Errors\n // from inside the predicate propagate (callers want to see why\n // their filter blew up rather than getting a silent passthrough).\n if (where !== undefined) {\n const record = await vault._decryptEnvelopeForBundleFilter(\n env as never,\n collName,\n )\n const ok = await where(record, { collection: collName, id })\n if (!ok) continue\n }\n kept[id] = env\n }\n next[collName] = kept\n }\n backup.collections = next\n return JSON.stringify(backup)\n}\n\n/**\n * Write a `.noydb` bundle for the given vault.\n *\n * Pipeline:\n * 1. Resolve or create the compartment's stable bundle handle\n * via `vault.getBundleHandle()` — same handle on\n * every export from the same vault instance, so cloud\n * adapters can use it as a primary key.\n * 2. `vault.dump()` → JSON string with encrypted records\n * inside.\n * 3. UTF-8 encode the dump string.\n * 4. Compress (brotli if available, gzip fallback by default).\n * 5. Compute SHA-256 of the compressed body for integrity.\n * 6. Build the minimum-disclosure header from format version,\n * handle, body length, body sha.\n * 7. Serialize: magic (4) + flags (1) + algo (1) + headerLen (4)\n * + header JSON (N) + compressed body (M).\n *\n * The output is a single `Uint8Array`. Consumers writing to disk\n * pass it to `fs.writeFile`; consumers uploading to cloud storage\n * pass it as the request body. The `@noy-db/file` adapter wraps\n * this with a `saveBundle(path, vault)` helper.\n */\nexport async function writeNoydbBundle(\n vault: Vault,\n opts: WriteNoydbBundleOptions = {},\n): Promise<Uint8Array> {\n if (opts.exportPassphrase !== undefined && opts.recipients !== undefined) {\n throw new Error(\n 'writeNoydbBundle: pass either exportPassphrase or recipients, not both',\n )\n }\n\n // #197/#215 — auto-unlock: normalize once, validate + build from the\n // same NormalizedAutoUnlock object so there's no double-normalize call.\n const normalizedAutoUnlock = normalizeAutoUnlock(opts)\n const autoUnlockMode = validateAutoUnlockOptions(opts, normalizedAutoUnlock)\n\n const handle = await vault.getBundleHandle()\n const dumpJson = await vault.dump()\n\n // Re-keying: when caller supplied recipients (or the single-recipient\n // shorthand), substitute the bundle's `keyrings` map with freshly\n // built recipient slots before slice filters run.\n const rekeyed = await applyRecipientRewrap(vault, dumpJson, opts)\n // Plaintext-tier filters run BEFORE\n // the metadata-only slice — that way the metadata pass sees the\n // already-trimmed record set and the two filter chains compose\n // cleanly.\n const plainFiltered = await applyPlaintextFilters(vault, rekeyed, opts)\n const filtered = applySliceFilters(plainFiltered, opts)\n\n // If no auto-unlock requested, body remains the raw dump JSON\n // (pre-#197 shape). Otherwise build the wrapped body containing the\n // dump + `_autoUnlock` blob and serialize.\n const bodyJsonStr = normalizedAutoUnlock === null\n ? filtered\n : JSON.stringify(await buildAutoUnlockWrapper(filtered, normalizedAutoUnlock))\n const dumpBytes = new TextEncoder().encode(bodyJsonStr)\n\n const { format, streamFormat } = selectCompression(opts.compression)\n const body = streamFormat === null\n ? dumpBytes\n : await pumpThroughStream(dumpBytes, new CompressionStream(streamFormat))\n\n const bodySha256 = await sha256Hex(body)\n\n // Snapshot the source vault's public envelope into the header\n // when one is persisted. `Vault.getPublicEnvelope` tolerates a\n // missing document and returns undefined, which we propagate as\n // \"no envelope in the header.\" Vaults without a\n // `_meta/public-envelope` document produce minimum-disclosure\n // headers exactly like before, preserving back-compat.\n const publicEnvelope = await vault.getPublicEnvelope()\n\n const header: NoydbBundleHeader = {\n formatVersion: NOYDB_BUNDLE_FORMAT_VERSION,\n handle,\n bodyBytes: body.length,\n bodySha256,\n ...(publicEnvelope !== undefined ? { publicEnvelope } : {}),\n ...(autoUnlockMode !== null ? { autoUnlock: autoUnlockMode } : {}),\n }\n const headerBytes = encodeBundleHeader(header)\n\n // Assemble the fixed prefix in a 10-byte buffer.\n const prefix = new Uint8Array(NOYDB_BUNDLE_PREFIX_BYTES)\n prefix.set(NOYDB_BUNDLE_MAGIC, 0)\n prefix[4] =\n (streamFormat === null ? 0 : FLAG_COMPRESSED) | FLAG_HAS_INTEGRITY_HASH\n prefix[5] = format\n writeUint32BE(prefix, 6, headerBytes.length)\n\n return concatBytes([prefix, headerBytes, body])\n}\n\n/**\n * Internal helper shared by both readers — parses just the prefix\n * + header region of a bundle without touching the body. Returns\n * the parsed header plus the offset where the body starts and the\n * compression algorithm needed to decompress it.\n *\n * Throws on any format violation: missing/invalid magic, truncated\n * prefix, header length larger than the file, or unknown\n * compression algorithm.\n */\nfunction parsePrefixAndHeader(bytes: Uint8Array): {\n header: NoydbBundleHeader\n bodyOffset: number\n algo: CompressionAlgo\n flags: number\n} {\n if (!hasNoydbBundleMagic(bytes)) {\n throw new Error(\n `Not a .noydb bundle: missing 'NDB1' magic prefix. The first 4 bytes ` +\n `are ${[...bytes.slice(0, 4)].map((b) => b.toString(16).padStart(2, '0')).join(' ')}.`,\n )\n }\n if (bytes.length < NOYDB_BUNDLE_PREFIX_BYTES) {\n throw new Error(\n `Truncated .noydb bundle: file is only ${bytes.length} bytes, ` +\n `which is less than the ${NOYDB_BUNDLE_PREFIX_BYTES}-byte fixed prefix.`,\n )\n }\n const flags = bytes[4]!\n const algo = bytes[5]!\n if (algo !== COMPRESSION_NONE && algo !== COMPRESSION_GZIP && algo !== COMPRESSION_BROTLI) {\n throw new Error(\n `.noydb bundle declares unknown compression algorithm ${algo}. ` +\n `Known values: 0 (none), 1 (gzip), 2 (brotli).`,\n )\n }\n const headerLength = readUint32BE(bytes, 6)\n const bodyOffset = NOYDB_BUNDLE_PREFIX_BYTES + headerLength\n if (bodyOffset > bytes.length) {\n throw new Error(\n `Truncated .noydb bundle: declared header length ${headerLength} ` +\n `would extend past end of file (${bytes.length} bytes).`,\n )\n }\n const headerBytes = bytes.slice(NOYDB_BUNDLE_PREFIX_BYTES, bodyOffset)\n const header = decodeBundleHeader(headerBytes)\n return { header, bodyOffset, algo: algo as CompressionAlgo, flags }\n}\n\n/**\n * Read just the bundle header — no body decompression, no\n * integrity verification. Intended for cloud-listing UIs that want\n * to show the handle and size before downloading the full body.\n *\n * Returns the same `NoydbBundleHeader` shape as the writer, with\n * minimum-disclosure validation already applied.\n *\n * **Cost** — O(prefix + header bytes). The header is normally well\n * under 1 KB, but may grow to roughly 256 KB when a `publicEnvelope`\n * with an inline icon is present. Cloud-listing UIs that previously\n * assumed sub-KB header reads should account for this when sizing\n * range requests against bundles that may carry icons.\n */\nexport function readNoydbBundleHeader(bytes: Uint8Array): NoydbBundleHeader {\n return parsePrefixAndHeader(bytes).header\n}\n\n/**\n * Read just the bundle's public envelope (`docs/subsystems/public-envelope.md`)\n * — without verifying the body or even parsing the dump JSON. Pass\n * the raw bundle bytes; receive the owner-curated metadata or\n * `undefined` if the bundle was written without one.\n *\n * Locale-resolves any `name` / `description` map fields when `locale`\n * is supplied. Omitting `locale` returns the raw envelope.\n *\n * Same security caveat as the on-vault read path — the public\n * envelope is **untrusted hint** in v1; the encrypted body remains\n * the source of truth for vault contents.\n */\nexport function readNoydbBundlePublicEnvelope(\n bytes: Uint8Array,\n opts: { readonly locale?: string } = {},\n): PublicEnvelope | undefined {\n const header = parsePrefixAndHeader(bytes).header\n const env = header.publicEnvelope\n if (!env) return undefined\n if (opts.locale === undefined) return env\n return {\n ...env,\n ...(env.name !== undefined ? { name: pickLocale(env.name, opts.locale, env.defaultLocale) } : {}),\n ...(env.description !== undefined ? { description: pickLocale(env.description, opts.locale, env.defaultLocale) } : {}),\n }\n}\n\n/**\n * Read a full `.noydb` bundle: validate magic + header, verify\n * integrity hash over the body bytes, decompress, and return the\n * original `vault.dump()` JSON string ready to pass to\n * `vault.load()`.\n *\n * Throws `BundleIntegrityError` if the body's actual SHA-256 does\n * not match the value declared in the header. Distinct from a\n * format error so consumers can pattern-match in catch blocks\n * (corrupted-in-transit vs malformed-by-producer).\n *\n * Note: this function does NOT take a passphrase. The dump JSON\n * inside the body still contains encrypted records — restoring\n * the vault requires `vault.load(dumpJson, passphrase)`\n * after this call. Splitting the layers keeps the bundle module\n * free of crypto concerns and lets the same code feed format\n * inspectors that never decrypt anything.\n */\nexport async function readNoydbBundle(\n bytes: Uint8Array,\n opts: ReadNoydbBundleOptions = {},\n): Promise<NoydbBundleReadResult> {\n const { header, bodyOffset, algo } = parsePrefixAndHeader(bytes)\n const body = bytes.slice(bodyOffset)\n\n // Length check before hash check — a length mismatch is the\n // cheapest tamper signal and produces a more actionable error.\n if (body.length !== header.bodyBytes) {\n throw new BundleIntegrityError(\n `body length ${body.length} does not match header.bodyBytes ` +\n `${header.bodyBytes}. The bundle was truncated or padded ` +\n `between write and read.`,\n )\n }\n\n const actualSha = await sha256Hex(body)\n if (actualSha !== header.bodySha256) {\n throw new BundleIntegrityError(\n `body sha256 ${actualSha} does not match header.bodySha256 ` +\n `${header.bodySha256}. The bundle bytes were modified between ` +\n `write and read — refuse to decompress.`,\n )\n }\n\n let dumpBytes: Uint8Array\n if (algo === COMPRESSION_NONE) {\n dumpBytes = body\n } else {\n const streamFormat: CompressionFormat =\n algo === COMPRESSION_BROTLI ? ('br' as CompressionFormat) : 'gzip'\n try {\n dumpBytes = await pumpThroughStream(body, new DecompressionStream(streamFormat))\n } catch (err) {\n throw new BundleIntegrityError(\n `decompression failed: ${(err as Error).message}. The bundle ` +\n `passed the integrity hash but the body is not valid ` +\n `${streamFormat} data — likely a producer bug.`,\n )\n }\n }\n\n const bodyString = new TextDecoder('utf-8', { fatal: true }).decode(dumpBytes)\n\n // #197 — when the header signaled an auto-unlock, the body is a\n // JSON wrapper carrying the dump string + the auto-unlock blob.\n // When absent, the body IS the raw dump JSON (pre-#197 shape).\n if (header.autoUnlock === undefined) {\n return { header, dumpJson: bodyString }\n }\n const { dump, blob } = parseAutoUnlockBody(bodyString)\n const autoUnlock = await resolveAutoUnlock(blob, opts)\n return { header, dumpJson: dump, autoUnlock }\n}\n","/**\n * Minimal ULID generator — zero dependencies, Web Crypto API only.\n *\n *. Used by the bundle writer to generate stable opaque\n * handles for `.noydb` containers.\n *\n * **What's a ULID?** A 128-bit identifier encoded as 26 Crockford\n * base32 characters. Layout:\n *\n * ```\n * 01HYABCDEFGHJKMNPQRSTVWXYZ\n * |--------||---------------|\n * 48-bit 80-bit\n * timestamp randomness\n * ```\n *\n * The first 10 chars encode a millisecond Unix timestamp (so ULIDs\n * sort lexicographically by creation time), and the remaining 16\n * chars are random. Crockford base32 omits I/L/O/U to avoid\n * ambiguity in handwriting and URLs.\n *\n * **Why hand-roll instead of pulling in `ulid`?** The package adds\n * a dep, the implementation is ~30 lines, and the bundle module\n * is the only consumer. Adding `ulid` would also drag in its own\n * crypto polyfill that we don't need on Node 18+ or modern\n * browsers.\n *\n * **Privacy consideration:** the timestamp prefix is observable in\n * the bundle header. This is a deliberate trade-off:\n * - Pro: lexicographic sortability lets bundle adapters list\n * newest-first without an extra index.\n * - Con: a casual observer can read the bundle's creation time\n * from the handle. They cannot read it from any OTHER field\n * (the header explicitly forbids `_exported_at`), and a\n * creation timestamp is the same kind of metadata that\n * filesystem mtime would already expose for a downloaded\n * bundle. The leak is therefore equivalent to what's already\n * visible from the file's mtime — not a new exposure.\n *\n * If a future use case needs timestamp-free handles, a v2 of the\n * format could specify \"use the random portion only\" without a\n * format break — `validateBundleHeader` only checks the regex\n * shape, not the encoded timestamp.\n */\n\n/**\n * Crockford base32 alphabet — omits I, L, O, U to avoid handwriting\n * and URL-encoding ambiguity. 32 characters covering 5 bits each.\n */\nconst CROCKFORD_ALPHABET = '0123456789ABCDEFGHJKMNPQRSTVWXYZ'\n\n/**\n * Encode a non-negative integer as a fixed-width Crockford base32\n * string. The width is fixed (not the natural log32 length) so\n * leading zeros are preserved — that's required for the timestamp\n * prefix to remain lexicographically sortable.\n *\n * Used twice: once for the 48-bit timestamp portion (10 chars) and\n * once for each 40-bit half of the randomness (8 chars × 2).\n */\nfunction encodeBase32(value: number, length: number): string {\n let out = ''\n let v = value\n for (let i = 0; i < length; i++) {\n out = CROCKFORD_ALPHABET[v % 32]! + out\n v = Math.floor(v / 32)\n }\n return out\n}\n\n/**\n * Generate a fresh ULID. Uses `crypto.getRandomValues` for the\n * randomness portion — same Web Crypto API the rest of the\n * codebase uses for IVs and salt.\n *\n * Returns a 26-character string. Calling twice in the same\n * millisecond produces two distinct ULIDs (the random portion\n * differs); ULIDs from the same millisecond are NOT guaranteed\n * to be monotonically ordered relative to each other, only\n * relative to ULIDs from a different millisecond. The bundle\n * format never relies on intra-millisecond ordering.\n */\nexport function generateULID(): string {\n const now = Date.now()\n\n // 48-bit timestamp → 10 Crockford base32 characters.\n // JavaScript's max safe integer is 2^53 - 1; Date.now() is well\n // within that range until the year ~285,000 AD. Splitting into\n // high and low 24-bit halves keeps every intermediate value\n // inside the safe-integer range and avoids any ambiguity in the\n // base32 encoder above.\n const timestampHigh = Math.floor(now / 0x1000000) // top 24 bits\n const timestampLow = now & 0xffffff // bottom 24 bits\n const tsPart =\n encodeBase32(timestampHigh, 5) + encodeBase32(timestampLow, 5)\n\n // 80-bit randomness → 16 Crockford base32 characters. Split into\n // two 40-bit halves so each fits in JavaScript's safe-integer\n // range (53 bits) and the base32 encoder doesn't have to deal\n // with bigints.\n const randBytes = new Uint8Array(10)\n crypto.getRandomValues(randBytes)\n\n // First 5 bytes (40 bits) → 8 Crockford base32 characters.\n // Reconstruct the 40-bit integer from bytes in big-endian order.\n // Multiplication by 2^32 (instead of bit-shift) avoids JavaScript's\n // 32-bit integer cast on the high byte.\n const rand1 =\n randBytes[0]! * 2 ** 32 +\n (randBytes[1]! << 24 >>> 0) +\n (randBytes[2]! << 16) +\n (randBytes[3]! << 8) +\n randBytes[4]!\n // Same for the second 5 bytes.\n const rand2 =\n randBytes[5]! * 2 ** 32 +\n (randBytes[6]! << 24 >>> 0) +\n (randBytes[7]! << 16) +\n (randBytes[8]! << 8) +\n randBytes[9]!\n const randPart = encodeBase32(rand1, 8) + encodeBase32(rand2, 8)\n\n return tsPart + randPart\n}\n\n/**\n * Validate that a string is a syntactically well-formed ULID. Used\n * by the bundle header validator. Does NOT verify that the\n * timestamp portion decodes to a sensible date — the format only\n * cares about the encoding shape.\n */\nexport function isULID(value: string): boolean {\n return /^[0-9A-HJKMNP-TV-Z]{26}$/.test(value)\n}\n"],"mappings":"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","names":["userCount","plaintextBytes"]}
1
+ {"version":3,"sources":["../../src/errors.ts","../../src/types.ts","../../src/meta/public-envelope/schema.ts","../../src/meta/public-envelope/storage.ts","../../src/bundle/ulid.ts","../../src/crypto.ts","../../src/persisted-schemas/storage.ts","../../src/history/ledger/constants.ts","../../src/history/ledger/entry.ts","../../src/history/ledger/hash.ts","../../src/directory/storage.ts","../../src/directory/visibility.ts","../../src/validation.ts","../../src/meta/user-envelope/types.ts","../../src/meta/user-envelope/storage.ts","../../src/meta/user-envelope/index.ts","../../src/team/keyring.ts","../../src/team/managed-passphrase.ts","../../src/history/ledger/patch.ts","../../src/history/ledger/store.ts","../../src/policy/errors.ts","../../src/team/wrapped-deks.ts","../../src/team/recovery.ts","../../src/team/rotate-recover.ts","../../src/team/peer-recover.ts","../../src/policy/storage.ts","../../src/auth-introspection/index.ts","../../src/meta/public-envelope/types.ts","../../src/meta/public-envelope/index.ts","../../src/crdt/strategy.ts","../../src/i18n/strategy.ts","../../src/team/tiers.ts","../../src/schema.ts","../../src/history/strategy.ts","../../src/query/predicate.ts","../../src/query/join.ts","../../src/query/live.ts","../../src/aggregate/strategy.ts","../../src/query/builder.ts","../../src/aggregate/canonical-key.ts","../../src/aggregate/groupby.ts","../../src/query/scan-builder.ts","../../src/query/index.ts","../../src/indexing/persisted-indexes.ts","../../src/indexing/lazy-builder.ts","../../src/indexing/strategy.ts","../../src/cache/lru.ts","../../src/cache/policy.ts","../../src/cache/index.ts","../../src/team/sync-strategy.ts","../../src/blobs/strategy.ts","../../src/tx/transaction.ts","../../src/derivations/executor.ts","../../src/derivations/stale.ts","../../src/materialized-views/dependency-analyzer.ts","../../src/materialized-views/query-hash.ts","../../src/materialized-views/registry.ts","../../src/materialized-views/executor.ts","../../src/materialized-views/stale.ts","../../src/guards/executor.ts","../../src/derivations/fanout-sidecar.ts","../../src/collection.ts","../../src/overlay-views/virtual-collection.ts","../../src/shadow/strategy.ts","../../src/consent/strategy.ts","../../src/periods/strategy.ts","../../src/refs.ts","../../src/i18n/dictionary.ts","../../src/periods/periods.ts","../../src/periods/index.ts","../../src/blobs/export-blobs.ts","../../src/blobs/blob-compaction.ts","../../src/team/magic-link-grant.ts","../../src/meta/user-envelope/api.ts","../../src/persisted-schemas/canonicalize.ts","../../src/persisted-schemas/derive.ts","../../src/persisted-schemas/register.ts","../../src/introspection/fields.ts","../../src/introspection/walk.ts","../../src/attestation/signer.ts","../../src/attestation/issue.ts","../../src/attestation/revoke.ts","../../src/guards/registry.ts","../../src/guards/read-only-facade.ts","../../src/derivations/strategy-hash.ts","../../src/derivations/registry.ts","../../src/overlay-views/registry.ts","../../src/team/delegation.ts","../../src/vault.ts","../../src/events.ts","../../src/team/authenticators.ts","../../src/session/unlock-state.ts","../../src/tx/strategy.ts","../../src/store/sync-policy.ts","../../src/session/strategy.ts","../../src/policy/presets.ts","../../src/policy/engine.ts","../../src/policy/index.ts","../../src/noydb.ts","../../src/bundle/index.ts","../../src/bundle/format.ts","../../src/bundle/bundle.ts","../../src/bundle/walk-closure.ts","../../src/bundle/describe-extraction.ts","../../src/bundle/extract-partition.ts","../../src/bundle/adopt-partition.ts"],"sourcesContent":["/**\n * All NOYDB error classes — a single import surface for `catch` blocks and\n * `instanceof` checks.\n *\n * ## Class hierarchy\n *\n * ```\n * Error\n * └─ NoydbError (code: string)\n * ├─ Crypto errors\n * │ ├─ DecryptionError — AES-GCM tag failure\n * │ ├─ TamperedError — ciphertext modified after write\n * │ └─ InvalidKeyError — wrong passphrase / corrupt keyring\n * ├─ Access errors\n * │ ├─ NoAccessError — no DEK for this collection\n * │ ├─ ReadOnlyError — ro permission, write attempted\n * │ ├─ PermissionDeniedError — role too low for operation\n * │ ├─ PrivilegeEscalationError — grant wider than grantor holds\n * │ └─ StoreCapabilityError — optional store method missing\n * ├─ Sync errors\n * │ ├─ ConflictError — optimistic-lock version mismatch\n * │ ├─ BundleVersionConflictError — bundle push rejected by remote\n * │ └─ NetworkError — push/pull network failure\n * ├─ Data errors\n * │ ├─ NotFoundError — get(id) on missing record\n * │ ├─ ValidationError — application-level guard failed\n * │ └─ SchemaValidationError — Standard Schema v1 rejection\n * ├─ Query errors\n * │ ├─ JoinTooLargeError — join row ceiling exceeded\n * │ ├─ DanglingReferenceError — strict ref() points at nothing\n * │ ├─ GroupCardinalityError — groupBy bucket cap exceeded\n * │ ├─ IndexRequiredError — lazy-mode query touches unindexed field\n * │ └─ IndexWriteFailureError — index side-car put/delete failed post-main\n * ├─ i18n / Dictionary errors\n * │ ├─ ReservedCollectionNameError\n * │ ├─ DictKeyMissingError\n * │ ├─ DictKeyInUseError\n * │ ├─ MissingTranslationError\n * │ ├─ LocaleNotSpecifiedError\n * │ └─ TranslatorNotConfiguredError\n * ├─ Backup errors\n * │ ├─ BackupLedgerError — hash-chain verification failed\n * │ └─ BackupCorruptedError — envelope hash mismatch in dump\n * ├─ Bundle errors\n * │ └─ BundleIntegrityError — .noydb body sha256 mismatch\n * └─ Session errors\n * ├─ SessionExpiredError\n * ├─ SessionNotFoundError\n * └─ SessionPolicyError\n * ```\n *\n * ## Catching all NOYDB errors\n *\n * ```ts\n * import { NoydbError, InvalidKeyError, ConflictError } from '@noy-db/hub'\n *\n * try {\n * await vault.unlock(passphrase)\n * } catch (e) {\n * if (e instanceof InvalidKeyError) { showBadPassphraseUI(); return }\n * if (e instanceof NoydbError) { logToSentry(e.code, e); return }\n * throw e // unexpected — re-throw\n * }\n * ```\n *\n * @module\n */\n\n/**\n * Base class for all NOYDB errors.\n *\n * Every error thrown by `@noy-db/hub` extends this class, so consumers can\n * catch all NOYDB errors in a single `catch (e) { if (e instanceof NoydbError) ... }`\n * block. The `code` field is a machine-readable string (e.g. `'DECRYPTION_FAILED'`)\n * suitable for `switch` statements and logging pipelines.\n */\nexport class NoydbError extends Error {\n /** Machine-readable error code. Stable across library versions. */\n readonly code: string\n\n constructor(code: string, message: string) {\n super(message)\n this.name = 'NoydbError'\n this.code = code\n }\n}\n\n// ─── Crypto Errors ─────────────────────────────────────────────────────\n\n/**\n * Thrown when AES-GCM decryption fails.\n *\n * The most common cause is a wrong passphrase or a corrupted ciphertext.\n * A `DecryptionError` at the wrong passphrase level is caught internally\n * and re-thrown as `InvalidKeyError` — so in practice this surfaces for\n * per-record corruption rather than authentication failures.\n */\nexport class DecryptionError extends NoydbError {\n constructor(message = 'Decryption failed') {\n super('DECRYPTION_FAILED', message)\n this.name = 'DecryptionError'\n }\n}\n\n/**\n * Thrown when GCM tag verification fails, indicating the ciphertext was\n * modified after encryption.\n *\n * AES-256-GCM is authenticated encryption — the tag over the ciphertext\n * is checked on every decrypt. If any byte was flipped (accidental\n * corruption or deliberate tampering), decryption throws this error.\n * Treat it as a security alert: the stored bytes are not what NOYDB wrote.\n */\nexport class TamperedError extends NoydbError {\n constructor(message = 'Data integrity check failed — record may have been tampered with') {\n super('TAMPERED', message)\n this.name = 'TamperedError'\n }\n}\n\n/**\n * Thrown when key unwrapping fails, typically because the passphrase is wrong\n * or the keyring file is corrupted.\n *\n * NOYDB uses AES-KW (RFC 3394) to wrap DEKs with the KEK. If AES-KW\n * unwrapping fails, it means either the KEK was derived from the wrong\n * passphrase (PBKDF2 with 600K iterations) or the keyring bytes are\n * corrupted. This is the error shown to the user on a failed unlock attempt.\n */\nexport class InvalidKeyError extends NoydbError {\n constructor(message = 'Invalid key — wrong passphrase or corrupted keyring') {\n super('INVALID_KEY', message)\n this.name = 'InvalidKeyError'\n }\n}\n\n/**\n * Thrown when a keyring's wrapped-DEK set unwraps partially — at least\n * one DEK succeeds (proving the KEK is correct) but at least one fails.\n * The passphrase is right; the failed entries are corrupted.\n *\n * This is distinct from {@link InvalidKeyError} so that\n * `NoydbOptions.onInvalidKey: 'reset'` does NOT fire — resetting on\n * partial corruption would destroy the still-valid DEKs and the data\n * they protect, which is silent data loss in response to a feature\n * designed for stale-credential recovery.\n */\nexport class KeyringCorruptError extends NoydbError {\n readonly failedCollections: readonly string[]\n readonly intactCount: number\n constructor(opts: { failedCollections: readonly string[]; intactCount: number; message?: string }) {\n super(\n 'KEYRING_CORRUPT',\n opts.message ??\n `Keyring has ${opts.failedCollections.length} corrupted wrapped DEK(s) ` +\n `(${opts.failedCollections.join(', ')}); ${opts.intactCount} other DEK(s) ` +\n `unwrapped successfully — the passphrase is correct, the entries are damaged. ` +\n `Do NOT use onInvalidKey: 'reset' here — that would destroy the intact DEKs.`,\n )\n this.name = 'KeyringCorruptError'\n this.failedCollections = opts.failedCollections\n this.intactCount = opts.intactCount\n }\n}\n\n// ─── Access Errors ─────────────────────────────────────────────────────\n\n/**\n * Thrown when the authenticated user does not have a DEK for the requested\n * collection — i.e. the collection is not in their keyring at all.\n *\n * This is the \"no key for this door\" error. It is different from\n * `ReadOnlyError` (user has a key but it only grants ro) and from\n * `PermissionDeniedError` (user's role doesn't allow the operation).\n */\nexport class NoAccessError extends NoydbError {\n constructor(message = 'No access — user does not have a key for this collection') {\n super('NO_ACCESS', message)\n this.name = 'NoAccessError'\n }\n}\n\n/**\n * Thrown when a user with read-only (`ro`) permission attempts a write\n * operation (`put` or `delete`) on a collection.\n *\n * The user has a DEK for the collection (they can decrypt and read), but\n * their keyring grants only `ro`. To fix: re-grant the user with `rw`\n * permission, or do not attempt writes as a viewer/client role.\n */\nexport class ReadOnlyError extends NoydbError {\n constructor(message = 'Read-only — user has ro permission on this collection') {\n super('READ_ONLY', message)\n this.name = 'ReadOnlyError'\n }\n}\n\n/**\n * Thrown when a write is attempted against a historical view produced\n * by `vault.at(timestamp)`. Time-machine views are read-only by\n * contract — mutating the past would require either the shadow-vault\n * mechanism or a ledger-history rewrite (which breaks\n * the tamper-evidence guarantee).\n *\n * Distinct from {@link ReadOnlyError} (keyring-level) and\n * {@link PermissionDeniedError} (role-level): this error is about the\n * *view* being historical, independent of the caller's permissions.\n */\nexport class ReadOnlyAtInstantError extends NoydbError {\n constructor(operation: string, timestamp: string) {\n super(\n 'READ_ONLY_AT_INSTANT',\n `Cannot ${operation}() on a vault view anchored at ${timestamp} — time-machine views are read-only`,\n )\n this.name = 'ReadOnlyAtInstantError'\n }\n}\n\n/**\n * Thrown when a write is attempted against a shadow-vault frame\n * produced by `vault.frame()`. Frames are read-only by contract —\n * the use case is screen-sharing / demos / compliance review where\n * the operator wants to prevent accidental edits.\n *\n * Behavioural enforcement only — the underlying keyring still holds\n * write-capable DEKs. See {@link VaultFrame} for the full caveat.\n */\nexport class ReadOnlyFrameError extends NoydbError {\n constructor(operation: string) {\n super(\n 'READ_ONLY_FRAME',\n `Cannot ${operation}() on a vault frame — frames are read-only presentations of the current vault`,\n )\n this.name = 'ReadOnlyFrameError'\n }\n}\n\n/**\n * Thrown when the authenticated user's role does not permit the requested\n * operation — e.g. a `viewer` calling `grantAccess()`, or an `operator`\n * calling `rotateKeys()`.\n *\n * This is a role-level check (what the user's role allows), distinct from\n * `NoAccessError` (collection not in keyring) and `ReadOnlyError` (in\n * keyring, but write not allowed).\n */\nexport class PermissionDeniedError extends NoydbError {\n constructor(message = 'Permission denied — insufficient role for this operation') {\n super('PERMISSION_DENIED', message)\n this.name = 'PermissionDeniedError'\n }\n}\n\n/**\n * Thrown when an `@noy-db/as-*` export is attempted without the\n * required capability bit on the invoking keyring.\n *\n * Two sub-cases discriminated by the `tier` field:\n *\n * - `tier: 'plaintext'` — a plaintext-tier export (`as-xlsx`,\n * `as-csv`, `as-blob`, `as-zip`, …) was attempted but the\n * keyring's `exportCapability.plaintext` does not include the\n * requested `format` (nor the `'*'` wildcard). Default for every\n * role is `plaintext: []` — the owner must positively grant.\n * - `tier: 'bundle'` — an encrypted `as-noydb` bundle export was\n * attempted but the keyring's `exportCapability.bundle` is\n * `false`. Default for `owner`/`admin` is `true`; for\n * `operator`/`viewer`/`client` it is `false`.\n *\n * Distinct from `PermissionDeniedError` (role-level check) and\n * `NoAccessError` (collection not readable). Surfaces separately so\n * UI layers can show a \"request the export capability from your\n * admin\" flow rather than a generic permission error.\n */\nexport class ExportCapabilityError extends NoydbError {\n readonly tier: 'plaintext' | 'bundle'\n readonly format?: string\n readonly userId: string\n\n constructor(opts: {\n tier: 'plaintext' | 'bundle'\n userId: string\n format?: string\n message?: string\n }) {\n const msg =\n opts.message ??\n (opts.tier === 'plaintext'\n ? `Export capability denied — keyring \"${opts.userId}\" is not granted plaintext-export capability for format \"${opts.format ?? '<unknown>'}\". Ask a vault owner or admin to grant it via vault.grant({ exportCapability: { plaintext: ['${opts.format ?? '<format>'}'] } }).`\n : `Export capability denied — keyring \"${opts.userId}\" is not granted encrypted-bundle export capability. Ask a vault owner or admin to grant it via vault.grant({ exportCapability: { bundle: true } }).`)\n super('EXPORT_CAPABILITY', msg)\n this.name = 'ExportCapabilityError'\n this.tier = opts.tier\n this.userId = opts.userId\n if (opts.format !== undefined) this.format = opts.format\n }\n}\n\n/**\n * Thrown when a keyring file's `expires_at` cutoff has passed.\n * Surfaced by `loadKeyring` before any DEK unwrap is attempted —\n * past the cutoff the slot refuses to open even with the right\n * passphrase. Distinct from PBKDF2 / unwrap errors so consumer code\n * can show a precise \"this bundle slot has expired\" message instead\n * of the generic decryption-failure UX.\n *\n * Used predominantly on `BundleRecipient` slots produced by\n * `writeNoydbBundle({ recipients: [...] })` to time-box audit access.\n */\nexport class KeyringExpiredError extends NoydbError {\n readonly userId: string\n readonly expiresAt: string\n constructor(opts: { userId: string; expiresAt: string }) {\n super(\n 'KEYRING_EXPIRED',\n `Keyring \"${opts.userId}\" expired at ${opts.expiresAt}. ` +\n 'The slot refuses to unlock past its expiry timestamp.',\n )\n this.name = 'KeyringExpiredError'\n this.userId = opts.userId\n this.expiresAt = opts.expiresAt\n }\n}\n\n/**\n * Thrown when an `@noy-db/as-*` import is attempted but the invoking\n * keyring lacks the required import-capability bit.\n *\n * - `tier: 'plaintext'` — a plaintext-tier import (`as-csv`, `as-json`,\n * `as-ndjson`, `as-zip`, …) was attempted but the keyring's\n * `importCapability.plaintext` does not include the requested\n * `format` (nor the `'*'` wildcard).\n * - `tier: 'bundle'` — a `.noydb` bundle import was attempted but the\n * keyring's `importCapability.bundle` is not `true`.\n *\n * Default for every role on every dimension is closed — owners and\n * admins must positively grant the capability. Distinct from\n * `PermissionDeniedError` and `NoAccessError` so UI layers can show a\n * specific \"request the import capability\" flow.\n */\nexport class ImportCapabilityError extends NoydbError {\n readonly tier: 'plaintext' | 'bundle'\n readonly format?: string\n readonly userId: string\n\n constructor(opts: {\n tier: 'plaintext' | 'bundle'\n userId: string\n format?: string\n message?: string\n }) {\n const msg =\n opts.message ??\n (opts.tier === 'plaintext'\n ? `Import capability denied — keyring \"${opts.userId}\" is not granted plaintext-import capability for format \"${opts.format ?? '<unknown>'}\". Ask a vault owner or admin to grant it via vault.grant({ importCapability: { plaintext: ['${opts.format ?? '<format>'}'] } }).`\n : `Import capability denied — keyring \"${opts.userId}\" is not granted encrypted-bundle import capability. Ask a vault owner or admin to grant it via vault.grant({ importCapability: { bundle: true } }).`)\n super('IMPORT_CAPABILITY', msg)\n this.name = 'ImportCapabilityError'\n this.tier = opts.tier\n this.userId = opts.userId\n if (opts.format !== undefined) this.format = opts.format\n }\n}\n\n/**\n * Thrown when a grant would give the grantee a permission the grantor\n * does not themselves hold — the \"admin cannot grant what admin cannot\n * do\" rule from the admin-delegation work.\n *\n * Distinct from `PermissionDeniedError` so callers can tell the two\n * cases apart in logs and tests:\n *\n * - `PermissionDeniedError` — \"you are not allowed to perform this\n * operation at all\" (wrong role).\n * - `PrivilegeEscalationError` — \"you are allowed to grant, but not\n * with these specific permissions\" (widening attempt).\n *\n * Under the admin model the grantee of an admin-grants-admin call\n * inherits the caller's entire DEK set by construction, so this error\n * is structurally unreachable in typical flows. The check and error\n * class exist so that future per-collection admin scoping cannot\n * accidentally bypass the subset rule — the guard is already wired in.\n *\n * `offendingCollection` carries the first collection name that failed\n * the subset check, to make the violation actionable in error output.\n */\n/**\n * Thrown when a caller invokes an API that requires an optional\n * store capability the active store does not implement.\n *\n * Today the only call site is `Noydb.listAccessibleVaults()`,\n * which depends on the optional `NoydbStore.listVaults()`\n * method. The error message names the missing method and the calling\n * API so consumers know exactly which combination is unsupported,\n * and the `capability` field is machine-readable so library code can\n * pattern-match in catch blocks (e.g. fall back to a candidate-list\n * shape).\n *\n * The class lives in `errors.ts` rather than as a generic\n * `ValidationError` because the diagnostic shape is different: a\n * `ValidationError` says \"the inputs you passed are wrong\"; this\n * error says \"the inputs are fine, but the store you wired up\n * doesn't support what you're asking for.\" Different fix, different\n * documentation.\n */\nexport class StoreCapabilityError extends NoydbError {\n /** The store method/capability that was missing. */\n readonly capability: string\n\n constructor(capability: string, callerApi: string, storeName?: string) {\n super(\n 'STORE_CAPABILITY',\n `${callerApi} requires the optional store capability \"${capability}\" ` +\n `but the active store${storeName ? ` (${storeName})` : ''} does not implement it. ` +\n `Use a store that supports \"${capability}\" (store-memory, store-file) or pass an explicit ` +\n `vault list to bypass enumeration.`,\n )\n this.name = 'StoreCapabilityError'\n this.capability = capability\n }\n}\n\nexport class PrivilegeEscalationError extends NoydbError {\n readonly offendingCollection: string\n\n constructor(offendingCollection: string, message?: string) {\n super(\n 'PRIVILEGE_ESCALATION',\n message ??\n `Privilege escalation: grantor has no DEK for collection \"${offendingCollection}\" and cannot grant access to it.`,\n )\n this.name = 'PrivilegeEscalationError'\n this.offendingCollection = offendingCollection\n }\n}\n\n/**\n * Thrown by `Collection.put` / `.delete` when the target record's\n * envelope `_ts` falls within a closed accounting period.\n *\n * Distinct from `ReadOnlyError` (keyring-level), `ReadOnlyAtInstantError`\n * (historical view), and `ReadOnlyFrameError` (shadow vault): this\n * error is about the STORED RECORD being sealed by an operator call\n * to `vault.closePeriod()`, independent of caller permissions or\n * view type. The `periodName` and `endDate` fields name the sealing\n * period so audit UIs can surface a \"this record is locked in\n * FY2026-Q1 (closed 2026-03-31)\" message without parsing the error\n * string.\n *\n * To apply a correction after close, book a compensating entry in a\n * new period rather than unlocking the old one. Re-opening a closed\n * period is deliberately unsupported.\n */\nexport class PeriodClosedError extends NoydbError {\n readonly periodName: string\n readonly endDate: string\n readonly recordTs: string\n\n constructor(periodName: string, endDate: string, recordTs: string) {\n super(\n 'PERIOD_CLOSED',\n `Cannot modify record (last written ${recordTs}) — sealed by closed period ` +\n `\"${periodName}\" (endDate: ${endDate}). Post a compensating entry in a ` +\n `new period instead.`,\n )\n this.name = 'PeriodClosedError'\n this.periodName = periodName\n this.endDate = endDate\n this.recordTs = recordTs\n }\n}\n\n/**\n * Thrown when a `put()` or `delete()` is rejected by a guard's `check`\n * function. The `reason` is the message the guard supplied — typically a\n * short business description (e.g. \"invoice is issued\"). The full\n * collection + id are surfaced so audit UIs can link back to the record.\n */\nexport class RecordLockedError extends NoydbError {\n readonly collection: string\n readonly id: string\n readonly reason: string\n\n constructor(collection: string, id: string, reason: string) {\n super(\n 'RECORD_LOCKED',\n `Cannot modify ${collection}/${id} — locked by guard: ${reason}. ` +\n `Use withTransactions({ amendment: true, reason }) with admin/owner role to override.`,\n )\n this.name = 'RecordLockedError'\n this.collection = collection\n this.id = id\n this.reason = reason\n }\n}\n\n/**\n * Thrown when a `put()` changes one or more fields that are frozen by a\n * `frozenFields` guard. The `fields` list contains the specific paths\n * that were detected as changed.\n */\nexport class FieldFrozenError extends NoydbError {\n readonly collection: string\n readonly id: string\n readonly fields: readonly string[]\n\n constructor(collection: string, id: string, fields: readonly string[]) {\n super(\n 'FIELD_FROZEN',\n `Cannot change frozen field(s) on ${collection}/${id}: ${fields.join(', ')}. ` +\n `Use withTransactions({ amendment: true, reason }) with admin/owner role to override.`,\n )\n this.name = 'FieldFrozenError'\n this.collection = collection\n this.id = id\n this.fields = fields\n }\n}\n\n/**\n * Thrown by an amendment invariant when the proposed change-set violates\n * the declared business rule (e.g. disbursement total not preserved).\n * Triggers a full transaction rollback via the existing revert pass.\n */\nexport class InvariantError extends NoydbError {\n constructor(message: string) {\n super('INVARIANT_VIOLATED', message)\n this.name = 'InvariantError'\n }\n}\n\n/**\n * Thrown at `withTransactions({ amendment: true })` open if the caller's\n * role is not in the guard's allowed amendment roles. Fail-fast: thrown\n * before any writes are attempted.\n */\nexport class AmendmentForbiddenError extends NoydbError {\n readonly userId: string\n readonly role: string\n\n constructor(userId: string, role: string) {\n super(\n 'AMENDMENT_FORBIDDEN',\n `User \"${userId}\" with role \"${role}\" cannot open an amendment transaction. ` +\n `Amendments require admin or owner role.`,\n )\n this.name = 'AmendmentForbiddenError'\n this.userId = userId\n this.role = role\n }\n}\n\n/**\n * Thrown by `listUsersWithEnvelopes` when the vault's user directory\n * has been disabled (via `db.setDirectoryEnabled(vault, false)`) and\n * the caller's role is neither `owner` nor `admin`. Owner/admin can\n * still enumerate users — the toggle is a UX privacy switch, not a\n * security boundary.\n *\n * Honest caveat: this is a UX flag, not a privacy guarantee. The\n * envelope ciphertext is still in the store, the keyring file is\n * still listed at `_keyring/*`, and anyone with direct store read\n * access can count keyrings without going through the hub. See\n * `docs/subsystems/user-envelope.md` → \"Directory visibility\".\n */\nexport class DirectoryDisabledError extends NoydbError {\n readonly vault: string\n\n constructor(vault: string) {\n super(\n 'DIRECTORY_DISABLED',\n `Vault \"${vault}\" has its user directory disabled. ` +\n `Only owners and admins can call listUsersWithEnvelopes() here. ` +\n `Use db.setDirectoryEnabled(vault, true) to re-enable.`,\n )\n this.name = 'DirectoryDisabledError'\n this.vault = vault\n }\n}\n\n// ─── Hierarchical Access Errors ─────────────────────\n\n/**\n * Thrown when a user tries to act at a tier they are not cleared for.\n *\n * This is the umbrella error for tier write refusals:\n * - `put({ tier: N })` when the user's keyring lacks tier-N DEK.\n * - `elevate(id, N)` when the caller cannot reach tier N.\n *\n * Distinct from `TierAccessDeniedError` which covers *read* refusals on\n * the invisibility/ghost path.\n */\nexport class TierNotGrantedError extends NoydbError {\n readonly tier: number\n readonly collection: string\n\n constructor(collection: string, tier: number) {\n super(\n 'TIER_NOT_GRANTED',\n `User has no DEK for tier ${tier} in collection \"${collection}\"`,\n )\n this.name = 'TierNotGrantedError'\n this.collection = collection\n this.tier = tier\n }\n}\n\n/**\n * Thrown when an elevated-handle operation runs after the elevation's\n * TTL expired. Reads continue at the original tier; only writes\n * through the scoped handle flip to throwing once expired.\n */\nexport class ElevationExpiredError extends NoydbError {\n readonly tier: number\n readonly expiresAt: number\n\n constructor(opts: { tier: number; expiresAt: number }) {\n super(\n 'ELEVATION_EXPIRED',\n `Elevation to tier ${opts.tier} expired at ${new Date(opts.expiresAt).toISOString()}`,\n )\n this.name = 'ElevationExpiredError'\n this.tier = opts.tier\n this.expiresAt = opts.expiresAt\n }\n}\n\n/**\n * Thrown by `vault.elevate(...)` when an elevation is already active\n * on the vault. Adopters must `release()` the existing handle before\n * starting a new elevation.\n */\nexport class AlreadyElevatedError extends NoydbError {\n readonly activeTier: number\n\n constructor(activeTier: number) {\n super(\n 'ALREADY_ELEVATED',\n `Vault is already elevated to tier ${activeTier}; release the existing handle first`,\n )\n this.name = 'AlreadyElevatedError'\n this.activeTier = activeTier\n }\n}\n\n/**\n * Thrown when `demote()` is called by someone who is not the original\n * elevator and not an owner.\n */\nexport class TierDemoteDeniedError extends NoydbError {\n constructor(id: string, tier: number) {\n super(\n 'TIER_DEMOTE_DENIED',\n `Only the original elevator or an owner can demote record \"${id}\" from tier ${tier}`,\n )\n this.name = 'TierDemoteDeniedError'\n }\n}\n\n/**\n * Thrown when `db.delegate()` is called against a user that has no\n * keyring in the target vault — the delegation token cannot be\n * constructed without the target user's KEK wrap.\n */\nexport class DelegationTargetMissingError extends NoydbError {\n readonly toUser: string\n\n constructor(toUser: string) {\n super(\n 'DELEGATION_TARGET_MISSING',\n `Delegation target user \"${toUser}\" has no keyring in this vault`,\n )\n this.name = 'DelegationTargetMissingError'\n this.toUser = toUser\n }\n}\n\n// ─── Sync Errors ───────────────────────────────────────────────────────\n\n/**\n * Thrown when a `put()` detects an optimistic concurrency conflict.\n *\n * NOYDB uses version numbers (`_v`) for optimistic locking. If a `put()`\n * is called with `expectedVersion: N` but the stored record is at version\n * `M ≠ N`, the write is rejected and the caller must re-read, re-apply their\n * change, and retry. The `version` field carries the actual stored version\n * so callers can decide whether to retry or surface the conflict to the user.\n */\nexport class ConflictError extends NoydbError {\n /** The actual stored version at the time of conflict. */\n readonly version: number\n\n constructor(version: number, message = 'Version conflict') {\n super('CONFLICT', message)\n this.name = 'ConflictError'\n this.version = version\n }\n}\n\n/**\n * Thrown by `LedgerStore.append()` after exhausting its CAS retry\n * budget under multi-writer contention. Two browser tabs, a\n * web app + an offline mobile peer, or a server worker pool all\n * producing ledger entries against the same vault can race on the\n * \"read head, write head+1\" cycle; the optimistic-CAS retry loop\n * resolves the race for `casAtomic: true` stores, but pathological\n * contention (or a buggy peer) can still exhaust the budget. When\n * that happens, the chain is intact — the failed writer simply\n * couldn't claim a slot. Caller's choice whether to retry, queue,\n * or surface the failure to the user.\n */\nexport class LedgerContentionError extends NoydbError {\n readonly attempts: number\n\n constructor(attempts: number) {\n super(\n 'LEDGER_CONTENTION',\n `LedgerStore.append: failed to claim a chain slot after ${attempts} optimistic-CAS retries`,\n )\n this.name = 'LedgerContentionError'\n this.attempts = attempts\n }\n}\n\n/**\n * Thrown when a bundle push is rejected because the remote has been updated\n * since the local bundle was last pulled.\n *\n * Unlike `ConflictError` (per-record), this is a whole-bundle conflict —\n * the remote's bundle handle has changed. The caller must pull the new\n * bundle, merge, and re-push. `remoteVersion` is the handle of the newer\n * remote bundle for use in diagnostics.\n */\nexport class BundleVersionConflictError extends NoydbError {\n /** The bundle handle of the newer remote version that rejected the push. */\n readonly remoteVersion: string\n\n constructor(remoteVersion: string, message = 'Bundle version conflict — remote has been updated') {\n super('BUNDLE_VERSION_CONFLICT', message)\n this.name = 'BundleVersionConflictError'\n this.remoteVersion = remoteVersion\n }\n}\n\n/**\n * Thrown when a sync operation (push or pull) fails due to a network error.\n *\n * NOYDB's offline-first design means network errors are expected during sync.\n * Callers should catch `NetworkError`, surface connectivity status in the UI,\n * and rely on the `SyncScheduler` to retry when connectivity is restored.\n */\nexport class NetworkError extends NoydbError {\n constructor(message = 'Network error') {\n super('NETWORK_ERROR', message)\n this.name = 'NetworkError'\n }\n}\n\n// ─── Data Errors ───────────────────────────────────────────────────────\n\n/**\n * Thrown when `collection.get(id)` is called with an ID that does not exist.\n *\n * NOYDB collections are memory-first, so this error is synchronous and cheap —\n * it does not make a network round-trip. Callers that expect the record to be\n * absent should use `collection.getOrNull(id)` instead.\n */\nexport class NotFoundError extends NoydbError {\n constructor(message = 'Record not found') {\n super('NOT_FOUND', message)\n this.name = 'NotFoundError'\n }\n}\n\n/**\n * Thrown when application-level validation fails before encryption.\n *\n * Distinct from `SchemaValidationError` (Standard Schema v1 validator)\n * and `MissingTranslationError` (i18nText). `ValidationError` is the\n * general-purpose validation base — use it for custom guards in `put()`\n * hooks or store middleware.\n */\nexport class ValidationError extends NoydbError {\n constructor(message = 'Validation error') {\n super('VALIDATION_ERROR', message)\n this.name = 'ValidationError'\n }\n}\n\n/**\n * Thrown when a Standard Schema v1 validator rejects a record on\n * `put()` (input validation) or on read (output validation). Carries\n * the raw issue list so callers can render field-level errors.\n *\n * `direction` distinguishes the two cases:\n * - `'input'`: the user passed bad data into `put()`. This is a\n * normal error case that application code should handle — typically\n * by showing validation messages in the UI.\n * - `'output'`: stored data does not match the current schema. This\n * indicates a schema drift (the schema was changed without\n * migrating the existing records) and should be treated as a bug\n * — the application should not swallow it silently.\n *\n * The `issues` type is deliberately `readonly unknown[]` on this class\n * so that `errors.ts` doesn't need to import from `schema.ts` (and\n * create a dependency cycle). Callers who know they're holding a\n * `SchemaValidationError` can cast to the more precise\n * `readonly StandardSchemaV1Issue[]` from `schema.ts`.\n */\nexport class SchemaValidationError extends NoydbError {\n readonly issues: readonly unknown[]\n readonly direction: 'input' | 'output'\n\n constructor(\n message: string,\n issues: readonly unknown[],\n direction: 'input' | 'output',\n ) {\n super('SCHEMA_VALIDATION_FAILED', message)\n this.name = 'SchemaValidationError'\n this.issues = issues\n this.direction = direction\n }\n}\n\n// ─── Query DSL Errors ─────────────────────────────────────────────────\n\n/**\n * Thrown when `.groupBy().aggregate()` produces more than the hard\n * cardinality cap (default 100_000 groups)..\n *\n * The cap exists because `.groupBy()` materializes one bucket per\n * distinct key value in memory, and runaway cardinality — a groupBy\n * on a high-uniqueness field like `id` or `createdAt` — is almost\n * always a query mistake rather than legitimate use. A hard error is\n * better than silent OOM: the consumer sees an actionable message\n * naming the field and the observed cardinality, with guidance to\n * either narrow the query with `.where()` or accept the ceiling\n * override.\n *\n * A separate one-shot warning fires at 10% of the cap (10_000\n * groups) so consumers get a heads-up before the hard error — same\n * pattern as `JoinTooLargeError` and the `.join()` row ceiling.\n *\n * **Not overridable in.** The 100k cap is a fixed constant so\n * the failure mode is consistent across the codebase; a\n * `{ maxGroups }` override can be added later without a break if a\n * real consumer asks.\n */\nexport class GroupCardinalityError extends NoydbError {\n /** The field being grouped on. */\n readonly field: string\n /** Observed number of distinct groups at the moment the cap tripped. */\n readonly cardinality: number\n /** The cap that was exceeded. */\n readonly maxGroups: number\n\n constructor(field: string, cardinality: number, maxGroups: number) {\n super(\n 'GROUP_CARDINALITY',\n `.groupBy(\"${field}\") produced ${cardinality} distinct groups, ` +\n `exceeding the ${maxGroups}-group ceiling. This is almost always a ` +\n `query mistake — grouping on a high-uniqueness field like \"id\" or ` +\n `\"createdAt\" produces one bucket per record. Narrow the query with ` +\n `.where() before grouping, or group on a lower-cardinality field ` +\n `(status, category, clientId). If you genuinely need high-cardinality ` +\n `grouping, file an issue with your use case.`,\n )\n this.name = 'GroupCardinalityError'\n this.field = field\n this.cardinality = cardinality\n this.maxGroups = maxGroups\n }\n}\n\n/**\n * Thrown in lazy mode when a `.query()` / `.where()` / `.orderBy()` clause\n * references a field that does not have a declared index.\n *\n * Lazy-mode queries only work when every touched field is indexed.\n * This is deliberate — silent scan-fallback would hide the performance\n * cliff that lazy-mode indexes exist to prevent.\n *\n * Payload:\n * - `collection` — name of the collection queried\n * - `touchedFields` — every field referenced by the query (filter + order)\n * - `missingFields` — subset of `touchedFields` that have no declared index\n */\nexport class IndexRequiredError extends NoydbError {\n readonly collection: string\n readonly touchedFields: readonly string[]\n readonly missingFields: readonly string[]\n\n constructor(args: { collection: string; touchedFields: readonly string[]; missingFields: readonly string[] }) {\n super(\n 'INDEX_REQUIRED',\n `Collection \"${args.collection}\": query references unindexed fields in lazy mode ` +\n `(missing: ${args.missingFields.join(', ')}). ` +\n `Declare an index on each field, or use collection.scan() for non-indexed iteration.`,\n )\n this.name = 'IndexRequiredError'\n this.collection = args.collection\n this.touchedFields = [...args.touchedFields]\n this.missingFields = [...args.missingFields]\n }\n}\n\n/**\n * Thrown (or surfaced via the `index:write-partial` event) when one or more\n * per-indexed-field side-car writes fail after the main record write has\n * already succeeded.\n *\n * Not thrown out of `.put()` / `.delete()` directly — those succeed when the\n * main record succeeds. Instead, `IndexWriteFailureError` instances are collected\n * into the session-scoped reconcile queue and emitted on the Collection\n * emitter as `index:write-partial`.\n *\n * Payload:\n * - `recordId` — the id of the main record whose side-car writes failed\n * - `field` — the indexed field whose side-car write failed\n * - `op` — `'put'` or `'delete'`, indicating which mutation was in flight\n * - `cause` — the underlying error from the store\n */\nexport class IndexWriteFailureError extends NoydbError {\n readonly recordId: string\n readonly field: string\n readonly op: 'put' | 'delete'\n override readonly cause: unknown\n\n constructor(args: { recordId: string; field: string; op: 'put' | 'delete'; cause: unknown }) {\n super(\n 'INDEX_WRITE_FAILURE',\n `Index side-car ${args.op} failed for field \"${args.field}\" on record \"${args.recordId}\"`,\n )\n this.name = 'IndexWriteFailureError'\n this.recordId = args.recordId\n this.field = args.field\n this.op = args.op\n this.cause = args.cause\n }\n}\n\n// ─── Bundle Format Errors ─────────────────────────────────\n\n/**\n * Thrown by `readNoydbBundle()` when the body bytes don't match\n * the integrity hash declared in the bundle header — i.e. someone\n * modified the bytes between write and read.\n *\n * Distinct from a generic `Error` (which would be thrown for\n * format violations like a missing magic prefix or malformed\n * header JSON) so consumers can pattern-match the corruption case\n * and handle it differently from a producer bug. A\n * `BundleIntegrityError` indicates \"the bytes you got are not\n * what was written\"; a plain `Error` from `parsePrefixAndHeader`\n * indicates \"what was written wasn't a valid bundle in the first\n * place.\"\n *\n * Also thrown when decompression fails after the integrity hash\n * passed — that's a producer bug (the wrong algorithm byte was\n * written) but it surfaces with the same error class because the\n * end result is \"the body cannot be turned back into a dump.\"\n */\nexport class BundleIntegrityError extends NoydbError {\n constructor(message: string) {\n super('BUNDLE_INTEGRITY', `.noydb bundle integrity check failed: ${message}`)\n this.name = 'BundleIntegrityError'\n }\n}\n\n/**\n * Thrown by `readNoydbBundle` (#197) when the bundle carries\n * sealed per-user passphrases but no supplied `SealingKeyProvider`\n * has a `.id` (= `pid`) matching the sealed entry's `pid`.\n *\n * Carries the failing pid + the user id so the recipient can\n * surface an actionable prompt:\n *\n * ```\n * BundleSealMismatchError: bundle carries sealed passphrase for user \"alice\"\n * under provider \"macos-keychain:com.acme.app/alice@acme.example\",\n * but no registered provider matches that pid.\n * ```\n *\n * Three resolution paths the message names (per foundation §11.9.4):\n *\n * 1. Configure a provider matching the pid and retry import.\n * 2. Pass `attemptUnsealAcrossProviders: true` to try each\n * registered provider regardless of pid.\n * 3. Inspect without unsealing — pass no `sealingProviders` to\n * receive the sealed entries unmodified for offline analysis.\n */\nexport class BundleSealMismatchError extends NoydbError {\n readonly userId: string\n readonly pid: string\n constructor(userId: string, pid: string) {\n super(\n 'BUNDLE_SEAL_MISMATCH',\n `bundle carries sealed passphrase for user \"${userId}\" under provider `\n + `\"${pid}\", but no registered provider matches that pid.\\n\\n`\n + 'Resolutions:\\n'\n + ' 1. Configure a provider matching the pid and retry import.\\n'\n + ' 2. Pass `attemptUnsealAcrossProviders: true` to try each registered\\n'\n + ' provider regardless of pid (extra credential prompts may surface).\\n'\n + ' 3. Inspect the bundle without unsealing — pass no `sealingProviders`\\n'\n + ' to receive the sealed entries unmodified for offline analysis.',\n )\n this.name = 'BundleSealMismatchError'\n this.userId = userId\n this.pid = pid\n }\n}\n\n// ─── i18n / Dictionary Errors ──────────────────────────\n\n/**\n * Thrown when `vault.collection()` is called with a name that is\n * reserved for NOYDB internal use (any name starting with `_dict_`).\n *\n * Dictionary collections are accessed exclusively via\n * `vault.dictionary(name)` — attempting to open one as a regular\n * collection would bypass the dictionary invariants (ACL, rename\n * tracking, reserved-name policy).\n */\nexport class ReservedCollectionNameError extends NoydbError {\n /** The rejected collection name. */\n readonly collectionName: string\n\n constructor(collectionName: string) {\n super(\n 'RESERVED_COLLECTION_NAME',\n `\"${collectionName}\" is a reserved collection name. ` +\n `Use vault.dictionary(\"${collectionName.replace(/^_dict_/, '')}\") ` +\n `to access dictionary collections.`,\n )\n this.name = 'ReservedCollectionNameError'\n this.collectionName = collectionName\n }\n}\n\n/**\n * Thrown by `DictionaryHandle.get()` and `DictionaryHandle.delete()` when\n * the requested key does not exist in the dictionary.\n *\n * Distinct from `NotFoundError` (which is for data records) so callers\n * can distinguish \"data record missing\" from \"dictionary key missing\"\n * without inspecting error messages.\n */\nexport class DictKeyMissingError extends NoydbError {\n /** The dictionary name. */\n readonly dictionaryName: string\n /** The key that was not found. */\n readonly key: string\n\n constructor(dictionaryName: string, key: string) {\n super(\n 'DICT_KEY_MISSING',\n `Dictionary \"${dictionaryName}\" has no entry for key \"${key}\".`,\n )\n this.name = 'DictKeyMissingError'\n this.dictionaryName = dictionaryName\n this.key = key\n }\n}\n\n/**\n * Thrown by `DictionaryHandle.delete()` in strict mode when the key to\n * be deleted is still referenced by one or more records.\n *\n * The caller must either rename the key first (the only sanctioned\n * mass-mutation path) or pass `{ mode: 'warn' }` to skip the check\n * (development only).\n */\nexport class DictKeyInUseError extends NoydbError {\n /** The dictionary name. */\n readonly dictionaryName: string\n /** The key that is still referenced. */\n readonly key: string\n /** Name of the first collection found to reference this key. */\n readonly usedBy: string\n /** Number of records in `usedBy` that reference this key. */\n readonly count: number\n\n constructor(\n dictionaryName: string,\n key: string,\n usedBy: string,\n count: number,\n ) {\n super(\n 'DICT_KEY_IN_USE',\n `Cannot delete key \"${key}\" from dictionary \"${dictionaryName}\": ` +\n `${count} record(s) in \"${usedBy}\" still reference it. ` +\n `Use dictionary.rename(\"${key}\", newKey) to rewrite references first.`,\n )\n this.name = 'DictKeyInUseError'\n this.dictionaryName = dictionaryName\n this.key = key\n this.usedBy = usedBy\n this.count = count\n }\n}\n\n/**\n * Thrown by `Collection.put()` when an `i18nText` field is missing one\n * or more required translations.\n *\n * The `missing` array names each locale code that was absent from the\n * field value. The `field` property names the field so callers can\n * render a field-level error message without parsing the string.\n */\nexport class MissingTranslationError extends NoydbError {\n /** The field name whose translation(s) are missing. */\n readonly field: string\n /** Locale codes that were required but absent. */\n readonly missing: readonly string[]\n\n constructor(field: string, missing: readonly string[], message?: string) {\n super(\n 'MISSING_TRANSLATION',\n message ??\n `Field \"${field}\": missing required translation(s): ${missing.join(', ')}.`,\n )\n this.name = 'MissingTranslationError'\n this.field = field\n this.missing = missing\n }\n}\n\n/**\n * Thrown when reading an `i18nText` field without specifying a locale —\n * either at the call site (`get(id, { locale })`) or on the vault\n * (`openVault(name, { locale })`).\n *\n * Also thrown when `resolveI18nText()` exhausts the fallback chain and\n * no translation is available for the requested locale.\n *\n * The `field` property names the field that triggered the error so the\n * caller can surface it in the UI.\n */\nexport class LocaleNotSpecifiedError extends NoydbError {\n /** The field name that required a locale. */\n readonly field: string\n\n constructor(field: string, message?: string) {\n super(\n 'LOCALE_NOT_SPECIFIED',\n message ??\n `Cannot read i18nText field \"${field}\" without a locale. ` +\n `Pass { locale } to get()/list()/query() or set a default via ` +\n `openVault(name, { locale }).`,\n )\n this.name = 'LocaleNotSpecifiedError'\n this.field = field\n }\n}\n\n// ─── Translator Errors ─────────────────────────────────────\n\n/**\n * Thrown when a collection has an `i18nText` field with\n * `autoTranslate: true` but no `plaintextTranslator` was configured\n * on `createNoydb()`.\n *\n * The error is raised at `put()` time (not at schema construction) so\n * the mis-configuration is surfaced by the first write rather than\n * silently at startup.\n */\nexport class TranslatorNotConfiguredError extends NoydbError {\n /** The field that requested auto-translation. */\n readonly field: string\n /** The collection the put was targeting. */\n readonly collection: string\n\n constructor(field: string, collection: string) {\n super(\n 'TRANSLATOR_NOT_CONFIGURED',\n `Field \"${field}\" in collection \"${collection}\" has autoTranslate: true, ` +\n `but no plaintextTranslator was configured on createNoydb(). ` +\n `Either configure a plaintextTranslator or remove autoTranslate from the schema.`,\n )\n this.name = 'TranslatorNotConfiguredError'\n this.field = field\n this.collection = collection\n }\n}\n\n// ─── Backup Errors ─────────────────────────────────────────\n\n/**\n * Thrown when `Vault.load()` finds that a backup's hash chain\n * doesn't verify, or that its embedded `ledgerHead.hash` doesn't\n * match the chain head reconstructed from the loaded entries.\n *\n * Distinct from `BackupCorruptedError` so callers can choose to\n * recover from one but not the other (e.g., a corrupted JSON file is\n * unrecoverable; a chain mismatch might mean the backup is from an\n * incompatible noy-db version).\n */\nexport class BackupLedgerError extends NoydbError {\n /** First-broken-entry index, if known. */\n readonly divergedAt?: number\n\n constructor(message: string, divergedAt?: number) {\n super('BACKUP_LEDGER', message)\n this.name = 'BackupLedgerError'\n if (divergedAt !== undefined) this.divergedAt = divergedAt\n }\n}\n\n/**\n * Thrown when `Vault.load()` finds that the backup's data\n * collection content doesn't match the ledger's recorded\n * `payloadHash`es. This is the \"envelope was tampered with after\n * dump\" detection — the chain itself can be intact, but if any\n * encrypted record bytes were swapped, this check catches it.\n */\nexport class BackupCorruptedError extends NoydbError {\n /** The (collection, id) pair whose envelope failed the hash check. */\n readonly collection: string\n readonly id: string\n\n constructor(collection: string, id: string, message: string) {\n super('BACKUP_CORRUPTED', message)\n this.name = 'BackupCorruptedError'\n this.collection = collection\n this.id = id\n }\n}\n\n/**\n * Thrown by partition-extraction primitives (#198 epic) when the\n * transitive-closure walk fails — e.g. the FK graph is deeper than\n * `maxDepth`, signalling a runaway or unexpectedly cyclic graph.\n */\nexport class PartitionExtractionError extends NoydbError {\n constructor(message: string) {\n super('PARTITION_EXTRACTION', message)\n this.name = 'PartitionExtractionError'\n }\n}\n\n/**\n * Thrown by `adoptPartition` (#207) when the transfer seal can't be\n * opened — a wrong/short transfer key (AES-GCM auth-tag failure) or a\n * malformed sealed payload.\n */\nexport class TransferSealError extends NoydbError {\n constructor(message: string) {\n super('TRANSFER_SEAL', message)\n this.name = 'TransferSealError'\n }\n}\n\n/**\n * Thrown when an adoption-lifecycle precondition fails — re-adopting a\n * partition already consumed in this store (#207), or owner-creation on a\n * vault that isn't in the adopted-unowned state (#208).\n */\nexport class AdoptionStateError extends NoydbError {\n constructor(message: string) {\n super('ADOPTION_STATE', message)\n this.name = 'AdoptionStateError'\n }\n}\n\n// ─── Attestation Errors ────────────────────────────────────\n\n/** Document-attestation failures: undeclared field-schema, non-owner issue, missing field, signer failure. */\nexport class AttestationError extends NoydbError {\n constructor(message: string) {\n super('ATTESTATION', message)\n this.name = 'AttestationError'\n }\n}\n\n// ─── Session Errors ───────────────────────────────────────\n\n/**\n * Thrown by `resolveSession()` when the session token's `expiresAt`\n * timestamp is in the past. The session key is also removed from the\n * in-memory store when this is thrown, so retrying with the same sessionId\n * will produce `SessionNotFoundError`.\n *\n * Separate from `SessionNotFoundError` so callers can distinguish between\n * \"session is gone\" (key store cleared, tab reloaded) and \"session is\n * still in the store but has exceeded its lifetime\" (idle timeout, absolute\n * timeout, policy-driven expiry). The remediation differs: expired sessions\n * should prompt a fresh unlock; not-found sessions may indicate a bug or a\n * cross-tab scenario where the session was never established.\n */\nexport class SessionExpiredError extends NoydbError {\n readonly sessionId: string\n\n constructor(sessionId: string) {\n super('SESSION_EXPIRED', `Session \"${sessionId}\" has expired. Re-unlock to continue.`)\n this.name = 'SessionExpiredError'\n this.sessionId = sessionId\n }\n}\n\n/**\n * Thrown by `resolveSession()` when the session key cannot be found in\n * the module-level store. This happens when:\n * - The session was explicitly revoked via `revokeSession()`.\n * - The JS context was reloaded (tab navigation, page refresh, worker restart).\n * - `Noydb.close()` was called (which calls `revokeAllSessions()`).\n * - The sessionId is wrong or was generated by a different JS context.\n *\n * The session token (if the caller holds it) is permanently useless after\n * this error — the key is gone and cannot be recovered.\n */\nexport class SessionNotFoundError extends NoydbError {\n readonly sessionId: string\n\n constructor(sessionId: string) {\n super('SESSION_NOT_FOUND', `Session key for \"${sessionId}\" not found. The session may have been revoked or the page reloaded.`)\n this.name = 'SessionNotFoundError'\n this.sessionId = sessionId\n }\n}\n\n/**\n * Thrown when a session policy blocks an operation — for example,\n * `requireReAuthFor: ['export']` is set and the caller attempts to\n * call `exportStream()` without re-authenticating for this session.\n *\n * The `operation` field names the specific operation that was blocked\n * (e.g. `'export'`, `'grant'`, `'rotate'`) so the caller can surface\n * a targeted prompt (\"Please re-enter your passphrase to export data\").\n */\nexport class SessionPolicyError extends NoydbError {\n readonly operation: string\n\n constructor(operation: string, message?: string) {\n super(\n 'SESSION_POLICY',\n message ?? `Operation \"${operation}\" requires re-authentication per the active session policy.`,\n )\n this.name = 'SessionPolicyError'\n this.operation = operation\n }\n}\n\n// ─── Query / Join Errors ────────────────────────────────────\n\n/**\n * Thrown when a `.join()` would exceed its configured row ceiling on\n * either side. The ceiling defaults to 50,000 per side and can be\n * overridden via the `{ maxRows }` option on `.join()`.\n *\n * Carries both row counts so the error message can show which side\n * tripped the limit (e.g. \"left had 60,000 rows, right had 1,200,\n * max was 50,000\"). The `side` field is machine-readable so test\n * code and devtools can match on it without regex-parsing the\n * message.\n *\n * The row ceiling exists because joins are bounded in-memory\n * operations over materialized record sets. Consumers whose\n * collections genuinely exceed the ceiling should track \n * (streaming joins over `scan()`) or filter the left side further\n * with `where()` / `limit()` before joining.\n */\nexport class JoinTooLargeError extends NoydbError {\n readonly leftRows: number\n readonly rightRows: number\n readonly maxRows: number\n readonly side: 'left' | 'right'\n\n constructor(opts: {\n leftRows: number\n rightRows: number\n maxRows: number\n side: 'left' | 'right'\n message: string\n }) {\n super('JOIN_TOO_LARGE', opts.message)\n this.name = 'JoinTooLargeError'\n this.leftRows = opts.leftRows\n this.rightRows = opts.rightRows\n this.maxRows = opts.maxRows\n this.side = opts.side\n }\n}\n\n/**\n * Thrown by `.join()` in strict `ref()` mode when a left-side record\n * points at a right-side id that does not exist in the target\n * collection.\n *\n * Distinct from `RefIntegrityError` so test code can pattern-match\n * on the *read-time* dangling case without catching *write-time*\n * integrity violations. Both indicate \"ref points at nothing\" but\n * happen at different lifecycle phases and deserve different\n * remediation in documentation: a RefIntegrityError on `put()`\n * means the input is invalid; a DanglingReferenceError on `.join()`\n * means stored data has drifted and `vault.checkIntegrity()`\n * is the right tool to find the full set of orphans.\n */\nexport class DanglingReferenceError extends NoydbError {\n readonly field: string\n readonly target: string\n readonly refId: string\n\n constructor(opts: {\n field: string\n target: string\n refId: string\n message: string\n }) {\n super('DANGLING_REFERENCE', opts.message)\n this.name = 'DanglingReferenceError'\n this.field = opts.field\n this.target = opts.target\n this.refId = opts.refId\n }\n}\n\n/**\n * Thrown by {@link sanitizeFilename} when an input filename cannot be\n * made safe — NUL byte, empty after normalization, missing\n * `opaqueId` for the opaque profile, `..` segment, or a `maxBytes`\n * cap too small to hold a single code point.\n */\nexport class FilenameSanitizationError extends NoydbError {\n constructor(message: string) {\n super('FILENAME_SANITIZATION', message)\n this.name = 'FilenameSanitizationError'\n }\n}\n\n/**\n * Thrown when a write target resolves OUTSIDE the requested\n * directory after sanitization — the canonical Zip-Slip class. The\n * sanitizer's job is to strip path-traversal segments; this error\n * is the defense-in-depth fallback at the FS write site.\n */\nexport class PathEscapeError extends NoydbError {\n readonly attempted: string\n readonly targetDir: string\n\n constructor(opts: { attempted: string; targetDir: string }) {\n super(\n 'PATH_ESCAPE',\n `Sanitized filename \"${opts.attempted}\" resolves outside target dir \"${opts.targetDir}\"`,\n )\n this.name = 'PathEscapeError'\n this.attempted = opts.attempted\n this.targetDir = opts.targetDir\n }\n}\n\n// ─── Derivation Errors ──────────────────────────────\n\n/**\n * Thrown at vault open if the derivation graph contains a cycle.\n * `path` is the offending chain (e.g. `['a', 'b', 'c', 'a']`).\n */\nexport class DerivationCycleError extends NoydbError {\n readonly path: readonly string[]\n\n constructor(path: readonly string[]) {\n super(\n 'DERIVATION_CYCLE',\n `Derivation graph contains a cycle: ${path.join(' → ')}. ` +\n `Refusing to open vault — break the cycle before retrying.`,\n )\n this.name = 'DerivationCycleError'\n this.path = path\n }\n}\n\n/**\n * Thrown when a cascade of source → output → source → … exceeds the\n * configured `maxDepth` (default 5).\n */\nexport class DerivationDepthError extends NoydbError {\n readonly limit: number\n readonly attempted: number\n\n constructor(limit: number, attempted: number) {\n super(\n 'DERIVATION_DEPTH',\n `Derivation cascade exceeded max depth ${limit} (attempted ${attempted}). ` +\n `Pass lifecycle: { maxDepth: N } to raise the limit if intentional.`,\n )\n this.name = 'DerivationDepthError'\n this.limit = limit\n this.attempted = attempted\n }\n}\n\n/**\n * Thrown at registration if a `withDerivation` strategy references an\n * output `collection` that isn't otherwise declared (no schema, no use\n * elsewhere). Surfacing this early catches typos in collection names.\n */\nexport class DerivationOutputUnknownError extends NoydbError {\n readonly collection: string\n\n constructor(collection: string) {\n super(\n 'DERIVATION_OUTPUT_UNKNOWN',\n `Derivation output collection \"${collection}\" is not declared on the vault. ` +\n `Register the collection (e.g. via schema) before registering a derivation that writes to it.`,\n )\n this.name = 'DerivationOutputUnknownError'\n this.collection = collection\n }\n}\n\n/**\n * Thrown when the user's `derive` function returns a value that doesn't\n * match the declared output spec (e.g. wrong shape, wrong key set).\n */\nexport class DerivationOutputShapeError extends NoydbError {\n readonly outputKey: string\n\n constructor(outputKey: string, detail: string) {\n super(\n 'DERIVATION_OUTPUT_SHAPE',\n `Derivation output \"${outputKey}\" has invalid shape: ${detail}.`,\n )\n this.name = 'DerivationOutputShapeError'\n this.outputKey = outputKey\n }\n}\n\n/**\n * Thrown by array-shape derivations (#200) when the `derive` function\n * returns more rows than the output's `maxFanout` cap. The cap exists\n * to keep dispatch cost bounded — without it a single source-row\n * update could fan out to thousands of derived rows, dominating the\n * write path.\n *\n * Defaults to `maxFanout: 64`. Raise on the output spec for\n * carry-forward expansion cases (e.g. monthly rows across multi-year\n * contracts).\n */\nexport class DerivationCapExceededError extends NoydbError {\n readonly outputKey: string\n readonly returned: number\n readonly maxFanout: number\n\n constructor(outputKey: string, returned: number, maxFanout: number) {\n super(\n 'DERIVATION_CAP_EXCEEDED',\n `Derivation array output \"${outputKey}\" returned ${returned} rows, exceeding `\n + `maxFanout=${maxFanout}. Raise \\`maxFanout\\` on the OutputSpec if this fanout `\n + 'is intended (the cap exists to keep dispatch cost bounded).',\n )\n this.name = 'DerivationCapExceededError'\n this.outputKey = outputKey\n this.returned = returned\n this.maxFanout = maxFanout\n }\n}\n\n/**\n * Thrown at vault open if the materialized-view graph contains a\n * cycle. `path` is the offending chain (e.g. `['a-mv', 'b-mv', 'a-mv']`).\n * Detected by the same shared DFS that catches `DerivationCycleError`;\n * surfaces with a distinct error type so consumers can disambiguate.\n */\nexport class MaterializedViewCycleError extends NoydbError {\n readonly path: readonly string[]\n\n constructor(path: readonly string[]) {\n super(\n 'MATERIALIZED_VIEW_CYCLE',\n `Materialized-view graph contains a cycle: ${path.join(' → ')}. ` +\n `Refusing to open vault — break the cycle before retrying.`,\n )\n this.name = 'MaterializedViewCycleError'\n this.path = path\n }\n}\n\n/**\n * Thrown at MV registration if the query references a source\n * collection that isn't declared on the vault. Surfacing this early\n * catches typos in collection names.\n */\nexport class MaterializedViewSourceUnknownError extends NoydbError {\n readonly mvName: string\n readonly collection: string\n\n constructor(mvName: string, collection: string) {\n super(\n 'MATERIALIZED_VIEW_SOURCE_UNKNOWN',\n `Materialized view \"${mvName}\" references unknown source collection \"${collection}\". ` +\n `Declare the collection (e.g. via schema or by writing to it once) before registering the MV.`,\n )\n this.name = 'MaterializedViewSourceUnknownError'\n this.mvName = mvName\n this.collection = collection\n }\n}\n\n/**\n * Thrown by the MV executor when a refresh produces more rows than\n * the configured ceiling. Default ceiling is 100k rows; override\n * per-MV via `maxRows`. Mirrors `JoinTooLargeError` /\n * `GroupCardinalityError` from the query DSL — the explosion is\n * detected BEFORE writes hit the store, so the source-write\n * transaction can roll back cleanly via strict-mode.\n */\nexport class MaterializedViewTooLargeError extends NoydbError {\n readonly mvName: string\n readonly expected: number\n readonly limit: number\n\n constructor(mvName: string, expected: number, limit: number) {\n super(\n 'MATERIALIZED_VIEW_TOO_LARGE',\n `Materialized view \"${mvName}\" would emit ${expected} rows, exceeding the configured limit of ${limit}. ` +\n `Override via { maxRows: N } on the MV strategy if intentional, or tighten the query's filter/groupBy.`,\n )\n this.name = 'MaterializedViewTooLargeError'\n this.mvName = mvName\n this.expected = expected\n this.limit = limit\n }\n}\n\n/**\n * Thrown by `withMaterializedView()` at registration time when the\n * strategy is structurally malformed. Distinct from\n * `MaterializedViewSourceUnknownError` (the source list is well-formed\n * but names a collection the vault doesn't know) and\n * `MaterializedViewCycleError` (the source graph has a cycle): this\n * error fires before either check, at the moment the spec is being\n * normalized.\n *\n * Today the trigger cases are all about the `query` / `unionSources`\n * dichotomy introduced by #165:\n * - both `query` and `unionSources` were set (mutually exclusive),\n * - neither `query` nor `unionSources` was set,\n * - `unionSources` has fewer than 2 arms,\n * - two arms in `unionSources` reference the same `collection`.\n *\n * The error message is prefixed with `[noy-db] withMaterializedView:`\n * so it's grep-friendly in logs and looks consistent with the existing\n * `ValidationError` messages from the same factory.\n */\nexport class MaterializedViewConfigError extends NoydbError {\n constructor(message: string) {\n super(\n 'MATERIALIZED_VIEW_CONFIG',\n `[noy-db] withMaterializedView: ${message}`,\n )\n this.name = 'MaterializedViewConfigError'\n }\n}\n\n/**\n * Thrown at vault open when a `withOverlayedView` declaration uses\n * another virtual-overlay name as its `base`. Multi-overlay stacking\n * is a v2 non-goal — the shallow expansion in\n * `QueryDependencyAnalyzer` would truncate at the inner overlay\n * name, leaving downstream MVs silently stale.\n */\nexport class OverlayBaseIsVirtualError extends NoydbError {\n readonly overlayName: string\n readonly base: string\n\n constructor(overlayName: string, base: string) {\n super(\n 'OVERLAY_BASE_IS_VIRTUAL',\n `withOverlayedView \"${overlayName}\": base \"${base}\" is another overlay's virtual name. ` +\n `Multi-overlay stacking is a v3 feature; base must reference a concrete collection (a real source or an MV output).`,\n )\n this.name = 'OverlayBaseIsVirtualError'\n this.overlayName = overlayName\n this.base = base\n }\n}\n\n/**\n * Thrown at vault open when a `withOverlayedView`'s `overlay`\n * references an unknown collection or an MV-owned collection. The\n * overlay collection is user-writable; MV-owned collections aren't.\n */\nexport class OverlayCollectionUnavailableError extends NoydbError {\n readonly overlayName: string\n readonly overlay: string\n\n constructor(overlayName: string, overlay: string) {\n super(\n 'OVERLAY_COLLECTION_UNAVAILABLE',\n `withOverlayedView \"${overlayName}\": overlay collection \"${overlay}\" is unavailable. ` +\n `It must be a real vault-known collection that is NOT itself an MV output collection.`,\n )\n this.name = 'OverlayCollectionUnavailableError'\n this.overlayName = overlayName\n this.overlay = overlay\n }\n}\n\n/**\n * Thrown at vault open when a `withOverlayedView`'s virtual `name`\n * collides with an MV output or a concrete source collection.\n */\nexport class OverlayNameCollisionError extends NoydbError {\n readonly overlayName: string\n\n constructor(overlayName: string) {\n super(\n 'OVERLAY_NAME_COLLISION',\n `withOverlayedView \"${overlayName}\": virtual name collides with an MV output or a concrete source collection. ` +\n `Pick a unique name for the virtual collection.`,\n )\n this.name = 'OverlayNameCollisionError'\n this.overlayName = overlayName\n }\n}\n\n/**\n * Thrown by the virtual overlay's `put(id, record)` when the\n * consumer-supplied `id` doesn't match `rowKey(record)`. Catches\n * fat-finger separator typos that would otherwise silently produce\n * orphaned overlay rows. Direct writes to the underlying overlay\n * collection (bypass the virtual layer) skip this validation.\n */\nexport class OverlayIdMismatchError extends NoydbError {\n readonly actual: string\n readonly expected: string\n\n constructor(actual: string, expected: string) {\n super(\n 'OVERLAY_ID_MISMATCH',\n `Overlay put(id, record): id \"${actual}\" does not match the base MV's rowKey(record) → \"${expected}\". ` +\n `Pass the row directly via .put(record) to derive the id, or fix the id to match the base MV's rowKey output.`,\n )\n this.name = 'OverlayIdMismatchError'\n this.actual = actual\n this.expected = expected\n }\n}\n","/**\n * Core types — the {@link NoydbStore} interface, envelope format, roles, and\n * all configuration shapes consumed by {@link createNoydb}.\n *\n * ## What lives here\n *\n * - **{@link NoydbStore}** — the 6-method contract every backend must implement\n * (`get`, `put`, `delete`, `list`, `loadAll`, `saveAll`).\n * - **{@link EncryptedEnvelope}** — the wire format stored by backends:\n * `{ _noydb, _v, _ts, _iv, _data }`. Backends only ever see this shape.\n * - **{@link Role} / {@link Permission}** — the access-control vocabulary\n * (`owner`, `admin`, `operator`, `viewer`, `client`).\n * - **{@link NoydbOptions}** — the full configuration object passed to\n * {@link createNoydb}.\n *\n * ## Extending the store interface\n *\n * All optional store capabilities (`ping`, `listPage`, `listSince`,\n * `presencePublish`, `presenceSubscribe`, `listVaults`) are additive extensions\n * discovered via `'method' in store`. Implementing them unlocks features but\n * is never required — core always falls back to the 6-method baseline.\n *\n * @module\n */\n\nimport type { StandardSchemaV1 } from './schema.js'\nimport type { SyncPolicy } from './store/sync-policy.js'\nimport type { BlobStrategy } from './blobs/strategy.js'\nimport type { IndexStrategy } from './indexing/strategy.js'\nimport type { AggregateStrategy } from './aggregate/strategy.js'\nimport type { CrdtStrategy } from './crdt/strategy.js'\nimport type { ConsentStrategy } from './consent/strategy.js'\nimport type { PeriodsStrategy } from './periods/strategy.js'\nimport type { ShadowStrategy } from './shadow/strategy.js'\nimport type { TxStrategy } from './tx/strategy.js'\nimport type { HistoryStrategy } from './history/strategy.js'\nimport type { I18nStrategy } from './i18n/strategy.js'\nimport type { SessionStrategy } from './session/strategy.js'\nimport type { SyncStrategy } from './team/sync-strategy.js'\nimport type { GuardStrategyHandleAny } from './guards/types.js'\nimport type { DerivationStrategyHandle } from './derivations/types.js'\nimport type { UnlockedKeyring } from './team/keyring.js'\nimport type { VaultPolicy } from './policy/types.js'\nimport type { PublicEnvelopeSchema } from './meta/public-envelope/types.js'\nimport type { MaterializedViewStrategyHandle } from './materialized-views/types.js'\nimport type { OverlayedViewStrategyHandle } from './overlay-views/types.js'\nimport type { SealingKeyProvider } from './team/managed-passphrase.js'\nimport type { ShamirRecoveryProvider } from './team/shamir-recovery-provider.js'\n\n/** Format version for encrypted record envelopes. */\nexport const NOYDB_FORMAT_VERSION = 1 as const\n\n/** Format version for keyring files. */\nexport const NOYDB_KEYRING_VERSION = 1 as const\n\n/** Format version for backup files. */\nexport const NOYDB_BACKUP_VERSION = 1 as const\n\n/** Format version for sync metadata. */\nexport const NOYDB_SYNC_VERSION = 1 as const\n\n// ─── Roles & Permissions ───────────────────────────────────────────────\n\n/**\n * Access role assigned to a user within a vault.\n *\n * Roles control both the operations a user can perform and which DEKs\n * they receive in their keyring:\n *\n * | Role | Collections | Can grant/revoke | Can export |\n * |------------|-----------------|:----------------:|:----------:|\n * | `owner` | all (rw) | Yes (all roles) | Yes |\n * | `admin` | all (rw) | Yes (≤ admin) | Yes |\n * | `operator` | explicit (rw) | No | ACL-scoped |\n * | `viewer` | all (ro) | No | Yes |\n * | `client` | explicit (ro) | No | ACL-scoped |\n */\nexport type Role = 'owner' | 'admin' | 'operator' | 'viewer' | 'client'\n\n/**\n * Read-write or read-only access on a collection.\n * Stored per-collection in the user's keyring.\n */\nexport type Permission = 'rw' | 'ro'\n\n/**\n * Map of collection name → permission level for a user's keyring entry.\n * `'*'` is the wildcard collection matching all collections in the vault.\n */\nexport type Permissions = Record<string, Permission>\n\n// ─── Encrypted Envelope ────────────────────────────────────────────────\n\n/** The encrypted wrapper stored by stores. Stores only ever see this. */\nexport interface EncryptedEnvelope {\n readonly _noydb: typeof NOYDB_FORMAT_VERSION\n readonly _v: number\n readonly _ts: string\n readonly _iv: string\n readonly _data: string\n /** User who created this version (unencrypted metadata). */\n readonly _by?: string\n /**\n * Hierarchical access tier. Omitted → tier 0.\n *\n * Unencrypted on purpose — the store reads it to route the envelope\n * to the right DEK slot without having to try-decrypt against every\n * tier. Only leaks the tier of each record, not any value\n * equivalence.\n */\n readonly _tier?: number\n /**\n * User id who last elevated this record. Used by\n * `demote()` to gate the reverse operation: only the original\n * elevator or an owner can demote a record back down. Cleared on\n * every successful demote so a later re-elevate requires the new\n * actor to own the demotion right.\n */\n readonly _elevatedBy?: string\n /**\n * Deterministic-encryption index. Map of field name →\n * base64 deterministic ciphertext. Present only when the collection\n * declares `deterministicFields` and the feature is acknowledged. The\n * field names are unencrypted (they're the index keys); the values\n * are AES-GCM ciphertext with an HKDF-derived deterministic IV.\n *\n * Enables blind equality search (`collection.findByDet(field,\n * value)`) without decrypting every record. Leaks equality as a known\n * side channel.\n */\n readonly _det?: Record<string, string>\n}\n\n/**\n * Placeholder returned by `getAtTier()` in `'ghost'` mode when a\n * record is at a tier the caller cannot decrypt. Record existence is\n * advertised — the id and tier are visible — but contents are\n * withheld. `canElevateFrom` lists user ids authorized to elevate\n * access for this caller when known; absent when the workflow is\n * not configured.\n */\nexport interface GhostRecord {\n readonly _ghost: true\n readonly _tier: number\n readonly canElevateFrom?: readonly string[]\n}\n\n/** Control what lower-tier reads see above their clearance. */\nexport type TierMode = 'invisibility' | 'ghost'\n\n/**\n * Event emitted when a record at a tier above the caller's inherent\n * clearance is read or written successfully (via elevation or\n * delegation). Always written to the ledger; subscribers get a\n * real-time feed.\n */\nexport interface CrossTierAccessEvent {\n readonly actor: string\n readonly collection: string\n readonly id: string\n readonly tier: number\n /** How the caller gained tier access: they elevated it, or a delegation is active. */\n readonly authorization: 'elevation' | 'delegation' | 'inherent'\n readonly op: 'get' | 'put' | 'elevate' | 'demote'\n readonly ts: string\n /**\n * When `authorization === 'elevation'`, the audit reason string the\n * caller passed to `vault.elevate(...)`. Empty for inherent /\n * delegation paths.\n */\n readonly reason?: string\n /**\n * When `authorization === 'elevation'`, the tier the caller's\n * keyring effectively held BEFORE elevation. Useful for audit\n * dashboards distinguishing \"operator elevating to 2\" from\n * \"inherent tier-2 write.\"\n */\n readonly elevatedFrom?: number\n}\n\n/**\n * A single deterministic-ciphertext index slot on an envelope. Stored\n * as `iv:data` (both base64, colon-separated) so a single string per\n * field keeps the envelope compact.\n */\nexport type DeterministicCipher = string\n\n// ─── Vault Snapshot ──────────────────────────────────────────────\n\n/** All records across all collections for a compartment. */\nexport type VaultSnapshot = Record<string, Record<string, EncryptedEnvelope>>\n\n/**\n * Result of a single page fetch via the optional `listPage` adapter extension.\n *\n * `items` carries the actual encrypted envelopes (not just ids) so the\n * caller can decrypt and emit a single record without an extra `get()`\n * round-trip per id. `nextCursor` is `null` on the final page.\n */\nexport interface ListPageResult {\n /** Encrypted envelopes for this page, in adapter-defined order. */\n items: Array<{ id: string; envelope: EncryptedEnvelope }>\n /** Opaque cursor for the next page, or `null` if this was the last page. */\n nextCursor: string | null\n}\n\n// ─── Store Interface ───────────────────────────────────────────────────\n\nexport interface NoydbStore {\n /**\n * Optional human-readable store name (e.g. 'memory', 'file', 'dynamo').\n * Used in diagnostic messages and the listPage fallback warning. Stores\n * are encouraged to set this so logs are clearer about which backend is\n * involved when something goes wrong.\n */\n name?: string\n\n /** Get a single record. Returns null if not found. */\n get(vault: string, collection: string, id: string): Promise<EncryptedEnvelope | null>\n\n /** Put a record. Throws ConflictError if expectedVersion doesn't match. */\n put(\n vault: string,\n collection: string,\n id: string,\n envelope: EncryptedEnvelope,\n expectedVersion?: number,\n ): Promise<void>\n\n /** Delete a record. */\n delete(vault: string, collection: string, id: string): Promise<void>\n\n /** List all record IDs in a collection. */\n list(vault: string, collection: string): Promise<string[]>\n\n /** Load all records for a vault (initial hydration). */\n loadAll(vault: string): Promise<VaultSnapshot>\n\n /** Save all records for a vault (bulk write / restore). */\n saveAll(vault: string, data: VaultSnapshot): Promise<void>\n\n /** Optional connectivity check for sync engine. */\n ping?(): Promise<boolean>\n\n /**\n * Optional: list record IDs in a collection that have `_ts` after `since`.\n * Used by partial sync (`pull({ modifiedSince })`). Stores that omit this\n * fall back to a full `loadAll` + client-side timestamp filter.\n */\n listSince?(vault: string, collection: string, since: string): Promise<string[]>\n\n /**\n * Optional pagination extension. Stores that implement `listPage` get\n * the streaming `Collection.scan()` fast path; stores that don't are\n * silently fallen back to a full `loadAll()` + slice (with a one-time\n * console.warn).\n *\n * `cursor` is opaque to the core — each store encodes its own paging\n * state (DynamoDB: base64 LastEvaluatedKey JSON; S3: ContinuationToken;\n * memory/file/browser: numeric offset of a sorted id list). Pass\n * `undefined` to start from the beginning.\n *\n * `limit` is a soft upper bound on `items.length`. Stores MAY return\n * fewer items even when more exist (e.g. if the underlying store has\n * its own page size cap), and MUST signal \"no more pages\" by returning\n * `nextCursor: null`.\n *\n * The 6-method core contract is unchanged — this is an additive\n * extension discovered via `'listPage' in adapter`.\n */\n listPage?(\n vault: string,\n collection: string,\n cursor?: string,\n limit?: number,\n ): Promise<ListPageResult>\n\n /**\n * Optional pub/sub for real-time presence.\n * Publish an encrypted payload to a presence channel.\n * Falls back to storage-based polling when absent.\n */\n presencePublish?(channel: string, payload: string): Promise<void>\n\n /**\n * Optional pub/sub for real-time presence.\n * Subscribe to a presence channel. Returns an unsubscribe function.\n * Falls back to storage-based polling when absent.\n */\n presenceSubscribe?(channel: string, callback: (payload: string) => void): () => void\n\n /**\n * Optional cross-vault enumeration extension.\n *\n * Returns the names of every top-level vault the store\n * currently stores. Used by `Noydb.listAccessibleVaults()` to\n * enumerate the universe of vaults before filtering down to\n * the ones the calling principal can actually unwrap.\n *\n * **Why this is optional:** the storage shape of compartments\n * differs across backends. Memory and file stores store\n * vaults as top-level keys / directories and can enumerate\n * them in O(1) calls. DynamoDB stores everything in a single table\n * keyed by `(compartment#collection, id)` — enumerating compartments\n * requires either a Scan (expensive, eventually consistent, leaks\n * ciphertext metadata) or a dedicated GSI that the consumer\n * provisioned. S3 needs a prefix list (cheap if enabled, ACL-sensitive\n * otherwise). Browser localStorage can scan keys by prefix.\n *\n * Stores that cannot implement `listVaults` cheaply or\n * cleanly should omit it. Core surfaces a `StoreCapabilityError`\n * with a clear message when a caller invokes\n * `listAccessibleVaults()` against a store that doesn't\n * provide this method, so consumers know to either upgrade their\n * store, provide a candidate list explicitly to `queryAcross()`,\n * or fall back to maintaining the compartment index out of band.\n *\n * **Privacy note:** `listVaults` returns *every* compartment\n * the store has, not just the ones the caller can access. The\n * existence-leak filtering (returning only compartments whose\n * keyring the caller can unwrap) happens in core, not in the\n * store. The store is trusted to know its own contents — that\n * is not a leak in the threat model. The leak the API guards\n * against is the *return value* of `listAccessibleVaults()`\n * exposing existence to a downstream observer who only sees that\n * function's output.\n *\n * The 6-method core contract is unchanged — this is an additive\n * extension discovered via `'listVaults' in store`.\n */\n listVaults?(): Promise<string[]>\n\n /**\n * Optional: generate a presigned URL for direct client download.\n * Only meaningful for object stores (S3, GCS) that support URL signing.\n * Returns a time-limited URL that fetches the encrypted envelope directly.\n * The caller must decrypt client-side (the URL returns ciphertext).\n */\n presignUrl?(vault: string, collection: string, id: string, expiresInSeconds?: number): Promise<string>\n\n /**\n * Optional: estimate current storage usage.\n * Returns `{ usedBytes, quotaBytes }` or null if the store cannot estimate.\n * Used by quota-aware routing to detect overflow conditions.\n */\n estimateUsage?(): Promise<{ usedBytes: number; quotaBytes: number } | null>\n\n /**\n * Optional multi-record atomic write.\n *\n * When present, `db.transaction(async (tx) => { ... })` uses this to\n * commit every staged op in one storage-layer transaction — either\n * all ops land or none do, regardless of which records they touch.\n * Every `TxOp.expectedVersion` (when set) must be honored atomically\n * alongside the write; any violation throws `ConflictError` and the\n * whole batch fails.\n *\n * Stores that omit this fall through to the hub's per-record OCC\n * fallback: pre-flight CAS check, then sequential `put`/`delete`\n * with best-effort unwind on mid-batch failure (see\n * `runTransaction` for the exact semantics and crash window).\n *\n * Native implementations: `to-memory` (single Map mutation),\n * `to-dynamo` (`TransactWriteItems`), `to-browser-idb` (one\n * `readwrite` transaction). File / S3 cannot implement this\n * atomically and should omit the method.\n */\n tx?(ops: readonly TxOp[]): Promise<void>\n}\n\n/**\n * A single staged operation inside a `db.transaction(fn)` commit. The\n * hub assembles `TxOp[]` from the user's `tx.collection().put/delete`\n * calls, encrypts any `record` values into `envelope`, and hands the\n * array to `NoydbStore.tx()` when the store supports atomic batch\n * writes. Stores that implement `tx()` MUST honor every\n * `expectedVersion` atomically against the stored envelope version.\n */\nexport interface TxOp {\n readonly type: 'put' | 'delete'\n readonly vault: string\n readonly collection: string\n readonly id: string\n /** Populated for `type: 'put'` — the encrypted envelope to write. */\n readonly envelope?: EncryptedEnvelope\n /** Optional per-record CAS. Mismatch must throw `ConflictError`. */\n readonly expectedVersion?: number\n}\n\n// ─── Store Factory Helper ──────────────────────────────────────────────\n\n/** Type-safe helper for creating store factories. */\nexport function createStore<TOptions>(\n factory: (options: TOptions) => NoydbStore,\n): (options: TOptions) => NoydbStore {\n return factory\n}\n\n// ─── Keyring ───────────────────────────────────────────────────────────\n\n/**\n * Interchange formats `@noy-db/as-*` packages can produce. `'*'` is a\n * wildcard granting every current + future plaintext format.\n */\nexport type ExportFormat =\n | 'xlsx'\n | 'csv'\n | 'json'\n | 'ndjson'\n | 'xml'\n | 'sql'\n | 'pdf'\n | 'blob'\n | 'zip'\n | '*'\n\n/**\n * Owner-granted export capability on a keyring.\n *\n * Two independent dimensions:\n *\n * - `plaintext` — per-format allowlist for record formatters + blob\n * extractors that emit plaintext bytes (`as-xlsx`, `as-csv`,\n * `as-blob`, `as-zip`, …). **Defaults to empty** for every role;\n * the owner/admin must positively grant per-format (or `'*'`).\n * - `bundle` — boolean for `.noydb` encrypted container export\n * (`as-noydb`). **Default policy: on for owner/admin, off for\n * operator/viewer/client** — applied when the field is absent or\n * undefined (see `hasExportCapability`).\n */\nexport interface ExportCapability {\n readonly plaintext?: readonly ExportFormat[]\n readonly bundle?: boolean\n}\n\n/**\n * Owner-granted import capability on a keyring (sibling of\n * `ExportCapability`, issue ).\n *\n * Two independent dimensions:\n *\n * - `plaintext` — per-format allowlist for `as-*` readers that ingest\n * plaintext bytes (`as-csv`, `as-json`, `as-ndjson`, `as-zip`, …).\n * Defaults to empty for every role; the owner/admin must positively\n * grant per-format (or `'*'`).\n * - `bundle` — boolean gate for `.noydb` bundle import. **Defaults to\n * `false` for every role**, including owner/admin. Import is more\n * dangerous than export (corrupts vs leaks), so the policy is\n * default-closed across the board — the owner explicitly opts a\n * keyring in via `db.grant({ importCapability: { bundle: true } })`.\n */\nexport interface ImportCapability {\n readonly plaintext?: readonly ExportFormat[]\n readonly bundle?: boolean\n}\n\n/**\n * Forward-declared on-disk shape for `VaultPolicy` — the actual policy\n * model lives in `policy/types.ts` (#9). Declared here as `unknown`-typed\n * map so types.ts has no dependency on the policy module while the\n * `KeyringFile.policy` field can still round-trip foreign documents.\n *\n * @internal\n */\nexport type VaultPolicyOnDisk = Record<string, unknown>\n\n/**\n * Recovery profile enrolled at vault creation (issue #10).\n *\n * - `paper` — `on-recovery` codes (the only end-to-end profile in v0.1.0-pre.5).\n * - `shamir` / `multi-channel` / `admin-mediated` — API surface ships;\n * per-profile dispatch lands in follow-up issues. Calling\n * `db.recoverPassphrase` against these throws\n * {@link RecoveryProfileNotImplementedError}.\n */\nexport type RecoveryEnrollment =\n | {\n readonly profile: 'paper'\n /** Number of single-use codes to print at enrollment. */\n readonly codes: number\n }\n | {\n readonly profile: 'shamir'\n readonly k: number\n readonly n: number\n readonly trustees: ReadonlyArray<string>\n }\n | {\n readonly profile: 'multi-channel'\n readonly email?: string\n readonly pin?: boolean\n readonly paperCodes?: number\n }\n | {\n readonly profile: 'admin-mediated'\n readonly grantorUserId: string\n }\n\n/**\n * One tier-2 authenticator slot inside a keyring file. Each slot\n * independently wraps the SAME KEK under a method-specific derived key\n * (LUKS pattern). Adding or removing a slot is a constant-time keyring\n * write — no DEK re-keying required.\n *\n * @see docs/subsystems/session-tiers.md → Tier 2 — Authenticate (multi-slot)\n */\n/**\n * Shared fields across all authenticator slot variants. The variant\n * (`KeyringAuthenticatorWrappingKEK` vs `KeyringAuthenticatorWrappingDEKs`)\n * carries the actual wrapped material; everything below is identity +\n * metadata only.\n */\ninterface KeyringAuthenticatorBase {\n /** Caller-chosen identifier — e.g. `'webauthn-yubikey-blue'`, `'oidc-google'`, `'password'`. */\n readonly id: string\n /** Method family — selects which `@noy-db/on-*` package handles unlock. */\n readonly method: 'webauthn' | 'oidc' | 'password'\n /** ISO-8601 timestamp at which the slot was added. */\n readonly enrolled_at: string\n /**\n * Which session tier ENROLLED this slot. Tier 1 enrolls a fresh slot;\n * tier 2 may add a sibling slot when the active policy permits.\n */\n readonly enrolled_via_tier: 1 | 2\n /**\n * Method-specific metadata: WebAuthn cred id, OIDC issuer/sub, PBKDF2\n * salt for `on-password`, etc. The schema is open by design — the\n * `@noy-db/on-*` package owns the contents.\n */\n readonly meta: Record<string, unknown>\n}\n\n/**\n * Slot that wraps the KEK directly under a method-derived AES-KW key.\n * Used by ceremonies where the on-* package can produce/recover an\n * extractable KEK from its own credential — WebAuthn (PRF-derived\n * wrapping key) and split-key OIDC.\n *\n * `wrapKind` is optional/absent on slots written before pre.8 — those\n * legacy slots are treated as wrap-KEK by default at unlock time.\n */\nexport interface KeyringAuthenticatorWrappingKEK extends KeyringAuthenticatorBase {\n readonly wrapKind?: 'kek'\n /** Base64 wrapped-KEK ciphertext under the method-derived key. */\n readonly wrapped_kek: string\n /** XOR guard — wrap-KEK slots must NOT carry wrap-DEKs material. */\n readonly wrapped_deks?: never\n /** XOR guard — wrap-KEK slots must NOT carry wrap-DEKs material. */\n readonly iv?: never\n}\n\n/**\n * Slot that wraps the DEK set (not the KEK) under a method-derived\n * AES-GCM key — sidesteps the non-extractable-KEK constraint by\n * encrypting the serialized `{ deks: { collection: rawDekBase64 } }`\n * directly. Mirrors the format used by `mintPaperRecoveryEntry`\n * (`PaperRecoveryEntry`) and `@noy-db/on-pin`'s `PinResumeState` —\n * the unified wrap-DEKs primitive across tier-0 / tier-2 / tier-3.\n *\n * Trade-off: a slot of this kind reconstructs `UnlockedKeyring` with\n * `kek: null` after unlock. That is semantically correct for tier-2\n * (sensitive ops like `enrollAuthenticator` / `rotatePassphrase`\n * require a tier-1 unlock anyway) and matches how `@noy-db/on-pin`\n * already behaves at tier 3.\n *\n * @see `mintPaperRecoveryEntry` in `team/recovery.ts` — same shape on\n * a different on-disk path (`_meta/recovery-paper`).\n */\nexport interface KeyringAuthenticatorWrappingDEKs extends KeyringAuthenticatorBase {\n readonly wrapKind: 'deks'\n /** Base64 AES-GCM ciphertext of `{ deks: { collection: base64rawDek } }`. */\n readonly wrapped_deks: string\n /** Base64 AES-GCM IV used for the `wrapped_deks` ciphertext. */\n readonly iv: string\n /** XOR guard — wrap-DEKs slots must NOT carry wrap-KEK material. */\n readonly wrapped_kek?: never\n}\n\n/**\n * Discriminated union over the two wrap-format variants. Reads from\n * disk should always go through this type so the variant is preserved.\n *\n * Discriminator: `wrapKind`. Absent → wrap-KEK (legacy / WebAuthn /\n * OIDC). Present and `'deks'` → wrap-DEKs (password / future on-* that\n * want to sidestep extractable-KEK).\n *\n * The type-level XOR enforces \"exactly one of `wrapped_kek` /\n * `wrapped_deks` is present\" — a structural guarantee that the runtime\n * dispatch is safe.\n */\nexport type KeyringAuthenticator =\n | KeyringAuthenticatorWrappingKEK\n | KeyringAuthenticatorWrappingDEKs\n\nexport interface KeyringFile {\n readonly _noydb_keyring: typeof NOYDB_KEYRING_VERSION\n readonly user_id: string\n readonly display_name: string\n readonly role: Role\n readonly permissions: Permissions\n readonly deks: Record<string, string>\n readonly salt: string\n readonly created_at: string\n readonly granted_by: string\n /**\n * Passphrase canary — base64 AES-KW-wrapped form of a known constant\n * 256-bit value, wrapped under the keyring's KEK (#113).\n *\n * Optional: pre-#113 keyrings load with no canary and fall back to\n * the multi-DEK corruption heuristic from #82. Keyrings written after\n * #113 carry one and let `loadKeyring` distinguish wrong-passphrase\n * from corruption even when ALL DEKs (including a single-DEK keyring's\n * sole DEK) are corrupted.\n *\n * AES-KW is deterministic — every write site mints fresh on each\n * persist; same KEK + same constant input always produces the same\n * ciphertext, so this round-trips without state.\n */\n readonly canary?: string\n /**\n * Tier-2 authenticator slots (multi-slot keyring extension).\n * Optional / append-only: keyring files written before the\n * extension load with an empty list. Each slot independently wraps\n * the same KEK; any one of them unlocks.\n *\n * @see KeyringAuthenticator\n */\n readonly authenticators?: readonly KeyringAuthenticator[]\n /**\n * Per-keyring policy override (reserved). The on-disk format\n * accepts the field for forward compatibility with the Option C\n * merge engine deferred to a later release; v1.0 reads only the\n * vault-level `_meta/policy` document, so this field is parsed and\n * round-tripped but never enforced.\n */\n readonly policy?: VaultPolicyOnDisk\n /**\n * Optional — authorization spec capability bits. Absent on keyrings written\n * before the RFC implementation. Loading falls back to role-based\n * defaults (owner/admin get bundle-on, everyone else off).\n */\n readonly export_capability?: ExportCapability\n /**\n * Optional bundle-slot expiry. ISO-8601 timestamp; past\n * the cutoff `loadKeyring` throws `KeyringExpiredError` before any\n * DEK unwrap is attempted. Useful for time-boxed audit access:\n * \"this slot works for 30 days then becomes opaque to its holder.\"\n *\n * Absent on live keyrings written via `db.grant()` — the field is\n * meaningful for `BundleRecipient` slots produced by\n * `writeNoydbBundle({ recipients: [...] })`. Setting it on a live\n * keyring is allowed but unusual.\n */\n readonly expires_at?: string\n /**\n * Optional — issue import-capability bits. Absent on keyrings\n * written before landed. Loading falls back to default-closed\n * for every role and every format.\n */\n readonly import_capability?: ImportCapability\n /**\n * hierarchical access clearance. Absent → 0 (advisory;\n * the real check is whether the DEK map carries a `collection#tier`\n * entry for the requested tier). Owners and admins default to the\n * highest tier they have DEKs for at grant time.\n */\n readonly clearance?: number\n}\n\n// ─── Backup ────────────────────────────────────────────────────────────\n\nexport interface VaultBackup {\n readonly _noydb_backup: typeof NOYDB_BACKUP_VERSION\n readonly _compartment: string\n readonly _exported_at: string\n readonly _exported_by: string\n readonly keyrings: Record<string, KeyringFile>\n readonly collections: VaultSnapshot\n /**\n * Internal collections (`_ledger`, `_ledger_deltas`, `_history`, `_sync`, …)\n * captured alongside the data collections. Optional for backwards\n * compat with backups, which only stored data collections —\n * loading a backup leaves the ledger empty (and `verifyBackupIntegrity`\n * skips the chain check, surfacing only a console warning).\n */\n readonly _internal?: VaultSnapshot\n /**\n * Verifiable-backup metadata. Embeds the ledger head at\n * dump time so `load()` can cross-check that the loaded chain matches\n * exactly what was exported. A backup whose chain has been tampered\n * with — either by modifying ledger entries or by modifying data\n * envelopes that the chain references — fails this check.\n *\n * Optional for backwards compat with backups; missing means\n * \"legacy backup, load with a warning, no integrity check\".\n */\n readonly ledgerHead?: {\n /** Hex sha256 of the canonical JSON of the last ledger entry. */\n readonly hash: string\n /** Sequential index of the last ledger entry. */\n readonly index: number\n /** ISO timestamp captured at dump time. */\n readonly ts: string\n }\n}\n\n// ─── Export ────────────────────────────────────────────────────────────\n\n/**\n * Options for `Vault.exportStream()` and `Vault.exportJSON()`.\n *\n * The defaults match the most common consumer pattern: one chunk per\n * collection, no ledger metadata. Per-record streaming and ledger-head\n * inclusion are opt-in because both add structure most consumers don't\n * need.\n */\nexport interface ExportStreamOptions {\n /**\n * `'collection'` (default) yields one chunk per collection with all\n * records bundled in `chunk.records`. `'record'` yields one chunk per\n * record, useful for arbitrarily large collections that should never\n * be materialized as a single array.\n */\n readonly granularity?: 'collection' | 'record'\n\n /**\n * When `true`, every chunk includes the current compartment ledger\n * head under `chunk.ledgerHead`. The value is identical across every\n * chunk in a single export (one ledger per compartment). Forward-\n * compatible with future partition work where the head would become\n * per-partition. Default: `false`.\n */\n readonly withLedgerHead?: boolean\n /**\n * When set to a BCP 47 locale string (e.g. `'th'`), `exportJSON()`\n * resolves all `dictKey` labels to that locale and omits the raw\n * `dictionaries` snapshot from the output. Has no effect\n * on `exportStream()` — format packages use the `chunk.dictionaries`\n * snapshot directly and apply their own locale strategy.\n *\n * Default: `undefined` — embed the raw snapshot under `_dictionaries`.\n */\n readonly resolveLabels?: string\n}\n\n/**\n * One chunk yielded by `Vault.exportStream()`.\n *\n * `granularity: 'collection'` yields one chunk per collection with the\n * full record array in `records`. `granularity: 'record'` yields one\n * chunk per record with `records` containing exactly one element — the\n * `schema` and `refs` metadata is repeated on every chunk so consumers\n * doing per-record streaming don't have to thread state across yields.\n */\nexport interface ExportChunk<T = unknown> {\n /** Collection name (no leading underscore — internal collections are filtered out). */\n readonly collection: string\n\n /**\n * Standard Schema validator attached to the collection at `collection()`\n * construction time, or `null` if no schema was provided. Surfaced so\n * downstream serializers (`@noy-db/as-*` packages, custom\n * exporters) can produce schema-aware output (typed CSV headers, XSD\n * generation, etc.) without poking at collection internals.\n */\n readonly schema: StandardSchemaV1<unknown, T> | null\n\n /**\n * Foreign-key references declared on the collection via the `refs`\n * option, as the `{ field → { target, mode } }` map produced by\n * `RefRegistry.getOutbound`. Empty object when no refs were declared.\n */\n readonly refs: Record<string, { readonly target: string; readonly mode: 'strict' | 'warn' | 'cascade' }>\n\n /**\n * Decrypted, ACL-scoped, schema-validated records. Length 1 in\n * `granularity: 'record'` mode, full collection in `granularity: 'collection'`\n * mode. Records are returned by reference from the collection's eager\n * cache where applicable — consumers must treat them as immutable.\n */\n readonly records: T[]\n\n /**\n * Dictionary snapshots for every `dictKey` field declared on this\n * collection. Captured once at stream-start and held\n * constant across all chunks within the same export — a rename\n * mid-export does not change the snapshot. `undefined` when the\n * collection has no `dictKeyFields`.\n *\n * Shape: `{ [fieldName]: { [stableKey]: { [locale]: label } } }`\n *\n * @example\n * ```ts\n * chunk.dictionaries?.status?.paid?.th // → 'ชำระแล้ว'\n * ```\n */\n readonly dictionaries?: Record<\n string, // field name\n Record<string, Record<string, string>> // stable key → locale → label\n >\n\n /**\n * Vault ledger head at export time. Present only when\n * `exportStream({ withLedgerHead: true })` was called. Identical\n * across every chunk in the same export — included on every chunk\n * for forward-compatibility with future per-partition ledgers, where\n * the value will differ per chunk.\n */\n readonly ledgerHead?: {\n readonly hash: string\n readonly index: number\n readonly ts: string\n }\n}\n\n// ─── Sync ──────────────────────────────────────────────────────────────\n\nexport interface DirtyEntry {\n readonly vault: string\n readonly collection: string\n readonly id: string\n readonly action: 'put' | 'delete'\n readonly version: number\n readonly timestamp: string\n}\n\nexport interface SyncMetadata {\n readonly _noydb_sync: typeof NOYDB_SYNC_VERSION\n readonly last_push: string | null\n readonly last_pull: string | null\n readonly dirty: DirtyEntry[]\n}\n\nexport interface Conflict {\n readonly vault: string\n readonly collection: string\n readonly id: string\n readonly local: EncryptedEnvelope\n readonly remote: EncryptedEnvelope\n readonly localVersion: number\n readonly remoteVersion: number\n /**\n * Present only when the collection uses `conflictPolicy: 'manual'`.\n * Call `resolve(winner)` to commit the winning envelope, or\n * `resolve(null)` to defer (conflict stays queued for the next sync).\n * Called synchronously inside the `sync:conflict` event handler.\n */\n readonly resolve?: (winner: EncryptedEnvelope | null) => void\n}\n\nexport type ConflictStrategy =\n | 'local-wins'\n | 'remote-wins'\n | 'version'\n | ((conflict: Conflict) => 'local' | 'remote')\n\n/**\n * Collection-level conflict policy.\n * Overrides the db-level `conflict` option for the specific collection.\n *\n * - `'last-writer-wins'` — higher `_ts` wins (timestamp LWW).\n * - `'first-writer-wins'` — lower `_v` wins (earlier version is preserved).\n * - `'manual'` — emits `sync:conflict` with a `resolve` callback. Call\n * `resolve(winner)` synchronously to commit or `resolve(null)` to defer.\n * - Custom fn — synchronous `(local: T, remote: T) => T`. Must be pure.\n */\nexport type ConflictPolicy<T> =\n | 'last-writer-wins'\n | 'first-writer-wins'\n | 'manual'\n | ((local: T, remote: T) => T)\n\n/**\n * Envelope-level resolver registered per collection with the SyncEngine.\n * Receives the `id` of the conflicting record and both envelopes.\n * Returns the winning envelope, or `null` to defer resolution.\n * @internal\n */\nexport type CollectionConflictResolver = (\n id: string,\n local: EncryptedEnvelope,\n remote: EncryptedEnvelope,\n) => Promise<EncryptedEnvelope | null>\n\n/** Options for targeted push operations. */\nexport interface PushOptions {\n /** Only push records belonging to these collections. Omit to push all dirty. */\n collections?: string[]\n}\n\n/** Options for targeted pull operations. */\nexport interface PullOptions {\n /** Only pull these collections. Omit to pull all. */\n collections?: string[]\n /**\n * Only pull records with `_ts` strictly after this ISO timestamp.\n * Stores that implement `listSince` use it directly; others fall back\n * to a full scan with client-side filtering.\n */\n modifiedSince?: string\n}\n\nexport interface PushResult {\n readonly pushed: number\n readonly conflicts: Conflict[]\n readonly errors: Error[]\n}\n\nexport interface PullResult {\n readonly pulled: number\n readonly conflicts: Conflict[]\n readonly errors: Error[]\n}\n\n/** Result of a sync transaction commit. */\nexport interface SyncTransactionResult {\n readonly status: 'committed' | 'conflict'\n readonly pushed: number\n readonly conflicts: Conflict[]\n}\n\nexport interface SyncStatus {\n readonly dirty: number\n readonly lastPush: string | null\n readonly lastPull: string | null\n readonly online: boolean\n}\n\n// ─── Sync Target ─────────────────────────────────────────\n\nexport type SyncTargetRole = 'sync-peer' | 'backup' | 'archive'\n\n/**\n * A sync target with role and optional per-target policy.\n *\n * | Role | Direction | Conflict resolution | Typical use |\n * |-------------|---------------|---------------------|--------------------------|\n * | `sync-peer` | Bidirectional | ConflictStrategy | DynamoDB live sync |\n * | `backup` | Push-only | N/A (receives merged)| S3 dump, Google Drive |\n * | `archive` | Push-only | N/A | IPFS, Git tags, S3 Lock |\n */\nexport interface SyncTarget {\n /** The store to sync with. */\n readonly store: NoydbStore\n /** Role determines sync direction and conflict handling. */\n readonly role: SyncTargetRole\n /** Per-target sync policy. Inherits store-category default when absent. */\n readonly policy?: SyncPolicy\n /** Human-readable label for DevTools and audit logs. */\n readonly label?: string\n}\n\n// ─── Events ────────────────────────────────────────────────────────────\n\nexport interface ChangeEvent {\n readonly vault: string\n readonly collection: string\n readonly id: string\n readonly action: 'put' | 'delete'\n}\n\nexport interface NoydbEventMap {\n 'change': ChangeEvent\n 'error': Error\n 'sync:push': PushResult\n 'sync:pull': PullResult\n 'sync:conflict': Conflict\n 'sync:online': void\n 'sync:offline': void\n 'sync:backup-error': { vault: string; target: string; error: Error }\n 'history:save': { vault: string; collection: string; id: string; version: number }\n 'history:prune': { vault: string; collection: string; id: string; pruned: number }\n /**\n * Emitted when a persisted-index side-car put/delete fails after the\n * main record write already succeeded. The main record is durable; the\n * index mirror may have drifted. Operators reconcile via\n * `collection.reconcileIndex(field)`.\n */\n 'index:write-partial': {\n vault: string\n collection: string\n id: string\n action: 'put' | 'delete'\n error: Error\n }\n /**\n * emitted by `Collection.ensurePersistedIndexesLoaded()`\n * once per field on first lazy-mode query when\n * `reconcileOnOpen: 'auto' | 'dry-run'` is configured. `applied` is\n * `0` in `'dry-run'` mode. `skipped` is reserved for a future\n * drift-stamp optimization that short-circuits the reconcile when\n * the mirror version matches what's on disk — currently always\n * `false` (the full reconcile runs every session).\n */\n 'index:reconciled': {\n vault: string\n collection: string\n field: string\n missing: readonly string[]\n stale: readonly string[]\n applied: number\n skipped: boolean\n }\n}\n\n// ─── Grant / Revoke ────────────────────────────────────────────────────\n\nexport interface GrantOptions {\n readonly userId: string\n readonly displayName: string\n readonly role: Role\n readonly passphrase: string\n readonly permissions?: Permissions\n /**\n * Optional `@noy-db/as-*` export capability. Omit or\n * leave undefined to apply role-based defaults (see\n * `hasExportCapability` and `ExportCapability`).\n */\n readonly exportCapability?: ExportCapability\n /**\n * Optional `@noy-db/as-*` import capability (issue ). Omit or\n * leave undefined for default-closed semantics — no plaintext format\n * is grantable until positively listed; bundle import is denied.\n */\n readonly importCapability?: ImportCapability\n /**\n * Skip phrase-format strength validation (issue #7). Defaults to\n * false — `grant()` rejects phrases that don't meet the configured\n * `PassphrasePolicy`. Test fixtures and CLI scripts pass `true`.\n */\n readonly allowWeakPassphrase?: boolean\n /**\n * Initial user-envelope payload for the new principal. Sealed under\n * the same vault DEK (the reserved `_users` collection's DEK) and\n * persisted alongside the keyring during grant.\n *\n * **Bootstrap-only.** Once the new user activates and writes their\n * own envelope, the own-only write rule kicks in — admins cannot\n * edit a teammate's envelope after activation. Use this field for\n * pre-fill at invite time (e.g. \"displayName: Bob, locale: en-US\")\n * and let the user take over from there.\n *\n * Hub does not introspect the payload; it is JSON-serialized and\n * encrypted opaquely. Apps own the schema.\n *\n * @see docs/superpowers/specs/2026-05-05-user-envelope-design.md → Lifecycle\n */\n readonly initialProfile?: unknown\n}\n\n/**\n * Caller payload for `db.updateUser` (#54). Mutate one or more\n * identity fields on an existing keyring without rotating any keys.\n *\n * `role`, `displayName`, and `permissions` live in the plaintext header\n * of `_keyring/<userId>` (the sync engine reads them without keys).\n * Mutating them is a JSON header swap — no DEK rewrap, no KEK\n * required, no authenticator slots touched. Tier-2 slots and recovery\n * enrollments survive unchanged. Last-write-wins through the existing\n * keyring put (same concurrency story as `db.grant` / `db.revoke`).\n *\n * Top-level fields are partial-merge: absent fields are not modified.\n * `null` on `displayName` clears the field (stored as the empty string;\n * UI consumers typically render the empty case by falling back to the\n * user id). `undefined` / absent leaves the field untouched. Mirrors\n * the `null`-as-clear convention `UserApi.updateMe` uses (#57).\n *\n * `permissions`, however, is a **full replacement** at the map level —\n * passing `{ invoices: 'rw' }` REPLACES the entire permissions map,\n * silently dropping any other entries. To partially update, read the\n * current keyring and merge: `permissions: { ...current, invoices: 'rw' }`.\n * To clear all permissions, pass `permissions: {}` explicitly.\n *\n * Role-elevation guard: the same hierarchy as `db.grant`. Admins can\n * change `admin` / `operator` / `viewer` / `client` to and from each\n * other; admins cannot promote to or demote from `owner`. Owners can\n * do anything. Non-admin callers (operator/viewer/client) cannot call\n * `db.updateUser` at all — for self-displayName changes, use\n * `vault.user.updateMe` (the user-envelope API).\n *\n * @see #54\n */\nexport interface UpdateUserOptions {\n readonly userId: string\n readonly role?: Role\n readonly displayName?: string | null\n readonly permissions?: Permissions\n}\n\nexport interface RevokeOptions {\n readonly userId: string\n readonly rotateKeys?: boolean\n\n /**\n * Cascade behavior when the revoked user is an admin who has granted\n * other admins.\n *\n * - `'strict'` (default) — recursively revoke every admin that the\n * target (transitively) granted. The cascade walks the\n * `granted_by` field on each keyring file and stops at non-admin\n * leaves. All affected collections are accumulated and rotated in\n * a single pass at the end, so cascade cost is O(records in\n * affected collections), not O(records × cascade depth).\n *\n * - `'warn'` — leave the descendant admins in place but emit a\n * `console.warn` listing them. Useful for diagnostic dry runs and\n * for environments where the operator wants to clean up the\n * delegation tree manually.\n *\n * No effect when the target is not an admin (operators, viewers, and\n * clients cannot grant other users, so they have no delegation\n * subtree to cascade through). Defaults to `'strict'`.\n */\n readonly cascade?: 'strict' | 'warn'\n}\n\n// ─── Cross-vault queries ──────────────────────────────\n\n/**\n * One entry returned by `Noydb.listAccessibleVaults()`. Carries\n * the compartment id and the role the calling principal holds in it,\n * so the consumer can decide how to fan out without re-checking\n * permissions per vault.\n */\nexport interface AccessibleVault {\n readonly id: string\n readonly role: Role\n}\n\n/**\n * Options for `Noydb.listAccessibleVaults()`.\n */\nexport interface ListAccessibleVaultsOptions {\n /**\n * Minimum role the caller must hold to include a vault in the\n * result. Vaults where the caller's role is strictly *below*\n * this threshold are silently excluded. Defaults to `'client'`,\n * which means \"every vault I can unwrap is returned.\" Set to\n * `'admin'` for \"vaults where I can grant/revoke,\" or\n * `'owner'` for \"vaults I own.\"\n *\n * The privilege ordering used:\n * `client (1) < viewer (2) < operator (3) < admin (4) < owner (5)`\n *\n * Note: `viewer` and `client` are conceptually peers in the ACL\n * (neither can grant), but `viewer` has read-all access while\n * `client` has only explicit-collection read. The numeric order\n * reflects \"how much can this principal see,\" not \"how much can\n * this principal modify.\"\n */\n readonly minRole?: Role\n}\n\n/**\n * Options for `Noydb.queryAcross()`.\n */\nexport interface QueryAcrossOptions {\n /**\n * Maximum number of compartments to process in parallel. Defaults\n * to `1` (sequential) — conservative because the per-compartment\n * callback typically does its own I/O and an unbounded fan-out can\n * exhaust adapter connections (DynamoDB throughput, S3 socket\n * limits, browser fetch concurrency).\n *\n * Set to `4` or `8` for cloud-backed compartments where parallelism\n * is the whole point of fanning out. Set to `1` (default) for local\n * adapters where the disk I/O serializes anyway.\n */\n readonly concurrency?: number\n}\n\n/**\n * One entry in the array returned by `Noydb.queryAcross()`. Either\n * `result` is set (callback succeeded for this compartment) or\n * `error` is set (callback threw, or compartment failed to open).\n *\n * Per-compartment errors do **not** abort the overall fan-out — every\n * compartment is given a chance to run its callback, and the\n * partition between success and failure is exposed in the return\n * value. Consumers that want fail-fast semantics can check\n * `r.error !== undefined` and short-circuit themselves.\n */\nexport type QueryAcrossResult<T> =\n | { readonly vault: string; readonly result: T; readonly error?: undefined }\n | { readonly vault: string; readonly result?: undefined; readonly error: Error }\n\n// ─── User Info ─────────────────────────────────────────────────────────\n\nexport interface UserInfo {\n readonly userId: string\n readonly displayName: string\n readonly role: Role\n readonly permissions: Permissions\n readonly createdAt: string\n readonly grantedBy: string\n}\n\n// ─── Session ───────────────────────────────────────────────\n\n/**\n * Operations that a session policy can require re-authentication for.\n * Passed as the `requireReAuthFor` array in `SessionPolicy`.\n */\nexport type ReAuthOperation = 'export' | 'grant' | 'revoke' | 'rotate' | 'changeSecret'\n\n/**\n * Session policy controlling lifetime, re-auth requirements, and\n * background-lock behavior.\n *\n * All timeout values are in milliseconds. `undefined` means \"no limit.\"\n * The policy is evaluated lazily — it does not start timers itself;\n * enforcement happens at the Noydb call site.\n */\nexport interface SessionPolicy {\n /**\n * Idle timeout in ms. If no NOYDB operation is performed for this\n * duration, the session is revoked on the next operation attempt\n * (which will throw `SessionExpiredError`). The idle clock resets\n * on every successful operation.\n *\n * Default: `undefined` (no idle timeout).\n */\n readonly idleTimeoutMs?: number\n\n /**\n * Absolute timeout in ms from session creation. After this duration\n * the session is unconditionally revoked regardless of activity.\n *\n * Default: `undefined` (no absolute timeout).\n */\n readonly absoluteTimeoutMs?: number\n\n /**\n * Operations that require the user to re-authenticate (re-enter their\n * passphrase or perform a fresh WebAuthn assertion) before proceeding,\n * even if the session is still alive.\n *\n * Common pattern: `requireReAuthFor: ['export', 'grant']` — allow\n * read/write operations in the background but demand a fresh credential\n * for high-risk mutations.\n *\n * Default: `[]` (no extra re-auth requirements).\n */\n readonly requireReAuthFor?: readonly ReAuthOperation[]\n\n /**\n * If `true`, the session is revoked when the page goes to the background\n * (visibilitychange event, `document.hidden === true`). Useful for\n * high-sensitivity deployments where leaving the tab is treated as\n * a session boundary.\n *\n * No-op in non-browser environments (Node.js, workers without document).\n * Default: `false`.\n */\n readonly lockOnBackground?: boolean\n}\n\n// ─── i18n / Locale ─────────────────────────────────────\n\n/**\n * Locale-aware read options. Pass to `Collection.get()`, `list()`,\n * `query()`, and `scan()` to trigger per-record locale resolution for\n * `dictKey` and `i18nText` fields.\n *\n * - **`locale: 'raw'`** — skip resolution for `i18nText` fields and\n * return the full `{ [locale]: string }` map. Dict key fields still\n * return the stable key (no `<field>Label` added).\n * - **`fallback`** — single locale code or ordered list. Use `'any'` as\n * the last element to fall back to any present translation.\n *\n * When neither the call-level locale nor the compartment's default locale\n * is set, reading a record with `i18nText` fields throws\n * `LocaleNotSpecifiedError`.\n */\nexport interface LocaleReadOptions {\n /**\n * The target locale code (e.g. `'th'`), or `'raw'` to return the full\n * language map without resolution.\n */\n readonly locale?: string\n /**\n * Fallback locale or ordered fallback chain. Use `'any'` as the last\n * element to fall back to any present translation.\n */\n readonly fallback?: string | readonly string[]\n}\n\n// ─── plaintextTranslator hook ──────────────────────────────\n\n/**\n * Context passed to the consumer-supplied `plaintextTranslator` function.\n * The hook receives the source text plus enough metadata to route it to the\n * right translation service and record what it did.\n */\nexport interface PlaintextTranslatorContext {\n /** The plaintext string to translate. */\n readonly text: string\n /** BCP 47 source locale (the locale the text is written in). */\n readonly from: string\n /** BCP 47 target locale to translate into. */\n readonly to: string\n /** The schema field name that triggered the translation. */\n readonly field: string\n /** The collection the record is being put into. */\n readonly collection: string\n}\n\n/**\n * A consumer-supplied async function that translates a single string\n * from one locale to another. noy-db ships no built-in translator.\n *\n * **Security:** this function receives plaintext. The consumer is\n * responsible for the data policy of whatever service it calls. See\n * `NOYDB_SPEC.md § Zero-Knowledge Storage` and the `plaintextTranslator`\n * JSDoc on `NoydbOptions` for the full invariant statement.\n */\nexport type PlaintextTranslatorFn = (\n ctx: PlaintextTranslatorContext,\n) => Promise<string>\n\n/**\n * One entry in the in-process translator audit log. Cleared when\n * `db.close()` is called — same lifetime as the KEK and DEKs.\n *\n * Deliberately omits any content hash or translated-text fingerprint\n * to prevent correlation attacks on the audit trail.\n */\nexport interface TranslatorAuditEntry {\n readonly type: 'translator-invocation'\n /** Schema field name that was translated. */\n readonly field: string\n /** Collection the record belongs to. */\n readonly collection: string\n /** Source locale. */\n readonly fromLocale: string\n /** Target locale. */\n readonly toLocale: string\n /**\n * Consumer-provided translator name from\n * `NoydbOptions.plaintextTranslatorName`. Defaults to `'anonymous'`\n * when not supplied.\n */\n readonly translatorName: string\n /** ISO 8601 timestamp of the invocation. */\n readonly timestamp: string\n /**\n * `true` when the result was served from the in-process cache rather\n * than by calling the translator function. Present only on cache hits\n * so the absence of the field also communicates a cache miss.\n */\n readonly cached?: true\n}\n\n// ─── Presence ─────────────────────────────────────────────\n\n/**\n * A presence peer entry. `lastSeen` is an ISO timestamp set by core on each\n * `update()` call. Stale entries (lastSeen older than `staleMs`) are filtered\n * before delivering to the subscriber callback.\n */\nexport interface PresencePeer<P> {\n readonly userId: string\n readonly payload: P\n readonly lastSeen: string\n}\n\n// ─── CRDT ─────────────────────────────────────────────────\n\n// Re-exported from crdt.ts so consumers only need one import path.\nexport type { CrdtMode, CrdtState, LwwMapState, RgaState, YjsState } from './crdt/crdt.js'\n\n// ─── Blob / Attachment Store ────────────────────────\n\n/**\n * Second store shape for blob-store backends (Drive, WebDAV, Git, iCloud)\n * that operate on whole-vault bundles rather than per-record KV.\n *\n * Implement `readBundle` / `writeBundle` instead of the six-method KV\n * contract. Use `wrapBundleStore()` from `@noy-db/hub` to convert to a\n * `NoydbStore` that the rest of the API consumes transparently.\n *\n * Named `NoydbBundleStore` (not `NoydbBundleAdapter`) for consistency\n * with the hub / to-* / in-* rename. Concrete implementations ship\n * in `@noy-db/to-*` packages starting in.\n */\nexport interface NoydbBundleStore {\n /** Discriminant for engine auto-detection of store shape. */\n readonly kind: 'bundle'\n /** Human-readable name for diagnostics (e.g. `'drive'`, `'webdav'`). */\n readonly name?: string\n /**\n * Read the entire vault as raw bytes. Returns `null` if no bundle exists\n * yet (first open of a brand-new vault).\n */\n readBundle(vaultId: string): Promise<{ bytes: Uint8Array; version: string } | null>\n /**\n * Write the entire vault as raw bytes. `expectedVersion` is the version\n * token from the last `readBundle` (or `null` for a first write).\n * Implementations MUST reject the write if the stored version has advanced\n * past `expectedVersion` — throw `BundleVersionConflictError`.\n * Returns the new version token on success.\n */\n writeBundle(\n vaultId: string,\n bytes: Uint8Array,\n expectedVersion: string | null,\n ): Promise<{ version: string }>\n /** Delete a vault bundle. Idempotent — no-op if the bundle does not exist. */\n deleteBundle(vaultId: string): Promise<void>\n /** List all vault bundles managed by this store. */\n listBundles(): Promise<Array<{ vaultId: string; version: string; size: number }>>\n}\n\n/**\n * Content-addressed blob object stored in the vault-level blob index.\n * Identified by HMAC-SHA-256(blobDEK, plaintext) — opaque to the store.\n *\n * Shared across all collections within a vault for deduplication: two\n * records that attach identical byte content reference the same `eTag`\n * and share a single set of encrypted chunks in `_blob_chunks`.\n */\nexport interface BlobObject {\n /** HMAC-SHA-256 hex of the original plaintext bytes, keyed by `_blob` DEK. */\n readonly eTag: string\n /** Original uncompressed size in bytes. */\n readonly size: number\n /** Compressed size in bytes (the payload that is actually encrypted and chunked). */\n readonly compressedSize: number\n /** Compression algorithm applied before encryption. */\n readonly compression: 'gzip' | 'none'\n /** Raw chunk size in bytes used at write time. Readers MUST use this value. */\n readonly chunkSize: number\n /** Total number of chunks written. Reader expects exactly this many. */\n readonly chunkCount: number\n /** MIME type if provided or auto-detected at upload time. */\n readonly mimeType?: string\n /** ISO timestamp of first upload. */\n readonly createdAt: string\n /** Live reference count — slots + published versions pointing to this blob. */\n readonly refCount: number\n /**\n * Hint indicating which store holds the chunk data.\n * Used by `routeStore` size-tiered routing: `'default'` for small blobs\n * stored inline (e.g. DynamoDB), `'blobs'` for large blobs in the overflow\n * store (e.g. S3). Absent when no routing is configured.\n */\n readonly storeHint?: 'default' | 'blobs'\n}\n\n// ─── Attachment types ─────────────────────────────────────────\n\n/** Single attachment metadata entry stored inside a record's attachment envelope. */\nexport interface AttachmentEntry {\n /** Content-addressed identifier (HMAC-SHA-256 of plaintext). */\n readonly eTag: string\n /** User-visible filename for the slot. */\n readonly filename: string\n /** Original uncompressed size in bytes. */\n readonly size: number\n /** MIME type, if provided or auto-detected at upload time. */\n readonly mimeType?: string\n /** ISO timestamp of the upload. */\n readonly uploadedAt: string\n /** User ID of the uploader, if available. */\n readonly uploadedBy?: string\n}\n\n/** Attachment entry annotated with its slot name, as returned by `AttachmentHandle.list()`. */\nexport type AttachmentInfo = AttachmentEntry & { readonly name: string }\n\n/** Options for `AttachmentHandle.put()`. */\nexport interface AttachmentPutOptions {\n /** Compress the attachment with gzip before encryption. Default: `true`. */\n compress?: boolean\n /** Chunk size in bytes. Default: `DEFAULT_CHUNK_SIZE` (256 KB). */\n chunkSize?: number\n /** MIME type to store with the attachment. Auto-detected from magic bytes if omitted. */\n mimeType?: string\n /** User ID to record as the uploader. Falls back to the active user's ID. */\n uploadedBy?: string\n}\n\n/** Options for `AttachmentHandle.response()`. */\nexport interface AttachmentResponseOptions {\n /**\n * Set `Content-Disposition: inline` so the browser renders the file\n * instead of downloading it. Default: `false` (attachment disposition).\n */\n inline?: boolean\n}\n\n/**\n * Slot record — mutable metadata linking a named slot on a record\n * to a `BlobObject` via its eTag.\n *\n * Multiple slots (even across different records) may reference the same\n * `eTag` — the underlying chunks are shared. Updating metadata creates\n * a new envelope version (`_v++`) while the blob data is unchanged.\n */\nexport interface SlotRecord {\n /** Reference to the `BlobObject` in `_blob_index`. */\n readonly eTag: string\n /** User-visible filename for the slot. */\n readonly filename: string\n /** Original uncompressed size in bytes (denormalized from `BlobObject`). */\n readonly size: number\n /** MIME type. Takes precedence over the MIME type stored in `BlobObject`. */\n readonly mimeType?: string\n /** ISO timestamp of the upload that set this slot. */\n readonly uploadedAt: string\n /** User ID of the uploader, if available. */\n readonly uploadedBy?: string\n}\n\n/** Result of `BlobSet.list()` — slot record plus its named slot key. */\nexport interface SlotInfo extends SlotRecord {\n /** The slot name (key in the record's slot map). */\n readonly name: string\n}\n\n/**\n * Explicitly published version snapshot — an independent reference to a\n * blob at a specific point in time.\n */\nexport interface VersionRecord {\n /** User-defined label (e.g. `'issued-2025-01'`, `'amendment-2025-02'`). */\n readonly label: string\n /** eTag of the blob snapshot at publish time — independent of the current slot. */\n readonly eTag: string\n /** ISO timestamp when the version was published. */\n readonly publishedAt: string\n /** User ID of the publisher, if available. */\n readonly publishedBy?: string\n}\n\n/** Options for `BlobSet.put()`. */\nexport interface BlobPutOptions {\n /** MIME type hint. If omitted, auto-detected from magic bytes. */\n mimeType?: string\n /**\n * Raw chunk size in bytes. Priority: this value > store.maxBlobBytes > 256 KB.\n */\n chunkSize?: number\n /**\n * Whether to gzip-compress bytes before encrypting. Default: `true`.\n * Auto-set to `false` for pre-compressed MIME types (JPEG, PNG, ZIP, etc.).\n */\n compress?: boolean\n /** User ID to record as `uploadedBy`. Defaults to the Noydb session user. */\n uploadedBy?: string\n}\n\n/** Options for `BlobSet.response()` and `BlobSet.responseVersion()`. */\nexport interface BlobResponseOptions {\n /**\n * When `true`, sets `Content-Disposition: inline; filename=\"...\"` so\n * the browser renders the file in the tab. Default (`false`) sets\n * `attachment; filename=\"...\"` which triggers a download.\n */\n inline?: boolean\n /** Override the filename in the Content-Disposition header. */\n filename?: string\n}\n\n// ─── Store Capabilities ─────────────────────────────\n\nexport type StoreAuthKind =\n | 'none'\n | 'filesystem'\n | 'api-key'\n | 'iam'\n | 'oauth'\n | 'kerberos'\n | 'browser-origin'\n\nexport interface StoreAuth {\n kind: StoreAuthKind | StoreAuthKind[]\n required: boolean\n flow: 'static' | 'oauth' | 'kerberos' | 'implicit'\n}\n\nexport interface StoreCapabilities {\n /**\n * true — the store's expectedVersion check and write are atomic at the\n * storage layer. Two concurrent puts with the same expectedVersion will\n * produce exactly one success and one ConflictError.\n * false — check and write are separate operations with a race window.\n */\n casAtomic: boolean\n auth: StoreAuth\n /**\n * true — the store implements {@link NoydbStore.tx} and commits\n * every op atomically at the storage layer. The hub's\n * `db.transaction(fn)` will delegate to `tx(ops)` and surface a\n * single pass/fail outcome. false (or absent) — no native\n * multi-record atomicity; the hub falls back to per-record OCC\n * with best-effort unwind on partial failure.\n */\n txAtomic?: boolean\n /**\n * Maximum raw bytes per blob chunk record.\n * `undefined` — no limit (S3, file, IDB); blob stored as single chunk.\n * `256 * 1024` — DynamoDB (400 KB item limit minus envelope overhead).\n * `5 * 1024 * 1024` — localStorage quota safety.\n */\n maxBlobBytes?: number\n}\n\n// ─── Factory Options ───────────────────────────────────────────────────\n\nexport interface NoydbOptions {\n /** Primary store (local storage). */\n readonly store: NoydbStore\n /**\n * tree-shake seam — optional blob strategy. Pass `withBlobs()`\n * from `@noy-db/hub/blobs` to enable `collection.blob(id)` storage.\n * When omitted, hub's blob machinery stays out of the bundle (ESM\n * tree-shaking) and `collection.blob(id)` throws with a pointer at\n * the subpath. `BlobStrategy` is `@internal` — users only construct\n * it via the subpath factory.\n *\n * @internal\n */\n readonly blobStrategy?: BlobStrategy\n /**\n * tree-shake seam — optional indexing strategy. Pass\n * `withIndexing()` from `@noy-db/hub/indexing` to enable eager-mode\n * `==/in` fast-paths, lazy-mode `.lazyQuery()`, rebuild/reconcile,\n * and auto-reconcile. When omitted, indexing code never reaches the\n * bundle; `.lazyQuery()` throws with a pointer at the subpath, and\n * eager-mode collections fall back to linear scans regardless of\n * `indexes: [...]` declarations. `IndexStrategy` is `@internal` —\n * users only construct it via the subpath factory.\n *\n * @internal\n */\n readonly indexStrategy?: IndexStrategy\n /**\n * tree-shake seam — optional aggregate strategy. Pass\n * `withAggregate()` from `@noy-db/hub/aggregate` to enable\n * `.aggregate()` and `.groupBy()` on Query. When omitted, those\n * methods throw with a pointer at the subpath; the ~886 LOC of\n * Aggregation + GroupedQuery machinery never reaches the bundle.\n * Streaming `scan().aggregate()` works independently of this\n * strategy — it doesn't use the `Aggregation` class.\n *\n * @internal\n */\n readonly aggregateStrategy?: AggregateStrategy\n /**\n * tree-shake seam — optional CRDT strategy. Required when\n * any collection is declared with `crdt: 'lww-map' | 'rga' | 'yjs'`;\n * otherwise the first put/sync-merge hitting the CRDT path throws.\n * When omitted, ~221 LOC of LWW-Map / RGA / merge helpers never\n * reach the bundle.\n *\n * @internal\n */\n readonly crdtStrategy?: CrdtStrategy\n /**\n * tree-shake seam — optional consent-audit strategy. Pass\n * `withConsent()` from `@noy-db/hub/consent` to enable per-op audit\n * writes into `_consent_audit` when a consent scope is active.\n * When omitted, `vault.consentAudit()` returns `[]` and writes are\n * no-ops; the consent module's ~194 LOC never reaches the bundle.\n *\n * @internal\n */\n readonly consentStrategy?: ConsentStrategy\n /**\n * tree-shake seam — optional periods strategy. Pass\n * `withPeriods()` from `@noy-db/hub/periods` to enable\n * `vault.closePeriod()` / `.openPeriod()` / write-guard on closed\n * periods. When omitted, `vault.listPeriods()` returns `[]` and\n * the write-guard is a no-op; the ~363 LOC of period validation +\n * ledger appending stay out of the bundle.\n *\n * @internal\n */\n readonly periodsStrategy?: PeriodsStrategy\n /**\n * tree-shake seam — optional VaultFrame strategy. Pass\n * `withShadow()` from `@noy-db/hub/shadow` to enable\n * `vault.frame()`. Without it, calling `vault.frame()` throws.\n *\n * @internal\n */\n readonly shadowStrategy?: ShadowStrategy\n /**\n * tree-shake seam — optional multi-record transactions. Pass\n * `withTransactions()` from `@noy-db/hub/tx` to enable\n * `db.transaction(fn)`. Without it, calling the method throws.\n *\n * @internal\n */\n readonly txStrategy?: TxStrategy\n /**\n * tree-shake seam — optional history + ledger + time-machine.\n * Pass `withHistory()` from `@noy-db/hub/history` to enable\n * per-record version snapshots, the hash-chained audit ledger, JSON\n * Patch deltas, `vault.ledger()`, `vault.at()`, and the\n * `collection.history()` / `getVersion()` / `revert()` / `diff()` /\n * `clearHistory()` / `pruneRecordHistory()` read APIs. When omitted,\n * snapshots/prune/clear are silent no-ops, the read APIs throw with\n * a pointer at the subpath, and ~1,880 LOC stay out of the bundle.\n *\n * @internal\n */\n readonly historyStrategy?: HistoryStrategy\n /**\n * tree-shake seam — optional i18n strategy. Pass `withI18n()`\n * from `@noy-db/hub/i18n` to enable `i18nText`/`dictKey` field\n * resolution on reads, `i18nText` validation on writes, and\n * `vault.dictionary(name)`. When omitted, locale resolution is the\n * identity (raw values returned), the validators throw with a\n * pointer to the subpath, and ~854 LOC of dictionary + locale\n * machinery stay out of the bundle.\n *\n * @internal\n */\n readonly i18nStrategy?: I18nStrategy\n /**\n * tree-shake seam — optional session-policy strategy. Pass\n * `withSession()` from `@noy-db/hub/session` to enable\n * `sessionPolicy` validation, `PolicyEnforcer` lifecycle (idle /\n * absolute timeouts, lockOnBackground), and global session-token\n * revocation. When omitted, setting `sessionPolicy` throws at\n * `createNoydb()` time, and ~495 LOC of policy + token machinery\n * stay out of the bundle.\n *\n * @internal\n */\n readonly sessionStrategy?: SessionStrategy\n /**\n * tree-shake seam — optional sync engine + presence strategy.\n * Pass `withSync()` from `@noy-db/hub/sync` to enable\n * `db.push()` / `pull()` / replication, `db.transaction(vault)`\n * for sync-aware transactions, and `collection.presence()`. When\n * omitted, configuring `sync` / calling these surfaces throws with\n * a pointer at the subpath, and ~856 LOC of replication + presence\n * machinery stay out of the bundle. Keyring stays core; grant/\n * revoke/magic-link/delegation tree-shake via direct imports.\n *\n * @internal\n */\n readonly syncStrategy?: SyncStrategy\n /**\n * Optional guard strategies — collection-level write guards. Each\n * handle is the output of `withGuard()` from `@noy-db/hub/guards`.\n * Multiple guards per collection are allowed; they are dispatched\n * in registration order on `collection.put()`.\n */\n readonly guardStrategies?: ReadonlyArray<GuardStrategyHandleAny>\n /**\n * Optional derivation strategies — source-to-output projections that\n * fire on `collection.put()`. Each handle is the output of\n * `withDerivation()` from `@noy-db/hub/derivations`. The vault\n * validates the derivation graph for cycles on `openVault`; a cyclic\n * graph throws `DerivationCycleError`.\n */\n readonly derivationStrategies?: ReadonlyArray<DerivationStrategyHandle>\n /**\n * Optional materialized-view strategies (#143, foundation in #150).\n * Each handle returned by `withMaterializedView()` from\n * `@noy-db/hub/materialized-views`. The vault runs unified cycle\n * detection across the MV + derivation graphs at `openVault`; a\n * cyclic graph throws `MaterializedViewCycleError`.\n */\n readonly materializedViewStrategies?: ReadonlyArray<MaterializedViewStrategyHandle>\n /**\n * Optional overlay strategies (#154). Each handle returned by\n * `withOverlayedView()` from `@noy-db/hub/overlay-views`. The vault\n * validates name uniqueness + base concreteness + overlay\n * availability at `openVault`; a clash throws one of the\n * `Overlay*Error` family.\n */\n readonly overlayedViewStrategies?: ReadonlyArray<OverlayedViewStrategyHandle>\n /** Optional remote store(s) for sync. Accepts a single store, a SyncTarget, or an array. */\n readonly sync?: NoydbStore | SyncTarget | SyncTarget[]\n /** User identifier. */\n readonly user: string\n /** Passphrase for key derivation. Required unless encrypt is false or `getKeyring` is provided. */\n readonly secret?: string\n /**\n * Optional callback that returns an unlocked keyring for a given vault.\n * Use this to plug in WebAuthn / OIDC / Shamir / any unlock path that\n * produces an `UnlockedKeyring` outside the passphrase model.\n *\n * When set, `secret` MUST NOT also be set — `createNoydb` throws if both\n * are supplied. When neither is set (and `encrypt !== false`), `createNoydb`\n * also throws.\n *\n * The callback is called lazily, on the first operation that needs the\n * keyring for a given vault. Noydb caches the returned keyring per-vault\n * for the lifetime of the instance, so the callback is invoked at most\n * once per `(instance, vault)` pair (assuming the callback resolves\n * successfully). If the callback rejects, the rejection surfaces from the\n * first vault operation that triggered the unlock; subsequent operations\n * will retry the callback.\n *\n * @example\n * ```ts\n * import { createNoydb } from '@noy-db/hub'\n * import { unlockWebAuthn } from '@noy-db/on-webauthn'\n *\n * const enrollment = await loadEnrollment()\n * const db = await createNoydb({\n * store,\n * user: 'alice',\n * getKeyring: (vault) => unlockWebAuthn(enrollment),\n * })\n * ```\n *\n * Note: this callback is responsible for both the \"open existing vault\"\n * and the \"create new vault\" cases. Unlike the passphrase path, there is\n * no automatic `NoAccessError` → `createOwnerKeyring` fallback, because\n * the callback owner has the UI context to decide which path to run.\n * For first-time bootstrap, use a passphrase or recovery code, enroll\n * WebAuthn from the unlocked keyring, then swap to `getKeyring` on\n * subsequent sessions.\n */\n readonly getKeyring?: (vault: string) => Promise<UnlockedKeyring>\n /**\n * Passphrase mode (#14). Default `'standard'`.\n *\n * - `'standard'` — the legacy flow. `secret` supplies the\n * plaintext passphrase, the user knows it, and the policy gate\n * `rotate-passphrase` is enabled.\n * - `'managed'` — rubber-hose-resistant mode. Hub generates a\n * 256-bit random passphrase at first open and seals it under\n * the provided `sealingKey`. The user never sees or types the\n * passphrase, defeating the $5-wrench attack. Mutually\n * exclusive with `secret` and `getKeyring`.\n *\n * @see docs/subsystems/session-tiers.md → Managed-passphrase mode\n */\n readonly passphraseMode?: 'standard' | 'managed'\n /**\n * Provider that seals/unseals the auto-generated managed-mode\n * passphrase. Required when `passphraseMode === 'managed'`; ignored\n * otherwise. Implementations live in per-platform packages\n * (`@noy-db/seal-macos-keychain`, `@noy-db/seal-wincred`,\n * `@noy-db/seal-libsecret`, `@noy-db/seal-aws-kms`, …).\n */\n readonly sealingKey?: SealingKeyProvider\n /** Required to use `profile: 'shamir'` recovery. Pass\n * `shamirRecoveryProvider()` from `@noy-db/on-shamir`. */\n readonly shamirRecovery?: ShamirRecoveryProvider\n /** Auth method. Default: 'passphrase'. */\n readonly auth?: 'passphrase' | 'biometric'\n /** Enable encryption. Default: true. */\n readonly encrypt?: boolean\n /** Conflict resolution strategy. Default: 'version'. */\n readonly conflict?: ConflictStrategy\n /**\n * Sync scheduling policy. Controls when push/pull fire.\n * Default inferred from store category: per-record → `on-change`,\n * bundle → `debounce 30s`.\n */\n readonly syncPolicy?: SyncPolicy\n /**\n * @deprecated Use `syncPolicy` instead. Kept for backward compatibility.\n * When both are supplied, `syncPolicy` takes precedence.\n */\n readonly autoSync?: boolean\n /**\n * @deprecated Use `syncPolicy` instead. Kept for backward compatibility.\n */\n readonly syncInterval?: number\n /**\n * Session timeout in ms. Clears keys after inactivity. Default: none.\n * @deprecated Use `sessionPolicy.idleTimeoutMs` instead. This field is\n * still honored for backwards compatibility but `sessionPolicy` takes\n * precedence when both are supplied.\n */\n readonly sessionTimeout?: number\n /**\n * Session policy controlling lifetime, re-auth requirements, and\n * background-lock behavior. When supplied, replaces the\n * legacy `sessionTimeout` field.\n */\n readonly sessionPolicy?: SessionPolicy\n /**\n * Validate passphrase strength against the phrase format\n * (`@noy-db/hub` issue #7) on first-time keyring creation. When\n * `true`, weak phrases throw {@link WeakPassphraseError} from\n * `createNoydb()` / `db.rotatePassphrase()`. Default: `false` for\n * back-compat in v0.1.x; planned to flip to `true` at v1.0.\n */\n readonly validatePassphrase?: boolean\n /**\n * Vault-level policy gate document (issue #9). When present, the hub\n * persists the merged policy at `_meta/policy` on first-time vault\n * creation and gates sensitive operations (`db.rotatePassphrase`,\n * `db.export*`, …) against it. Omitted ⇒ the engine uses\n * {@link PERSONAL_POLICY}. Use {@link STRICT_POLICY} for regulated\n * deployments.\n *\n * The on-disk document is the source of truth — the policy field\n * is only honored at vault creation; subsequent runs read from\n * `_meta/policy`. Use `db.updatePolicy()` to change it deliberately.\n *\n * Imported from `@noy-db/hub` as a type-only reference; the runtime\n * import lives in `policy/index.ts`.\n */\n readonly policy?: VaultPolicy\n /**\n * Mandatory recovery profile enrollment (issue #10). Vaults with\n * `recover-passphrase` enabled MUST register at least one profile\n * before being production-ready, otherwise `createNoydb()` throws\n * {@link RecoveryNotEnrolledError}. Set\n * `policy.gates['recover-passphrase'].enabled = false` to\n * deliberately opt out of recovery (passphrase loss = data loss).\n *\n * v0.1.0-pre.5 supports the `'paper'` profile end-to-end. Other\n * profiles ship the API shape and throw\n * {@link RecoveryProfileNotImplementedError} during use.\n */\n readonly recovery?: ReadonlyArray<RecoveryEnrollment>\n /**\n * When `true`, `createNoydb` rejects vaults with no recovery\n * entries persisted (per the spec's mandatory-enrollment\n * requirement). Default `false` for v0.1.x back-compat; planned to\n * flip to `true` at v1.0. Apps in regulated environments should\n * turn this on now.\n */\n readonly requireRecovery?: boolean\n /**\n * What to do when `openVault` finds an existing keyring in the store that\n * cannot be decrypted with the supplied credentials (`InvalidKeyError`).\n *\n * - `'error'` (default) — propagate the error. The app must prompt the user\n * to supply the correct credentials or clear both the data and auth stores.\n * - `'reset'` — delete the stale keyring and re-initialise the vault from\n * scratch using the current credentials. Use this when the data store can\n * become detached from the auth store (e.g. the user cleared the IndexedDB\n * data records but not the keyring row, or a WebAuthn credential was rotated).\n * **All previously encrypted data is unrecoverable after a reset.**\n *\n * Only applies to the passphrase (`secret`) path. When `getKeyring` is used,\n * the callback is responsible for handling stale-keyring detection itself.\n */\n readonly onInvalidKey?: 'error' | 'reset'\n /**\n * Enable the public envelope subsystem (`docs/subsystems/public-envelope.md`).\n * Pass `true` for the default schema (every standard field, 256 KB\n * icon cap, 200-char text cap), or a `PublicEnvelopeSchema` to\n * narrow what the owner can set. Off by default — vaults written\n * by hubs without this option carry no envelope, full stop.\n */\n readonly publicEnvelope?: true | PublicEnvelopeSchema\n /** Audit history configuration. */\n readonly history?: HistoryConfig\n /**\n * Consumer-supplied translation function for `i18nText` fields with\n * `autoTranslate: true`.\n *\n * ⚠ **`plaintextTranslator` receives unencrypted text.** Configuring\n * this hook causes plaintext to leave noy-db's zero-knowledge boundary\n * over whatever channel the consumer's implementation uses. noy-db ships\n * no built-in translator and adds no translator SDKs as dependencies.\n * The consumer chooses and owns the data policy of the external service.\n *\n * Per-field opt-in via `autoTranslate: true` on `i18nText()`. Calling\n * `put()` on a collection with `autoTranslate: true` fields while this\n * option is absent throws `TranslatorNotConfiguredError`.\n *\n * See `NOYDB_SPEC.md § Zero-Knowledge Storage` for the invariant text.\n */\n readonly plaintextTranslator?: PlaintextTranslatorFn\n /**\n * Human-readable name for the translator, recorded in the in-process\n * audit log (e.g. `'deepl-pro-with-dpa'`, `'self-hosted-llama-7b'`).\n * Defaults to `'anonymous'` when not supplied.\n */\n readonly plaintextTranslatorName?: string\n}\n\n// ─── History / Audit Trail ─────────────────────────────────────────────\n\n/** History configuration. */\nexport interface HistoryConfig {\n /** Enable history tracking. Default: true. */\n readonly enabled?: boolean\n /** Maximum history entries per record. Oldest pruned on overflow. Default: unlimited. */\n readonly maxVersions?: number\n}\n\n/** Options for querying history. */\nexport interface HistoryOptions {\n /** Start date (inclusive), ISO 8601. */\n readonly from?: string\n /** End date (inclusive), ISO 8601. */\n readonly to?: string\n /** Maximum entries to return. */\n readonly limit?: number\n}\n\n/** Options for pruning history. */\nexport interface PruneOptions {\n /** Keep only the N most recent versions. */\n readonly keepVersions?: number\n /** Delete versions older than this date, ISO 8601. */\n readonly beforeDate?: string\n}\n\n/** A decrypted history entry. */\nexport interface HistoryEntry<T> {\n readonly version: number\n readonly timestamp: string\n readonly userId: string\n readonly record: T\n}\n\n// ─── Bulk operations ──────────────────────────────────────\n\n/** Per-item options for `Collection.putMany()`. */\nexport interface PutManyItemOptions {\n /**\n * Optimistic-concurrency check: fail this item if the stored version\n * is not `expectedVersion`. Honored only in `atomic: true` mode;\n * ignored in the default best-effort loop.\n */\n readonly expectedVersion?: number\n}\n\n/**\n * Batch-level options for `Collection.putMany()` and `deleteMany()`.\n *\n * `atomic: true` switches the call from best-effort loop\n * to all-or-nothing: a pre-flight CAS check runs first, then every op\n * is executed; any mid-batch failure triggers a best-effort revert.\n * On failure in atomic mode the whole call throws — you won't get a\n * partial `PutManyResult`. On success the result mirrors the default\n * loop's shape.\n */\nexport interface PutManyOptions {\n readonly atomic?: boolean\n}\n\n/** Result of `Collection.putMany()`. */\nexport interface PutManyResult {\n /** `true` iff every entry succeeded. */\n readonly ok: boolean\n /** IDs that were successfully written. */\n readonly success: readonly string[]\n /** Entries that failed, with the error that prevented each write. */\n readonly failures: ReadonlyArray<{ readonly id: string; readonly error: Error }>\n}\n\n/** Result of `Collection.deleteMany()`. Same shape as `PutManyResult`. */\nexport interface DeleteManyResult {\n readonly ok: boolean\n readonly success: readonly string[]\n readonly failures: ReadonlyArray<{ readonly id: string; readonly error: Error }>\n}\n","/**\n * Validate-on-write for the public envelope. Runs at every\n * `setPublicEnvelope` call; the developer's schema decides which\n * fields are allowed and the size caps that apply.\n *\n * @module\n */\nimport { ValidationError } from '../../errors.js'\nimport type {\n PublicEnvelope,\n PublicEnvelopeText,\n ResolvedPublicEnvelopeSchema,\n PublicEnvelopeField,\n} from './types.js'\n\n/** Owner-supplied input — the subset of {@link PublicEnvelope} the owner can set. */\nexport interface SetPublicEnvelopeInput {\n readonly name?: PublicEnvelopeText\n readonly description?: PublicEnvelopeText\n readonly icon?: string\n readonly defaultLocale?: string\n}\n\nconst DATA_URL_PREFIX = /^data:([a-zA-Z0-9.+-]+\\/[a-zA-Z0-9.+-]+);base64,/\n\n/**\n * Validate an owner-supplied envelope input against the developer's\n * resolved schema. Throws `ValidationError` on the first violation;\n * returns void on success.\n *\n * The validator is deliberately strict: every fail mode is a hard\n * error rather than a silent drop, so the owner finds out immediately\n * which field they oversized rather than discovering a truncated\n * label months later.\n */\nexport function validatePublicEnvelopeInput(\n input: SetPublicEnvelopeInput,\n schema: ResolvedPublicEnvelopeSchema,\n): void {\n const allowed = new Set<PublicEnvelopeField>(schema.fields)\n\n // Reject any key not in the schema's allowed-field list.\n for (const key of Object.keys(input)) {\n const known: PublicEnvelopeField | undefined =\n key === 'name' || key === 'description' || key === 'icon' || key === 'defaultLocale'\n ? key\n : undefined\n if (!known) {\n throw new ValidationError(\n `setPublicEnvelope: unknown field \"${key}\". ` +\n `Allowed fields: ${[...allowed].join(', ')}.`,\n )\n }\n if (!allowed.has(known)) {\n throw new ValidationError(\n `setPublicEnvelope: field \"${known}\" is not enabled in this vault's schema. ` +\n `Allowed fields: ${[...allowed].join(', ')}.`,\n )\n }\n }\n\n if (input.name !== undefined) {\n validateText(input.name, 'name', schema.maxStringChars)\n }\n if (input.description !== undefined) {\n validateText(input.description, 'description', schema.maxStringChars)\n }\n if (input.icon !== undefined) {\n validateIcon(input.icon, schema)\n }\n if (input.defaultLocale !== undefined && typeof input.defaultLocale !== 'string') {\n throw new ValidationError(\n `setPublicEnvelope: defaultLocale must be a string (BCP-47), got ${typeof input.defaultLocale}.`,\n )\n }\n}\n\nfunction validateText(\n value: PublicEnvelopeText,\n field: string,\n maxChars: number,\n): void {\n if (typeof value === 'string') {\n if (value.length > maxChars) {\n throw new ValidationError(\n `setPublicEnvelope: ${field} exceeds the ${maxChars}-character cap (got ${value.length}).`,\n )\n }\n return\n }\n if (typeof value !== 'object' || value === null || Array.isArray(value)) {\n throw new ValidationError(\n `setPublicEnvelope: ${field} must be a string or { [locale]: string } map, got ${typeof value}.`,\n )\n }\n // Locale map. Each value must be a non-empty string within the cap.\n for (const [locale, str] of Object.entries(value)) {\n if (typeof str !== 'string') {\n throw new ValidationError(\n `setPublicEnvelope: ${field}[${locale}] must be a string, got ${typeof str}.`,\n )\n }\n if (str.length > maxChars) {\n throw new ValidationError(\n `setPublicEnvelope: ${field}[${locale}] exceeds the ${maxChars}-character cap (got ${str.length}).`,\n )\n }\n }\n}\n\nfunction validateIcon(icon: string, schema: ResolvedPublicEnvelopeSchema): void {\n if (typeof icon !== 'string') {\n throw new ValidationError(\n `setPublicEnvelope: icon must be a data: URL string, got ${typeof icon}.`,\n )\n }\n if (icon.length > schema.maxIconBytes) {\n throw new ValidationError(\n `setPublicEnvelope: icon exceeds the ${schema.maxIconBytes}-byte cap (got ${icon.length}).`,\n )\n }\n const m = DATA_URL_PREFIX.exec(icon)\n if (!m) {\n throw new ValidationError(\n 'setPublicEnvelope: icon must be a base64 data URL ' +\n '(`data:image/png;base64,…` or `data:image/svg+xml;base64,…`). ' +\n 'External URLs are not supported in v1.',\n )\n }\n const mime = m[1]!\n if (!schema.iconMimeTypes.includes(mime)) {\n throw new ValidationError(\n `setPublicEnvelope: icon MIME type \"${mime}\" is not allowed. ` +\n `Permitted types: ${schema.iconMimeTypes.join(', ')}.`,\n )\n }\n}\n\n/**\n * Lightweight runtime predicate — used by the bundle header\n * validator to recognise a public envelope without requiring it.\n */\nexport function isPublicEnvelope(x: unknown): x is PublicEnvelope {\n if (x === null || typeof x !== 'object' || Array.isArray(x)) return false\n const obj = x as Record<string, unknown>\n return obj['_noydb_public'] === 1 && typeof obj['version'] === 'number'\n}\n","/**\n * Persistence helpers for `_meta/public-envelope`. Mirrors the\n * bypass-AES pattern used by `_meta/handle` and `_meta/policy` —\n * the document is plaintext JSON, the envelope's `_iv` field is\n * left empty.\n *\n * @module\n */\nimport type { NoydbStore, EncryptedEnvelope } from '../../types.js'\nimport { NOYDB_FORMAT_VERSION } from '../../types.js'\nimport type { PublicEnvelope } from './types.js'\nimport { isPublicEnvelope } from './schema.js'\n\n/** Reserved id for the vault-level public envelope record. */\nexport const PUBLIC_ENVELOPE_RECORD_ID = 'public-envelope'\n\n/**\n * Read the public envelope from `_meta/public-envelope`. Returns\n * `undefined` when no envelope has been persisted (fresh vault, or\n * a vault written before the feature was enabled). Tolerates\n * corrupted documents — a JSON parse failure surfaces as `undefined`,\n * not a thrown error, mirroring `_meta/handle`'s contract.\n */\nexport async function loadPublicEnvelope(\n store: NoydbStore,\n vault: string,\n): Promise<PublicEnvelope | undefined> {\n const envelope = await store.get(vault, '_meta', PUBLIC_ENVELOPE_RECORD_ID)\n if (!envelope) return undefined\n try {\n const parsed = JSON.parse(envelope._data) as unknown\n if (!isPublicEnvelope(parsed)) return undefined\n return parsed\n } catch {\n return undefined\n }\n}\n\n/** Persist the public envelope. Idempotent — overwrites any prior write. */\nexport async function savePublicEnvelope(\n store: NoydbStore,\n vault: string,\n envelope: PublicEnvelope,\n): Promise<void> {\n const wireEnvelope: EncryptedEnvelope = {\n _noydb: NOYDB_FORMAT_VERSION,\n _v: 1,\n _ts: new Date().toISOString(),\n _iv: '',\n _data: JSON.stringify(envelope),\n }\n await store.put(vault, '_meta', PUBLIC_ENVELOPE_RECORD_ID, wireEnvelope)\n}\n\n/**\n * Public, no-key reader. Plain function, not a method on `Noydb` —\n * the whole point is it works without an authenticated session, so\n * a UI can show \"Acme 2026 Tax Records\" before unlocking.\n *\n * Resolves any `name` / `description` locale map through the supplied\n * `locale` (when provided). Omitting `locale` returns the raw\n * envelope, which a multilingual picker can render as it pleases.\n */\nexport async function readPublicEnvelope(\n store: NoydbStore,\n vault: string,\n opts: { readonly locale?: string } = {},\n): Promise<PublicEnvelope | undefined> {\n const raw = await loadPublicEnvelope(store, vault)\n if (!raw) return undefined\n if (opts.locale === undefined) return raw\n return resolveLocale(raw, opts.locale)\n}\n\n/**\n * Resolve the locale-map fields (`name`, `description`) of a public\n * envelope to plain strings for the requested locale, falling back\n * through `defaultLocale` and then any available translation.\n *\n * @internal\n */\nexport function resolveLocale(\n envelope: PublicEnvelope,\n locale: string,\n): PublicEnvelope {\n return {\n ...envelope,\n ...(envelope.name !== undefined ? { name: pickLocale(envelope.name, locale, envelope.defaultLocale) } : {}),\n ...(envelope.description !== undefined ? { description: pickLocale(envelope.description, locale, envelope.defaultLocale) } : {}),\n }\n}\n\n/**\n * Resolve a `string | { [locale]: string }` value to a string for a\n * given locale. Exported so the bundle reader can reuse it without\n * duplicating the fallback chain.\n *\n * **Looser than `resolveI18nText`** — the hub's record-field resolver\n * throws `LocaleNotSpecifiedError` when no translation matches, but\n * label semantics deliberately prefer \"show *something*\" over\n * throwing. The fallback chain is:\n * 1. Exact `locale` match.\n * 2. `defaultLocale` (when supplied on the envelope).\n * 3. First non-empty value in the map.\n * 4. Empty string (only if every translation is empty — pathological).\n *\n * This deviation is documented in `docs/subsystems/public-envelope.md`.\n */\nexport function pickLocale(\n value: string | Record<string, string>,\n locale: string,\n defaultLocale: string | undefined,\n): string {\n if (typeof value === 'string') return value\n if (value[locale] !== undefined && value[locale] !== '') return value[locale]\n if (defaultLocale && value[defaultLocale] !== undefined && value[defaultLocale] !== '') {\n return value[defaultLocale]\n }\n for (const v of Object.values(value)) {\n if (v !== '') return v\n }\n return ''\n}\n","/**\n * Minimal ULID generator — zero dependencies, Web Crypto API only.\n *\n *. Used by the bundle writer to generate stable opaque\n * handles for `.noydb` containers.\n *\n * **What's a ULID?** A 128-bit identifier encoded as 26 Crockford\n * base32 characters. Layout:\n *\n * ```\n * 01HYABCDEFGHJKMNPQRSTVWXYZ\n * |--------||---------------|\n * 48-bit 80-bit\n * timestamp randomness\n * ```\n *\n * The first 10 chars encode a millisecond Unix timestamp (so ULIDs\n * sort lexicographically by creation time), and the remaining 16\n * chars are random. Crockford base32 omits I/L/O/U to avoid\n * ambiguity in handwriting and URLs.\n *\n * **Why hand-roll instead of pulling in `ulid`?** The package adds\n * a dep, the implementation is ~30 lines, and the bundle module\n * is the only consumer. Adding `ulid` would also drag in its own\n * crypto polyfill that we don't need on Node 18+ or modern\n * browsers.\n *\n * **Privacy consideration:** the timestamp prefix is observable in\n * the bundle header. This is a deliberate trade-off:\n * - Pro: lexicographic sortability lets bundle adapters list\n * newest-first without an extra index.\n * - Con: a casual observer can read the bundle's creation time\n * from the handle. They cannot read it from any OTHER field\n * (the header explicitly forbids `_exported_at`), and a\n * creation timestamp is the same kind of metadata that\n * filesystem mtime would already expose for a downloaded\n * bundle. The leak is therefore equivalent to what's already\n * visible from the file's mtime — not a new exposure.\n *\n * If a future use case needs timestamp-free handles, a v2 of the\n * format could specify \"use the random portion only\" without a\n * format break — `validateBundleHeader` only checks the regex\n * shape, not the encoded timestamp.\n */\n\n/**\n * Crockford base32 alphabet — omits I, L, O, U to avoid handwriting\n * and URL-encoding ambiguity. 32 characters covering 5 bits each.\n */\nconst CROCKFORD_ALPHABET = '0123456789ABCDEFGHJKMNPQRSTVWXYZ'\n\n/**\n * Encode a non-negative integer as a fixed-width Crockford base32\n * string. The width is fixed (not the natural log32 length) so\n * leading zeros are preserved — that's required for the timestamp\n * prefix to remain lexicographically sortable.\n *\n * Used twice: once for the 48-bit timestamp portion (10 chars) and\n * once for each 40-bit half of the randomness (8 chars × 2).\n */\nfunction encodeBase32(value: number, length: number): string {\n let out = ''\n let v = value\n for (let i = 0; i < length; i++) {\n out = CROCKFORD_ALPHABET[v % 32]! + out\n v = Math.floor(v / 32)\n }\n return out\n}\n\n/**\n * Generate a fresh ULID. Uses `crypto.getRandomValues` for the\n * randomness portion — same Web Crypto API the rest of the\n * codebase uses for IVs and salt.\n *\n * Returns a 26-character string. Calling twice in the same\n * millisecond produces two distinct ULIDs (the random portion\n * differs); ULIDs from the same millisecond are NOT guaranteed\n * to be monotonically ordered relative to each other, only\n * relative to ULIDs from a different millisecond. The bundle\n * format never relies on intra-millisecond ordering.\n */\nexport function generateULID(): string {\n const now = Date.now()\n\n // 48-bit timestamp → 10 Crockford base32 characters.\n // JavaScript's max safe integer is 2^53 - 1; Date.now() is well\n // within that range until the year ~285,000 AD. Splitting into\n // high and low 24-bit halves keeps every intermediate value\n // inside the safe-integer range and avoids any ambiguity in the\n // base32 encoder above.\n const timestampHigh = Math.floor(now / 0x1000000) // top 24 bits\n const timestampLow = now & 0xffffff // bottom 24 bits\n const tsPart =\n encodeBase32(timestampHigh, 5) + encodeBase32(timestampLow, 5)\n\n // 80-bit randomness → 16 Crockford base32 characters. Split into\n // two 40-bit halves so each fits in JavaScript's safe-integer\n // range (53 bits) and the base32 encoder doesn't have to deal\n // with bigints.\n const randBytes = new Uint8Array(10)\n crypto.getRandomValues(randBytes)\n\n // First 5 bytes (40 bits) → 8 Crockford base32 characters.\n // Reconstruct the 40-bit integer from bytes in big-endian order.\n // Multiplication by 2^32 (instead of bit-shift) avoids JavaScript's\n // 32-bit integer cast on the high byte.\n const rand1 =\n randBytes[0]! * 2 ** 32 +\n (randBytes[1]! << 24 >>> 0) +\n (randBytes[2]! << 16) +\n (randBytes[3]! << 8) +\n randBytes[4]!\n // Same for the second 5 bytes.\n const rand2 =\n randBytes[5]! * 2 ** 32 +\n (randBytes[6]! << 24 >>> 0) +\n (randBytes[7]! << 16) +\n (randBytes[8]! << 8) +\n randBytes[9]!\n const randPart = encodeBase32(rand1, 8) + encodeBase32(rand2, 8)\n\n return tsPart + randPart\n}\n\n/**\n * Validate that a string is a syntactically well-formed ULID. Used\n * by the bundle header validator. Does NOT verify that the\n * timestamp portion decodes to a sensible date — the format only\n * cares about the encoding shape.\n */\nexport function isULID(value: string): boolean {\n return /^[0-9A-HJKMNP-TV-Z]{26}$/.test(value)\n}\n","/**\n * Cryptographic primitives — thin wrappers around the Web Crypto API.\n *\n * ## Design principle\n *\n * **Zero npm crypto dependencies.** Every operation uses `globalThis.crypto.subtle`,\n * which is available natively in Node.js ≥ 18, all modern browsers, and\n * Deno/Bun. This avoids supply-chain risk from third-party crypto packages and\n * ensures the library stays auditable.\n *\n * ## Algorithms\n *\n * | Use case | Algorithm | Parameters |\n * |----------|-----------|------------|\n * | Key derivation | PBKDF2-SHA256 | 600,000 iterations, 32-byte salt |\n * | Record encryption | AES-256-GCM | 12-byte random IV per operation |\n * | DEK wrapping | AES-KW (RFC 3394) | 256-bit KEK |\n * | Binary encrypt | AES-256-GCM | same as record encryption |\n * | Integrity | HMAC-SHA256 | for presence channels |\n * | Content hash | SHA-256 | for ledger and bundle integrity |\n *\n * ## Key lifecycle\n *\n * ```\n * passphrase + salt\n * └─► deriveKey() → KEK (CryptoKey, extractable: false)\n * └─► wrapKey() → wrapped DEK bytes [stored in keyring]\n * └─► unwrapKey() → DEK (CryptoKey) [memory only during session]\n * └─► encrypt() / decrypt() → ciphertext / plaintext\n * ```\n *\n * IVs are generated fresh by {@link generateIV} on every encrypt call.\n * Reusing an IV with the same key would break GCM's authentication guarantee —\n * this function should be the only place IVs are produced.\n *\n * @module\n */\n\nimport { DecryptionError, InvalidKeyError, TamperedError } from './errors.js'\n\nconst PBKDF2_ITERATIONS = 600_000\nconst SALT_BYTES = 32\nconst IV_BYTES = 12\nconst KEY_BITS = 256\n\nconst subtle = globalThis.crypto.subtle\n\n// ─── Key Derivation ────────────────────────────────────────────────────\n\n/** Derive a KEK from a passphrase and salt using PBKDF2-SHA256. */\nexport async function deriveKey(\n passphrase: string,\n salt: Uint8Array,\n): Promise<CryptoKey> {\n const keyMaterial = await subtle.importKey(\n 'raw',\n new TextEncoder().encode(passphrase),\n 'PBKDF2',\n false,\n ['deriveKey'],\n )\n\n return subtle.deriveKey(\n {\n name: 'PBKDF2',\n salt: salt as BufferSource,\n iterations: PBKDF2_ITERATIONS,\n hash: 'SHA-256',\n },\n keyMaterial,\n { name: 'AES-KW', length: KEY_BITS },\n false,\n ['wrapKey', 'unwrapKey'],\n )\n}\n\n// ─── DEK Generation ────────────────────────────────────────────────────\n\n/** Generate a random AES-256-GCM data encryption key. */\nexport async function generateDEK(): Promise<CryptoKey> {\n return subtle.generateKey(\n { name: 'AES-GCM', length: KEY_BITS },\n true, // extractable — needed for AES-KW wrapping\n ['encrypt', 'decrypt'],\n )\n}\n\n// ─── Key Wrapping ──────────────────────────────────────────────────────\n\n/** Wrap (encrypt) a DEK with a KEK using AES-KW. Returns base64 string. */\nexport async function wrapKey(dek: CryptoKey, kek: CryptoKey): Promise<string> {\n const wrapped = await subtle.wrapKey('raw', dek, kek, 'AES-KW')\n return bufferToBase64(wrapped)\n}\n\n/** Unwrap (decrypt) a DEK from base64 string using a KEK. */\nexport async function unwrapKey(\n wrappedBase64: string,\n kek: CryptoKey,\n): Promise<CryptoKey> {\n try {\n return await subtle.unwrapKey(\n 'raw',\n base64ToBuffer(wrappedBase64) as BufferSource,\n kek,\n 'AES-KW',\n { name: 'AES-GCM', length: KEY_BITS },\n true,\n ['encrypt', 'decrypt'],\n )\n } catch {\n throw new InvalidKeyError()\n }\n}\n\n// ─── Encrypt / Decrypt ─────────────────────────────────────────────────\n\nexport interface EncryptResult {\n iv: string // base64\n data: string // base64\n}\n\n/** Encrypt plaintext JSON string with AES-256-GCM. Fresh IV per call. */\nexport async function encrypt(\n plaintext: string,\n dek: CryptoKey,\n): Promise<EncryptResult> {\n const iv = generateIV()\n const encoded = new TextEncoder().encode(plaintext)\n\n const ciphertext = await subtle.encrypt(\n { name: 'AES-GCM', iv: iv as BufferSource },\n dek,\n encoded,\n )\n\n return {\n iv: bufferToBase64(iv),\n data: bufferToBase64(ciphertext),\n }\n}\n\n/** Decrypt AES-256-GCM ciphertext. Throws on wrong key or tampered data. */\nexport async function decrypt(\n ivBase64: string,\n dataBase64: string,\n dek: CryptoKey,\n): Promise<string> {\n const iv = base64ToBuffer(ivBase64)\n const ciphertext = base64ToBuffer(dataBase64)\n\n try {\n const plaintext = await subtle.decrypt(\n { name: 'AES-GCM', iv: iv as BufferSource },\n dek,\n ciphertext as BufferSource,\n )\n return new TextDecoder().decode(plaintext)\n } catch (err) {\n if (err instanceof Error && err.name === 'OperationError') {\n throw new TamperedError()\n }\n throw new DecryptionError(\n err instanceof Error ? err.message : 'Decryption failed',\n )\n }\n}\n\n// ─── Binary Encrypt / Decrypt ────────\n\n/**\n * Encrypt raw bytes with AES-256-GCM using a fresh random IV.\n * Used by the attachment store so binary blobs avoid double base64 encoding\n * (the existing `encrypt()` function calls `TextEncoder` on a string — here\n * we pass the `Uint8Array` directly to `subtle.encrypt`).\n */\nexport async function encryptBytes(\n data: Uint8Array,\n dek: CryptoKey,\n): Promise<EncryptResult> {\n const iv = generateIV()\n const ciphertext = await subtle.encrypt(\n { name: 'AES-GCM', iv: iv as BufferSource },\n dek,\n data as unknown as BufferSource,\n )\n return {\n iv: bufferToBase64(iv),\n data: bufferToBase64(ciphertext),\n }\n}\n\n/**\n * Decrypt AES-256-GCM ciphertext back to raw bytes.\n * Counterpart to `encryptBytes`. Throws `TamperedError` on auth-tag failure.\n */\nexport async function decryptBytes(\n ivBase64: string,\n dataBase64: string,\n dek: CryptoKey,\n): Promise<Uint8Array> {\n const iv = base64ToBuffer(ivBase64)\n const ciphertext = base64ToBuffer(dataBase64)\n try {\n const plaintext = await subtle.decrypt(\n { name: 'AES-GCM', iv: iv as BufferSource },\n dek,\n ciphertext as BufferSource,\n )\n return new Uint8Array(plaintext)\n } catch (err) {\n if (err instanceof Error && err.name === 'OperationError') {\n throw new TamperedError()\n }\n throw new DecryptionError(\n err instanceof Error ? err.message : 'Decryption failed',\n )\n }\n}\n\n/**\n * SHA-256 hex digest of raw bytes. Used to derive content-addressed\n * eTags for blob deduplication. Computed on plaintext bytes\n * before compression and encryption so the eTag identifies content, not\n * ciphertext, and survives re-encryption (key rotation, re-upload).\n */\nexport async function sha256Hex(data: Uint8Array): Promise<string> {\n const hash = await subtle.digest('SHA-256', data as unknown as BufferSource)\n return Array.from(new Uint8Array(hash))\n .map((b) => b.toString(16).padStart(2, '0'))\n .join('')\n}\n\n// ─── HMAC-SHA-256 ─────────────────────────────\n\n/**\n * Compute HMAC-SHA-256(key, data) and return hex string.\n *\n * Used to derive content-addressed eTags that are opaque to the store:\n * ```\n * eTag = hmacSha256Hex(blobDEK, plaintext)\n * ```\n *\n * Unlike a plain SHA-256, the HMAC is keyed by the vault-shared `_blob` DEK,\n * so an attacker with store access cannot pre-compute eTags for known files.\n * Deduplication still works within a vault (same key + same content = same eTag).\n */\nexport async function hmacSha256Hex(key: CryptoKey, data: Uint8Array): Promise<string> {\n // Export AES-GCM DEK raw bytes → import as HMAC key\n const rawKey = await subtle.exportKey('raw', key)\n const hmacKey = await subtle.importKey(\n 'raw',\n rawKey,\n { name: 'HMAC', hash: 'SHA-256' },\n false,\n ['sign'],\n )\n const sig = await subtle.sign('HMAC', hmacKey, data as unknown as BufferSource)\n return Array.from(new Uint8Array(sig))\n .map((b) => b.toString(16).padStart(2, '0'))\n .join('')\n}\n\n// ─── AAD-aware Binary Encrypt / Decrypt ──\n\n/**\n * Encrypt raw bytes with AES-256-GCM using Additional Authenticated Data.\n *\n * The AAD binds each chunk to its parent blob and position, preventing\n * chunk reorder, substitution, and truncation attacks:\n * ```\n * AAD = UTF-8(\"{eTag}:{chunkIndex}:{chunkCount}\")\n * ```\n *\n * The AAD is NOT stored — the reader reconstructs it from `BlobObject`\n * metadata and passes it to `decryptBytesWithAAD`.\n */\nexport async function encryptBytesWithAAD(\n data: Uint8Array,\n dek: CryptoKey,\n aad: Uint8Array,\n): Promise<EncryptResult> {\n const iv = generateIV()\n const ciphertext = await subtle.encrypt(\n {\n name: 'AES-GCM',\n iv: iv as BufferSource,\n additionalData: aad as BufferSource,\n },\n dek,\n data as unknown as BufferSource,\n )\n return {\n iv: bufferToBase64(iv),\n data: bufferToBase64(ciphertext),\n }\n}\n\n/**\n * Decrypt AES-256-GCM ciphertext with AAD verification.\n *\n * If the AAD does not match the one used at encryption time (e.g. because\n * a chunk was reordered or substituted from another blob), the GCM auth\n * tag fails and this throws `TamperedError`.\n */\nexport async function decryptBytesWithAAD(\n ivBase64: string,\n dataBase64: string,\n dek: CryptoKey,\n aad: Uint8Array,\n): Promise<Uint8Array> {\n const iv = base64ToBuffer(ivBase64)\n const ciphertext = base64ToBuffer(dataBase64)\n try {\n const plaintext = await subtle.decrypt(\n {\n name: 'AES-GCM',\n iv: iv as BufferSource,\n additionalData: aad as BufferSource,\n },\n dek,\n ciphertext as BufferSource,\n )\n return new Uint8Array(plaintext)\n } catch (err) {\n if (err instanceof Error && err.name === 'OperationError') {\n throw new TamperedError()\n }\n throw new DecryptionError(\n err instanceof Error ? err.message : 'Decryption failed',\n )\n }\n}\n\n// ─── Presence Key Derivation ──────────────────────────────\n\n/**\n * Derive an AES-256-GCM presence key from a collection DEK using HKDF-SHA256.\n *\n * The presence key is domain-separated from the data DEK by the fixed salt\n * `'noydb-presence'` and the `info` = collection name. This means:\n * - The adapter never sees the presence key.\n * - Presence payloads rotate automatically when the collection DEK is rotated.\n * - Revoked users cannot derive the new presence key after a DEK rotation.\n *\n * @param dek The collection's AES-256-GCM DEK (extractable).\n * @param collectionName Used as the HKDF `info` parameter for domain separation.\n * @returns A non-extractable AES-256-GCM key suitable for presence payload encryption.\n */\nexport async function derivePresenceKey(dek: CryptoKey, collectionName: string): Promise<CryptoKey> {\n // Step 1: export DEK raw bytes\n const rawDek = await subtle.exportKey('raw', dek)\n\n // Step 2: import as HKDF key material\n const hkdfKey = await subtle.importKey(\n 'raw',\n rawDek,\n 'HKDF',\n false,\n ['deriveBits'],\n )\n\n // Step 3: derive 256 bits with salt='noydb-presence' and info=collectionName\n const salt = new TextEncoder().encode('noydb-presence')\n const info = new TextEncoder().encode(collectionName)\n const bits = await subtle.deriveBits(\n { name: 'HKDF', hash: 'SHA-256', salt, info },\n hkdfKey,\n KEY_BITS,\n )\n\n // Step 4: import derived bits as AES-GCM key\n return subtle.importKey(\n 'raw',\n bits,\n { name: 'AES-GCM', length: KEY_BITS },\n false,\n ['encrypt', 'decrypt'],\n )\n}\n\n// ─── Deterministic Encryption ────────────────────────────\n\n/**\n * Derive a deterministic 12-byte IV from `{ DEK, context, plaintext }`\n * via HKDF-SHA256. Given the same three inputs, the IV is identical, so\n * `encryptDeterministic` produces the same ciphertext on every call —\n * which is precisely what enables blind equality search on encrypted\n * fields.\n *\n * **The side channel this opens.** Two records whose field value is the\n * same produce the same ciphertext. An observer with store access can\n * therefore tell which records share a value — not *what* the value is,\n * but the equivalence class. This is the well-known trade-off of\n * deterministic encryption and is why the feature is strictly opt-in\n * per field, guarded by `acknowledgeDeterministicRisk: true` at\n * collection creation.\n *\n * The context string MUST include the collection name and field name,\n * so:\n * - The same plaintext in two different fields encrypts differently\n * (no cross-field equality leak).\n * - The same plaintext in two different collections (different DEKs)\n * encrypts differently by virtue of the key, even before HKDF\n * domain separation kicks in.\n */\nasync function deriveDeterministicIV(\n dek: CryptoKey,\n context: string,\n plaintext: string,\n): Promise<Uint8Array> {\n const rawDek = await subtle.exportKey('raw', dek)\n const hkdfKey = await subtle.importKey('raw', rawDek, 'HKDF', false, ['deriveBits'])\n const salt = new TextEncoder().encode('noydb-deterministic-v1')\n const info = new TextEncoder().encode(`${context}\\x00${plaintext}`)\n const bits = await subtle.deriveBits(\n { name: 'HKDF', hash: 'SHA-256', salt, info },\n hkdfKey,\n IV_BYTES * 8,\n )\n return new Uint8Array(bits)\n}\n\n/**\n * Encrypt a plaintext string with AES-256-GCM and a deterministic,\n * HKDF-derived IV.\n *\n * The same `{ dek, context, plaintext }` triple always produces the\n * same `{ iv, data }` — call this twice and you can string-compare the\n * ciphertexts to check equality of the inputs without decrypting them.\n *\n * @param context Domain-separation string — by convention\n * `'<collection>/<field>'`. Different contexts encrypt\n * the same plaintext to different ciphertexts, so\n * `email` in collection `users` does not collide with\n * `email` in collection `customers`.\n */\nexport async function encryptDeterministic(\n plaintext: string,\n dek: CryptoKey,\n context: string,\n): Promise<EncryptResult> {\n const iv = await deriveDeterministicIV(dek, context, plaintext)\n const encoded = new TextEncoder().encode(plaintext)\n const ciphertext = await subtle.encrypt(\n { name: 'AES-GCM', iv: iv as BufferSource },\n dek,\n encoded,\n )\n return {\n iv: bufferToBase64(iv),\n data: bufferToBase64(ciphertext),\n }\n}\n\n/**\n * Counterpart to {@link encryptDeterministic}. The IV is stored\n * alongside the ciphertext (exactly like the randomized path), so\n * decrypt uses the stored IV and verifies the GCM auth tag — a tampered\n * ciphertext throws `TamperedError` just like randomized AES-GCM.\n */\nexport async function decryptDeterministic(\n ivBase64: string,\n dataBase64: string,\n dek: CryptoKey,\n): Promise<string> {\n return decrypt(ivBase64, dataBase64, dek)\n}\n\n// ─── Random Generation ─────────────────────────────────────────────────\n\n/** Generate a random 12-byte IV for AES-GCM. */\nexport function generateIV(): Uint8Array {\n return globalThis.crypto.getRandomValues(new Uint8Array(IV_BYTES))\n}\n\n/** Generate a random 32-byte salt for PBKDF2. */\nexport function generateSalt(): Uint8Array {\n return globalThis.crypto.getRandomValues(new Uint8Array(SALT_BYTES))\n}\n\n// ─── Base64 Helpers ────────────────────────────────────────────────────\n\nexport function bufferToBase64(buffer: ArrayBuffer | Uint8Array): string {\n const bytes = buffer instanceof Uint8Array ? buffer : new Uint8Array(buffer)\n let binary = ''\n for (let i = 0; i < bytes.length; i++) {\n binary += String.fromCharCode(bytes[i]!)\n }\n return btoa(binary)\n}\n\nexport function base64ToBuffer(base64: string): Uint8Array<ArrayBuffer> {\n const binary = atob(base64)\n const bytes = new Uint8Array(binary.length)\n for (let i = 0; i < binary.length; i++) {\n bytes[i] = binary.charCodeAt(i)\n }\n return bytes\n}\n","/**\n * Read / write the per-collection persisted-schema envelope. Mirrors the\n * standard noy-db record envelope shape and is **AES-GCM encrypted with\n * the collection's DEK** — the schema body (field names, enum values,\n * constraints) is sensitive metadata, so it gets the same encryption\n * envelope as the records it describes.\n *\n * Storage layout:\n *\n * <vault>/_schemas/<collection> → EncryptedEnvelope\n *\n * The DEK passed to {@link savePersistedSchema} / {@link loadPersistedSchema}\n * is the same key the collection uses for its records.\n *\n * @module\n */\n\nimport { encrypt, decrypt } from '../crypto.js'\nimport { NOYDB_FORMAT_VERSION } from '../types.js'\nimport type { NoydbStore, EncryptedEnvelope } from '../types.js'\nimport type { PersistedSchemaEnvelope } from './types.js'\n\n/** Reserved collection name where persisted schemas live. */\nexport const SCHEMAS_COLLECTION = '_schemas' as const\n\n/**\n * Read and decrypt the persisted-schema envelope for one collection.\n * Returns `undefined` when no envelope has been written or when decryption\n * fails (e.g. wrong DEK passed). Tolerates corrupted records — JSON parse\n * failures surface as `undefined`, mirroring `_meta/handle`'s contract.\n */\nexport async function loadPersistedSchema(\n store: NoydbStore,\n vault: string,\n collection: string,\n dek: CryptoKey,\n): Promise<PersistedSchemaEnvelope | undefined> {\n const envelope = await store.get(vault, SCHEMAS_COLLECTION, collection)\n if (!envelope) return undefined\n try {\n const plaintext = await decrypt(envelope._iv, envelope._data, dek)\n const parsed = JSON.parse(plaintext) as PersistedSchemaEnvelope\n if (parsed._noydb_schema !== 1) return undefined\n return parsed\n } catch {\n return undefined\n }\n}\n\n/**\n * Encrypt and persist a schema envelope for one collection. Always\n * overwrites any prior write (callers gate on hash equality before calling\n * to avoid no-op writes).\n */\nexport async function savePersistedSchema(\n store: NoydbStore,\n vault: string,\n collection: string,\n dek: CryptoKey,\n payload: PersistedSchemaEnvelope,\n): Promise<void> {\n const json = JSON.stringify(payload)\n const { iv, data } = await encrypt(json, dek)\n const prior = await store.get(vault, SCHEMAS_COLLECTION, collection)\n const env: EncryptedEnvelope = {\n _noydb: NOYDB_FORMAT_VERSION,\n _v: (prior?._v ?? 0) + 1,\n _ts: new Date().toISOString(),\n _iv: iv,\n _data: data,\n }\n await store.put(vault, SCHEMAS_COLLECTION, collection, env)\n}\n","/**\n * Ledger storage constants — pinned in their own leaf module so\n * always-on core code (vault.ts, dictionary.ts) can import them\n * without dragging the `LedgerStore` class into the bundle.\n *\n * `splitting: true` in tsup is not enough on its own: when a\n * source file exports both pure constants and a heavyweight class,\n * the bundler keeps the entire chunk reachable from any importer.\n * Extracting the constants lets the floor scenario import them\n * without paying for the class.\n *\n * @internal\n */\n\n/** The internal collection name used for ledger entry storage. */\nexport const LEDGER_COLLECTION = '_ledger'\n\n/**\n * The internal collection name used for delta payload storage.\n *\n * Deltas live in a sibling collection (not inside `_ledger`) for two\n * reasons:\n *\n * 1. **Listing efficiency.** `ledger.loadAllEntries()` calls\n * `adapter.list(_ledger)` which would otherwise return every\n * delta key alongside every entry key. Splitting them keeps the\n * list small (one key per ledger entry) and the delta reads\n * keyed by the entry's index.\n *\n * 2. **Prune-friendliness.** A future `pruneHistory()` will delete\n * old deltas while keeping the ledger chain intact (folding old\n * deltas into a base snapshot). Separating the storage makes\n * that deletion a targeted operation on one collection instead\n * of a filter across a mixed list.\n *\n * Both collections share the same ledger DEK — one DEK, two\n * internal collections, same zero-knowledge guarantees.\n */\nexport const LEDGER_DELTAS_COLLECTION = '_ledger_deltas'\n","/**\n * Ledger entry shape + canonical JSON + sha256 helpers.\n *\n * This file holds the PURE primitives used by the hash-chained ledger:\n * the entry type, the deterministic (sort-stable) JSON encoder, and\n * the sha256 hasher that produces `prevHash` and `ledger.head()`.\n *\n * Everything here is validator-free and side-effect free — the only\n * runtime dep is Web Crypto's `subtle.digest` for the sha256 call,\n * which we already use for every other hashing operation in the core.\n *\n * The hash chain property works like this:\n *\n * hash(entry[i]) = sha256(canonicalJSON(entry[i]))\n * entry[i+1].prevHash = hash(entry[i])\n *\n * Any modification to `entry[i]` (field values, field order, whitespace)\n * produces a different `hash(entry[i])`, which means `entry[i+1]`'s\n * stored `prevHash` no longer matches the recomputed hash, which means\n * `verify()` returns `{ ok: false, divergedAt: i + 1 }`. The chain is\n * append-only and tamper-evident without external anchoring.\n */\n\n/**\n * A single ledger entry in its plaintext form — what gets serialized,\n * hashed, and then encrypted with the ledger DEK before being written\n * to the `_ledger/` adapter collection.\n *\n * ## Why hash the ciphertext, not the plaintext?\n *\n * `payloadHash` is the sha256 of the record's ENCRYPTED envelope bytes,\n * not its plaintext. This matters:\n *\n * 1. **Zero-knowledge preserved.** A user (or a third party) can\n * verify the ledger against the stored envelopes without any\n * decryption keys. The adapter layer already holds only\n * ciphertext, so hashing the ciphertext keeps the ledger at the\n * same privacy level as the adapter.\n *\n * 2. **Determinism.** Plaintext → ciphertext is randomized by the\n * fresh per-write IV, so `hash(plaintext)` would need extra\n * normalization. `hash(ciphertext)` is already deterministic and\n * unique per write.\n *\n * 3. **Detection property.** If an attacker modifies even one byte of\n * the stored ciphertext (trying to flip a record), the hash\n * changes, the ledger's recorded `payloadHash` no longer matches,\n * and a data-integrity check fails. We don't do that check in\n * `verify()` today, but the\n * hook is there for a future `verifyIntegrity()` follow-up.\n *\n * Fields marked `op`, `collection`, `id`, `version`, `ts`, `actor` are\n * plaintext METADATA about the operation — NOT the record itself. The\n * entry is still encrypted at rest via the ledger DEK, but adapters\n * could theoretically infer operation patterns from the sizes and\n * timestamps. This is an accepted trade-off for the tamper-evidence\n * property; full ORAM-level privacy is out of scope for noy-db.\n */\nexport interface LedgerEntry {\n /**\n * Zero-based sequential position of this entry in the chain. The\n * canonical adapter key is this number zero-padded to 10 digits\n * (`\"0000000001\"`) so lexicographic ordering matches numeric order.\n */\n readonly index: number\n\n /**\n * Hex-encoded sha256 of the canonical JSON of the PREVIOUS entry.\n * The genesis entry (index 0) has `prevHash === ''` — the first\n * entry in a fresh vault has nothing to point back to.\n */\n readonly prevHash: string\n\n /**\n * Which kind of mutation this entry records. only supports\n * data operations (`put`, `delete`, `amendment`). Access-control\n * operations (`grant`, `revoke`, `rotate`) will be added in a\n * follow-up once the keyring write path is instrumented — that's\n * tracked in the epic issue.\n *\n * `'amendment'` is the multi-record audit entry written by the\n * guards subsystem when an admin/owner uses `withTransactions(...)`\n * to repair a constraint-violating state. See `amendment` field\n * below for the structured payload.\n *\n * `'lifecycle'` records a non-data audit event (e.g. partition\n * handover, #226) — `collection`/`id` are empty and the event detail\n * lives in `reason` (e.g. `'partition-handed-over:<sealId>'`). Like\n * `amendment`, it carries no data envelope, so `verifyBackupIntegrity`\n * skips it in the data cross-check (it still participates in the\n * tamper-evident chain).\n */\n readonly op: 'put' | 'delete' | 'amendment' | 'lifecycle'\n\n /** The collection the mutation targeted. */\n readonly collection: string\n\n /** The record id the mutation targeted. */\n readonly id: string\n\n /**\n * The record version AFTER this mutation. For `put` this is the\n * newly assigned version; for `delete` this is the version that\n * was deleted (the last version visible to reads).\n */\n readonly version: number\n\n /** ISO timestamp of the mutation. */\n readonly ts: string\n\n /** User id of the actor who performed the mutation. */\n readonly actor: string\n\n /**\n * Hex-encoded sha256 of the encrypted envelope's `_data` field.\n * For `put`, this is the hash of the new ciphertext. For `delete`,\n * it's the hash of the last visible ciphertext at deletion time,\n * or the empty string if nothing was there to delete. Hashing the\n * ciphertext (not the plaintext) preserves zero-knowledge — see\n * the file docstring.\n */\n readonly payloadHash: string\n\n /**\n * Optional human-readable tag describing why this mutation happened\n * (#1). Threaded through `collection.put(_, _, { reason })`. Common\n * values include `'import:csv'`, `'import:json'`, `'import:xlsx'` from\n * `as-*` ImportPlan.apply(), but consumers can use any string for\n * domain-specific audit filtering. Auto-strip via `canonicalJson` —\n * absent on the wire, never serialized as `null`.\n *\n * Audit consumers filter: `entries.filter(e => e.reason?.startsWith('import:'))`.\n */\n readonly reason?: string\n\n /**\n * Optional hex-encoded sha256 of the encrypted JSON Patch delta\n * blob stored alongside this entry in `_ledger_deltas/`. Present\n * only for `put` operations that had a previous version — the\n * genesis put of a new record, and every `delete`, leave this\n * field undefined.\n *\n * The delta payload itself lives in a sibling internal collection\n * (`_ledger_deltas/<paddedIndex>`) and is encrypted with the\n * ledger DEK. Callers use `ledger.loadDelta(index)` to decrypt and\n * deserialize it when reconstructing a historical version.\n *\n * Why optional instead of always-present: the first put of a\n * record has no previous version to diff against, so storing an\n * empty patch would be noise. For deletes there's no \"next\" state\n * to describe with a delta. Both cases set this field to undefined.\n *\n * Note: the canonical-JSON hasher treats `undefined` as invalid\n * (it's one of the guard rails), so on the wire this field is\n * either `{ deltaHash: '<hex>' }` or absent from the JSON\n * entirely — never `{ deltaHash: undefined }`.\n */\n readonly deltaHash?: string\n\n /**\n * Present only when `op === 'amendment'`. Records the human reason,\n * the role of the actor, the (collection, id, vBefore, vAfter) tuple\n * for every record touched, and which guard invariants passed.\n *\n * See docs/superpowers/specs/2026-05-18-guards-design.md.\n */\n readonly amendment?: {\n readonly reason: string\n readonly role: 'admin' | 'owner'\n readonly changes: ReadonlyArray<{\n readonly collection: string\n readonly id: string\n readonly vBefore: number\n readonly vAfter: number\n }>\n readonly invariantsPassed: ReadonlyArray<string>\n }\n}\n\n/**\n * Canonical (sort-stable) JSON encoder.\n *\n * This function is the load-bearing primitive of the hash chain:\n * `sha256(canonicalJSON(entry))` must produce the same hex string\n * every time, on every machine, for the same logical entry — otherwise\n * `verify()` would return `{ ok: false }` on cross-platform reads.\n *\n * JavaScript's `JSON.stringify` is almost canonical, but NOT quite:\n * it preserves the insertion order of object keys, which means\n * `{a:1,b:2}` and `{b:2,a:1}` serialize differently. We fix this by\n * recursively walking objects and sorting their keys before\n * concatenation.\n *\n * Arrays keep their original order (reordering them would change\n * semantics). Numbers, strings, booleans, and `null` use the default\n * JSON encoding. `undefined` and functions are rejected — ledger\n * entries are plain data, and silently dropping `undefined` would\n * break the \"same input → same hash\" property if a caller forgot to\n * omit a field.\n *\n * Performance: one pass per nesting level; O(n log n) for key sorting\n * at each object. Entries are small (< 1 KB) so this is negligible\n * compared to the sha256 call.\n */\nexport function canonicalJson(value: unknown): string {\n if (value === null) return 'null'\n if (typeof value === 'boolean') return value ? 'true' : 'false'\n if (typeof value === 'number') {\n if (!Number.isFinite(value)) {\n throw new Error(\n `canonicalJson: refusing to encode non-finite number ${String(value)}`,\n )\n }\n return JSON.stringify(value)\n }\n if (typeof value === 'string') return JSON.stringify(value)\n if (typeof value === 'bigint') {\n throw new Error('canonicalJson: BigInt is not JSON-serializable')\n }\n if (typeof value === 'undefined' || typeof value === 'function') {\n throw new Error(\n `canonicalJson: refusing to encode ${typeof value} — include all fields explicitly`,\n )\n }\n if (Array.isArray(value)) {\n return '[' + value.map((v) => canonicalJson(v)).join(',') + ']'\n }\n if (typeof value === 'object') {\n const obj = value as Record<string, unknown>\n const keys = Object.keys(obj).sort()\n const parts: string[] = []\n for (const key of keys) {\n parts.push(JSON.stringify(key) + ':' + canonicalJson(obj[key]))\n }\n return '{' + parts.join(',') + '}'\n }\n throw new Error(`canonicalJson: unexpected value type: ${typeof value}`)\n}\n\n/**\n * Compute a hex-encoded sha256 of a string via Web Crypto's subtle API.\n *\n * We use hex (not base64) for hashes because hex is case-insensitive,\n * fixed-length (64 chars), and easier to compare visually in debug\n * output. Base64 would save a few bytes in storage but every encrypted\n * ledger entry is already much larger than the hash itself.\n */\nexport async function sha256Hex(input: string): Promise<string> {\n const bytes = new TextEncoder().encode(input)\n const digest = await globalThis.crypto.subtle.digest('SHA-256', bytes)\n return bytesToHex(new Uint8Array(digest))\n}\n\n/**\n * Compute the canonical hash of a ledger entry. Short wrapper around\n * `canonicalJson` + `sha256Hex`; callers use this instead of composing\n * the two functions every time, so any future change to the hashing\n * pipeline (e.g., adding a domain-separation prefix) lives in one place.\n */\nexport async function hashEntry(entry: LedgerEntry): Promise<string> {\n return sha256Hex(canonicalJson(entry))\n}\n\n/** Convert a Uint8Array to a lowercase hex string. */\nfunction bytesToHex(bytes: Uint8Array): string {\n const hex = new Array<string>(bytes.length)\n for (let i = 0; i < bytes.length; i++) {\n // Non-null assertion: indexing a Uint8Array within bounds always\n // returns a number, but the compiler's noUncheckedIndexedAccess\n // flag widens it to `number | undefined`. Safe here by construction.\n hex[i] = (bytes[i] ?? 0).toString(16).padStart(2, '0')\n }\n return hex.join('')\n}\n\n/**\n * Pad an index to the canonical 10-digit form used as the adapter key.\n * Ten digits is enough for ~10 billion ledger entries per vault\n * — far beyond any realistic use case, but cheap enough that the extra\n * digits don't hurt storage.\n */\nexport function paddedIndex(index: number): string {\n return String(index).padStart(10, '0')\n}\n\n/** Parse a padded adapter key back into a number. Returns NaN on malformed input. */\nexport function parseIndex(key: string): number {\n return Number.parseInt(key, 10)\n}\n","/**\n * Envelope payload hash — pinned in its own leaf module so consumers\n * (DictionaryHandle, the active history strategy) can import it\n * without dragging in the `LedgerStore` class.\n *\n * see `constants.ts` for the broader rationale.\n *\n * @internal\n */\n\nimport type { EncryptedEnvelope } from '../../types.js'\nimport { sha256Hex } from './entry.js'\n\n/**\n * Compute the `payloadHash` value for an encrypted envelope. Used by\n * `LedgerStore.append` for both put (hash the new envelope) and\n * delete (hash the previous envelope) paths, and by\n * `DictionaryHandle` so its ledger entries match the same contract.\n *\n * Returns the empty string when there is no envelope (delete of a\n * never-existed record). The empty string tolerated by the ledger\n * entry's `payloadHash` field as the canonical \"nothing here\" value.\n */\nexport async function envelopePayloadHash(\n envelope: EncryptedEnvelope | null,\n): Promise<string> {\n if (!envelope) return ''\n // `_data` is a base64 string for encrypted envelopes and the raw\n // JSON for plaintext ones. Both are strings, so a single sha256Hex\n // call works for both modes — the hash value differs between\n // encrypted/plaintext compartments because the bytes on disk\n // differ.\n return sha256Hex(envelope._data)\n}\n","/**\n * Persistence helpers for the vault-level user-directory toggle\n * (`_meta/directory`). Mirrors the bypass-AES pattern used by\n * `_meta/policy` — the directory document is plain JSON, the\n * envelope's `_iv` field is left empty.\n *\n * @see docs/subsystems/user-envelope.md → Directory visibility\n * @see docs/subsystems/plaintext-bypass.md — every `_iv: ''` write site\n *\n * @module\n */\nimport type { NoydbStore, EncryptedEnvelope } from '../types.js'\nimport { NOYDB_FORMAT_VERSION } from '../types.js'\nimport type { DirectoryConfig } from './types.js'\n\n/** Reserved collection name for vault-level metadata documents. */\nexport const META_COLLECTION = '_meta'\n/** Reserved id for the vault-level directory document. */\nexport const DIRECTORY_RECORD_ID = 'directory'\n\n/**\n * Read the directory toggle from `_meta/directory`. Returns `undefined`\n * when no document has been persisted — callers treat that as the\n * default-on case (`{ enabled: true }`).\n *\n * Tolerates corrupted documents the same way `_meta/policy` does: a\n * JSON parse failure surfaces as `undefined`, not a thrown error, so a\n * bad write never permanently breaks team enumeration.\n */\nexport async function readDirectoryConfig(\n store: NoydbStore,\n vault: string,\n): Promise<DirectoryConfig | undefined> {\n const envelope = await store.get(vault, META_COLLECTION, DIRECTORY_RECORD_ID)\n if (!envelope) return undefined\n try {\n const parsed = JSON.parse(envelope._data) as unknown\n if (!isDirectoryConfig(parsed)) return undefined\n return parsed\n } catch {\n return undefined\n }\n}\n\n/**\n * Persist the directory toggle at `_meta/directory`. Idempotent — call\n * on every `db.setDirectoryEnabled()` invocation. Owner-only at the\n * caller site; this primitive does not check roles.\n */\nexport async function persistDirectoryConfig(\n store: NoydbStore,\n vault: string,\n config: DirectoryConfig,\n): Promise<void> {\n const envelope: EncryptedEnvelope = {\n _noydb: NOYDB_FORMAT_VERSION,\n _v: 1,\n _ts: new Date().toISOString(),\n _iv: '',\n _data: JSON.stringify({ enabled: config.enabled }),\n }\n await store.put(vault, META_COLLECTION, DIRECTORY_RECORD_ID, envelope)\n}\n\nfunction isDirectoryConfig(x: unknown): x is DirectoryConfig {\n if (x === null || typeof x !== 'object') return false\n if (!('enabled' in x)) return false\n return typeof (x as { enabled: unknown }).enabled === 'boolean'\n}\n","/**\n * Persistence helpers for the per-user visibility flag\n * (`_meta/visibility/<keyringId>`). Mirrors the bypass-AES pattern used\n * by `_meta/policy` — the visibility document is plain JSON, the\n * envelope's `_iv` field is left empty.\n *\n * Stored alongside the keyring file rather than inside the encrypted\n * user envelope (`_users/<keyringId>`) because:\n *\n * - `UserEnvelope<T>.data` is opaque-to-hub by contract — hub does not\n * introspect or reserve any keys inside it. Adding `hidden` there\n * would violate that contract.\n * - `listUsersWithEnvelopes` filters by the flag, and the filter must\n * work even when decryption fails (legacy keyrings predating the\n * envelope feature, or a corrupted envelope).\n *\n * @see docs/subsystems/user-envelope.md → Directory visibility\n * @see docs/subsystems/plaintext-bypass.md — every `_iv: ''` write site\n *\n * @module\n */\nimport type { NoydbStore, EncryptedEnvelope } from '../types.js'\nimport { NOYDB_FORMAT_VERSION } from '../types.js'\nimport type { UserVisibility } from './types.js'\nimport { META_COLLECTION } from './storage.js'\n\n/** Prefix for per-user visibility records inside `_meta`. */\nexport const VISIBILITY_RECORD_PREFIX = 'visibility/'\n\n/** Compose the `_meta` record id for a keyring's visibility doc. */\nexport function visibilityRecordId(keyringId: string): string {\n return VISIBILITY_RECORD_PREFIX + keyringId\n}\n\n/**\n * Read the visibility flag for `keyringId`. Returns `undefined` when no\n * document has been persisted — callers treat that as the default-visible\n * case (`{ hidden: false }`).\n */\nexport async function readUserVisibility(\n store: NoydbStore,\n vault: string,\n keyringId: string,\n): Promise<UserVisibility | undefined> {\n const envelope = await store.get(vault, META_COLLECTION, visibilityRecordId(keyringId))\n if (!envelope) return undefined\n try {\n const parsed = JSON.parse(envelope._data) as unknown\n if (!isUserVisibility(parsed)) return undefined\n return parsed\n } catch {\n return undefined\n }\n}\n\n/**\n * Persist the visibility flag for `keyringId` at\n * `_meta/visibility/<keyringId>`. Idempotent — call on every\n * `vault.user.setMyVisibility()` invocation. Own-only at the caller\n * site; this primitive does not enforce keyring ownership.\n */\nexport async function persistUserVisibility(\n store: NoydbStore,\n vault: string,\n keyringId: string,\n visibility: UserVisibility,\n): Promise<void> {\n const envelope: EncryptedEnvelope = {\n _noydb: NOYDB_FORMAT_VERSION,\n _v: 1,\n _ts: new Date().toISOString(),\n _iv: '',\n _data: JSON.stringify({ hidden: visibility.hidden }),\n }\n await store.put(vault, META_COLLECTION, visibilityRecordId(keyringId), envelope)\n}\n\n/**\n * Delete the visibility flag for `keyringId`. Called from `revoke()`\n * alongside `deleteUserEnvelope` so the sidecar does not leak to a\n * re-granted principal with the same `userId`. Idempotent — the store's\n * `delete()` is already a no-op when the record is absent.\n */\nexport async function deleteUserVisibility(\n store: NoydbStore,\n vault: string,\n keyringId: string,\n): Promise<void> {\n await store.delete(vault, META_COLLECTION, visibilityRecordId(keyringId))\n}\n\nfunction isUserVisibility(x: unknown): x is UserVisibility {\n if (x === null || typeof x !== 'object') return false\n if (!('hidden' in x)) return false\n return typeof (x as { hidden: unknown }).hidden === 'boolean'\n}\n","/**\n * Passphrase validation — phrase format (per the three-tier session-tiers\n * design, locked 2026-05-04).\n *\n * Passphrases are **phrases**: multiple simple words, easy to remember,\n * structurally constrained so a weak choice cannot silently collapse the\n * security floor. The format is intentionally narrow: lowercase letters\n * and single spaces only, no punctuation, no symbols, no digits.\n *\n * - Default minimum: 6 words (~77 bits with the 7,776-word EFF list).\n * - Strict minimum: 8 words (~103 bits).\n * - Per-word minimum: 3 characters (excludes \"a\", \"is\", \"of\").\n * - Adjacent repeats rejected (\"the the\").\n *\n * The hub runs validation default-on at every passphrase ingress\n * (`createOwnerKeyring`, `grant`, `rotatePassphrase`); test fixtures and\n * CLI scripts override via `{ allowWeakPassphrase: true }`.\n *\n * @module\n */\nimport { NoydbError, ValidationError } from './errors.js'\n\n/** All reasons a phrase can be rejected. */\nexport type WeakPassphraseReason =\n | 'empty'\n | 'invalid-chars'\n | 'leading-or-trailing-space'\n | 'double-space'\n | 'too-few-words'\n | 'word-too-short'\n | 'repeated-adjacent'\n\n/** Per-vault knobs. Aligns with `VaultPolicy.passphrase`. */\nexport interface PassphrasePolicy {\n /** Minimum number of words. Default 6. Strict policy uses 8. */\n readonly minWords?: number\n /** Minimum characters per word. Default 3. */\n readonly minWordLength?: number\n /** Reject adjacent identical words (\"the the\"). Default true. */\n readonly rejectRepeatedAdjacent?: boolean\n /**\n * Override the default character-class rule (`/^[a-z]+( [a-z]+)*$/`).\n *\n * The hub's strict default is lowercase-letters-and-single-spaces\n * because that's what the EFF wordlist generator emits and what\n * most attacker password lists are keyed on. Use this knob to allow\n * digits, uppercase, hyphens, or non-Latin scripts when the\n * consumer's audience needs them — e.g.:\n *\n * ```ts\n * // Thai + English mix with digits permitted\n * pattern: /^[\\p{L}0-9 ]+( [\\p{L}0-9 ]+)*$/u\n *\n * // Allow uppercase + hyphens (passphrase-with-hyphens style)\n * pattern: /^[A-Za-z]+([- ][A-Za-z]+)*$/\n * ```\n *\n * The OTHER structural rules still apply (min-words split by space,\n * min-word-length, repeated-adjacent, leading/trailing whitespace,\n * double-space). For non-space-delimited word semantics, use\n * {@link customValidator} instead.\n *\n * Added in pre.8 (#31).\n */\n readonly pattern?: RegExp\n /**\n * Replace ALL validation entirely with a custom function. When set,\n * none of the other PassphrasePolicy fields apply — the consumer\n * owns every rule (word splitting, character classes, entropy\n * thresholds, allowlist/denylist). Use sparingly; this is the\n * escape hatch for domain-specific phrase formats:\n *\n * - Localized wordlists with non-space word boundaries\n * - BIP-39 seed phrases (24 words, fixed wordlist, etc.)\n * - Organization-specific HR password policies\n *\n * The returned `PassphraseValidationResult` is what\n * {@link assertStrongPassphrase} dispatches on — `ok: true` accepts;\n * `ok: false` throws `WeakPassphraseError` with the supplied reason.\n *\n * Added in pre.8 (#31).\n */\n readonly customValidator?: (phrase: string) => PassphraseValidationResult\n}\n\n/** Result of a check. Discriminated union — compile-time exhaustive. */\nexport type PassphraseValidationResult =\n | { readonly ok: true; readonly words: number }\n | {\n readonly ok: false\n readonly reason: WeakPassphraseReason\n readonly minimum?: number\n readonly got?: number\n }\n\n/**\n * Thrown by `assertStrongPassphrase()` and by every hub ingress\n * point (`createOwnerKeyring`, `grant`, `rotatePassphrase`) when a\n * supplied phrase fails the structural rules above.\n */\nexport class WeakPassphraseError extends NoydbError {\n readonly reason: WeakPassphraseReason\n readonly suggestion: string\n constructor(reason: WeakPassphraseReason, suggestion: string) {\n super('WEAK_PASSPHRASE', `Weak passphrase (${reason}). ${suggestion}`)\n this.name = 'WeakPassphraseError'\n this.reason = reason\n this.suggestion = suggestion\n }\n}\n\nconst DEFAULT_MIN_WORDS = 6\nconst DEFAULT_MIN_WORD_LENGTH = 3\n\nconst SUGGESTIONS: Record<WeakPassphraseReason, string> = {\n empty: 'Provide a phrase of at least 6 lowercase words separated by single spaces.',\n 'invalid-chars':\n 'Use only lowercase letters [a-z] and single spaces. No punctuation, symbols, digits, or uppercase.',\n 'leading-or-trailing-space': 'Trim leading and trailing spaces.',\n 'double-space': 'Use exactly one space between words.',\n 'too-few-words':\n 'Use at least 6 words by default (8 under strict policy). Example: \"correct horse battery staple printer toaster\".',\n 'word-too-short': 'Each word must be at least 3 characters. Drop short fillers like \"a\", \"is\", \"of\".',\n 'repeated-adjacent': 'Avoid repeating the same word twice in a row.',\n}\n\n/**\n * Inspect a phrase against the format rules and return a structured\n * verdict. Never throws — callers either branch on `ok` or pass the\n * result to {@link assertStrongPassphrase} for the throwing flavour.\n */\nexport function validatePassphrase(\n s: string,\n opts?: PassphrasePolicy,\n): PassphraseValidationResult {\n // Escape hatch: customValidator owns the entire decision. None of\n // the structural rules below run when this is set — the consumer is\n // responsible for the full validation contract.\n if (opts?.customValidator) {\n return opts.customValidator(s)\n }\n\n const minWords = opts?.minWords ?? DEFAULT_MIN_WORDS\n const minWordLength = opts?.minWordLength ?? DEFAULT_MIN_WORD_LENGTH\n const rejectRepeated = opts?.rejectRepeatedAdjacent ?? true\n\n if (s.length === 0) {\n return { ok: false, reason: 'empty' }\n }\n\n if (s !== s.trim()) {\n return { ok: false, reason: 'leading-or-trailing-space' }\n }\n\n if (s.includes(' ')) {\n return { ok: false, reason: 'double-space' }\n }\n\n // The default character class is lowercase-letters-and-spaces;\n // consumers can override via PassphrasePolicy.pattern (e.g. to\n // allow digits, uppercase, or non-Latin scripts). Word splitting\n // below remains space-based — for non-space word semantics the\n // consumer should use customValidator instead.\n const charPattern = opts?.pattern ?? /^[a-z]+( [a-z]+)*$/\n if (!charPattern.test(s)) {\n return { ok: false, reason: 'invalid-chars' }\n }\n\n const words = s.split(' ')\n\n if (words.length < minWords) {\n return { ok: false, reason: 'too-few-words', minimum: minWords, got: words.length }\n }\n\n for (const w of words) {\n if (w.length < minWordLength) {\n return { ok: false, reason: 'word-too-short', minimum: minWordLength, got: w.length }\n }\n }\n\n if (rejectRepeated) {\n for (let i = 1; i < words.length; i++) {\n if (words[i] === words[i - 1]) {\n return { ok: false, reason: 'repeated-adjacent' }\n }\n }\n }\n\n return { ok: true, words: words.length }\n}\n\n/**\n * Throw {@link WeakPassphraseError} when the phrase fails. Used by\n * `createOwnerKeyring`, `grant`, and `rotatePassphrase` at ingress.\n *\n * Pass `{ allowWeakPassphrase: true }` to bypass — intended for test\n * fixtures, CLI scripts, and dev environments. The override never\n * loosens the cryptographic key derivation; it only relaxes the\n * structural-strength gate.\n */\nexport function assertStrongPassphrase(\n s: string,\n opts?: PassphrasePolicy & { allowWeakPassphrase?: boolean },\n): void {\n if (opts?.allowWeakPassphrase) return\n const result = validatePassphrase(s, opts)\n if (result.ok) return\n throw new WeakPassphraseError(result.reason, SUGGESTIONS[result.reason])\n}\n\n/**\n * Estimate the entropy of a phrase, given the EFF 7,776-word list as\n * the assumed wordlist. ~12.9 bits per word.\n *\n * Returns 0 for any input that fails the phrase format — character-class\n * estimates aren't comparable to phrase entropy, and surfacing 0 makes\n * weak inputs visible in any UI that displays an entropy meter.\n */\nexport function estimateEntropy(passphrase: string): number {\n const result = validatePassphrase(passphrase)\n if (!result.ok) return 0\n return Math.round(result.words * Math.log2(7776))\n}\n\n/**\n * Internal compatibility shim. Older code paths used the throwing\n * `validatePassphrase(s)` directly; some still do via re-exports. Routes\n * to the new `assertStrongPassphrase` so the contract holds for both\n * shapes during the transition. New code should call\n * {@link assertStrongPassphrase} directly.\n *\n * @internal\n */\nexport function legacyAssertPassphrase(s: string): void {\n try {\n assertStrongPassphrase(s)\n } catch (err) {\n if (err instanceof WeakPassphraseError) {\n throw new ValidationError(err.message)\n }\n throw err\n }\n}\n","/**\n * Type surface for the per-principal user envelope subsystem.\n *\n * @see docs/superpowers/specs/2026-05-05-user-envelope-design.md\n *\n * @module\n */\nimport { NoydbError } from '../../errors.js'\n\n/**\n * Thin reader view of a user envelope. The on-disk shape is the standard\n * {@link import('../../types.js').EncryptedEnvelope}; this is what callers\n * see after the storage layer has decrypted the payload.\n *\n * Hub commits to the `keyringId` ⇔ `userId` identity and the `_v` / `_ts`\n * envelope metadata. The `data` payload is fully app-defined — hub does\n * not introspect, validate, or reserve any keys inside it.\n */\nexport interface UserEnvelope<T> {\n /** The principal id this envelope belongs to. Equals the keyring `user_id`. */\n readonly keyringId: string\n /** App-owned payload. Opaque to hub. */\n readonly data: T\n /** Optimistic-concurrency version. Increments on every write. */\n readonly _v: number\n /** ISO timestamp of the last write. */\n readonly _ts: string\n}\n\n/**\n * Soft cap on the JSON-serialized payload size. Generous (a typical\n * profile + preferences + small app annex is ~1 KiB); rejects accidental\n * \"stuff app state in here\" anti-patterns.\n */\nexport const USER_ENVELOPE_MAX_BYTES = 64 * 1024\n\n/**\n * Reserved store collection name for user envelopes. Starts with `_` so the\n * keyring grant machinery propagates the DEK to every granted user via the\n * existing system-collection DEK propagation path in `team/keyring.ts`.\n */\nexport const USER_ENVELOPE_COLLECTION = '_users'\n\n/**\n * Thrown when a user-envelope payload exceeds {@link USER_ENVELOPE_MAX_BYTES}\n * after JSON-serialization. The error carries the actual size so callers\n * can decide whether to trim or split.\n */\nexport class UserEnvelopeOversizedError extends NoydbError {\n readonly bytes: number\n readonly limit: number\n constructor(bytes: number, limit: number = USER_ENVELOPE_MAX_BYTES) {\n super(\n 'USER_ENVELOPE_OVERSIZED',\n `User envelope payload is ${bytes} bytes; soft cap is ${limit} bytes. ` +\n `Move large data into the vault's regular collections.`,\n )\n this.name = 'UserEnvelopeOversizedError'\n this.bytes = bytes\n this.limit = limit\n }\n}\n","/**\n * Persistence helpers for per-principal user envelopes stored at\n * `_users/<keyringId>` (logically: `_meta/user/<keyringId>`).\n *\n * Unlike `_meta/policy` and `_meta/handle` which are plaintext, user\n * envelopes carry user data and are encrypted with a dedicated\n * {@link USER_ENVELOPE_COLLECTION} DEK (provisioned at vault open and\n * propagated to every keyring via the system-collection DEK path in\n * `team/keyring.ts`).\n *\n * This module is the **storage primitive** layer. The public API\n * (`vault.user.*`) sits on top of this; permission gates, own-only\n * write enforcement, and presence-channel propagation live there.\n *\n * @see docs/superpowers/specs/2026-05-05-user-envelope-design.md\n *\n * @module\n */\nimport type { NoydbStore, EncryptedEnvelope } from '../../types.js'\nimport { NOYDB_FORMAT_VERSION } from '../../types.js'\nimport { encrypt, decrypt } from '../../crypto.js'\nimport { ConflictError } from '../../errors.js'\nimport {\n USER_ENVELOPE_COLLECTION,\n USER_ENVELOPE_MAX_BYTES,\n UserEnvelopeOversizedError,\n type UserEnvelope,\n} from './types.js'\n\n/**\n * Read and decrypt the user envelope for `keyringId`. Returns `null`\n * when no envelope has been persisted (either the principal has never\n * called `updateMe`, or the keyring predates this feature).\n *\n * Decryption errors propagate — a tampered or wrong-keyed envelope\n * surfaces as the underlying crypto error rather than masquerading as\n * \"not found\".\n */\nexport async function loadUserEnvelope<T = unknown>(\n store: NoydbStore,\n vault: string,\n keyringId: string,\n dek: CryptoKey,\n): Promise<UserEnvelope<T> | null> {\n const envelope = await store.get(vault, USER_ENVELOPE_COLLECTION, keyringId)\n if (!envelope) return null\n const plaintext = await decrypt(envelope._iv, envelope._data, dek)\n const data = JSON.parse(plaintext) as T\n return {\n keyringId,\n data,\n _v: envelope._v,\n _ts: envelope._ts,\n }\n}\n\n/**\n * Encrypt and persist the user envelope for `keyringId`. The new\n * version is `(prior._v ?? 0) + 1`. Pass `expectedVersion` to enable\n * optimistic-concurrency checks: a mismatch with the stored version\n * throws {@link ConflictError} with the actual stored version.\n *\n * `expectedVersion: 0` means \"expect no prior envelope\"; the write\n * succeeds only if no envelope exists yet.\n *\n * Soft-caps the JSON-serialized payload at {@link USER_ENVELOPE_MAX_BYTES};\n * larger payloads throw {@link UserEnvelopeOversizedError}.\n */\nexport async function saveUserEnvelope<T>(\n store: NoydbStore,\n vault: string,\n keyringId: string,\n payload: T,\n dek: CryptoKey,\n expectedVersion?: number,\n): Promise<UserEnvelope<T>> {\n const json = JSON.stringify(payload)\n // TextEncoder counts bytes correctly for multi-byte UTF-8 (Thai text,\n // emoji, etc.) — JSON.stringify().length would undercount.\n const bytes = new TextEncoder().encode(json).byteLength\n if (bytes > USER_ENVELOPE_MAX_BYTES) {\n throw new UserEnvelopeOversizedError(bytes)\n }\n\n const prior = await store.get(vault, USER_ENVELOPE_COLLECTION, keyringId)\n if (expectedVersion !== undefined) {\n const priorVersion = prior?._v ?? 0\n if (priorVersion !== expectedVersion) {\n throw new ConflictError(\n priorVersion,\n `User envelope for \"${keyringId}\" expected version ${expectedVersion}, ` +\n `actual ${priorVersion}`,\n )\n }\n }\n\n const nextVersion = (prior?._v ?? 0) + 1\n const ts = new Date().toISOString()\n const { iv, data } = await encrypt(json, dek)\n\n const envelope: EncryptedEnvelope = {\n _noydb: NOYDB_FORMAT_VERSION,\n _v: nextVersion,\n _ts: ts,\n _iv: iv,\n _data: data,\n }\n await store.put(vault, USER_ENVELOPE_COLLECTION, keyringId, envelope)\n\n return {\n keyringId,\n data: payload,\n _v: nextVersion,\n _ts: ts,\n }\n}\n\n/**\n * Delete the user envelope for `keyringId`. Idempotent — no error if\n * the envelope is already absent. Called from the keyring revoke path\n * (cascade-delete) and is a no-op for keyrings that never wrote.\n */\nexport async function deleteUserEnvelope(\n store: NoydbStore,\n vault: string,\n keyringId: string,\n): Promise<void> {\n await store.delete(vault, USER_ENVELOPE_COLLECTION, keyringId)\n}\n\n/**\n * List the keyring ids that have a user envelope persisted in `vault`.\n * Order is store-defined — callers that need a stable order should sort.\n */\nexport async function listUserEnvelopeIds(\n store: NoydbStore,\n vault: string,\n): Promise<string[]> {\n return store.list(vault, USER_ENVELOPE_COLLECTION)\n}\n","/**\n * Per-principal user envelope subsystem — storage + types.\n *\n * @see docs/superpowers/specs/2026-05-05-user-envelope-design.md\n *\n * @module\n */\nexport {\n USER_ENVELOPE_COLLECTION,\n USER_ENVELOPE_MAX_BYTES,\n UserEnvelopeOversizedError,\n type UserEnvelope,\n} from './types.js'\n\nexport {\n loadUserEnvelope,\n saveUserEnvelope,\n deleteUserEnvelope,\n listUserEnvelopeIds,\n} from './storage.js'\n","import type { NoydbStore, KeyringFile, KeyringAuthenticator, Role, Permissions, GrantOptions, RevokeOptions, UpdateUserOptions, UserInfo, EncryptedEnvelope, ExportCapability, ExportFormat, ImportCapability, VaultPolicyOnDisk } from '../types.js'\nimport { NOYDB_KEYRING_VERSION, NOYDB_FORMAT_VERSION } from '../types.js'\nimport {\n deriveKey,\n generateDEK,\n generateSalt,\n wrapKey,\n unwrapKey,\n encrypt,\n decrypt,\n bufferToBase64,\n base64ToBuffer,\n} from '../crypto.js'\nimport { NoAccessError, PermissionDeniedError, PrivilegeEscalationError, KeyringExpiredError, KeyringCorruptError, InvalidKeyError, ValidationError, DirectoryDisabledError } from '../errors.js'\nimport { readDirectoryConfig } from '../directory/storage.js'\nimport { readUserVisibility, deleteUserVisibility } from '../directory/visibility.js'\nimport { assertStrongPassphrase, type PassphrasePolicy } from '../validation.js'\nimport {\n saveUserEnvelope,\n loadUserEnvelope as loadUserEnvelopeFn,\n deleteUserEnvelope,\n USER_ENVELOPE_COLLECTION,\n type UserEnvelope as UserEnvelopeReader,\n} from '../meta/user-envelope/index.js'\n\n// ─── Roles that can grant/revoke ───────────────────────────────────────\n\n/**\n * Roles that an `admin` is allowed to grant and revoke.\n *\n * Includes `'admin'` itself: the model bottlenecked all admin\n * onboarding through the single `owner` principal, which made lateral\n * delegation impossible and left a single-owner bus-factor risk\n * unresolved even when multiple trusted humans existed. opens up\n * admin↔admin lateral delegation, with two guardrails:\n *\n * 1. **No privilege escalation.** Enforced in `grant()`: every DEK\n * wrapped into the new admin's keyring must be present in the\n * grantor's own DEK set. Today this is structurally trivially\n * true (admin grants always inherit the full caller DEK set),\n * but the check is wired in so future per-collection admin scoping\n * cannot accidentally bypass it. See `PrivilegeEscalationError`.\n *\n * 2. **Cascade on revoke.** Enforced in `revoke()`: when an admin is\n * revoked, every admin they (transitively) granted is either\n * revoked too (`cascade: 'strict'`, default) or left in place with\n * a console warning (`cascade: 'warn'`). The walk uses the\n * `granted_by` field on each keyring file as the parent pointer.\n */\nconst ADMIN_GRANTABLE_TARGETS: readonly Role[] = ['operator', 'viewer', 'client', 'admin']\n\nfunction canGrant(callerRole: Role, targetRole: Role): boolean {\n if (callerRole === 'owner') return true\n if (callerRole === 'admin') return ADMIN_GRANTABLE_TARGETS.includes(targetRole)\n return false\n}\n\nfunction canRevoke(callerRole: Role, targetRole: Role): boolean {\n if (targetRole === 'owner') return false // owner cannot be revoked\n if (callerRole === 'owner') return true\n if (callerRole === 'admin') return ADMIN_GRANTABLE_TARGETS.includes(targetRole)\n return false\n}\n\n/**\n * Whether `callerRole` can mutate a keyring whose role is (or becomes)\n * `targetRole`. Used by `updateKeyringIdentity` (#54).\n *\n * Mirrors `canGrant`'s hierarchy: admins manage admin/operator/viewer/\n * client laterally; admins cannot create or destroy `owner`-shaped\n * keyrings. Owner can do anything.\n *\n * Both the OLD role and the NEW role must satisfy this check —\n * otherwise admin could elevate themselves (`admin → owner`) or demote\n * an owner (`owner → admin`) under cover of \"update.\"\n */\nfunction canUpdateRole(callerRole: Role, targetRole: Role): boolean {\n if (callerRole === 'owner') return true\n if (callerRole === 'admin') return ADMIN_GRANTABLE_TARGETS.includes(targetRole)\n return false\n}\n\n// ─── Unlocked Keyring ──────────────────────────────────────────────────\n\n/** In-memory representation of an unlocked keyring. */\nexport interface UnlockedKeyring {\n readonly userId: string\n readonly displayName: string\n readonly role: Role\n readonly permissions: Permissions\n readonly deks: Map<string, CryptoKey>\n /**\n * The KEK, when this keyring was unlocked via tier 1 (passphrase) or\n * a wrap-KEK tier-2 method (WebAuthn / OIDC). `null` when the\n * keyring was opened via:\n *\n * - Unencrypted mode (no KEK exists)\n * - Tier-3 PIN quick-resume (`@noy-db/on-pin`)\n * - Wrap-DEKs tier-2 unlock (`@noy-db/on-password`'s\n * `verifyPasswordSlot` after #26 Path C)\n * - Session-state restore (`session/session.ts`)\n * - Dev-unlock fixture (`session/dev-unlock.ts`)\n *\n * Consumers performing tier-1 operations that need the KEK\n * (DEK rewrap, keyring persist, delegation issue/unwrap) must\n * null-check and throw a clear error if absent — re-authenticate\n * at tier 1 first to recover the KEK.\n *\n * Tightened from `CryptoKey` to `CryptoKey | null` in pre.8 (#41).\n * The runtime contract has always allowed null; the type now\n * matches reality.\n */\n readonly kek: CryptoKey | null\n readonly salt: Uint8Array\n /**\n * `@noy-db/as-*` export capability. Absent when the\n * keyring was written before this RFC landed — role-based defaults\n * apply via `hasExportCapability`.\n */\n readonly exportCapability?: ExportCapability\n /**\n * `@noy-db/as-*` import capability. Absent when the\n * keyring was written before the import-capability extension\n * landed — default-closed semantics\n * apply via `hasImportCapability` (no plaintext format granted, no\n * bundle import granted, regardless of role).\n */\n readonly importCapability?: ImportCapability\n /**\n * Tier-2 authenticator slots — readonly snapshot loaded from the\n * keyring file. Mutations go through `enrollAuthenticator` /\n * `removeAuthenticator` (issue #11), which write back via\n * `persistKeyring`. Always defined; loads with an empty array for\n * keyrings written before the multi-slot extension landed.\n */\n readonly authenticators: readonly KeyringAuthenticator[]\n /**\n * Reserved per-keyring policy override (forward-compat for Option C\n * — see {@link VaultPolicyOnDisk}). v1.0 round-trips this field but\n * never enforces it; the gate engine uses `_meta/policy` only.\n */\n readonly policy?: VaultPolicyOnDisk\n}\n\n// ─── Passphrase canary (#113) ──────────────────────────────────────────\n//\n// The canary is a fixed 256-bit AES-GCM key (32 zero bytes), wrapped\n// under the keyring's KEK with AES-KW. Because AES-KW is deterministic\n// (RFC 3394 fixed IV), wrapping the same constant under the same KEK\n// always yields the same ciphertext — so every write site can mint\n// fresh on each persist without round-tripping a `canary` field\n// through UnlockedKeyring.\n//\n// On load, the canary unwraps cleanly iff the KEK is correct AND the\n// canary bytes on disk are intact. Combined with each-DEK try/catch,\n// this distinguishes wrong-passphrase (canary fails AND every DEK fails)\n// from corruption (canary succeeds OR at least one DEK succeeds) —\n// closing the all-DEKs-corrupt and single-DEK ambiguities that the\n// pre-canary heuristic from #82 / #99 left open.\n\nconst CANARY_PLAINTEXT_BYTES = new Uint8Array(32)\nlet canaryKeyPromise: Promise<CryptoKey> | null = null\n\nfunction getCanaryKey(): Promise<CryptoKey> {\n if (canaryKeyPromise === null) {\n canaryKeyPromise = globalThis.crypto.subtle.importKey(\n 'raw',\n CANARY_PLAINTEXT_BYTES as BufferSource,\n { name: 'AES-GCM', length: 256 },\n true, // extractable so AES-KW can wrap it\n ['encrypt', 'decrypt'],\n )\n }\n return canaryKeyPromise\n}\n\n/** Mint a fresh wrapped-canary string. Deterministic for a given KEK. */\nexport async function mintKeyringCanary(kek: CryptoKey): Promise<string> {\n const canaryKey = await getCanaryKey()\n return wrapKey(canaryKey, kek)\n}\n\n/** Try to unwrap the canary. Returns true iff KEK + canary bytes are intact. */\nasync function verifyKeyringCanary(wrappedCanary: string, kek: CryptoKey): Promise<boolean> {\n try {\n await unwrapKey(wrappedCanary, kek)\n return true\n } catch {\n return false\n }\n}\n\n// ─── Load / Create ─────────────────────────────────────────────────────\n\n/** Load and unlock a user's keyring for a vault. */\nexport async function loadKeyring(\n adapter: NoydbStore,\n vault: string,\n userId: string,\n passphrase: string,\n): Promise<UnlockedKeyring> {\n const envelope = await adapter.get(vault, '_keyring', userId)\n\n if (!envelope) {\n throw new NoAccessError(`No keyring found for user \"${userId}\" in vault \"${vault}\"`)\n }\n\n const keyringFile = JSON.parse(envelope._data) as KeyringFile\n\n // — refuse to unwrap an expired slot. Check happens before any\n // KEK derivation so an expired slot doesn't leak timing on the\n // passphrase. Comparison uses Date.parse → ms-since-epoch; an\n // unparseable expires_at is treated as \"no expiry\" so a malformed\n // value can't silently lock users out (it'll surface in tests).\n if (keyringFile.expires_at !== undefined) {\n const cutoff = Date.parse(keyringFile.expires_at)\n if (Number.isFinite(cutoff) && Date.now() >= cutoff) {\n throw new KeyringExpiredError({ userId: keyringFile.user_id, expiresAt: keyringFile.expires_at })\n }\n }\n\n const salt = base64ToBuffer(keyringFile.salt)\n const kek = await deriveKey(passphrase, salt)\n\n // Verify the canary first when present. A canary success proves the\n // KEK is correct independent of any DEK byte — so subsequent DEK\n // unwrap failures are unambiguously corruption, not wrong-pass. A\n // canary failure with at least one DEK success indicates the KEK\n // is correct but the canary itself is corrupt. (#113)\n // `null` sentinel = legacy keyring without canary; falls back to the\n // multi-DEK heuristic from #82 / #99.\n const canaryOk: boolean | null = keyringFile.canary !== undefined\n ? await verifyKeyringCanary(keyringFile.canary, kek)\n : null\n\n // Unwrap each DEK independently — collect successes and failures.\n const deks = new Map<string, CryptoKey>()\n const failedCollections: string[] = []\n let firstUnwrapError: unknown = null\n for (const [collName, wrappedDek] of Object.entries(keyringFile.deks)) {\n try {\n const dek = await unwrapKey(wrappedDek, kek)\n deks.set(collName, dek)\n } catch (err) {\n failedCollections.push(collName)\n if (firstUnwrapError === null) firstUnwrapError = err\n }\n }\n\n if (canaryOk === true) {\n // KEK proven correct by the canary. Any DEK failure is corruption.\n if (failedCollections.length > 0) {\n throw new KeyringCorruptError({ failedCollections, intactCount: deks.size })\n }\n } else if (canaryOk === false) {\n // Canary failed. If any DEK unwrapped, KEK is correct → canary bytes\n // are corrupted (rare; reported under the '_canary' sentinel).\n if (deks.size > 0) {\n throw new KeyringCorruptError({\n failedCollections: [...failedCollections, '_canary'],\n intactCount: deks.size,\n })\n }\n // Canary failed AND no DEK unwrapped — wrong KEK (or whole-file\n // corruption). Surface the original InvalidKeyError so\n // onInvalidKey: 'reset' can fire its documented recovery path.\n throw firstUnwrapError instanceof Error ? firstUnwrapError : new InvalidKeyError()\n } else {\n // Legacy keyring (no canary). Fall back to the multi-DEK heuristic.\n if (failedCollections.length > 0) {\n if (deks.size > 0) {\n throw new KeyringCorruptError({ failedCollections, intactCount: deks.size })\n }\n throw firstUnwrapError instanceof Error ? firstUnwrapError : new InvalidKeyError()\n }\n }\n\n return {\n userId: keyringFile.user_id,\n displayName: keyringFile.display_name,\n role: keyringFile.role,\n permissions: keyringFile.permissions,\n deks,\n kek,\n salt,\n authenticators: keyringFile.authenticators ?? [],\n ...(keyringFile.export_capability !== undefined && { exportCapability: keyringFile.export_capability }),\n ...(keyringFile.import_capability !== undefined && { importCapability: keyringFile.import_capability }),\n ...(keyringFile.policy !== undefined && { policy: keyringFile.policy }),\n }\n}\n\n/**\n * Create the initial owner keyring for a new vault.\n *\n * Pass `{ validate: true }` (or a `PassphrasePolicy`) to gate creation\n * on the phrase-format strength rules — `Noydb` threads this from\n * `NoydbOptions.validatePassphrase`. Direct callers (CLI, scripts,\n * test fixtures) opt in explicitly.\n */\nexport async function createOwnerKeyring(\n adapter: NoydbStore,\n vault: string,\n userId: string,\n passphrase: string,\n passphraseOpts?: PassphrasePolicy & { validate?: boolean; allowWeakPassphrase?: boolean },\n): Promise<UnlockedKeyring> {\n if (passphraseOpts?.validate && !passphraseOpts.allowWeakPassphrase) {\n assertStrongPassphrase(passphrase, passphraseOpts)\n }\n const salt = generateSalt()\n const kek = await deriveKey(passphrase, salt)\n\n // Eager-provision the _users DEK at owner creation. This guarantees\n // every subsequent grant inherits it via the existing\n // collName.startsWith('_') propagation in grant() — so multi-principal\n // user-envelope reads (alice reading bob's profile) work for new\n // vaults without any per-keyring DEK rotation. Pre-existing vaults\n // get the DEK lazily on first vault.user.* access (which only\n // materializes a single-principal DEK that won't propagate\n // retroactively — that's the documented \"lazy creation for\n // pre-existing keyrings\" rollout note in the spec).\n const userEnvelopeDek = await generateDEK()\n const wrappedUserEnvelopeDek = await wrapKey(userEnvelopeDek, kek)\n const canary = await mintKeyringCanary(kek)\n\n const keyringFile: KeyringFile = {\n _noydb_keyring: NOYDB_KEYRING_VERSION,\n user_id: userId,\n display_name: userId,\n role: 'owner',\n permissions: {},\n deks: { [USER_ENVELOPE_COLLECTION]: wrappedUserEnvelopeDek },\n salt: bufferToBase64(salt),\n created_at: new Date().toISOString(),\n granted_by: userId,\n canary,\n }\n\n await writeKeyringFile(adapter, vault, userId, keyringFile)\n\n return {\n userId,\n displayName: userId,\n role: 'owner',\n permissions: {},\n deks: new Map([[USER_ENVELOPE_COLLECTION, userEnvelopeDek]]),\n kek,\n salt,\n authenticators: [],\n }\n}\n\n// ─── Grant ─────────────────────────────────────────────────────────────\n\n/** Grant access to a new user. Caller must have grant privilege. */\nexport async function grant(\n adapter: NoydbStore,\n vault: string,\n callerKeyring: UnlockedKeyring,\n options: GrantOptions,\n): Promise<void> {\n if (!callerKeyring.kek) {\n throw new ValidationError(\n 'grant: caller keyring has no KEK — tier-2 wrap-DEKs and tier-3 PIN-resume ' +\n 'sessions cannot grant access to other users. Re-authenticate at tier 1 ' +\n '(passphrase) before granting.',\n )\n }\n\n if (!canGrant(callerKeyring.role, options.role)) {\n throw new PermissionDeniedError(\n `Role \"${callerKeyring.role}\" cannot grant role \"${options.role}\"`,\n )\n }\n\n // Optional strength validation — opt-in via grant({ validatePassphrase: true })\n // or via the calling Noydb's NoydbOptions.validatePassphrase flag.\n // The override `allowWeakPassphrase: true` skips even when validate is on.\n if (\n (options as { validatePassphrase?: boolean }).validatePassphrase &&\n !options.allowWeakPassphrase\n ) {\n assertStrongPassphrase(options.passphrase)\n }\n\n // Determine which collections the new user gets access to\n const permissions = resolvePermissions(options.role, options.permissions)\n\n // Derive the new user's KEK from their passphrase\n const newSalt = generateSalt()\n const newKek = await deriveKey(options.passphrase, newSalt)\n\n // Wrap the appropriate DEKs with the new user's KEK\n const wrappedDeks: Record<string, string> = {}\n for (const collName of Object.keys(permissions)) {\n const dek = callerKeyring.deks.get(collName)\n if (dek) {\n wrappedDeks[collName] = await wrapKey(dek, newKek)\n }\n }\n\n // For owner/admin/viewer roles, wrap ALL known DEKs\n if (options.role === 'owner' || options.role === 'admin' || options.role === 'viewer') {\n for (const [collName, dek] of callerKeyring.deks) {\n if (!(collName in wrappedDeks)) {\n wrappedDeks[collName] = await wrapKey(dek, newKek)\n }\n }\n }\n\n // For ALL roles, propagate system-prefixed collection DEKs\n // (`_ledger`, `_history`, `_sync`, …). These are internal collections\n // that any user with access to the vault must be able to\n // read and write — for example, the hash-chained ledger writes\n // an entry on every put/delete, so operators and clients with write\n // access to a single data collection still need the `_ledger` DEK.\n //\n // Trade-off: a granted user can decrypt every system-collection\n // entry, including ones they would not otherwise have access to\n // (e.g., an operator on `invoices` can read ledger entries for\n // mutations in `salaries`). This is a metadata leak, not a\n // plaintext leak — the ledger entries record collection names,\n // record ids, and ciphertext hashes, but never plaintext records.\n // Per-collection ledger DEKs are tracked as a follow-up.\n for (const [collName, dek] of callerKeyring.deks) {\n if (collName.startsWith('_') && !(collName in wrappedDeks)) {\n wrappedDeks[collName] = await wrapKey(dek, newKek)\n }\n }\n\n // Anti-privilege-escalation check. Every DEK we just\n // wrapped into the new keyring must come from the caller's own DEK\n // set — the grantor cannot give the grantee access to a collection\n // they themselves can't read. Today this is structurally trivially\n // satisfied because every wrapped DEK was looked up in\n // `callerKeyring.deks` above, but the explicit check is wired in\n // so a future change (per-collection admin scoping, escrow-based\n // re-wrapping, etc.) cannot accidentally let a widening grant\n // through. See `PrivilegeEscalationError` for the rationale.\n for (const collName of Object.keys(wrappedDeks)) {\n if (!callerKeyring.deks.has(collName)) {\n throw new PrivilegeEscalationError(collName)\n }\n }\n\n const canary = await mintKeyringCanary(newKek)\n const keyringFile: KeyringFile = {\n _noydb_keyring: NOYDB_KEYRING_VERSION,\n user_id: options.userId,\n display_name: options.displayName,\n role: options.role,\n permissions,\n deks: wrappedDeks,\n salt: bufferToBase64(newSalt),\n created_at: new Date().toISOString(),\n granted_by: callerKeyring.userId,\n canary,\n ...(options.exportCapability !== undefined && { export_capability: options.exportCapability }),\n ...(options.importCapability !== undefined && { import_capability: options.importCapability }),\n }\n\n await writeKeyringFile(adapter, vault, options.userId, keyringFile)\n\n // User envelope bootstrap. Seeded with `options.initialProfile` if\n // provided, otherwise an empty `{}`. Encrypted with the caller's\n // _users DEK — which is the same DEK that was wrapped into the new\n // keyring's `wrappedDeks[USER_ENVELOPE_COLLECTION]` above (system-\n // collection propagation), so the new user can decrypt it on first\n // open. Skipped silently if the caller has no _users DEK (pre-feature\n // vault upgrade path — documented \"lazy creation for pre-existing\n // keyrings\" in the spec).\n const userEnvelopeDek = callerKeyring.deks.get(USER_ENVELOPE_COLLECTION)\n if (userEnvelopeDek) {\n const initialPayload = options.initialProfile ?? {}\n await saveUserEnvelope(\n adapter,\n vault,\n options.userId,\n initialPayload,\n userEnvelopeDek,\n )\n }\n}\n\n// ─── Revoke ────────────────────────────────────────────────────────────\n\n/**\n * Walk every keyring in the vault to find admins that the given\n * `rootUserId` (transitively) granted, via the `granted_by` parent\n * pointer recorded on each keyring file.\n *\n * Returns the set of descendant admin user-ids in DFS order, NOT\n * including the root itself. Non-admin descendants are excluded\n * because operators/viewers/clients cannot grant other users — they\n * are leaves in the delegation tree and cleaning them up is the\n * caller's job (or the next rotate, since they'd lose key access\n * anyway when the cascading admin's collections rotate).\n *\n * The walk uses a visited set keyed by user-id so cycles introduced\n * by re-grants (admin-A revoked, then re-granted later by admin-B who\n * was originally granted by A) terminate cleanly.\n */\nasync function findAdminDescendants(\n adapter: NoydbStore,\n vault: string,\n rootUserId: string,\n): Promise<string[]> {\n const allUserIds = await adapter.list(vault, '_keyring')\n\n // Build a map: parentUserId → child KeyringFiles. We only ever\n // descend into admins, so non-admin children are skipped at the\n // edge level rather than after a recursive call.\n const childrenByParent = new Map<string, string[]>()\n for (const userId of allUserIds) {\n const env = await adapter.get(vault, '_keyring', userId)\n if (!env) continue\n const kf = JSON.parse(env._data) as KeyringFile\n if (kf.role !== 'admin') continue // only admins can grant — leaves are uninteresting\n if (kf.user_id === rootUserId) continue // self-edges are noise\n const list = childrenByParent.get(kf.granted_by) ?? []\n list.push(kf.user_id)\n childrenByParent.set(kf.granted_by, list)\n }\n\n const visited = new Set<string>()\n const order: string[] = []\n const stack: string[] = [...(childrenByParent.get(rootUserId) ?? [])]\n while (stack.length > 0) {\n const next = stack.pop()!\n if (visited.has(next)) continue\n visited.add(next)\n order.push(next)\n for (const grandchild of childrenByParent.get(next) ?? []) {\n if (!visited.has(grandchild)) stack.push(grandchild)\n }\n }\n return order\n}\n\n/** Revoke a user's access. Optionally rotate keys for affected collections. */\nexport async function revoke(\n adapter: NoydbStore,\n vault: string,\n callerKeyring: UnlockedKeyring,\n options: RevokeOptions,\n): Promise<void> {\n // Load the target's keyring to check their role\n const targetEnvelope = await adapter.get(vault, '_keyring', options.userId)\n if (!targetEnvelope) {\n throw new NoAccessError(`User \"${options.userId}\" has no keyring in vault \"${vault}\"`)\n }\n\n const targetKeyring = JSON.parse(targetEnvelope._data) as KeyringFile\n\n if (!canRevoke(callerKeyring.role, targetKeyring.role)) {\n throw new PermissionDeniedError(\n `Role \"${callerKeyring.role}\" cannot revoke role \"${targetKeyring.role}\"`,\n )\n }\n\n // Cascade-on-revoke. Only meaningful when the target is\n // an admin — operators/viewers/clients cannot grant other users so\n // they have no delegation subtree to walk.\n const cascadeMode = options.cascade ?? 'strict'\n const usersToRevoke: string[] = [options.userId]\n const affectedCollections = new Set(Object.keys(targetKeyring.deks))\n\n if (targetKeyring.role === 'admin') {\n const descendants = await findAdminDescendants(adapter, vault, options.userId)\n if (descendants.length > 0) {\n if (cascadeMode === 'warn') {\n // Diagnostic mode: leave the descendants in place but make\n // them visible. The owner / a different admin can clean up\n // manually. The single console.warn is intentionally noisy\n // (a list, not a count) so the operator sees exactly which\n // keyrings will become orphans.\n console.warn(\n `[noy-db] revoke(${options.userId}): cascade='warn' — leaving ` +\n `${descendants.length} descendant admin(s) in place: ` +\n `${descendants.join(', ')}. These admins were granted by the revoked user ` +\n `(transitively) and will become orphans in the delegation tree.`,\n )\n } else {\n // Strict mode (default): pull every descendant into the\n // revoke set. We collect their affected collections too so\n // the single rotation pass at the end covers everything.\n for (const userId of descendants) {\n const descEnv = await adapter.get(vault, '_keyring', userId)\n if (!descEnv) continue\n const descKf = JSON.parse(descEnv._data) as KeyringFile\n usersToRevoke.push(userId)\n for (const c of Object.keys(descKf.deks)) affectedCollections.add(c)\n }\n }\n }\n }\n\n // Delete every keyring in the revoke set. Order doesn't matter\n // because each keyring file is independent on disk; we don't have\n // referential integrity to maintain across deletes.\n for (const userId of usersToRevoke) {\n await adapter.delete(vault, '_keyring', userId)\n // Cascade-delete the principal's user envelope. Idempotent — no\n // error when the envelope was never written (e.g. the user was\n // granted but never authenticated to write their own profile).\n await deleteUserEnvelope(adapter, vault, userId)\n // Also drop the visibility sidecar at `_meta/visibility/<userId>`.\n // If the same `userId` is re-granted later (rare for humans,\n // possible for service accounts and test fixtures), the new\n // principal must start with a fresh visibility state instead of\n // silently inheriting the revoked user's `hidden` flag.\n await deleteUserVisibility(adapter, vault, userId)\n }\n\n // Single rotation pass at the end. The cost is O(records in\n // affected collections), NOT O(records × cascade depth) — every\n // descendant's collections were unioned into `affectedCollections`\n // before we got here, so the rotation re-encrypts each affected\n // record exactly once regardless of how deep the cascade went.\n if (options.rotateKeys !== false && affectedCollections.size > 0) {\n await rotateKeys(adapter, vault, callerKeyring, [...affectedCollections])\n }\n}\n\n// ─── Update User (#54) ─────────────────────────────────────────────────\n\n/**\n * Mutate `role`, `displayName`, and/or `permissions` on an existing\n * keyring. Pure plaintext-header rewrite — no DEK rewrap, no KEK\n * required, no authenticator slots touched. Tier-2 enrollments and\n * recovery codes survive the operation.\n *\n * Role-elevation guard: BOTH the old role AND the new role must\n * satisfy `canUpdateRole(callerRole, _)`. This blocks the two\n * privilege-escalation shapes:\n * - admin elevates someone (or themselves) to owner\n * - admin demotes an owner to a role they then control\n *\n * Owner is always allowed. Admin manages admin / operator / viewer /\n * client laterally.\n *\n * Identity preserved: same userId, same DEK wrappings. Last-write-wins\n * through the standard keyring put (same concurrency story as `grant`\n * and `revoke`).\n *\n * @throws `NoAccessError` when no keyring exists for the target.\n * @throws `PermissionDeniedError` when the role hierarchy rejects.\n * @throws `ValidationError` when the diff is empty (nothing to update).\n *\n * @see #54\n */\nexport async function updateKeyringIdentity(\n adapter: NoydbStore,\n vault: string,\n callerKeyring: UnlockedKeyring,\n options: UpdateUserOptions,\n): Promise<void> {\n if (\n options.role === undefined &&\n options.displayName === undefined &&\n options.permissions === undefined\n ) {\n throw new ValidationError(\n `updateUser: at least one of role / displayName / permissions must be provided ` +\n `(userId: \"${options.userId}\").`,\n )\n }\n\n const env = await adapter.get(vault, '_keyring', options.userId)\n if (!env) {\n throw new NoAccessError(\n `updateUser: user \"${options.userId}\" has no keyring in vault \"${vault}\".`,\n )\n }\n const target = JSON.parse(env._data) as KeyringFile\n\n // Role-elevation guard. The OLD role must be one this caller is\n // allowed to manage, AND the NEW role (if changing) must be too.\n // Two-sided check: blocks admin→owner promotion (new side) and\n // demoting an owner (old side).\n if (!canUpdateRole(callerKeyring.role, target.role)) {\n throw new PermissionDeniedError(\n `Role \"${callerKeyring.role}\" cannot update a keyring with role \"${target.role}\"`,\n )\n }\n if (\n options.role !== undefined &&\n options.role !== target.role &&\n !canUpdateRole(callerKeyring.role, options.role)\n ) {\n throw new PermissionDeniedError(\n `Role \"${callerKeyring.role}\" cannot promote target to role \"${options.role}\"`,\n )\n }\n\n const next: KeyringFile = {\n ...target,\n ...(options.role !== undefined && { role: options.role }),\n ...(options.displayName !== undefined && {\n // null clears the field (stored as \"\"); a string sets it.\n display_name: options.displayName ?? '',\n }),\n ...(options.permissions !== undefined && { permissions: options.permissions }),\n }\n\n await writeKeyringFile(adapter, vault, options.userId, next)\n}\n\n// ─── Key Rotation ──────────────────────────────────────────────────────\n\n/**\n * Rotate DEKs for specified collections:\n * 1. Generate new DEKs\n * 2. Re-encrypt all records in affected collections\n * 3. Re-wrap new DEKs for all remaining users\n */\nexport async function rotateKeys(\n adapter: NoydbStore,\n vault: string,\n callerKeyring: UnlockedKeyring,\n collections: string[],\n): Promise<void> {\n // Generate new DEKs for each affected collection\n const newDeks = new Map<string, CryptoKey>()\n for (const collName of collections) {\n newDeks.set(collName, await generateDEK())\n }\n\n // Re-encrypt all records in affected collections\n for (const collName of collections) {\n const oldDek = callerKeyring.deks.get(collName)\n const newDek = newDeks.get(collName)!\n if (!oldDek) continue\n\n const ids = await adapter.list(vault, collName)\n for (const id of ids) {\n const envelope = await adapter.get(vault, collName, id)\n if (!envelope || !envelope._iv) continue\n\n // Decrypt with old DEK\n const plaintext = await decrypt(envelope._iv, envelope._data, oldDek)\n\n // Re-encrypt with new DEK\n const { iv, data } = await encrypt(plaintext, newDek)\n const newEnvelope: EncryptedEnvelope = {\n _noydb: NOYDB_FORMAT_VERSION,\n _v: envelope._v,\n _ts: new Date().toISOString(),\n _iv: iv,\n _data: data,\n }\n await adapter.put(vault, collName, id, newEnvelope)\n }\n }\n\n // Update caller's keyring with new DEKs\n for (const [collName, newDek] of newDeks) {\n callerKeyring.deks.set(collName, newDek)\n }\n await persistKeyring(adapter, vault, callerKeyring)\n\n // Update all remaining users' keyrings with re-wrapped new DEKs\n const userIds = await adapter.list(vault, '_keyring')\n for (const userId of userIds) {\n if (userId === callerKeyring.userId) continue\n\n const userEnvelope = await adapter.get(vault, '_keyring', userId)\n if (!userEnvelope) continue\n\n const userKeyringFile = JSON.parse(userEnvelope._data) as KeyringFile\n // Note: we can't derive other users' KEKs to re-wrap DEKs for them.\n // Rotation requires users to re-unlock and be re-granted after the caller\n // re-wraps with the raw DEKs held in memory. See rotation flow below.\n // The trick: import the user's KEK from their salt? No — we need their passphrase.\n //\n // Per the spec: the caller (owner/admin) wraps the new DEKs with each remaining\n // user's KEK. But we can't derive their KEK without their passphrase.\n //\n // Real solution from the spec: the caller wraps the DEK using the approach of\n // reading each user's existing wrapping. Since we can't derive their KEK,\n // we use a RE-KEYING approach: the new DEK is wrapped with a key-wrapping-key\n // that we CAN derive — we use the existing wrapped DEK as proof that the user\n // had access, and we replace it with the new wrapped DEK.\n //\n // Practical approach: Since the owner/admin has all raw DEKs in memory,\n // and each user's keyring contains their salt, we need the users to\n // re-authenticate to get the new wrapped keys. This is the standard approach.\n //\n // For NOYDB Phase 2: we'll update the keyring file to include a \"pending_rekey\"\n // flag. Users will get new DEKs on next login when the owner provides them.\n //\n // SIMPLER approach used here: Since the owner performed the rotation,\n // the owner has both old and new DEKs. We store a \"rekey token\" that the\n // user can use to unwrap: we wrap the new DEK with the OLD DEK (which the\n // user can still unwrap from their keyring, since their keyring has the old\n // wrapped DEK and their KEK can unwrap it).\n\n // Actually even simpler: we just need the user's KEK. We don't have it.\n // The spec says the owner wraps new DEKs for each remaining user.\n // This requires knowing each user's KEK (or having a shared secret).\n //\n // The CORRECT implementation from the spec: the owner/admin has all DEKs.\n // Each user's keyring stores DEKs wrapped with THAT USER's KEK.\n // To re-wrap, we need each user's KEK — which we can't get.\n //\n // Real-world solution: use a KEY ESCROW approach where the owner stores\n // each user's wrapping key (not their passphrase, but a key derived from\n // the grant process). During grant, the owner stores a copy of the new user's\n // KEK (wrapped with the owner's KEK) so they can re-wrap later.\n //\n // For now: mark the user's keyring as needing rekey. The user will need to\n // re-authenticate (owner provides new passphrase or re-grants).\n\n // Update: simplest correct approach — during grant, we store the user's KEK\n // wrapped with the owner's KEK in a separate escrow field. Then during rotation,\n // the owner unwraps the user's KEK from escrow and wraps the new DEKs.\n //\n // BUT: that means we need to change the KeyringFile format.\n // For Phase 2 MVP: just delete the user's old DEK entries and require re-grant.\n // This is secure (revoked keys are gone) but inconvenient (remaining users\n // need re-grant for rotated collections).\n\n // PHASE 2 APPROACH: Remove the affected collection DEKs from remaining users'\n // keyrings. The owner must re-grant access to those collections.\n // This is correct and secure — just requires the owner to re-run grant().\n\n const updatedDeks = { ...userKeyringFile.deks }\n for (const collName of collections) {\n delete updatedDeks[collName]\n }\n\n const updatedPermissions = { ...userKeyringFile.permissions }\n for (const collName of collections) {\n delete updatedPermissions[collName]\n }\n\n const updatedKeyring: KeyringFile = {\n ...userKeyringFile,\n deks: updatedDeks,\n permissions: updatedPermissions,\n }\n\n await writeKeyringFile(adapter, vault, userId, updatedKeyring)\n }\n}\n\n// ─── Change Secret ─────────────────────────────────────────────────────\n\n/**\n * Change the user's passphrase. Re-wraps every DEK under the new KEK.\n *\n * Validates the new passphrase against the strength rules unless\n * `allowWeakPassphrase: true` is passed. Mirrors `rotatePassphrase`'s\n * default-on validation contract.\n *\n * `db.rotatePassphrase()` adds a `checkGate('rotate-passphrase')` step\n * on top of this primitive and additionally requires the OLD passphrase\n * for re-derivation; `changeSecret` reuses the cached unlocked KEK so\n * the OLD passphrase is not retyped.\n */\nexport async function changeSecret(\n adapter: NoydbStore,\n vault: string,\n keyring: UnlockedKeyring,\n newPassphrase: string,\n passphraseOpts?: PassphrasePolicy & { allowWeakPassphrase?: boolean },\n): Promise<UnlockedKeyring> {\n if (!passphraseOpts?.allowWeakPassphrase) {\n assertStrongPassphrase(newPassphrase, passphraseOpts)\n }\n const newSalt = generateSalt()\n const newKek = await deriveKey(newPassphrase, newSalt)\n\n // Re-wrap all DEKs with the new KEK\n const wrappedDeks: Record<string, string> = {}\n for (const [collName, dek] of keyring.deks) {\n wrappedDeks[collName] = await wrapKey(dek, newKek)\n }\n\n const canary = await mintKeyringCanary(newKek)\n const keyringFile: KeyringFile = {\n _noydb_keyring: NOYDB_KEYRING_VERSION,\n user_id: keyring.userId,\n display_name: keyring.displayName,\n role: keyring.role,\n permissions: keyring.permissions,\n deks: wrappedDeks,\n salt: bufferToBase64(newSalt),\n created_at: new Date().toISOString(),\n granted_by: keyring.userId,\n canary,\n }\n\n await writeKeyringFile(adapter, vault, keyring.userId, keyringFile)\n\n return {\n userId: keyring.userId,\n displayName: keyring.displayName,\n role: keyring.role,\n permissions: keyring.permissions,\n deks: keyring.deks, // Same DEKs, different wrapping\n kek: newKek,\n salt: newSalt,\n // Tier-2 slots are NOT preserved through `changeSecret` —\n // each slot wraps the OLD KEK, so the new keyring has no\n // authenticator slots until the user re-enrolls. The higher-level\n // `db.rotatePassphrase()` (#10) preserves slots by rewrapping the\n // KEK reference, not the KEK itself.\n authenticators: [],\n ...(keyring.policy !== undefined && { policy: keyring.policy }),\n }\n}\n\n// ─── Bundle recipients ──────────────────────────────────────────\n\n/**\n * Recipient slot in a re-keyed `.noydb` bundle. Each slot becomes its\n * own keyring file inside the bundle, sealed with its own passphrase.\n * Same role/permission semantics as `db.grant()` but no adapter side\n * effect — the slot only exists inside the bundle bytes.\n *\n * @public\n */\nexport interface BundleRecipient {\n /** User id stamped onto the keyring file in the bundle. */\n readonly id: string\n /** Optional display name. Defaults to `id`. */\n readonly displayName?: string\n /** Passphrase the recipient will type to unlock. */\n readonly passphrase: string\n /** Role on the destination vault. Defaults to `'viewer'`. */\n readonly role?: Role\n /**\n * Per-collection permissions. When omitted, role defaults apply.\n * Restricting permissions here ALSO restricts which DEKs are wrapped\n * into the slot — a slot with `{ invoices: 'ro' }` cannot decrypt\n * other collections even though their ciphertext sits in the bundle.\n */\n readonly permissions?: Permissions\n /**\n * Optional `as-*` export grants on the destination vault.\n * Mirrors the `exportCapability` field on a live keyring.\n */\n readonly exportCapability?: ExportCapability\n /**\n * Optional `as-*` import grants on the destination vault.\n * Mirrors the `importCapability` field on a live keyring.\n * Default-closed: no plaintext format granted, no bundle import.\n */\n readonly importCapability?: ImportCapability\n /**\n * Optional bundle-slot expiry. ISO-8601 timestamp; past the\n * cutoff this slot's keyring refuses to load with\n * `KeyringExpiredError`. Time-boxed audit access pattern: \"this\n * slot works for 30 days then becomes opaque to its holder.\"\n */\n readonly expiresAt?: string\n}\n\n/**\n * Build a `KeyringFile` for one bundle recipient, given the source\n * vault's unwrapped DEKs. Mirrors `grant()` minus the adapter write —\n * the produced file is meant to be embedded in the bundle's\n * `keyrings` map, never persisted to the source vault.\n *\n * Privilege-escalation check still runs: every DEK wrapped into the\n * recipient's keyring must come from the source's own DEK set.\n *\n * @internal\n */\nexport async function buildRecipientKeyringFile(\n callerKeyring: UnlockedKeyring,\n recipient: BundleRecipient,\n): Promise<KeyringFile> {\n if (!callerKeyring.kek) {\n throw new ValidationError(\n 'buildRecipientKeyringFile: caller keyring has no KEK — tier-2 wrap-DEKs ' +\n 'and tier-3 PIN-resume sessions cannot create bundle recipients. ' +\n 'Re-authenticate at tier 1 (passphrase) before building a bundle.',\n )\n }\n\n const role: Role = recipient.role ?? 'viewer'\n const permissions = resolvePermissions(role, recipient.permissions)\n\n const newSalt = generateSalt()\n const newKek = await deriveKey(recipient.passphrase, newSalt)\n\n const wrappedDeks: Record<string, string> = {}\n\n // Collections the recipient was explicitly granted permission to.\n for (const collName of Object.keys(permissions)) {\n const dek = callerKeyring.deks.get(collName)\n if (dek) {\n wrappedDeks[collName] = await wrapKey(dek, newKek)\n }\n }\n\n // owner / admin / viewer: wrap every known DEK (matches grant).\n if (role === 'owner' || role === 'admin' || role === 'viewer') {\n for (const [collName, dek] of callerKeyring.deks) {\n if (!(collName in wrappedDeks)) {\n wrappedDeks[collName] = await wrapKey(dek, newKek)\n }\n }\n }\n\n // Always propagate system-prefixed collection DEKs (`_ledger`, etc.) —\n // the recipient needs them to verify the bundle on import.\n for (const [collName, dek] of callerKeyring.deks) {\n if (collName.startsWith('_') && !(collName in wrappedDeks)) {\n wrappedDeks[collName] = await wrapKey(dek, newKek)\n }\n }\n\n // Anti-privilege-escalation: every wrapped DEK must come from the\n // caller's own DEK set. Belt-and-braces with the lookups above.\n for (const collName of Object.keys(wrappedDeks)) {\n if (!callerKeyring.deks.has(collName)) {\n throw new PrivilegeEscalationError(collName)\n }\n }\n\n const canary = await mintKeyringCanary(newKek)\n return {\n _noydb_keyring: NOYDB_KEYRING_VERSION,\n user_id: recipient.id,\n display_name: recipient.displayName ?? recipient.id,\n role,\n permissions,\n deks: wrappedDeks,\n salt: bufferToBase64(newSalt),\n created_at: new Date().toISOString(),\n granted_by: callerKeyring.userId,\n canary,\n ...(recipient.exportCapability !== undefined\n ? { export_capability: recipient.exportCapability }\n : {}),\n ...(recipient.importCapability !== undefined\n ? { import_capability: recipient.importCapability }\n : {}),\n ...(recipient.expiresAt !== undefined\n ? { expires_at: recipient.expiresAt }\n : {}),\n }\n}\n\n// ─── List Users ────────────────────────────────────────────────────────\n\n/** List all users with access to a vault. */\nexport async function listUsers(\n adapter: NoydbStore,\n vault: string,\n): Promise<UserInfo[]> {\n const userIds = await adapter.list(vault, '_keyring')\n const users: UserInfo[] = []\n\n for (const userId of userIds) {\n const envelope = await adapter.get(vault, '_keyring', userId)\n if (!envelope) continue\n const kf = JSON.parse(envelope._data) as KeyringFile\n users.push({\n userId: kf.user_id,\n displayName: kf.display_name,\n role: kf.role,\n permissions: kf.permissions,\n createdAt: kf.created_at,\n grantedBy: kf.granted_by,\n })\n }\n\n return users\n}\n\n/**\n * Optional filter knobs for {@link listUsersWithEnvelopes}.\n *\n * - `includeHidden` — when true, principals with `_meta/visibility/<id>`\n * set to `{ hidden: true }` are returned alongside everyone else.\n * Requires `owner` or `admin` callerRole; lower roles get\n * {@link import('../errors.js').PermissionDeniedError}.\n */\nexport interface ListUsersOptions {\n readonly includeHidden?: boolean\n}\n\n/**\n * Joined enumeration: every keyring + its `_users/<keyringId>`\n * envelope side by side. Convenience for admin UIs that want to\n * render team-member lists with profile data (\"Bob — operator —\n * 'Bob the Auditor' avatar X locale fr-FR\") in a single pass.\n *\n * `userEnvelopeDek` is the vault's `_users` collection DEK\n * (`vault.getDEK('_users')`); used to decrypt every envelope.\n *\n * `callerRole` (#122) drives the directory-visibility checks:\n *\n * - When the vault's `_meta/directory` document has `enabled: false`,\n * only `owner` and `admin` callers may enumerate; anyone else gets\n * {@link import('../errors.js').DirectoryDisabledError}.\n * - Principals with `_meta/visibility/<id>` set to `{ hidden: true }`\n * are filtered out by default. `owner`/`admin` callers can pass\n * `{ includeHidden: true }` to see them; lower roles passing that\n * option get `PermissionDeniedError`.\n *\n * Honest caveat (#122): these filters are a UX hint, not a security\n * boundary. The keyring file is still listed at `_keyring/*` and the\n * envelope ciphertext at `_users/*`. A caller with direct store access\n * — or a caller that calls this function with `callerRole: 'owner'`\n * unconditionally — sees every principal. The protection is only as\n * strong as the role the calling layer passes in. The hub-level wrapper\n * on `Vault` sources `callerRole` from the unlocked keyring's `role`\n * field, which is signed-by-construction (it lives in the user's own\n * keyring file). See `docs/subsystems/user-envelope.md` →\n * \"Directory visibility\".\n *\n * Principals without a persisted envelope (legacy keyrings predating\n * the user-envelope feature) come back with `envelope: null`. The\n * caller chooses how to render — usually \"fall back to keyring's\n * `displayName`\".\n *\n * Order matches `listUsers()` (store-defined; sort if you need a\n * stable display order).\n */\nexport async function listUsersWithEnvelopes<T = unknown>(\n adapter: NoydbStore,\n vault: string,\n userEnvelopeDek: CryptoKey,\n callerRole: Role,\n options: ListUsersOptions = {},\n): Promise<Array<{ user: UserInfo; envelope: UserEnvelopeReader<T> | null }>> {\n const isPrivileged = callerRole === 'owner' || callerRole === 'admin'\n\n // 1. Vault-level directory toggle.\n const dirConfig = await readDirectoryConfig(adapter, vault)\n if (dirConfig?.enabled === false && !isPrivileged) {\n throw new DirectoryDisabledError(vault)\n }\n\n // 2. `includeHidden` requires admin/owner.\n if (options.includeHidden && !isPrivileged) {\n throw new PermissionDeniedError(\n 'Permission denied — listUsersWithEnvelopes({ includeHidden: true }) requires owner or admin role',\n )\n }\n\n const users = await listUsers(adapter, vault)\n const out: Array<{ user: UserInfo; envelope: UserEnvelopeReader<T> | null }> = []\n for (const user of users) {\n if (!options.includeHidden) {\n const visibility = await readUserVisibility(adapter, vault, user.userId)\n if (visibility?.hidden) continue\n }\n const envelope = await loadUserEnvelopeFn<T>(\n adapter,\n vault,\n user.userId,\n userEnvelopeDek,\n )\n out.push({ user, envelope })\n }\n return out\n}\n\n\n// ─── DEK Management ────────────────────────────────────────────────────\n\n/** Ensure a DEK exists for a collection. Generates one if new. */\nexport async function ensureCollectionDEK(\n adapter: NoydbStore,\n vault: string,\n keyring: UnlockedKeyring,\n): Promise<(collectionName: string) => Promise<CryptoKey>> {\n // Dedupe concurrent first-time DEK creates per collection. Without\n // this, two concurrent `getDEK('foo')` calls both pass the `existing`\n // check (the Map is empty), both generate fresh DEKs, and the second\n // `set` overwrites the first — making any envelope encrypted with\n // the discarded DEK fail to decrypt later (TamperedError on read).\n // Pre-existing race exposed by the multi-writer ledger work in #296.\n const inFlight = new Map<string, Promise<CryptoKey>>()\n return async (collectionName: string): Promise<CryptoKey> => {\n const existing = keyring.deks.get(collectionName)\n if (existing) return existing\n const pending = inFlight.get(collectionName)\n if (pending) return pending\n\n const promise = (async () => {\n const dek = await generateDEK()\n keyring.deks.set(collectionName, dek)\n await persistKeyring(adapter, vault, keyring)\n return dek\n })()\n inFlight.set(collectionName, promise)\n try {\n return await promise\n } finally {\n inFlight.delete(collectionName)\n }\n }\n}\n\n// ─── Permission Checks ─────────────────────────────────────────────────\n\n/** Check if a user has write permission for a collection. */\nexport function hasWritePermission(keyring: UnlockedKeyring, collectionName: string): boolean {\n if (keyring.role === 'owner' || keyring.role === 'admin') return true\n if (keyring.role === 'viewer' || keyring.role === 'client') return false\n return keyring.permissions[collectionName] === 'rw'\n}\n\n/** Check if a user has any access to a collection. */\nexport function hasAccess(keyring: UnlockedKeyring, collectionName: string): boolean {\n if (keyring.role === 'owner' || keyring.role === 'admin' || keyring.role === 'viewer') return true\n return collectionName in keyring.permissions\n}\n\n// ─── Helpers ───────────────────────────────────────────────────────────\n\n/** Persist a keyring file to the adapter. */\nexport async function persistKeyring(\n adapter: NoydbStore,\n vault: string,\n keyring: UnlockedKeyring,\n): Promise<void> {\n if (!keyring.kek) {\n throw new ValidationError(\n 'persistKeyring: keyring.kek is null — cannot wrap DEKs without the KEK. ' +\n 'This typically means the keyring was opened via tier-3 PIN resume, ' +\n 'session restore, or a wrap-DEKs tier-2 unlock. Re-authenticate at ' +\n 'tier 1 (passphrase) before persisting.',\n )\n }\n const wrappedDeks: Record<string, string> = {}\n for (const [collName, dek] of keyring.deks) {\n wrappedDeks[collName] = await wrapKey(dek, keyring.kek)\n }\n const canary = await mintKeyringCanary(keyring.kek)\n\n const keyringFile: KeyringFile = {\n _noydb_keyring: NOYDB_KEYRING_VERSION,\n user_id: keyring.userId,\n display_name: keyring.displayName,\n role: keyring.role,\n permissions: keyring.permissions,\n deks: wrappedDeks,\n salt: bufferToBase64(keyring.salt),\n created_at: new Date().toISOString(),\n granted_by: keyring.userId,\n canary,\n ...(keyring.exportCapability !== undefined && { export_capability: keyring.exportCapability }),\n ...(keyring.importCapability !== undefined && { import_capability: keyring.importCapability }),\n ...(keyring.authenticators.length > 0 && { authenticators: keyring.authenticators }),\n ...(keyring.policy !== undefined && { policy: keyring.policy }),\n }\n\n await writeKeyringFile(adapter, vault, keyring.userId, keyringFile)\n}\n\n// ─── Export capability ──────────────────────────────────────\n\n/**\n * Role-based default policy for the encrypted-bundle capability.\n *\n * Applied when `keyring.exportCapability` is absent or\n * `exportCapability.bundle` is undefined:\n *\n * - `owner` / `admin` → `true` (happy-path backup without friction)\n * - `operator` / `viewer` / `client` → `false` (explicit grant required)\n *\n * Rationale: a bundle is inert without the KEK, so an owner backing up\n * their own vault doesn't need friction; a non-admin role producing a\n * bundle for an external party does, because the bundle outlives\n * keyring revocation.\n */\nfunction defaultBundleCapability(role: Role): boolean {\n return role === 'owner' || role === 'admin'\n}\n\n/**\n * Check whether a keyring is authorised for a given `@noy-db/as-*`\n * export tier.\n *\n * - `tier: 'plaintext'` — returns true iff `exportCapability.plaintext`\n * contains the requested `format` or the `'*'` wildcard. Default for\n * every role is empty — no grant, no plaintext export.\n * - `tier: 'bundle'` — returns `exportCapability.bundle` if present, or\n * the role-based default otherwise (owner/admin → true, else false).\n *\n * `@noy-db/as-*` packages MUST call this before invoking the underlying\n * export primitive. Rogue forks that skip the check are caught by code\n * review — the single-entry-point contract is a convention, not a\n * runtime invariant. Vault-level gated wrappers\n * (`vault.exportRecords` / `exportBlobs` / `writeBundle`) will land in a\n * follow-up PR to enforce at the primitive level.\n */\nexport function hasExportCapability(\n keyring: UnlockedKeyring,\n tier: 'plaintext',\n format: ExportFormat,\n): boolean\nexport function hasExportCapability(\n keyring: UnlockedKeyring,\n tier: 'bundle',\n): boolean\nexport function hasExportCapability(\n keyring: UnlockedKeyring,\n tier: 'plaintext' | 'bundle',\n format?: ExportFormat,\n): boolean {\n const cap = keyring.exportCapability\n if (tier === 'plaintext') {\n const allowed = cap?.plaintext ?? []\n return allowed.includes('*') || (format !== undefined && allowed.includes(format))\n }\n // tier === 'bundle'\n return cap?.bundle ?? defaultBundleCapability(keyring.role)\n}\n\n/**\n * Same-shape inspector for an `ExportCapability` value that isn't yet\n * attached to a keyring (e.g. for previewing a grant before applying).\n * Role must be supplied separately so bundle defaults can be computed.\n */\nexport function evaluateExportCapability(\n capability: ExportCapability | undefined,\n role: Role,\n tier: 'plaintext',\n format: ExportFormat,\n): boolean\nexport function evaluateExportCapability(\n capability: ExportCapability | undefined,\n role: Role,\n tier: 'bundle',\n): boolean\nexport function evaluateExportCapability(\n capability: ExportCapability | undefined,\n role: Role,\n tier: 'plaintext' | 'bundle',\n format?: ExportFormat,\n): boolean {\n if (tier === 'plaintext') {\n const allowed = capability?.plaintext ?? []\n return allowed.includes('*') || (format !== undefined && allowed.includes(format))\n }\n return capability?.bundle ?? defaultBundleCapability(role)\n}\n\n// ─── Import capability (issue ) ────────────────────────────────────\n\n/**\n * Check whether a keyring is authorised for a given `@noy-db/as-*`\n * import tier (issue ).\n *\n * - `tier: 'plaintext'` — true iff `importCapability.plaintext`\n * contains the requested `format` or the `'*'` wildcard.\n * - `tier: 'bundle'` — true iff `importCapability.bundle === true`.\n *\n * **Default-closed for every role on every dimension** — including\n * owner. Import is more dangerous than export (corrupts vs leaks), so\n * the policy refuses to assume intent. Owners must positively grant\n * the capability via `vault.grant({ importCapability: ... })`.\n */\nexport function hasImportCapability(\n keyring: UnlockedKeyring,\n tier: 'plaintext',\n format: ExportFormat,\n): boolean\nexport function hasImportCapability(\n keyring: UnlockedKeyring,\n tier: 'bundle',\n): boolean\nexport function hasImportCapability(\n keyring: UnlockedKeyring,\n tier: 'plaintext' | 'bundle',\n format?: ExportFormat,\n): boolean {\n const cap = keyring.importCapability\n if (tier === 'plaintext') {\n const allowed = cap?.plaintext ?? []\n return allowed.includes('*') || (format !== undefined && allowed.includes(format))\n }\n // tier === 'bundle' — closed default for every role\n return cap?.bundle === true\n}\n\n/**\n * Same-shape inspector for an `ImportCapability` value that isn't yet\n * attached to a keyring (e.g. previewing a grant before applying).\n * `role` is accepted for symmetry with `evaluateExportCapability` even\n * though the import policy ignores it — bundle defaults are\n * role-agnostic and closed.\n */\nexport function evaluateImportCapability(\n capability: ImportCapability | undefined,\n role: Role,\n tier: 'plaintext',\n format: ExportFormat,\n): boolean\nexport function evaluateImportCapability(\n capability: ImportCapability | undefined,\n role: Role,\n tier: 'bundle',\n): boolean\nexport function evaluateImportCapability(\n capability: ImportCapability | undefined,\n _role: Role,\n tier: 'plaintext' | 'bundle',\n format?: ExportFormat,\n): boolean {\n if (tier === 'plaintext') {\n const allowed = capability?.plaintext ?? []\n return allowed.includes('*') || (format !== undefined && allowed.includes(format))\n }\n return capability?.bundle === true\n}\n\nfunction resolvePermissions(role: Role, explicit?: Permissions): Permissions {\n if (role === 'owner' || role === 'admin' || role === 'viewer') return {}\n return explicit ?? {}\n}\n\nasync function writeKeyringFile(\n adapter: NoydbStore,\n vault: string,\n userId: string,\n keyringFile: KeyringFile,\n): Promise<void> {\n const envelope = {\n _noydb: 1 as const,\n _v: 1,\n _ts: new Date().toISOString(),\n _iv: '',\n _data: JSON.stringify(keyringFile),\n }\n await adapter.put(vault, '_keyring', userId, envelope)\n}\n","/**\n * Managed-passphrase mode — issue #14, rubber-hose-resistant vaults.\n *\n * A vault mode where the passphrase is machine-generated and never\n * exposed to the user, sealed under a developer-provided\n * {@link SealingKeyProvider} (macOS Keychain, Windows Credential\n * Manager, libsecret, AWS KMS, …). The user has no secret to give\n * up to coercion — they can't reveal what they don't know.\n *\n * ## Components in this file\n *\n * - {@link SealingKeyProvider} — the interface concrete providers\n * implement. Provider implementations live OUTSIDE hub (per-\n * platform packages).\n * - {@link MemorySealingKeyProvider} — in-memory test provider; uses\n * a deterministic per-instance \"key\" so two providers with\n * different ids cannot unseal each other's outputs.\n * - {@link RecipientHint} — public material a sender uses to seal\n * plaintext for a specific recipient; published by\n * {@link RecipientSealer.publishRecipientHint} and transported\n * out-of-band to the sender before bundle writes.\n * - {@link RecipientSealer} — interface for asymmetric/granted\n * providers that support recipient-target sealing (RSA-OAEP,\n * cloud-KMS asymmetric, etc.); distinct from self-only\n * {@link SealingKeyProvider} (macOS Keychain, WebAuthn-PRF).\n * - {@link MemoryRecipientSealer} — in-process reference\n * implementation of both `RecipientSealer` and\n * `SealingKeyProvider` using real WebCrypto RSA-OAEP + AES-GCM;\n * safe for tests and same-process sender/recipient scenarios.\n * - {@link loadSealedPassphrase} / {@link saveSealedPassphrase} —\n * plaintext envelope storage at `_meta/sealed-passphrase`.\n * Mirrors the `_meta/handle` and `_meta/public-envelope` AES-\n * GCM-bypassed patterns. The sealing layer (provider's job)\n * is the security boundary; hub doesn't have a key to encrypt\n * with at this layer — that's the whole point of the design.\n * - {@link resolveManagedSecret} — orchestrates the \"generate +\n * seal + persist on first open; unseal on reopen\" flow.\n * Returns the plaintext passphrase string that the rest of the\n * `createNoydb` keyring path consumes.\n *\n * Slice 1 of #14. Deferred to follow-ups:\n * - Block `rotate-passphrase` policy gate under managed mode.\n * - Mandatory strong-recovery enforcement (depends on #10).\n * - Recovery flow under managed mode (generates fresh sealed phrase).\n *\n * @see docs/subsystems/session-tiers.md → Managed-passphrase mode\n *\n * @module\n */\n\nimport type { NoydbStore, EncryptedEnvelope } from '../types.js'\nimport { NOYDB_FORMAT_VERSION } from '../types.js'\n\n/**\n * The contract concrete providers (per-platform key stores) implement\n * to seal and unseal a hub-generated random passphrase. The plaintext\n * passphrase NEVER leaves hub-controlled memory in unsealed form —\n * the provider receives the bytes, returns opaque sealed bytes, and\n * later reverses the operation. Hub treats the sealed bytes as\n * fully opaque.\n *\n * Implementations live OUTSIDE `@noy-db/hub` (separate packages\n * per the issue's \"Concrete providers (live outside hub)\" note):\n *\n * | Platform | Package (TBD) | Backing |\n * |---|---|---|\n * | macOS | `@noy-db/seal-macos-keychain` | Security.framework |\n * | Windows | `@noy-db/seal-wincred` | Credential Manager |\n * | Linux | `@noy-db/seal-libsecret` | libsecret / secret-service |\n * | Cloud / server | `@noy-db/seal-aws-kms` | AWS KMS Decrypt |\n */\nexport interface SealingKeyProvider {\n /**\n * Non-sensitive identifier disclosed in the persisted envelope.\n * Surfaced to consumers via `loadSealedPassphrase().providerId` so\n * a vault opened with the wrong provider class can detect the\n * mismatch and surface a clear error. NOT secret — fine to log.\n *\n * Suggested format: `<family>:<scope>` — e.g. `macos-keychain:com.acme.app`,\n * `aws-kms:arn:aws:kms:us-east-1:123:key/abc`. The hub never\n * parses this; it's purely audit metadata.\n */\n readonly id: string\n\n /** Seal raw passphrase bytes. Output bytes are opaque to hub. */\n seal(passphrase: Uint8Array): Promise<Uint8Array>\n\n /**\n * Reverse {@link seal}. MUST throw on tamper, wrong-provider, or\n * any other failure — hub treats a thrown error as \"this provider\n * cannot unlock this vault\" and surfaces it to the caller.\n */\n unseal(sealed: Uint8Array): Promise<Uint8Array>\n}\n\n/**\n * In-memory test provider. NOT secure — uses a deterministic\n * per-instance \"key\" (16-byte SHA-256 of `id`) XOR'd over the\n * passphrase plus a 4-byte provider-id fingerprint prefix. The XOR is\n * sufficient to make different `id` values produce mutually-unsealable\n * outputs (the contract tests for that), but offers ZERO real\n * confidentiality — never use outside tests.\n *\n * Replace with a real platform provider in production.\n */\nexport class MemorySealingKeyProvider implements SealingKeyProvider {\n readonly id: string\n private readonly fingerprint: Uint8Array\n private readonly keyBytes: Uint8Array\n\n constructor(opts: { id: string }) {\n this.id = opts.id\n // Deterministic 4-byte fingerprint of the provider id, prepended\n // to every sealed output so we can detect \"wrong provider\" at\n // unseal time without leaking anything sensitive about either\n // provider's actual key material.\n const encoded = new TextEncoder().encode(opts.id)\n let h = 0\n for (let i = 0; i < encoded.length; i++) {\n h = (h * 31 + encoded[i]!) >>> 0\n }\n this.fingerprint = new Uint8Array([\n (h >>> 24) & 0xff, (h >>> 16) & 0xff, (h >>> 8) & 0xff, h & 0xff,\n ])\n // Deterministic 16-byte \"key\" derived from the id by repeating\n // the fingerprint with offsets. Good enough for the XOR-stream\n // test cipher; never confuse this with real key derivation.\n this.keyBytes = new Uint8Array(16)\n for (let i = 0; i < 16; i++) {\n this.keyBytes[i] = this.fingerprint[i % 4]! ^ (i * 17)\n }\n }\n\n async seal(passphrase: Uint8Array): Promise<Uint8Array> {\n const out = new Uint8Array(4 + passphrase.length)\n out.set(this.fingerprint, 0)\n for (let i = 0; i < passphrase.length; i++) {\n out[4 + i] = passphrase[i]! ^ this.keyBytes[i % 16]!\n }\n return out\n }\n\n async unseal(sealed: Uint8Array): Promise<Uint8Array> {\n if (sealed.length < 4) {\n throw new Error('MemorySealingKeyProvider: sealed input too short')\n }\n for (let i = 0; i < 4; i++) {\n if (sealed[i] !== this.fingerprint[i]) {\n throw new Error(\n `MemorySealingKeyProvider(\"${this.id}\"): provider-id mismatch on unseal `\n + '(sealed bytes were produced by a different provider)',\n )\n }\n }\n const body = sealed.subarray(4)\n const out = new Uint8Array(body.length)\n for (let i = 0; i < body.length; i++) {\n out[i] = body[i]! ^ this.keyBytes[i % 16]!\n }\n return out\n }\n}\n\n/**\n * Public material a sender uses to seal-for-this-recipient. Published by\n * a recipient's RecipientSealer; transported to the sender out-of-band\n * (email, S3, in-app message). The sender obtains the hint, supplies it\n * to writeNoydbBundle's sealedCredentials.perUser[userId].hint, and the\n * hub seals each user's credential against it. Per foundation §11.4.\n */\nexport type RecipientHint = {\n readonly v: 1\n /** Recipient's provider id; matches the SealedAutoUnlockEntry.pid they'll unseal under. */\n readonly pid: string\n /** Algorithm the sender uses to produce the seal. Slice 1 ships RSA-OAEP-SHA256 only. */\n readonly alg: 'rsa-oaep-sha256'\n /** Public material — alg-specific. For 'rsa-oaep-sha256': { publicKeyPem: string }. */\n readonly material: Readonly<Record<string, unknown>>\n}\n\n/**\n * Handover-capable provider. Implemented additionally by asymmetric/granted\n * providers (cloud-KMS asymmetric, Azure RSA Key Vault, AWS KMS with grant).\n * Self-only providers (macOS Keychain, env-var, WebAuthn-PRF) do NOT\n * implement this — the §11.2 capability matrix lives in the type system.\n *\n * Per foundation §11.4. A function that requires recipient-target sealing\n * takes `RecipientSealer`, not `SealingKeyProvider` — the compiler rejects\n * passing a self-only provider at the spec site.\n */\nexport interface RecipientSealer {\n readonly id: string\n /** Produce hint material a sender uses to seal-for-this-recipient. */\n publishRecipientHint(): Promise<RecipientHint>\n /**\n * Seal plaintext for the recipient described by `hint`. Returns opaque\n * bytes — same contract as `SealingKeyProvider.seal()`. The bundle\n * layer base64-encodes the bytes into `SealedAutoUnlockEntry.sealed`\n * without inspecting them.\n */\n sealForRecipient(plaintext: Uint8Array, hint: RecipientHint): Promise<Uint8Array>\n}\n\n/**\n * Reference implementation of `RecipientSealer` + `SealingKeyProvider`.\n * Uses WebCrypto RSA-OAEP-SHA256 (2048-bit) to wrap a fresh 32-byte\n * AES-GCM CEK, AES-GCM-encrypts plaintext under it, and packs the\n * result into a self-describing TLV:\n *\n * byte 0 : version (0x01)\n * bytes 1..256 : RSA-OAEP-wrapped CEK (fixed 256 bytes at RSA-2048)\n * bytes 257..268: AES-GCM IV (12 bytes)\n * bytes 269.. : AES-GCM ciphertext ‖ 16-byte tag\n *\n * Implements BOTH interfaces. `seal(plaintext)` (self-target) is just\n * `sealForRecipient(plaintext, this own hint)` — same TLV. Convenient\n * for tests where one provider plays both ends. Real cloud providers\n * (`at-aws-kms`, etc.) will pick their own internal layouts; the only\n * contract is round-trip identity.\n *\n * SAFE for production within its scope — the cryptography is real\n * (RSA-OAEP + AES-GCM via WebCrypto), but the keypair lives in-process\n * and is regenerated on every construction. Not suitable as a managed\n * keychain; use it for tests and for shipping bundles where the\n * recipient instance lives in the same process as the sender (rare).\n */\nexport class MemoryRecipientSealer implements SealingKeyProvider, RecipientSealer {\n readonly id: string\n private readonly keypair: Promise<CryptoKeyPair>\n\n constructor(opts: { id: string }) {\n this.id = opts.id\n this.keypair = crypto.subtle.generateKey(\n { name: 'RSA-OAEP', modulusLength: 2048, publicExponent: new Uint8Array([1, 0, 1]), hash: 'SHA-256' },\n true,\n ['encrypt', 'decrypt'],\n )\n }\n\n async publishRecipientHint(): Promise<RecipientHint> {\n const { publicKey } = await this.keypair\n const spki = await crypto.subtle.exportKey('spki', publicKey)\n const pem = '-----BEGIN PUBLIC KEY-----\\n'\n + bytesToBase64(new Uint8Array(spki)).match(/.{1,64}/g)!.join('\\n')\n + '\\n-----END PUBLIC KEY-----\\n'\n return { v: 1, pid: this.id, alg: 'rsa-oaep-sha256', material: { publicKeyPem: pem } }\n }\n\n async sealForRecipient(plaintext: Uint8Array, hint: RecipientHint): Promise<Uint8Array> {\n if (hint.v !== 1) {\n throw new Error(`MemoryRecipientSealer.sealForRecipient: unsupported hint.v ${String(hint.v)} (expected 1)`)\n }\n if (hint.alg !== 'rsa-oaep-sha256') {\n throw new Error(`MemoryRecipientSealer.sealForRecipient: unsupported hint.alg '${String(hint.alg)}' (expected 'rsa-oaep-sha256')`)\n }\n const pem = hint.material['publicKeyPem']\n if (typeof pem !== 'string') {\n throw new Error('MemoryRecipientSealer.sealForRecipient: hint.material.publicKeyPem missing or not a string')\n }\n // Parse PEM → SPKI bytes.\n const b64 = pem.replace(/-----BEGIN PUBLIC KEY-----/, '').replace(/-----END PUBLIC KEY-----/, '').replace(/\\s+/g, '')\n const spki = base64ToBytes(b64)\n const recipientPub = await crypto.subtle.importKey(\n 'spki', spki as BufferSource,\n { name: 'RSA-OAEP', hash: 'SHA-256' },\n false, ['encrypt'],\n )\n // Mint fresh CEK + IV, AES-GCM encrypt plaintext.\n const cekBytes = crypto.getRandomValues(new Uint8Array(32))\n const cek = await crypto.subtle.importKey('raw', cekBytes as BufferSource, 'AES-GCM', false, ['encrypt'])\n const iv = crypto.getRandomValues(new Uint8Array(12))\n const ct = new Uint8Array(await crypto.subtle.encrypt({ name: 'AES-GCM', iv: iv as BufferSource }, cek, plaintext as BufferSource))\n // RSA-OAEP-wrap the CEK bytes.\n const wrapped = new Uint8Array(await crypto.subtle.encrypt({ name: 'RSA-OAEP' }, recipientPub, cekBytes as BufferSource))\n cekBytes.fill(0)\n if (wrapped.length !== 256) {\n throw new Error(`MemoryRecipientSealer.sealForRecipient: expected 256-byte RSA-OAEP wrap, got ${wrapped.length}`)\n }\n // TLV layout.\n const out = new Uint8Array(1 + 256 + 12 + ct.length)\n out[0] = 0x01\n out.set(wrapped, 1)\n out.set(iv, 1 + 256)\n out.set(ct, 1 + 256 + 12)\n return out\n }\n\n async seal(plaintext: Uint8Array): Promise<Uint8Array> {\n const hint = await this.publishRecipientHint()\n return this.sealForRecipient(plaintext, hint)\n }\n\n async unseal(bytes: Uint8Array): Promise<Uint8Array> {\n if (bytes.length < 1 + 256 + 12 + 16) {\n throw new Error('MemoryRecipientSealer.unseal: sealed input too short')\n }\n if (bytes[0] !== 0x01) {\n throw new Error(`MemoryRecipientSealer.unseal: unknown TLV version ${bytes[0]}`)\n }\n const wrapped = bytes.subarray(1, 1 + 256)\n const iv = bytes.subarray(1 + 256, 1 + 256 + 12)\n const ct = bytes.subarray(1 + 256 + 12)\n const { privateKey } = await this.keypair\n const cekBytes = new Uint8Array(await crypto.subtle.decrypt({ name: 'RSA-OAEP' }, privateKey, wrapped as BufferSource))\n const cek = await crypto.subtle.importKey('raw', cekBytes as BufferSource, 'AES-GCM', false, ['decrypt'])\n const pt = new Uint8Array(await crypto.subtle.decrypt({ name: 'AES-GCM', iv: iv as BufferSource }, cek, ct as BufferSource))\n cekBytes.fill(0)\n return pt\n }\n}\n\n// ─── Persisted envelope ────────────────────────────────────────────────\n\n/** Reserved id for the managed-passphrase envelope under `_meta`. */\nexport const SEALED_PASSPHRASE_RECORD_ID = 'sealed-passphrase' as const\n\n/** Plaintext payload stored inside the `_meta/sealed-passphrase` envelope. */\nexport interface SealedPassphrase {\n readonly _noydb_sealed: 1\n readonly providerId: string\n /** Sealed bytes. Base64-encoded on the wire; decoded on load. */\n readonly sealed: Uint8Array\n}\n\n/**\n * Wire-format envelope persisted at `_meta/sealed-passphrase` for\n * managed-mode vaults. The provider produces raw sealed bytes via\n * {@link SealingKeyProvider.seal}; this wrapper carries the dispatch\n * metadata hub needs to pick the right provider on the unseal path.\n *\n * Stability boundary: once shipped, the wire format only grows by\n * adding optional fields. See the at-* sealing dimension foundation\n * doc, §11.9.1.\n *\n * v1 shape (this release): `{ v: 1, _noydb_sealed: 1, pid, payload }`.\n *\n * Legacy shape (pre.14, pre.15): `{ _noydb_sealed: 1, providerId, sealed }`\n * — accepted on read for backwards compatibility; never produced on\n * write going forward.\n */\nexport interface SealedEnvelope {\n /** Envelope schema version. v1 is the shape shipped in pre.16. */\n readonly v: 1\n /** Magic marker for forensics + legacy-shape detection. */\n readonly _noydb_sealed: 1\n /** Matches the producing provider's `.id`. Dispatch key on unseal. */\n readonly pid: string\n /** Sealed bytes from the provider, base64-encoded on the wire. */\n readonly payload: string\n}\n\nfunction bytesToBase64(bytes: Uint8Array): string {\n let binary = ''\n for (let i = 0; i < bytes.length; i++) binary += String.fromCharCode(bytes[i]!)\n return btoa(binary)\n}\n\nfunction base64ToBytes(b64: string): Uint8Array {\n const binary = atob(b64)\n const out = new Uint8Array(binary.length)\n for (let i = 0; i < binary.length; i++) out[i] = binary.charCodeAt(i)\n return out\n}\n\n/**\n * Parse a `_meta/sealed-passphrase` `_data` JSON string into the\n * in-memory {@link SealedPassphrase} representation. Accepts both:\n *\n * 1. v1 wire format `{ v: 1, _noydb_sealed: 1, pid, payload }` —\n * the shape produced from pre.16 onward.\n * 2. Legacy wire format `{ _noydb_sealed: 1, providerId, sealed }` —\n * the shape produced in pre.14/pre.15. Read-only; never written\n * going forward.\n *\n * Returns `undefined` for any input that doesn't match either shape,\n * so callers can fall back to \"no managed-mode envelope present.\"\n *\n * @internal — exported only for the migration safety-net test suite.\n */\nexport function parseSealedEnvelope(raw: unknown): SealedPassphrase | undefined {\n if (typeof raw !== 'object' || raw === null) return undefined\n const r = raw as Record<string, unknown>\n if (r._noydb_sealed !== 1) return undefined\n\n // v1 shape — preferred.\n if (\n r.v === 1\n && typeof r.pid === 'string'\n && typeof r.payload === 'string'\n ) {\n return {\n _noydb_sealed: 1,\n providerId: r.pid,\n sealed: base64ToBytes(r.payload),\n }\n }\n\n // Legacy shape — pre.14 / pre.15. Accept on read for compat.\n if (\n typeof r.providerId === 'string'\n && typeof r.sealed === 'string'\n ) {\n return {\n _noydb_sealed: 1,\n providerId: r.providerId,\n sealed: base64ToBytes(r.sealed),\n }\n }\n\n return undefined\n}\n\nexport async function saveSealedPassphrase(\n store: NoydbStore,\n vault: string,\n payload: { readonly providerId: string; readonly sealed: Uint8Array },\n): Promise<void> {\n const persisted: SealedEnvelope = {\n v: 1,\n _noydb_sealed: 1,\n pid: payload.providerId,\n payload: bytesToBase64(payload.sealed),\n }\n const prior = await store.get(vault, '_meta', SEALED_PASSPHRASE_RECORD_ID)\n const env: EncryptedEnvelope = {\n _noydb: NOYDB_FORMAT_VERSION,\n _v: (prior?._v ?? 0) + 1,\n _ts: new Date().toISOString(),\n // AES-GCM bypassed — the sealing layer is the security boundary.\n _iv: '',\n _data: JSON.stringify(persisted),\n }\n await store.put(vault, '_meta', SEALED_PASSPHRASE_RECORD_ID, env)\n}\n\nexport async function loadSealedPassphrase(\n store: NoydbStore,\n vault: string,\n): Promise<SealedPassphrase | undefined> {\n const envelope = await store.get(vault, '_meta', SEALED_PASSPHRASE_RECORD_ID)\n if (!envelope) return undefined\n try {\n return parseSealedEnvelope(JSON.parse(envelope._data))\n } catch {\n return undefined\n }\n}\n\n// ─── createNoydb orchestration ─────────────────────────────────────────\n\n/**\n * Resolve the effective plaintext passphrase string for a managed-mode\n * vault. Two paths:\n *\n * 1. **First open (no envelope persisted):** generate a 256-bit random\n * via `crypto.getRandomValues`, base64-encode for use as a\n * passphrase string, seal the underlying bytes under the\n * provider, persist `_meta/sealed-passphrase`, return the\n * base64 string.\n *\n * 2. **Reopen (envelope exists):** read + unseal + decode → return.\n * A different provider whose `seal` output disagrees on the\n * stored bytes throws here, surfaced as a clear error.\n *\n * The returned string is the same shape that `secret:` would take in\n * standard mode — the rest of the keyring path consumes it\n * unchanged.\n *\n * @internal — called from `createNoydb` / `getKeyringInternal`.\n */\nexport async function resolveManagedSecret(\n store: NoydbStore,\n vault: string,\n provider: SealingKeyProvider,\n): Promise<string> {\n const existing = await loadSealedPassphrase(store, vault)\n if (existing) {\n if (existing.providerId !== provider.id) {\n throw new Error(\n `Managed-mode vault \"${vault}\" was sealed under provider id `\n + `\"${existing.providerId}\" but the current SealingKeyProvider is `\n + `\"${provider.id}\". Pass the same provider that originally enrolled `\n + 'the vault, or treat this as a fresh enrollment and clear '\n + '`_meta/sealed-passphrase` first.',\n )\n }\n const plaintext = await provider.unseal(existing.sealed)\n return bytesToBase64(plaintext)\n }\n\n // First open: mint a 256-bit random, seal, persist.\n const random = new Uint8Array(32)\n globalThis.crypto.getRandomValues(random)\n const sealed = await provider.seal(random)\n await saveSealedPassphrase(store, vault, { providerId: provider.id, sealed })\n return bytesToBase64(random)\n}\n","/**\n * RFC 6902 JSON Patch — compute + apply.\n *\n * This module is the \"delta history\" primitive: instead of\n * snapshotting the full record on every put (the behavior),\n * `Collection.put` computes a JSON Patch from the previous version to\n * the new version and stores only the patch in the ledger. To\n * reconstruct version N, we walk from the genesis snapshot forward\n * applying patches. Storage scales with **edit size**, not record\n * size — a 10 KB record edited 1000 times costs ~10 KB of deltas\n * instead of ~10 MB of snapshots.\n *\n * ## Why hand-roll instead of using a library?\n *\n * RFC 6902 has good libraries (`fast-json-patch`, `rfc6902`) but every\n * single one of them adds a runtime dependency to `@noy-db/core`. The\n * \"zero runtime dependencies\" promise is one of the core's load-bearing\n * features, and the patch surface we actually need is small enough\n * (~150 LoC) that vendoring is the right call.\n *\n * What we implement:\n * - `add` — insert a value at a path\n * - `remove` — delete the value at a path\n * - `replace` — overwrite the value at a path\n *\n * What we deliberately skip (out of scope for the ledger use):\n * - `move` and `copy` — optimizations; the diff algorithm doesn't\n * emit them, so the apply path doesn't need them\n * - `test` — used for transactional patches; we already have\n * optimistic concurrency via `_v` at the envelope layer\n * - Sophisticated array diffing (LCS, edit distance) — we treat\n * arrays as atomic values and emit a single `replace` op when\n * they differ. The accounting domain has small arrays where this\n * is fine; if we ever need patch-level array diffing we can add\n * it without changing the storage format.\n *\n * ## Path encoding (RFC 6902 §3)\n *\n * Paths look like `/foo/bar/0`. Each path segment is either an object\n * key or a numeric array index. Two characters need escaping inside\n * keys: `~` becomes `~0` and `/` becomes `~1`. We implement both.\n *\n * Empty path (`\"\"`) refers to the root document. Only `replace` makes\n * sense at the root, and our diff function emits it as a top-level\n * `replace` when `prev` and `next` differ in shape (object vs array,\n * primitive vs object, etc.).\n */\n\n/** A single JSON Patch operation. Subset of RFC 6902 — see file docstring. */\nexport type JsonPatchOp =\n | { readonly op: 'add'; readonly path: string; readonly value: unknown }\n | { readonly op: 'remove'; readonly path: string }\n | { readonly op: 'replace'; readonly path: string; readonly value: unknown }\n\n/** A complete JSON Patch document — an array of operations. */\nexport type JsonPatch = readonly JsonPatchOp[]\n\n// ─── Compute (diff) ──────────────────────────────────────────────────\n\n/**\n * Compute a JSON Patch that, when applied to `prev`, produces `next`.\n *\n * The algorithm is a straightforward recursive object walk:\n *\n * 1. If both inputs are plain objects (and not arrays/null):\n * - For each key in `prev`, recurse if `next` has it, else emit `remove`\n * - For each key in `next` not in `prev`, emit `add`\n * 2. If both inputs are arrays AND structurally equal, no-op.\n * Otherwise emit a single `replace` for the whole array.\n * 3. If both inputs are deeply equal primitives, no-op.\n * 4. Otherwise emit a `replace` at the current path.\n *\n * We do not minimize patches across move-like rearrangements — every\n * generated patch is straightforward enough to apply by hand if you\n * had to debug it.\n */\nexport function computePatch(prev: unknown, next: unknown): JsonPatch {\n const ops: JsonPatchOp[] = []\n diff(prev, next, '', ops)\n return ops\n}\n\nfunction diff(\n prev: unknown,\n next: unknown,\n path: string,\n out: JsonPatchOp[],\n): void {\n // Both null / both undefined → no-op (we don't differentiate them\n // in JSON terms; canonicalJson would reject undefined anyway).\n if (prev === next) return\n\n // One side null, the other not → straight replace.\n if (prev === null || next === null) {\n out.push({ op: 'replace', path, value: next })\n return\n }\n\n const prevIsArray = Array.isArray(prev)\n const nextIsArray = Array.isArray(next)\n const prevIsObject = typeof prev === 'object' && !prevIsArray\n const nextIsObject = typeof next === 'object' && !nextIsArray\n\n // Type changed (e.g., object → primitive, array → object). Replace.\n if (prevIsArray !== nextIsArray || prevIsObject !== nextIsObject) {\n out.push({ op: 'replace', path, value: next })\n return\n }\n\n // Both arrays. We don't do clever LCS-based diffing — emit a single\n // replace for the whole array if they differ. See file docstring for\n // the rationale.\n if (prevIsArray && nextIsArray) {\n if (!arrayDeepEqual(prev as unknown[], next as unknown[])) {\n out.push({ op: 'replace', path, value: next })\n }\n return\n }\n\n // Both plain objects. Recurse key by key.\n if (prevIsObject && nextIsObject) {\n const prevObj = prev as Record<string, unknown>\n const nextObj = next as Record<string, unknown>\n const prevKeys = Object.keys(prevObj)\n const nextKeys = Object.keys(nextObj)\n\n // Handle removes and overlapping recursions in one pass over prev.\n for (const key of prevKeys) {\n const childPath = path + '/' + escapePathSegment(key)\n if (!(key in nextObj)) {\n out.push({ op: 'remove', path: childPath })\n } else {\n diff(prevObj[key], nextObj[key], childPath, out)\n }\n }\n // Handle adds.\n for (const key of nextKeys) {\n if (!(key in prevObj)) {\n out.push({\n op: 'add',\n path: path + '/' + escapePathSegment(key),\n value: nextObj[key],\n })\n }\n }\n return\n }\n\n // Two primitives that aren't strictly equal — replace.\n out.push({ op: 'replace', path, value: next })\n}\n\nfunction arrayDeepEqual(a: unknown[], b: unknown[]): boolean {\n if (a.length !== b.length) return false\n for (let i = 0; i < a.length; i++) {\n if (!deepEqual(a[i], b[i])) return false\n }\n return true\n}\n\nfunction deepEqual(a: unknown, b: unknown): boolean {\n if (a === b) return true\n if (a === null || b === null) return false\n if (typeof a !== typeof b) return false\n if (typeof a !== 'object') return false\n const aArray = Array.isArray(a)\n const bArray = Array.isArray(b)\n if (aArray !== bArray) return false\n if (aArray && bArray) return arrayDeepEqual(a, b as unknown[])\n const aObj = a as Record<string, unknown>\n const bObj = b as Record<string, unknown>\n const aKeys = Object.keys(aObj)\n const bKeys = Object.keys(bObj)\n if (aKeys.length !== bKeys.length) return false\n for (const key of aKeys) {\n if (!(key in bObj)) return false\n if (!deepEqual(aObj[key], bObj[key])) return false\n }\n return true\n}\n\n// ─── Apply ──────────────────────────────────────────────────────────\n\n/**\n * Apply a JSON Patch to a base document and return the result.\n *\n * The base document is **not mutated** — every op clones the parent\n * container before writing to it, so the caller's reference to `base`\n * stays untouched. This costs an extra allocation per op but makes\n * the apply pipeline reorderable and safe to interrupt.\n *\n * Throws on:\n * - Removing a path that doesn't exist\n * - Adding to a path whose parent doesn't exist\n * - A path component that doesn't match the document shape (e.g.,\n * trying to step into a primitive)\n *\n * Throwing is the right behavior for the ledger use case: a failed\n * apply means the chain is corrupted, which should be loud rather\n * than silently producing a wrong reconstruction.\n */\nexport function applyPatch<T = unknown>(base: T, patch: JsonPatch): T {\n let result: unknown = clone(base)\n for (const op of patch) {\n result = applyOp(result, op)\n }\n return result as T\n}\n\nfunction applyOp(doc: unknown, op: JsonPatchOp): unknown {\n // Empty path → operation targets the root. Only `replace` and `add`\n // make sense at the root, but we handle `remove` for completeness\n // (root removal returns null).\n if (op.path === '') {\n if (op.op === 'remove') return null\n return clone(op.value)\n }\n\n const segments = parsePath(op.path)\n return walkAndApply(doc, segments, op)\n}\n\nfunction walkAndApply(\n doc: unknown,\n segments: string[],\n op: JsonPatchOp,\n): unknown {\n if (segments.length === 0) {\n // Should never happen — empty path is handled in applyOp().\n throw new Error('walkAndApply: empty segments (internal error)')\n }\n\n const [head, ...rest] = segments\n if (head === undefined) throw new Error('walkAndApply: undefined segment')\n\n if (rest.length === 0) {\n return applyAtTerminal(doc, head, op)\n }\n\n // Recurse into the child container, then rebuild the parent with\n // the modified child.\n if (Array.isArray(doc)) {\n const idx = parseArrayIndex(head, doc.length)\n const child = doc[idx]\n const newChild = walkAndApply(child, rest, op)\n const next = doc.slice()\n next[idx] = newChild\n return next\n }\n if (doc !== null && typeof doc === 'object') {\n const obj = doc as Record<string, unknown>\n if (!(head in obj)) {\n throw new Error(`applyPatch: path segment \"${head}\" not found in object`)\n }\n const newChild = walkAndApply(obj[head], rest, op)\n return { ...obj, [head]: newChild }\n }\n throw new Error(\n `applyPatch: cannot step into ${typeof doc} at segment \"${head}\"`,\n )\n}\n\nfunction applyAtTerminal(\n doc: unknown,\n segment: string,\n op: JsonPatchOp,\n): unknown {\n if (Array.isArray(doc)) {\n const idx =\n segment === '-' ? doc.length : parseArrayIndex(segment, doc.length + 1)\n const next = doc.slice()\n if (op.op === 'remove') {\n next.splice(idx, 1)\n return next\n }\n if (op.op === 'add') {\n next.splice(idx, 0, clone(op.value))\n return next\n }\n if (op.op === 'replace') {\n if (idx >= doc.length) {\n throw new Error(\n `applyPatch: replace at out-of-bounds array index ${idx}`,\n )\n }\n next[idx] = clone(op.value)\n return next\n }\n }\n if (doc !== null && typeof doc === 'object') {\n const obj = doc as Record<string, unknown>\n if (op.op === 'remove') {\n if (!(segment in obj)) {\n throw new Error(\n `applyPatch: remove on missing key \"${segment}\"`,\n )\n }\n const next = { ...obj }\n delete next[segment]\n return next\n }\n if (op.op === 'add') {\n // RFC 6902: `add` on an existing key replaces it.\n return { ...obj, [segment]: clone(op.value) }\n }\n if (op.op === 'replace') {\n if (!(segment in obj)) {\n throw new Error(\n `applyPatch: replace on missing key \"${segment}\"`,\n )\n }\n return { ...obj, [segment]: clone(op.value) }\n }\n }\n throw new Error(\n `applyPatch: cannot apply ${op.op} at terminal segment \"${segment}\"`,\n )\n}\n\n// ─── Path encoding (RFC 6902 §3) ─────────────────────────────────────\n\n/**\n * Escape a single path segment per RFC 6902 §3:\n * `~` → `~0`\n * `/` → `~1`\n *\n * Order matters: `~` must be escaped first, otherwise the `~1` we\n * just emitted would be re-escaped to `~01`.\n */\nfunction escapePathSegment(segment: string): string {\n return segment.replace(/~/g, '~0').replace(/\\//g, '~1')\n}\n\nfunction unescapePathSegment(segment: string): string {\n return segment.replace(/~1/g, '/').replace(/~0/g, '~')\n}\n\nfunction parsePath(path: string): string[] {\n if (!path.startsWith('/')) {\n throw new Error(`applyPatch: path must start with '/', got \"${path}\"`)\n }\n return path\n .slice(1)\n .split('/')\n .map(unescapePathSegment)\n}\n\nfunction parseArrayIndex(segment: string, max: number): number {\n if (!/^\\d+$/.test(segment)) {\n throw new Error(\n `applyPatch: array index must be a non-negative integer, got \"${segment}\"`,\n )\n }\n const idx = Number.parseInt(segment, 10)\n if (idx < 0 || idx > max) {\n throw new Error(\n `applyPatch: array index ${idx} out of range [0, ${max}]`,\n )\n }\n return idx\n}\n\n// ─── Cheap structural clone ─────────────────────────────────────────\n\n/**\n * Plain-JSON clone via JSON.parse(JSON.stringify(value)).\n *\n * Faster than `structuredClone` for our use because (a) we know our\n * inputs are JSON-compatible (no Dates, Maps, or BigInts — anything\n * else gets rejected by canonicalJson upstream), and (b) `structuredClone`\n * has overhead for handling arbitrary structured data we don't need.\n *\n * For tiny ledger entries (< 1 KB), the JSON round-trip is in the\n * single-digit microsecond range.\n */\nfunction clone<T>(value: T): T {\n if (value === null || value === undefined) return value\n if (typeof value !== 'object') return value\n return JSON.parse(JSON.stringify(value)) as T\n}\n","/**\n * `LedgerStore` — read/write access to a compartment's hash-chained\n * audit log.\n *\n * The store is a thin wrapper around the adapter's `_ledger/` internal\n * collection. Every append:\n *\n * 1. Loads the current head (or treats an empty ledger as head = -1)\n * 2. Computes `prevHash` = sha256(canonicalJson(head))\n * 3. Builds the new entry with `index = head.index + 1`\n * 4. Encrypts the entry with the compartment's ledger DEK\n * 5. Writes the encrypted envelope to `_ledger/<paddedIndex>`\n *\n * `verify()` walks the chain from genesis forward and returns\n * `{ ok: true, head }` on success or `{ ok: false, divergedAt }` on the\n * first broken link.\n *\n * ## Thread / concurrency model\n *\n * For we assume a **single writer per vault**. Two\n * concurrent `append()` calls would race on the \"read head, write\n * head+1\" cycle and could produce a broken chain. The sync engine\n * is the primary concurrent-writer scenario, and it uses\n * optimistic-concurrency via `expectedVersion` on the adapter — but\n * the ledger path has no such guard today. Multi-writer hardening is a\n * follow-up.\n *\n * Single-writer usage IS safe, including across process restarts:\n * `head()` reads the adapter fresh each call, so a crash between the\n * adapter.put of a data record and the ledger append just means the\n * ledger is missing an entry for that record. `verify()` still\n * succeeds; a future `verifyIntegrity()` helper can cross-check the\n * ledger against the data collections to catch the gap.\n *\n * ## Why hide the ledger from `vault.collection()`?\n *\n * The `_ledger` name starts with `_`, matching the existing prefix\n * convention for internal collections (`_keyring`, `_sync`,\n * `_history`). The Vault's public `collection()` method already\n * returns entries for any name, but `loadAll()` filters out\n * underscore-prefixed collections so backups and exports don't leak\n * ledger metadata. We keep the ledger accessible ONLY via\n * `vault.ledger()` to enforce the hash-chain invariants — direct\n * puts via `collection('_ledger')` would bypass the `append()` logic.\n */\n\nimport type { NoydbStore, EncryptedEnvelope } from '../../types.js'\nimport { NOYDB_FORMAT_VERSION } from '../../types.js'\nimport { encrypt, decrypt } from '../../crypto.js'\nimport { ConflictError, LedgerContentionError } from '../../errors.js'\nimport {\n canonicalJson,\n hashEntry,\n paddedIndex,\n sha256Hex,\n type LedgerEntry,\n} from './entry.js'\nimport type { JsonPatch } from './patch.js'\nimport { applyPatch } from './patch.js'\nimport { LEDGER_COLLECTION, LEDGER_DELTAS_COLLECTION } from './constants.js'\nimport { envelopePayloadHash } from './hash.js'\n\n/**\n * Maximum optimistic-CAS retries on the ledger head. Each failed\n * attempt invalidates the head cache, re-reads, and retries with a\n * fresh next-index. After N failures we surface\n * `LedgerContentionError` so the caller can decide whether to retry,\n * queue, or alert.\n */\nconst MAX_APPEND_ATTEMPTS = 8\n\n// — re-export the constants + helper so any existing\n// `import { LEDGER_COLLECTION } from '...store.js'` paths keep\n// working. Internal core paths (vault.ts) import from the leaf\n// modules directly to avoid pulling this file's class into the\n// floor bundle.\nexport { LEDGER_COLLECTION, LEDGER_DELTAS_COLLECTION, envelopePayloadHash }\n\n/**\n * Input shape for `LedgerStore.append()`. The caller supplies the\n * operation metadata; the store fills in `index` and `prevHash`.\n */\nexport interface AppendInput {\n op: LedgerEntry['op']\n collection: string\n id: string\n version: number\n actor: string\n payloadHash: string\n /**\n * Optional JSON Patch representing the delta from the previous\n * version to the new version. Present only for `put` operations\n * that had a previous version; omitted for genesis puts and for\n * deletes. When present, `LedgerStore.append` persists the patch\n * in `_ledger_deltas/<paddedIndex>` and records its sha256 hash\n * as the entry's `deltaHash` field.\n */\n delta?: JsonPatch\n /**\n * Present only for `op === 'amendment'` — structured audit\n * payload for multi-record repair operations performed via\n * `withTransactions(...)`. Carried through verbatim to the\n * resulting ledger entry.\n */\n amendment?: LedgerEntry['amendment']\n /**\n * Optional human-readable tag describing why this mutation happened\n * (#1). Threaded from `collection.put(_, _, { reason })`.\n * Carried verbatim onto the resulting ledger entry's `reason` field;\n * omitted from canonical JSON when undefined.\n */\n reason?: string\n}\n\n/**\n * Result of `LedgerStore.verify()`. On success, `head` is the hash of\n * the last entry — the same value that should be published to any\n * external anchoring service (blockchain, OpenTimestamps, etc.). On\n * failure, `divergedAt` is the 0-based index of the first entry whose\n * recorded `prevHash` does not match the recomputed hash of its\n * predecessor. Entries at `divergedAt` and later are untrustworthy;\n * entries before that index are still valid.\n */\nexport type VerifyResult =\n | { readonly ok: true; readonly head: string; readonly length: number }\n | {\n readonly ok: false\n readonly divergedAt: number\n readonly expected: string\n readonly actual: string\n }\n\n/**\n * A LedgerStore is bound to a single vault. Callers obtain one\n * via `vault.ledger()` — there is no public constructor to keep\n * the hash-chain invariants in one place.\n *\n * The class holds no mutable state beyond its dependencies (adapter,\n * vault name, DEK resolver, actor id). Every method reads the\n * adapter fresh so multiple instances against the same vault\n * see each other's writes immediately (at the cost of re-parsing the\n * ledger on every head() / verify() call; acceptable at scale).\n */\nexport class LedgerStore {\n private readonly adapter: NoydbStore\n private readonly vault: string\n private readonly encrypted: boolean\n private readonly getDEK: (collectionName: string) => Promise<CryptoKey>\n private readonly actor: string\n\n /**\n * In-memory cache of the chain head — the most recently appended\n * entry along with its precomputed hash. Without this, every\n * `append()` would re-load every prior entry to recompute the\n * prevHash, making N puts O(N²) — a 1K-record stress test goes from\n * < 100ms to a multi-second timeout.\n *\n * The cache is populated on first read (`append`, `head`, `verify`)\n * and updated in-place on every successful `append`. Single-writer\n * usage (the assumption) keeps it consistent. A second\n * LedgerStore instance writing to the same vault would not\n * see the first instance's appends in its cached state — that's the\n * concurrency caveat documented at the class level.\n *\n * Sentinel `undefined` means \"not yet loaded\"; an explicit `null`\n * value means \"loaded and confirmed empty\" — distinguishing these\n * matters because an empty ledger is a valid state (genesis prevHash\n * is the empty string), and we don't want to re-scan the adapter\n * just because the chain is freshly initialized.\n */\n private headCache: { entry: LedgerEntry; hash: string } | null | undefined = undefined\n\n constructor(opts: {\n adapter: NoydbStore\n vault: string\n encrypted: boolean\n getDEK: (collectionName: string) => Promise<CryptoKey>\n actor: string\n }) {\n this.adapter = opts.adapter\n this.vault = opts.vault\n this.encrypted = opts.encrypted\n this.getDEK = opts.getDEK\n this.actor = opts.actor\n }\n\n /**\n * Lazily load (or return cached) the current chain head. The cache\n * sentinel is `undefined` until first access; after the first call,\n * the cache holds either a `{ entry, hash }` for non-empty ledgers\n * or `null` for empty ones.\n */\n private async getCachedHead(): Promise<{ entry: LedgerEntry; hash: string } | null> {\n if (this.headCache !== undefined) return this.headCache\n const entries = await this.loadAllEntries()\n const last = entries[entries.length - 1]\n if (!last) {\n this.headCache = null\n return null\n }\n this.headCache = { entry: last, hash: await hashEntry(last) }\n return this.headCache\n }\n\n /**\n * Append a new entry to the ledger. Returns the full entry that was\n * written (with its assigned index and computed prevHash) so the\n * caller can use the hash for downstream purposes (e.g., embedding\n * in a verifiable backup).\n *\n * This is the **only** way to add entries. Direct adapter writes to\n * `_ledger/` would bypass the chain math and would be caught by the\n * next `verify()` call as a divergence.\n *\n * ## Multi-writer correctness\n *\n * Append is implemented as an optimistic-CAS retry loop. On every\n * attempt:\n *\n * 1. Read fresh head (cache invalidated on retry).\n * 2. Compute `nextIndex = head.index + 1`, `prevHash = hash(head)`.\n * 3. Encrypt delta payload IN MEMORY (no adapter write yet) so we\n * can compute `deltaHash` before claiming the chain slot.\n * 4. Build + encrypt the entry envelope.\n * 5. `adapter.put(_ledger, paddedIndex, envelope, expectedVersion: 0)`\n * — the `expectedVersion: 0` asserts \"this slot must not exist.\"\n * Stores with `casAtomic: true` honor the CAS check; under\n * contention the second writer's put throws `ConflictError`.\n * 6. On `ConflictError`: invalidate the head cache, sleep with\n * bounded backoff + jitter, retry. After `MAX_APPEND_ATTEMPTS`\n * retries throw {@link LedgerContentionError}.\n * 7. On success: write the delta envelope (if any) at the same\n * index. Update the head cache.\n *\n * Entry-first ordering matters: writing the delta first under\n * contention would orphan delta records at indices the writer never\n * actually claimed. The deltaHash is computed off the encrypted\n * envelope's `_data` field, which doesn't require the envelope to\n * be persisted.\n *\n * Stores with `casAtomic: false` (file, s3, r2 by default) silently\n * accept the `expectedVersion: 0` argument and proceed without a\n * CAS check. Concurrent appends against those stores remain\n * best-effort — pair them with an advisory lock or with sync\n * single-writer discipline.\n */\n async append(input: AppendInput): Promise<LedgerEntry> {\n let lastConflict: ConflictError | undefined\n for (let attempt = 0; attempt < MAX_APPEND_ATTEMPTS; attempt++) {\n // Force a fresh head read on every retry. The first attempt may\n // hit the cache; subsequent attempts must re-scan the adapter\n // because the prior conflict means our cached state is stale.\n if (attempt > 0) {\n this.headCache = undefined\n }\n try {\n return await this.appendOnce(input)\n } catch (err) {\n if (err instanceof ConflictError) {\n lastConflict = err\n if (attempt < MAX_APPEND_ATTEMPTS - 1) {\n await sleepBackoff(attempt)\n }\n continue\n }\n throw err\n }\n }\n void lastConflict\n throw new LedgerContentionError(MAX_APPEND_ATTEMPTS)\n }\n\n /**\n * One attempt at the append cycle. Throws `ConflictError` when the\n * CAS check on the entry put fails — `append()` catches that and\n * retries. Any other error propagates to the caller.\n */\n private async appendOnce(input: AppendInput): Promise<LedgerEntry> {\n const cached = await this.getCachedHead()\n const lastEntry = cached?.entry\n const prevHash = cached?.hash ?? ''\n const nextIndex = lastEntry ? lastEntry.index + 1 : 0\n\n // Encrypt the delta in memory so we can compute deltaHash WITHOUT\n // claiming the deltas slot yet — entry-put is the chain claim.\n let deltaEnvelope: EncryptedEnvelope | undefined\n let deltaHash: string | undefined\n if (input.delta !== undefined) {\n deltaEnvelope = await this.encryptDelta(input.delta)\n deltaHash = await sha256Hex(deltaEnvelope._data)\n }\n\n // Build the entry. Conditionally include `deltaHash` so\n // canonicalJson (which rejects undefined) never sees it when\n // there's no delta.\n const entryBase = {\n index: nextIndex,\n prevHash,\n op: input.op,\n collection: input.collection,\n id: input.id,\n version: input.version,\n ts: new Date().toISOString(),\n actor: input.actor === '' ? this.actor : input.actor,\n payloadHash: input.payloadHash,\n } as const\n const entry: LedgerEntry = {\n ...entryBase,\n ...(deltaHash !== undefined ? { deltaHash } : {}),\n ...(input.amendment !== undefined ? { amendment: input.amendment } : {}),\n ...(input.reason !== undefined ? { reason: input.reason } : {}),\n }\n\n const envelope = await this.encryptEntry(entry)\n // expectedVersion: 0 ≡ \"the slot must not yet exist.\" Honored by\n // casAtomic stores; silently passed through by non-CAS stores.\n await this.adapter.put(\n this.vault,\n LEDGER_COLLECTION,\n paddedIndex(entry.index),\n envelope,\n 0,\n )\n\n // Chain slot claimed. Now write the delta record (if any).\n if (deltaEnvelope) {\n await this.adapter.put(\n this.vault,\n LEDGER_DELTAS_COLLECTION,\n paddedIndex(entry.index),\n deltaEnvelope,\n 0,\n )\n }\n\n // Update the head cache so the next append() doesn't re-scan the\n // adapter.\n this.headCache = { entry, hash: await hashEntry(entry) }\n return entry\n }\n\n /**\n * Load a delta payload by its entry index. Returns `null` if the\n * entry at that index doesn't reference a delta (genesis puts and\n * deletes leave the slot empty) or if the delta row is missing\n * (possible after a `pruneHistory` fold).\n *\n * The caller is responsible for deciding what to do with a missing\n * delta — `ledger.reconstruct()` uses it as a \"stop walking\n * backward\" signal and falls back to the on-disk current value.\n */\n async loadDelta(index: number): Promise<JsonPatch | null> {\n const envelope = await this.adapter.get(\n this.vault,\n LEDGER_DELTAS_COLLECTION,\n paddedIndex(index),\n )\n if (!envelope) return null\n if (!this.encrypted) {\n return JSON.parse(envelope._data) as JsonPatch\n }\n const dek = await this.getDEK(LEDGER_COLLECTION)\n const json = await decrypt(envelope._iv, envelope._data, dek)\n return JSON.parse(json) as JsonPatch\n }\n\n /** Encrypt a JSON Patch into an envelope for storage. Mirrors encryptEntry. */\n private async encryptDelta(patch: JsonPatch): Promise<EncryptedEnvelope> {\n const json = JSON.stringify(patch)\n if (!this.encrypted) {\n return {\n _noydb: NOYDB_FORMAT_VERSION,\n _v: 1,\n _ts: new Date().toISOString(),\n _iv: '',\n _data: json,\n _by: this.actor,\n }\n }\n const dek = await this.getDEK(LEDGER_COLLECTION)\n const { iv, data } = await encrypt(json, dek)\n return {\n _noydb: NOYDB_FORMAT_VERSION,\n _v: 1,\n _ts: new Date().toISOString(),\n _iv: iv,\n _data: data,\n _by: this.actor,\n }\n }\n\n /**\n * Read all entries in ascending-index order. Used internally by\n * `append()`, `head()`, `verify()`, and `entries()`. Decryption is\n * serial because the entries are tiny and the overhead of a Promise\n * pool would dominate at realistic chain lengths (< 100K entries).\n */\n async loadAllEntries(): Promise<LedgerEntry[]> {\n const keys = await this.adapter.list(this.vault, LEDGER_COLLECTION)\n // Sort lexicographically, which matches numeric order because\n // keys are zero-padded to 10 digits.\n keys.sort()\n const entries: LedgerEntry[] = []\n for (const key of keys) {\n const envelope = await this.adapter.get(\n this.vault,\n LEDGER_COLLECTION,\n key,\n )\n if (!envelope) continue\n entries.push(await this.decryptEntry(envelope))\n }\n return entries\n }\n\n /**\n * Return the current head of the ledger: the last entry, its hash,\n * and the total chain length. `null` on an empty ledger so callers\n * can distinguish \"no history yet\" from \"empty history\".\n */\n async head(): Promise<\n | { readonly entry: LedgerEntry; readonly hash: string; readonly length: number }\n | null\n > {\n const cached = await this.getCachedHead()\n if (!cached) return null\n // `length` is `entry.index + 1` because indices are zero-based and\n // contiguous. We don't need to re-scan the adapter to compute it.\n return {\n entry: cached.entry,\n hash: cached.hash,\n length: cached.entry.index + 1,\n }\n }\n\n /**\n * Return entries in the requested half-open range `[from, to)`.\n * Defaults: `from = 0`, `to = length`. The indices are clipped to\n * the valid range; no error is thrown for out-of-range queries.\n */\n async entries(opts: { from?: number; to?: number } = {}): Promise<LedgerEntry[]> {\n const all = await this.loadAllEntries()\n const from = Math.max(0, opts.from ?? 0)\n const to = Math.min(all.length, opts.to ?? all.length)\n return all.slice(from, to)\n }\n\n /**\n * Reconstruct a record's state at a given historical version by\n * walking the ledger's delta chain backward from the current state.\n *\n * ## Algorithm\n *\n * Ledger deltas are stored in **reverse** form — each entry's\n * patch describes how to undo that put, transforming the new\n * record back into the previous one. `reconstruct` exploits this\n * by:\n *\n * 1. Finding every ledger entry for `(collection, id)` in the\n * chain, sorted by index ascending.\n * 2. Starting from `current` (the present value of the record,\n * as held by the caller — typically fetched via\n * `Collection.get()`).\n * 3. Walking entries in **descending** index order and applying\n * each entry's reverse patch, stopping when we reach the\n * entry whose version equals `atVersion`.\n *\n * The result is the record as it existed immediately AFTER the\n * put at `atVersion`. To get the state at the genesis put\n * (version 1), the walk runs all the way back through every put\n * after the first.\n *\n * ## Caveats\n *\n * - **Delete entries** break the walk: once we see a delete, the\n * record didn't exist before that point, so there's nothing to\n * reconstruct. We return `null` in that case.\n * - **Missing deltas** (e.g., after `pruneHistory` folds old\n * entries into a base snapshot) also stop the walk. does\n * not ship pruneHistory, so today this only happens if an entry\n * was deleted out-of-band.\n * - The caller MUST pass the correct current value. Passing a\n * mutated object would corrupt the reconstruction — the patch\n * chain is only valid against the exact state that was in\n * effect when the most recent put happened.\n *\n * For, `reconstruct` is the only way to read a historical\n * version via deltas. The legacy `_history` collection still\n * holds full snapshots and `Collection.getVersion()` still reads\n * from there — the two paths coexist until pruneHistory lands in\n * a follow-up and delta becomes the default.\n */\n async reconstruct<T>(\n collection: string,\n id: string,\n current: T,\n atVersion: number,\n ): Promise<T | null> {\n const all = await this.loadAllEntries()\n // Filter to entries for this (collection, id), in ascending index.\n const matching = all.filter(\n (e) => e.collection === collection && e.id === id,\n )\n if (matching.length === 0) {\n // No ledger history at all; the current state IS version 1\n // (or there's nothing), so the only valid atVersion is the\n // current record's version. We can't verify that here, so\n // return current if atVersion is plausible, null otherwise.\n return null\n }\n\n // Walk entries in descending index order, applying each reverse\n // delta until we reach the target version.\n let state: T | null = current\n for (let i = matching.length - 1; i >= 0; i--) {\n const entry = matching[i]\n if (!entry) continue\n\n // Defensive: skip every non-put/non-delete op variant. The\n // outer filter on `e.collection === collection && e.id === id`\n // already excludes `amendment` entries (their collection/id are\n // empty strings), but a top-of-loop guard keeps the walker\n // robust if a future op variant slips through the filter.\n if (entry.op !== 'put' && entry.op !== 'delete') continue\n\n // Match check FIRST — before applying this entry's reverse\n // patch. `state` at this point is the record state immediately\n // after this entry's put (or before this entry's delete), so\n // if the caller asked for this exact version, we're done.\n if (entry.version === atVersion && entry.op !== 'delete') {\n return state\n }\n\n if (entry.op === 'delete') {\n // A delete erases the live state. If the caller asks for a\n // version older than the delete we should continue walking\n // (state becomes null and the next put resets it). But we\n // can't reconstruct that pre-delete state from the current\n // in-memory `state` — the delete has no reverse patch. So\n // anything past this point is unreachable; return null.\n return null\n }\n\n if (entry.deltaHash === undefined) {\n // Genesis put — the earliest state for this lifecycle. We\n // can't walk further back. If the caller asked for exactly\n // this version, return the current state (we already failed\n // the match check above because a fresh genesis after a\n // delete can have version === atVersion). Otherwise the\n // target is unreachable from here.\n if (entry.version === atVersion) return state\n return null\n }\n\n const patch = await this.loadDelta(entry.index)\n if (!patch) {\n // Delta row is missing (probably pruned). Stop walking.\n return null\n }\n\n if (state === null) {\n // We're trying to walk back across a delete range and there's\n // nothing to apply a reverse patch to. Bail.\n return null\n }\n\n state = applyPatch(state, patch)\n }\n\n // Ran off the end of the walk without matching. The target\n // version doesn't exist in this record's chain.\n return null\n }\n\n /**\n * Walk the chain from genesis forward and verify every link.\n *\n * Returns `{ ok: true, head, length }` if every entry's `prevHash`\n * matches the recomputed hash of its predecessor (and the genesis\n * entry's `prevHash` is the empty string).\n *\n * Returns `{ ok: false, divergedAt, expected, actual }` on the first\n * mismatch. `divergedAt` is the 0-based index of the BROKEN entry\n * — entries before that index still verify cleanly; entries at and\n * after `divergedAt` are untrustworthy.\n *\n * This method detects:\n * - Mutated entry content (fields changed)\n * - Reordered entries (if any adjacent pair swaps, the prevHash\n * of the second no longer matches)\n * - Inserted entries (the inserted entry's prevHash likely fails,\n * and the following entry's prevHash definitely fails)\n * - Deleted entries (the entry after the deletion sees a wrong\n * prevHash)\n *\n * It does NOT detect:\n * - Tampering with the DATA collections that bypassed the ledger\n * entirely (e.g., an attacker who modifies records without\n * appending matching ledger entries — this is why we also\n * plan a `verifyIntegrity()` helper in a follow-up)\n * - Truncation of the chain at the tail (dropping the last N\n * entries leaves a shorter but still consistent chain). External\n * anchoring of `head.hash` to a trusted service is the defense\n * against this.\n */\n async verify(): Promise<VerifyResult> {\n const entries = await this.loadAllEntries()\n let expectedPrevHash = ''\n for (let i = 0; i < entries.length; i++) {\n const entry = entries[i]\n if (!entry) continue\n if (entry.prevHash !== expectedPrevHash) {\n return {\n ok: false,\n divergedAt: i,\n expected: expectedPrevHash,\n actual: entry.prevHash,\n }\n }\n if (entry.index !== i) {\n // An entry whose stored index doesn't match its position in\n // the sorted list means someone rewrote the adapter keys.\n // Treat as divergence.\n return {\n ok: false,\n divergedAt: i,\n expected: `index=${i}`,\n actual: `index=${entry.index}`,\n }\n }\n expectedPrevHash = await hashEntry(entry)\n }\n return {\n ok: true,\n head: expectedPrevHash,\n length: entries.length,\n }\n }\n\n // ─── Encryption plumbing ─────────────────────────────────────────\n\n /**\n * Serialize + encrypt a ledger entry into an EncryptedEnvelope. The\n * envelope's `_v` field is set to `entry.index + 1` so the usual\n * optimistic-concurrency machinery has a reasonable version number\n * to compare against (the ledger is append-only, so concurrent\n * writes should always bump the index).\n */\n private async encryptEntry(entry: LedgerEntry): Promise<EncryptedEnvelope> {\n const json = canonicalJson(entry)\n if (!this.encrypted) {\n return {\n _noydb: NOYDB_FORMAT_VERSION,\n _v: entry.index + 1,\n _ts: entry.ts,\n _iv: '',\n _data: json,\n _by: entry.actor,\n }\n }\n const dek = await this.getDEK(LEDGER_COLLECTION)\n const { iv, data } = await encrypt(json, dek)\n return {\n _noydb: NOYDB_FORMAT_VERSION,\n _v: entry.index + 1,\n _ts: entry.ts,\n _iv: iv,\n _data: data,\n _by: entry.actor,\n }\n }\n\n /** Decrypt an envelope into a LedgerEntry. Throws on bad key / tamper. */\n private async decryptEntry(envelope: EncryptedEnvelope): Promise<LedgerEntry> {\n if (!this.encrypted) {\n return JSON.parse(envelope._data) as LedgerEntry\n }\n const dek = await this.getDEK(LEDGER_COLLECTION)\n const json = await decrypt(envelope._iv, envelope._data, dek)\n return JSON.parse(json) as LedgerEntry\n }\n}\n\n// `envelopePayloadHash` was moved to `./hash.ts` so it can be\n// imported by core code without dragging this file's `LedgerStore`\n// class into the floor bundle. The re-export at the top of this\n// file keeps the original `import { envelopePayloadHash } from '.../store.js'`\n// path working.\n\n/**\n * Exponential backoff with jitter for the append CAS retry loop.\n * Attempt 0 → ~5–10 ms, attempt 7 → ~640–1280 ms. Jitter avoids the\n * thundering-herd problem when multiple writers collide repeatedly.\n */\nfunction sleepBackoff(attempt: number): Promise<void> {\n const base = 5 * Math.pow(2, attempt)\n const jitter = Math.random() * base\n return new Promise((resolve) => setTimeout(resolve, base + jitter))\n}\n","import { NoydbError } from '../errors.js'\nimport type { GateName, GatePolicy } from './types.js'\n\n/**\n * Why a gate denied a request. Stable across hub versions so consumers\n * can switch on the value in error UIs.\n */\nexport type PolicyDenyReason =\n | 'insufficient-tier'\n | 'missing-factor'\n | 'stale-proof'\n | 'disabled'\n | 'shared-device-blocked'\n\n/**\n * Thrown by {@link checkGate} when the active session does not meet\n * the gate's requirements. Carries the gate name, the reason, and the\n * full required {@link GatePolicy} so error UIs can prompt the user\n * for the missing factor without re-reading the policy document.\n */\nexport class PolicyDeniedError extends NoydbError {\n readonly gate: GateName\n readonly reason: PolicyDenyReason\n readonly required: GatePolicy\n constructor(gate: GateName, reason: PolicyDenyReason, required: GatePolicy, message?: string) {\n super(\n 'POLICY_DENIED',\n message ?? `Gate \"${gate}\" denied: ${reason}.`,\n )\n this.name = 'PolicyDeniedError'\n this.gate = gate\n this.reason = reason\n this.required = required\n }\n}\n\n/**\n * Raised by `createNoydb({ ... })` when the developer omits a recovery\n * profile and `recover-passphrase` is not explicitly disabled. Vaults\n * MUST have at least one recovery path enrolled before being\n * production-ready (paper, shamir, multi-channel, or admin-mediated).\n *\n * The error references issue #10 in its message so a developer hitting\n * it gets a one-line pointer to the design.\n */\nexport class RecoveryNotEnrolledError extends NoydbError {\n constructor(\n message =\n 'Recovery profile not enrolled. Pass `recovery: [{ profile: \"paper\", codes: 10 }]` ' +\n 'to `createNoydb()`, or set `policy.gates[\"recover-passphrase\"].enabled = false` to ' +\n 'opt out of recovery (passphrase loss = data loss). See docs/subsystems/session-tiers.md.',\n ) {\n super('RECOVERY_NOT_ENROLLED', message)\n this.name = 'RecoveryNotEnrolledError'\n }\n}\n\n/**\n * Raised by `openVault` when a managed-passphrase-mode vault has no\n * STRONG recovery profile enrolled (#195).\n *\n * Managed mode means the user never types a passphrase — the unlock\n * material lives in a `SealingKeyProvider` (`at-*` package). If that\n * provider's key is lost AND no strong recovery is enrolled, the\n * vault is irrecoverable. To prevent that footgun, managed-mode vaults\n * require at least one strong recovery profile (Shamir today;\n * multi-channel / admin-mediated when those ship).\n *\n * Paper recovery alone is NOT strong under managed mode: the user has\n * no memorized passphrase to fall back on, so losing the paper sheet =\n * losing every record permanently.\n *\n * Bootstrap with `db.openVaultAndEnrollRecovery(vault, { recovery: [{ profile: \"shamir\", k, n }] })`\n * to atomically create-and-enroll, or call `db.enrollRecovery(vault, { profile: \"shamir\", ... })`\n * separately before re-attempting `openVault`.\n */\nexport class ManagedRecoveryNotEnrolledError extends NoydbError {\n readonly vault: string\n constructor(vault: string) {\n super(\n 'MANAGED_RECOVERY_NOT_ENROLLED',\n `Managed-mode vault \"${vault}\" requires at least one strong recovery profile `\n + '(Shamir today; multi-channel / admin-mediated when they ship). Paper alone is '\n + 'NOT strong under managed mode — losing the paper sheet would mean losing every '\n + 'record permanently. '\n + `Bootstrap with \\`db.openVaultAndEnrollRecovery(\"${vault}\", { recovery: [{ profile: \"shamir\", k: 2, n: 3 }] })\\`, `\n + 'or call `db.enrollRecovery(vault, { profile: \"shamir\", k, n })` separately, '\n + 'then re-attempt `openVault`.',\n )\n this.name = 'ManagedRecoveryNotEnrolledError'\n this.vault = vault\n }\n}\n\n/**\n * Raised by `db.recoverPassphrase` / `db.enrollRecovery` /\n * `db.rotateRecovery` when the developer requests a recovery profile\n * not yet wired in this hub release.\n *\n * Implemented: `paper` (#10, pre.5) and `shamir` (#196 slice 1, pre.16).\n * Pending: `multi-channel` and `admin-mediated` (tracked under #196\n * follow-up slices).\n *\n * The carried `profile` and `tracking` fields let consumers steer the\n * UI (\"multi-channel recovery is not yet wired up — open issue #N to follow\").\n */\nexport class RecoveryProfileNotImplementedError extends NoydbError {\n readonly profile: string\n readonly tracking: string\n constructor(profile: string, tracking: string) {\n super(\n 'RECOVERY_PROFILE_NOT_IMPLEMENTED',\n `Recovery profile \"${profile}\" is not yet implemented in this hub release. ` +\n `Tracking: ${tracking}. Use the \"paper\" profile via @noy-db/on-recovery in the meantime.`,\n )\n this.name = 'RecoveryProfileNotImplementedError'\n this.profile = profile\n this.tracking = tracking\n }\n}\n","/**\n * **Wrap-DEKs primitive (#44)** — a single canonical shape for the\n * pattern of \"serialize a DEK set, encrypt it under a credential-derived\n * AES-GCM key.\" Used by:\n *\n * - **tier-0** — paper recovery entries (`_meta/recovery-paper`),\n * credential = the printed code.\n * - **tier-2** — password authenticator slots (`KeyringFile.authenticators`,\n * `wrapKind: 'deks'`), credential = the user's password.\n *\n * **Not** used by `@noy-db/on-pin` — tier-3 wraps the DEK set under\n * the same conceptual pattern but at **100,000 PBKDF2 iterations**\n * (vs the 600,000 here), because the protection window for a PIN\n * slot is short (idle-timeout-bounded, typically 15 min) and 600k\n * iterations would make every PIN-resume noticeably slow. The wire\n * formats are deliberately incompatible. See `@noy-db/on-pin`'s\n * `PIN_PBKDF2_ITERATIONS` and the threat-model rationale in its\n * module docstring.\n *\n * Before #44, the same crypto lived in two places: `mintPaperRecoveryEntry`\n * (in `team/recovery.ts`) and `enrollPasswordAuthenticator` (in\n * `@noy-db/on-password`). Both functions did identical work — PBKDF2\n * the credential, AES-GCM-encrypt the JSON-serialized DEK set — but\n * their implementations had drifted apart enough that fixing a bug\n * in one wouldn't fix the other.\n *\n * This module owns the canonical implementation. Consumers compose:\n *\n * - `mintPaperRecoveryEntry` is now a thin wrapper that calls\n * `mintWrappedDeksBlob` and adds `{ codeId, enrolledAt }`.\n * - `enrollPasswordAuthenticator` calls `mintWrappedDeksBlob` and\n * wraps the result in the slot envelope.\n *\n * @module\n */\n\nconst PBKDF2_ITERATIONS = 600_000\nconst SALT_BYTES = 32\nconst IV_BYTES = 12\n\nconst subtle = globalThis.crypto.subtle\n\n// ─── Type ──────────────────────────────────────────────────────────────\n\n/**\n * The wrap-DEKs primitive — a serialized + AES-GCM-encrypted DEK set\n * keyed under a credential-derived key.\n *\n * All three fields are base64-encoded so the blob is JSON-safe and\n * round-trips through `_meta/*` envelopes (which carry plaintext\n * JSON in `_data`).\n *\n * Composition: `PaperRecoveryEntry extends WrappedDeksBlob` plus\n * `{ codeId, enrolledAt }`. `KeyringAuthenticatorWrappingDEKs`\n * carries the same three fields with `salt` stored in `meta` for\n * slot-format back-compat (#44 defers moving it to top-level).\n */\nexport interface WrappedDeksBlob {\n /** Base64 PBKDF2 salt for the credential-derived wrapping key. */\n readonly salt: string\n /** Base64 AES-GCM IV used for the `wrappedDeks` ciphertext. */\n readonly iv: string\n /** Base64 AES-GCM ciphertext of `{ deks: { collection: base64rawDek } }`. */\n readonly wrappedDeks: string\n}\n\n// ─── Mint ──────────────────────────────────────────────────────────────\n\n/**\n * Mint a fresh `WrappedDeksBlob` from a DEK set + a string credential.\n *\n * Generates a random salt + IV, derives a 256-bit AES-GCM key via\n * PBKDF2-SHA256(credential, salt, 600K), serializes the DEK set as\n * `{ deks: { coll: rawBase64 } }`, and AES-GCM-encrypts.\n *\n * The `credential` is the user-typed string (recovery code, password,\n * PIN). Caller normalization rules apply (e.g. paper\n * recovery uppercase-strips the code before reaching this function).\n *\n * @param deks - DEK set to wrap. Each DEK must be exportable via\n * `subtle.exportKey('raw', dek)` (the hub mints DEKs\n * this way; consumers feeding non-extractable keys\n * will get `InvalidAccessError` from WebCrypto).\n * @param credential - String input the consumer minted (paper code,\n * password, PIN). Treated as opaque bytes by PBKDF2.\n */\nexport async function mintWrappedDeksBlob(\n deks: Map<string, CryptoKey>,\n credential: string,\n): Promise<WrappedDeksBlob> {\n const salt = crypto.getRandomValues(new Uint8Array(SALT_BYTES))\n const iv = crypto.getRandomValues(new Uint8Array(IV_BYTES))\n const wrappingKey = await deriveWrappingKey(credential, salt)\n\n // Serialize the DEK set as JSON `{ deks: { collection: base64 } }`.\n const exported: Record<string, string> = {}\n for (const [coll, dek] of deks) {\n const raw = await subtle.exportKey('raw', dek)\n exported[coll] = bytesToBase64(new Uint8Array(raw))\n }\n const plaintext = new TextEncoder().encode(JSON.stringify({ deks: exported }))\n const ciphertext = await subtle.encrypt(\n { name: 'AES-GCM', iv: iv as BufferSource },\n wrappingKey,\n plaintext as BufferSource,\n )\n\n return {\n salt: bytesToBase64(salt),\n iv: bytesToBase64(iv),\n wrappedDeks: bytesToBase64(new Uint8Array(ciphertext)),\n }\n}\n\n// ─── Unwrap ────────────────────────────────────────────────────────────\n\n/**\n * Reverse of {@link mintWrappedDeksBlob}. Re-derives the wrapping key\n * from the credential + stored salt, AES-GCM-decrypts the wrapped DEK\n * set, and re-imports each DEK as an extractable AES-GCM CryptoKey.\n *\n * Throws (AES-GCM auth tag failure) when the credential doesn't\n * match the blob. Callers iterating over multiple blobs (e.g. paper\n * recovery's \"try every entry until one matches\") should catch.\n */\nexport async function unwrapDeksFromBlob(\n blob: WrappedDeksBlob,\n credential: string,\n): Promise<Map<string, CryptoKey>> {\n const wrappingKey = await deriveWrappingKey(credential, base64ToBytes(blob.salt))\n const plaintext = await subtle.decrypt(\n { name: 'AES-GCM', iv: base64ToBytes(blob.iv) as BufferSource },\n wrappingKey,\n base64ToBytes(blob.wrappedDeks) as BufferSource,\n )\n const parsed = JSON.parse(new TextDecoder().decode(plaintext)) as { deks: Record<string, string> }\n const deks = new Map<string, CryptoKey>()\n for (const [coll, b64] of Object.entries(parsed.deks)) {\n const raw = base64ToBytes(b64)\n const key = await subtle.importKey(\n 'raw',\n raw as BufferSource,\n { name: 'AES-GCM', length: 256 },\n true,\n ['encrypt', 'decrypt'],\n )\n deks.set(coll, key)\n }\n return deks\n}\n\n// ─── Internals ─────────────────────────────────────────────────────────\n\nasync function deriveWrappingKey(credential: string, salt: Uint8Array): Promise<CryptoKey> {\n const ikm = await subtle.importKey(\n 'raw',\n new TextEncoder().encode(credential),\n 'PBKDF2',\n false,\n ['deriveKey'],\n )\n return subtle.deriveKey(\n {\n name: 'PBKDF2',\n salt: salt as BufferSource,\n iterations: PBKDF2_ITERATIONS,\n hash: 'SHA-256',\n },\n ikm,\n { name: 'AES-GCM', length: 256 },\n false,\n ['encrypt', 'decrypt'],\n )\n}\n\nfunction bytesToBase64(b: Uint8Array): string {\n let s = ''\n for (const x of b) s += String.fromCharCode(x)\n return btoa(s)\n}\n\nfunction base64ToBytes(b64: string): Uint8Array {\n const s = atob(b64)\n const out = new Uint8Array(s.length)\n for (let i = 0; i < s.length; i++) out[i] = s.charCodeAt(i)\n return out\n}\n","/**\n * Recovery profile persistence + dispatch — issue #10.\n *\n * v0.1.0-pre.5 wires the **paper** profile end-to-end through\n * `@noy-db/on-recovery`. The other three profiles (Shamir,\n * multi-channel, admin-mediated) ship the API surface and throw\n * {@link RecoveryProfileNotImplementedError} during use; per-profile\n * dispatch lands in follow-up issues.\n *\n * Storage layout:\n *\n * ```\n * _meta/recovery-paper — JSON { entries: RecoveryCodeEntry[] } produced by `on-recovery`.\n * _meta/recovery-shamir — reserved\n * _meta/recovery-multi — reserved\n * _meta/recovery-admin — reserved\n * ```\n *\n * Like `_meta/policy` and `_meta/handle`, the documents are plain JSON\n * with empty `_iv` — the recovery-code wrapping is what protects the\n * KEK; the entries themselves are inert without the user's code.\n *\n * @module\n */\nimport type { NoydbStore, EncryptedEnvelope } from '../types.js'\nimport { NOYDB_FORMAT_VERSION } from '../types.js'\nimport {\n mintWrappedDeksBlob,\n unwrapDeksFromBlob,\n type WrappedDeksBlob,\n} from './wrapped-deks.js'\nimport type { ShamirRecoveryProvider } from './shamir-recovery-provider.js'\n\n/**\n * One paper recovery code as persisted in `_meta/recovery-paper`.\n *\n * The hub's KEK is intentionally non-extractable (see `crypto.ts`),\n * so the recovery entry can't AES-KW-wrap the KEK directly. Instead\n * we wrap a serialized DEK set: the entry holds the AES-GCM\n * ciphertext of `{ deks: { collection: rawDekBase64 } }`. Recovery\n * deserializes the DEK set, then mints a fresh KEK from the new\n * passphrase and rewraps the DEKs under it.\n *\n * This is the same pattern `@noy-db/on-pin` uses for tier-3 quick\n * resume — the cryptographic guarantee is identical (AES-GCM with a\n * PBKDF2-derived key), and it sidesteps the non-extractable-KEK\n * constraint cleanly.\n *\n * Type-level composition (#44): `PaperRecoveryEntry extends\n * WrappedDeksBlob` — the three crypto fields (`salt`, `iv`,\n * `wrappedDeks`) come from the shared primitive; `codeId` and\n * `enrolledAt` are paper-recovery's own metadata. Wire format\n * unchanged.\n */\nexport interface PaperRecoveryEntry extends WrappedDeksBlob {\n readonly codeId: string\n readonly enrolledAt: string\n}\n\nexport interface PaperRecoveryDoc {\n readonly _noydb_recovery: 1\n readonly profile: 'paper'\n readonly entries: ReadonlyArray<PaperRecoveryEntry>\n}\n\nconst PAPER_DOC_ID = 'recovery-paper'\n\n/** Read the paper-recovery entries. Returns empty array when absent. */\nexport async function loadPaperRecoveryEntries(\n store: NoydbStore,\n vault: string,\n): Promise<ReadonlyArray<PaperRecoveryEntry>> {\n const env = await store.get(vault, '_meta', PAPER_DOC_ID)\n if (!env) return []\n try {\n const doc = JSON.parse(env._data) as PaperRecoveryDoc\n if (doc.profile !== 'paper' || !Array.isArray(doc.entries)) return []\n return doc.entries\n } catch {\n return []\n }\n}\n\n/** Replace the paper-recovery entries (used after burn-on-recovery). */\nexport async function savePaperRecoveryEntries(\n store: NoydbStore,\n vault: string,\n entries: ReadonlyArray<PaperRecoveryEntry>,\n): Promise<void> {\n const doc: PaperRecoveryDoc = {\n _noydb_recovery: 1,\n profile: 'paper',\n entries,\n }\n const envelope: EncryptedEnvelope = {\n _noydb: NOYDB_FORMAT_VERSION,\n _v: 1,\n _ts: new Date().toISOString(),\n _iv: '',\n _data: JSON.stringify(doc),\n }\n await store.put(vault, '_meta', PAPER_DOC_ID, envelope)\n}\n\n/** Drop a single paper-recovery entry (burn-on-use). */\nexport async function burnPaperRecoveryEntry(\n store: NoydbStore,\n vault: string,\n codeId: string,\n): Promise<void> {\n const entries = await loadPaperRecoveryEntries(store, vault)\n const remaining = entries.filter((e) => e.codeId !== codeId)\n await savePaperRecoveryEntries(store, vault, remaining)\n}\n\n/** Whether at least one recovery profile has any enrolled entries. */\nexport async function hasRecoveryEnrolled(\n store: NoydbStore,\n vault: string,\n): Promise<boolean> {\n const paper = await loadPaperRecoveryEntries(store, vault)\n if (paper.length > 0) return true\n const shamir = await loadShamirRecoveryEntries(store, vault)\n return shamir.length > 0\n}\n\n/**\n * Whether at least one **strong** recovery profile is enrolled (#195).\n *\n * \"Strong\" excludes paper-alone — under managed-passphrase mode the\n * user has no memorized passphrase, so a stolen/lost paper sheet\n * would be a single point of total loss. Strong profiles today:\n *\n * - `shamir` (k-of-n threshold; survives loss of up to n-k shares)\n * - `multi-channel` (when shipped — #196 follow-up slice)\n * - `admin-mediated` (when shipped — #196 follow-up slice)\n *\n * Managed mode requires this check to pass before `openVault` returns.\n */\nexport async function hasStrongRecoveryEnrolled(\n store: NoydbStore,\n vault: string,\n): Promise<boolean> {\n const shamir = await loadShamirRecoveryEntries(store, vault)\n return shamir.length > 0\n // When multi-channel / admin-mediated land, extend this check.\n}\n\n// ─── Shamir recovery (#196 slice 1) ──────────────────────────────────────\n\n/**\n * One Shamir-recovery entry as persisted in `_meta/recovery-shamir`.\n *\n * Like {@link PaperRecoveryEntry}, the entry composes\n * {@link WrappedDeksBlob} (DEKs wrapped under a fresh ephemeral\n * recovery secret) with profile-specific metadata. Unlike paper, the\n * \"credential\" was never visible to the user — it was 32 random\n * bytes split into N Shamir shares at enrollment. The shares ARE\n * the credential; the user holds them, the hub never sees them\n * again after `enrollRecovery` returns.\n *\n * Per the spec §5: the recovery secret is base64-encoded and\n * passed as the `credential` arg to\n * {@link mintWrappedDeksBlob} / {@link unwrapDeksFromBlob}. The\n * PBKDF2 round over high-entropy input is harmless overhead — it\n * keeps the shared primitive unchanged while letting Shamir reuse\n * the same wrapping pipeline as paper.\n */\nexport interface ShamirRecoveryEntry extends WrappedDeksBlob {\n /** Stable id for this entry. Allows multiple Shamir splits to coexist. */\n readonly entryId: string\n /** Threshold — minimum shares to reconstruct. */\n readonly k: number\n /** Total shares minted at enrollment. */\n readonly n: number\n /** x-coordinates of the n minted shares. Informational. Omitted as of 0.2\n * (string-level provider doesn't expose share x-coords); kept optional so\n * pre-0.2 entries still read. */\n readonly xCoords?: ReadonlyArray<number>\n /** ISO timestamp. */\n readonly enrolledAt: string\n /** Optional caller-supplied label (e.g., \"2-of-3 board escrow\"). */\n readonly label?: string\n}\n\nexport interface ShamirRecoveryDoc {\n readonly _noydb_recovery: 1\n readonly profile: 'shamir'\n readonly entries: ReadonlyArray<ShamirRecoveryEntry>\n}\n\nconst SHAMIR_DOC_ID = 'recovery-shamir'\n\n/** Read the Shamir-recovery entries. Returns empty array when absent. */\nexport async function loadShamirRecoveryEntries(\n store: NoydbStore,\n vault: string,\n): Promise<ReadonlyArray<ShamirRecoveryEntry>> {\n const env = await store.get(vault, '_meta', SHAMIR_DOC_ID)\n if (!env) return []\n try {\n const doc = JSON.parse(env._data) as ShamirRecoveryDoc\n if (doc.profile !== 'shamir' || !Array.isArray(doc.entries)) return []\n return doc.entries\n } catch {\n return []\n }\n}\n\n/** Replace the Shamir-recovery entries (used by enrollment and rotation). */\nexport async function saveShamirRecoveryEntries(\n store: NoydbStore,\n vault: string,\n entries: ReadonlyArray<ShamirRecoveryEntry>,\n): Promise<void> {\n const doc: ShamirRecoveryDoc = {\n _noydb_recovery: 1,\n profile: 'shamir',\n entries,\n }\n const envelope: EncryptedEnvelope = {\n _noydb: NOYDB_FORMAT_VERSION,\n _v: 1,\n _ts: new Date().toISOString(),\n _iv: '',\n _data: JSON.stringify(doc),\n }\n await store.put(vault, '_meta', SHAMIR_DOC_ID, envelope)\n}\n\n/**\n * Mint a fresh Shamir recovery entry from a DEK set.\n *\n * 1. Generates a 32-byte recovery secret.\n * 2. Wraps the DEK set under that secret via\n * {@link mintWrappedDeksBlob} (the recovery secret is base64-\n * encoded as the credential string — PBKDF2 over high-entropy\n * input is harmless overhead).\n * 3. Splits the recovery secret via Shamir into `n` shares with\n * threshold `k`.\n * 4. Zeros the in-memory recovery secret after wrapping + splitting.\n *\n * Returns:\n * - `entry` — the {@link ShamirRecoveryEntry} to persist.\n * - `shareStrings` — the `n` Base32-encoded share strings to\n * return to the caller. The HUB MUST NOT PERSIST THESE; once\n * returned they are the user's responsibility.\n *\n * @param deks - DEK set to wrap.\n * @param entryId - Stable id for this entry (caller-supplied or\n * hub-generated).\n * @param k - Threshold (>= 2).\n * @param n - Total shares (k <= n <= 255).\n * @param label - Optional caller label.\n */\nexport async function mintShamirRecoveryEntry(\n provider: ShamirRecoveryProvider,\n deks: Map<string, CryptoKey>,\n entryId: string,\n k: number,\n n: number,\n label?: string,\n): Promise<{ entry: ShamirRecoveryEntry; shareStrings: string[] }> {\n const recoverySecret = crypto.getRandomValues(new Uint8Array(32))\n try {\n const credential = bytesToBase64(recoverySecret)\n const blob = await mintWrappedDeksBlob(deks, credential)\n const shareStrings = provider.splitToShares(recoverySecret, k, n)\n const entry: ShamirRecoveryEntry = {\n ...blob, entryId, k, n,\n enrolledAt: new Date().toISOString(),\n ...(label !== undefined && { label }),\n }\n return { entry, shareStrings }\n } finally {\n recoverySecret.fill(0)\n }\n}\n\n/**\n * Decrypt a Shamir recovery entry to recover the raw DEK set.\n *\n * Combines K or more `shares`, reconstructs the recovery secret,\n * unwraps the DEKs via {@link unwrapDeksFromBlob}.\n *\n * Throws (AES-GCM auth-tag mismatch) when the shares don't combine\n * to the secret originally used to mint the entry — typically\n * because they came from a different enrollment or were tampered\n * with. Callers iterating multiple entries should catch.\n */\nexport async function unwrapDeksFromShamirEntry(\n provider: ShamirRecoveryProvider,\n entry: ShamirRecoveryEntry,\n shareStrings: readonly string[],\n): Promise<Map<string, CryptoKey>> {\n if (shareStrings.length < entry.k) {\n throw new Error(\n `Insufficient shares: this Shamir entry needs ${entry.k} of ${entry.n}, `\n + `but ${shareStrings.length} were provided.`,\n )\n }\n const secret = provider.combineShares(shareStrings)\n try {\n return await unwrapDeksFromBlob(entry, bytesToBase64(secret))\n } finally {\n secret.fill(0)\n }\n}\n\nfunction bytesToBase64(b: Uint8Array): string {\n let s = ''\n for (const x of b) s += String.fromCharCode(x)\n return btoa(s)\n}\n\n/**\n * Generate one paper-recovery entry from an unlocked DEK set.\n *\n * Returns the serializable entry (persisted via\n * {@link savePaperRecoveryEntries}). The recovery flow unwraps the\n * DEK set, then mints a fresh KEK from the user's new passphrase.\n *\n * Thin wrapper over {@link mintWrappedDeksBlob} (#44) — the crypto\n * lives in the shared primitive; this function just adds paper-\n * recovery's own metadata (`codeId`, `enrolledAt`).\n *\n * @param deks Map of collection-name → DEK (extractable).\n * @param code The plaintext recovery code (caller-supplied;\n * pair this with `@noy-db/on-recovery`'s code\n * generator/parser if available).\n * @param codeId Stable id used by `burnPaperRecoveryEntry`.\n */\nexport async function mintPaperRecoveryEntry(\n deks: Map<string, CryptoKey>,\n code: string,\n codeId: string,\n): Promise<PaperRecoveryEntry> {\n const blob = await mintWrappedDeksBlob(deks, code)\n return {\n ...blob,\n codeId,\n enrolledAt: new Date().toISOString(),\n }\n}\n\n/**\n * Decrypt a recovery entry to recover the raw DEK set. Used by the\n * `recoverPassphrase` flow after the user's code has been parsed.\n *\n * Thin wrapper over {@link unwrapDeksFromBlob} (#44).\n *\n * @throws when the code does not match the entry (AES-GCM auth tag fail).\n */\nexport async function unwrapDeksFromPaperEntry(\n entry: PaperRecoveryEntry,\n code: string,\n): Promise<Map<string, CryptoKey>> {\n return unwrapDeksFromBlob(entry, code)\n}\n\n// Legacy crypto helpers (deriveRecoveryWrappingKey, bytesToBase64,\n// base64ToBytes) were inlined here pre-#44. They now live in the\n// canonical wrap-DEKs primitive at `./wrapped-deks.ts` and are\n// reached via `mintWrappedDeksBlob` / `unwrapDeksFromBlob`.\n","/**\n * Tier-1 change flows — `rotatePassphrase` (user remembers old) and\n * `recoverPassphrase` (user supplies a recovery proof). Issue #10.\n *\n * The two flows share the post-verification half — fresh salt, fresh\n * KEK, rewrap every DEK — and differ only in how they re-derive the\n * old KEK:\n *\n * - **Rotate**: derive from the supplied `oldPassphrase`.\n * - **Recover (paper)**: unwrap from a `RecoveryCodeEntry` using a\n * user-supplied recovery code. The entry is burned on success.\n *\n * The non-paper recovery profiles (Shamir, multi-channel,\n * admin-mediated) are not yet wired — calling them throws\n * {@link RecoveryProfileNotImplementedError} with a tracking link.\n *\n * @module\n */\nimport type { NoydbStore, KeyringFile } from '../types.js'\nimport { NOYDB_KEYRING_VERSION } from '../types.js'\nimport {\n deriveKey,\n generateSalt,\n wrapKey,\n unwrapKey,\n bufferToBase64,\n base64ToBuffer,\n} from '../crypto.js'\nimport { InvalidKeyError, NoAccessError } from '../errors.js'\nimport {\n RecoveryProfileNotImplementedError,\n} from '../policy/errors.js'\nimport {\n loadPaperRecoveryEntries,\n burnPaperRecoveryEntry,\n unwrapDeksFromPaperEntry,\n loadShamirRecoveryEntries,\n unwrapDeksFromShamirEntry,\n type PaperRecoveryEntry,\n type ShamirRecoveryEntry,\n} from './recovery.js'\nimport type { ShamirRecoveryProvider } from './shamir-recovery-provider.js'\nimport { assertStrongPassphrase, type PassphrasePolicy } from '../validation.js'\nimport type { UnlockedKeyring } from './keyring.js'\nimport { mintKeyringCanary } from './keyring.js'\nimport type { KeyringAuthenticator } from '../types.js'\nimport type { EnrollAuthenticatorOptions } from './authenticators.js'\nimport { ValidationError } from '../errors.js'\n\n/**\n * Context handed to a {@link SlotRewrapCeremony} when `rotatePassphrase`\n * preserves a tier-2 slot. The ceremony's job is to re-derive its\n * method-specific wrapping material (PRF assertion, PBKDF2 of the\n * password, etc.) and wrap the freshly rewrapped DEK set under\n * the new wrapping key.\n *\n * Two surfaces are exposed:\n *\n * - `newDeks` — the rewrapped (extractable) DEK set the slot will\n * wrap. This is what `mintPaperRecoveryEntry` / `enrollPassword-\n * Authenticator` / `wrapKeyringSummary` (in `@noy-db/on-webauthn`)\n * all consume; effectively the canonical input for every\n * post-Path C tier-2 ceremony.\n *\n * - `newKek` — the freshly-derived KEK (extractable for the\n * ceremony scope only). Only relevant for forward-compatibility\n * with a hypothetical future on-* package that wants to wrap the\n * KEK itself under a method-derived key. None of the shipped\n * on-* packages need this; they all operate on `newDeks`.\n *\n * The ceremony MUST preserve `oldSlot.id` and `oldSlot.method` in the\n * returned `EnrollAuthenticatorOptions`. Hub validates these — a\n * mismatch throws `ValidationError` (prevents slot-type swap mid-\n * rotation, e.g. converting a webauthn slot to a password slot under\n * cover of preservation).\n */\nexport interface SlotRewrapContext {\n readonly newKek: CryptoKey\n readonly newDeks: Map<string, CryptoKey>\n readonly oldSlot: KeyringAuthenticator\n}\n\n/**\n * Callback that re-enrolls one tier-2 slot during `rotatePassphrase`.\n * Returns the new slot's `EnrollAuthenticatorOptions` — same shape\n * the consumer would pass to `db.enrollAuthenticator` for a fresh\n * enrollment. Hub persists the result atomically with the rotation.\n */\nexport type SlotRewrapCeremony = (\n ctx: SlotRewrapContext,\n) => Promise<EnrollAuthenticatorOptions>\n\n/** Caller payload for {@link rotatePassphrase}. */\nexport interface RotatePassphraseInput {\n readonly oldPassphrase: string\n readonly newPassphrase: string\n readonly passphrasePolicy?: PassphrasePolicy\n readonly allowWeakPassphrase?: boolean\n /**\n * Map of slot id → re-enrolment ceremony. Slots whose id appears\n * here are PRESERVED across rotation (the ceremony re-derives the\n * method-specific wrapping under the new keyring); slots whose id\n * is absent are DROPPED (the pre-#29 behavior).\n *\n * Without this map, `rotatePassphrase` retains the pre-pre.8\n * behavior of wiping every tier-2 slot. Consumers building a\n * \"rotate without losing my biometric\" flow supply ceremonies for\n * each slot they want to keep.\n *\n * If a ceremony throws, the entire rotation throws — no partial\n * state. Callers wrap individual ceremonies in try/catch + return\n * a sentinel if they want graceful degradation per slot.\n *\n * Added in pre.8 (#29).\n */\n readonly slotCeremonies?: { readonly [slotId: string]: SlotRewrapCeremony }\n}\n\n/**\n * Re-derive the user's KEK from `oldPassphrase`, rewrap every DEK\n * under a freshly-derived KEK from `newPassphrase`, and persist.\n *\n * Tier-2 authenticator slots are dropped UNLESS the caller supplies\n * a `slotCeremonies` map (#29) — each ceremony re-derives its\n * method-specific wrapping under the new keyring, and hub persists\n * the rewrapped slots atomically with the rotation. Slots whose id\n * isn't in the map are still dropped (pre-pre.8 behavior).\n *\n * @throws `InvalidKeyError` if `oldPassphrase` does not unwrap the keyring.\n * @throws `WeakPassphraseError` if `newPassphrase` fails the strength rule.\n * @throws `ValidationError` if a ceremony's result mismatches the\n * slot's id or method (anti-slot-swap guard).\n */\nexport async function rotatePassphrase(\n store: NoydbStore,\n vault: string,\n userId: string,\n input: RotatePassphraseInput,\n): Promise<UnlockedKeyring> {\n if (!input.allowWeakPassphrase) {\n assertStrongPassphrase(input.newPassphrase, input.passphrasePolicy)\n }\n\n const env = await store.get(vault, '_keyring', userId)\n if (!env) {\n throw new NoAccessError(`No keyring found for user \"${userId}\" in vault \"${vault}\".`)\n }\n const file = JSON.parse(env._data) as KeyringFile\n const oldSalt = base64ToBuffer(file.salt)\n const oldKek = await deriveKey(input.oldPassphrase, oldSalt)\n\n // Unwrap every DEK with the OLD KEK first — this also validates the\n // passphrase (a bad KEK throws InvalidKeyError on the first unwrap).\n const deks = new Map<string, CryptoKey>()\n for (const [coll, wrapped] of Object.entries(file.deks)) {\n deks.set(coll, await unwrapKey(wrapped, oldKek))\n }\n\n const newSalt = generateSalt()\n const newKek = await deriveKey(input.newPassphrase, newSalt)\n\n // Rewrap with the new KEK.\n const wrappedDeks: Record<string, string> = {}\n for (const [coll, dek] of deks) {\n wrappedDeks[coll] = await wrapKey(dek, newKek)\n }\n\n // Slot rewrap (#29). Without slotCeremonies, we drop every existing\n // slot — the pre-pre.8 behavior. With a ceremony map, slots whose\n // id appears in the map are preserved; the rest are dropped.\n const oldSlots = file.authenticators ?? []\n const newSlots: KeyringAuthenticator[] = []\n if (input.slotCeremonies && oldSlots.length > 0) {\n for (const oldSlot of oldSlots) {\n const ceremony = input.slotCeremonies[oldSlot.id]\n if (!ceremony) continue // drop — same as pre-#29 behavior\n\n const result = await ceremony({ newKek, newDeks: deks, oldSlot })\n\n // Anti-slot-swap guard. The ceremony MUST preserve identity —\n // a mismatch would let the consumer convert a webauthn slot to\n // a password slot mid-rotation, which would silently change\n // the security profile of the slot under cover of \"rotation.\"\n if (result.id !== oldSlot.id) {\n throw new ValidationError(\n `slotCeremonies['${oldSlot.id}'] returned id=\"${result.id}\". ` +\n 'The id must match the rotated slot — a ceremony cannot ' +\n 'change a slot\\'s identity.',\n )\n }\n if (result.method !== oldSlot.method) {\n throw new ValidationError(\n `slotCeremonies['${oldSlot.id}'] returned method=\"${result.method}\", ` +\n `expected \"${oldSlot.method}\". The method must match the rotated ` +\n 'slot — a ceremony cannot change the auth method (e.g. webauthn ' +\n '→ password) under cover of rotation.',\n )\n }\n // wrapKind absent on legacy slots / wrap-KEK enroll inputs; treat as 'kek'.\n const oldWrapKind = oldSlot.wrapKind ?? 'kek'\n const newWrapKind = result.wrapKind ?? 'kek'\n if (oldWrapKind !== newWrapKind) {\n throw new ValidationError(\n `slotCeremonies['${oldSlot.id}'] returned wrapKind=\"${newWrapKind}\", ` +\n `expected \"${oldWrapKind}\". The wrap format must match the rotated ` +\n 'slot — a ceremony cannot change the wrap shape (e.g. wrap-KEK → ' +\n 'wrap-DEKs) under cover of rotation, since that would silently ' +\n 'change the session tier produced at unlock.',\n )\n }\n\n // Build the persisted slot from the ceremony result. Mirrors\n // the same construction `enrollAuthenticator` does — wrap-DEKs\n // variants carry { wrapped_deks, iv }; wrap-KEK variants\n // carry { wrapped_kek }.\n const baseFields = {\n id: result.id,\n method: result.method,\n // Preserve original enrolled_at — rotation is rewrapping, not\n // re-enrollment. The slot's enrolment timestamp tracks when\n // the user originally added the slot, not when it was last\n // rewrapped. Forensics consumers reading enrolled_at are\n // tracking the slot's ORIGIN, not its CURRENT wrapping.\n enrolled_at: oldSlot.enrolled_at,\n enrolled_via_tier: result.enrolled_via_tier ?? oldSlot.enrolled_via_tier,\n meta: result.meta,\n } as const\n const newSlot: KeyringAuthenticator = result.wrapKind === 'deks'\n ? {\n ...baseFields,\n wrapKind: 'deks',\n wrapped_deks: result.wrapped_deks,\n iv: result.iv,\n }\n : {\n ...baseFields,\n wrapped_kek: result.wrapped_kek,\n }\n newSlots.push(newSlot)\n }\n }\n\n const canary = await mintKeyringCanary(newKek)\n const next: KeyringFile = {\n ...file,\n _noydb_keyring: NOYDB_KEYRING_VERSION,\n deks: wrappedDeks,\n salt: bufferToBase64(newSalt),\n authenticators: newSlots,\n canary,\n }\n\n await writeKeyringFile(store, vault, userId, next)\n\n return {\n userId: file.user_id,\n displayName: file.display_name,\n role: file.role,\n permissions: file.permissions,\n deks,\n kek: newKek,\n salt: newSalt,\n authenticators: newSlots,\n ...(file.export_capability !== undefined && { exportCapability: file.export_capability }),\n ...(file.import_capability !== undefined && { importCapability: file.import_capability }),\n }\n}\n\n/**\n * Caller payload for {@link recoverPassphrase}.\n *\n * As of #196 slice 1, `paper` and `shamir` are wired end-to-end.\n * The remaining two profiles (`multi-channel`, `admin-mediated`)\n * stay outside the union and throw\n * {@link RecoveryProfileNotImplementedError} at the runtime guard\n * when bypassed via `as unknown as RecoveryProof`.\n */\nexport type RecoveryProof =\n | { readonly profile: 'paper'; readonly payload: { readonly code: string } }\n | { readonly profile: 'shamir'; readonly payload: {\n /** Optional disambiguator when multiple Shamir entries are enrolled.\n * When omitted, hub tries each entry until one combines. */\n readonly entryId?: string\n /** K or more opaque share strings, as returned by `ShamirRecoveryProvider.splitToShares`. */\n readonly shares: ReadonlyArray<string>\n } }\n\nexport interface RecoverPassphraseInput {\n readonly newPassphrase: string\n readonly recoveryProof: RecoveryProof\n readonly passphrasePolicy?: PassphrasePolicy\n readonly allowWeakPassphrase?: boolean\n /**\n * After a successful paper-recovery, replace ALL remaining recovery\n * entries with freshly-minted ones. Defaults to `true` (defensive).\n *\n * Rationale (issue #36): the user just demonstrated they had access\n * to AT LEAST one code. The remaining codes from the same printed\n * sheet may also be compromised — photographed, leaked via a\n * screen-share slip, or in the hands of whoever stole the sheet.\n * Auto-rotation closes the window without requiring consumer action.\n *\n * Set to `false` to preserve the original behavior (only the matched\n * code is burned; the rest stay valid).\n *\n * Hub-side orchestration is non-atomic with the recovery itself:\n * if the rotation step fails after a successful burn, the user\n * falls back to the pre-rotation state (remaining codes still\n * valid). Strictly safer than the previous default — a failed\n * rotation degrades gracefully rather than leaving the vault\n * locked or codes dual-existing.\n */\n readonly rotateRemainingCodes?: boolean\n /**\n * Number of fresh codes to mint when `rotateRemainingCodes` is on.\n * Defaults to the count of remaining entries POST-burn (e.g. if\n * the user enrolled 8 originally and just consumed 1, defaults to\n * 7). Pass an explicit number to mint a different count — useful\n * when the consumer wants to refresh to a target N regardless of\n * how many were left.\n */\n readonly newCodeCount?: number\n /**\n * Override the default raw-code generator. The default is hub's\n * {@link generateULID} — uppercase Crockford-Base32, 26 chars,\n * passes through `normalizePaperCode` untouched.\n *\n * Pass `() => generateRawCode()` from `@noy-db/on-recovery` when\n * the consumer prefers the Base32 + checksum format with hyphenated\n * display. The `mintPaperRecoveryEntry` helper accepts any string —\n * the generator just needs to produce a high-entropy unique value.\n */\n readonly codeGenerator?: () => string\n}\n\n/**\n * Return shape of `db.recoverPassphrase`. `newCodes` is populated when\n * `rotateRemainingCodes` was enabled and at least one entry was\n * rotated; an empty array means no rotation happened (rotation\n * disabled, or no remaining codes after burn). Show the codes to the\n * user once — they are the canonical credential for future recovery\n * and CANNOT be retrieved again.\n */\nexport interface RecoverPassphraseResult {\n readonly newCodes: readonly string[]\n}\n\n/**\n * Input for {@link Noydb.rotateRecovery} (#121) — deliberate\n * recovery-credential regeneration when the user knows their\n * passphrase but wants a fresh sheet (paper) or fresh shares\n * (shamir). Symmetric to {@link RotatePassphraseInput}.\n */\nexport type RotateRecoveryOptions =\n | {\n readonly profile: 'paper'\n /** How many fresh codes to mint. Default: existing sheet size. */\n readonly count?: number\n /** Optional code generator — see {@link RecoverPassphraseInput.codeGenerator}. */\n readonly codeGenerator?: () => string\n }\n | {\n readonly profile: 'shamir'\n /** New threshold. */\n readonly k: number\n /** New total share count. */\n readonly n: number\n /** Disambiguator when multiple Shamir entries exist; required if there are 2+. */\n readonly entryId?: string\n /** Optional updated label. */\n readonly label?: string\n }\n\n/**\n * Result of {@link Noydb.rotateRecovery}. Shape varies by profile:\n *\n * - `paper` → `{ newCodes: string[] }` (and `entryId === 'paper-batch'`)\n * - `shamir` → `{ newShares: string[], entryId }`\n *\n * `newCodes` is populated for paper rotations; `newShares` for\n * Shamir rotations. Both are show-once — the hub does not\n * retain them.\n */\nexport interface RotateRecoveryResult {\n readonly newCodes?: readonly string[]\n readonly newShares?: readonly string[]\n readonly entryId?: string\n}\n\n/**\n * Result of {@link Noydb.enrollRecovery}. Shape varies by profile:\n *\n * - `paper` → `{ entryId: 'paper-batch' }` (caller minted the\n * entries; this is a sentinel since paper enrollments are batch-shaped).\n * - `shamir` → `{ entryId, shares: string[] }` — shares are\n * show-once; the hub does not retain them.\n */\nexport interface EnrollRecoveryResult {\n readonly entryId: string\n readonly shares?: readonly string[]\n}\n\n/**\n * Input shape for {@link Noydb.enrollRecovery} and\n * {@link Noydb.openVaultAndEnrollRecovery} (#195). Discriminated\n * union over recovery profiles.\n *\n * - `paper`: caller pre-mints entries (typically via\n * `mintPaperRecoveryEntry` or `@noy-db/on-recovery`'s\n * `generateRecoveryCodeSet`) and passes them in. The hub stores\n * them and surfaces an opaque batch id.\n * - `shamir`: hub mints the recovery secret + the shares at\n * enrollment time. The shares are returned in\n * {@link EnrollRecoveryResult.shares} (show-once); the hub never\n * retains them.\n *\n * Multi-channel and admin-mediated will be added when the respective\n * dispatch slices ship.\n */\nexport type RecoveryEnrollmentInput =\n | { readonly profile: 'paper'; readonly entries: ReadonlyArray<PaperRecoveryEntry> }\n | {\n readonly profile: 'shamir'\n readonly k: number\n readonly n: number\n readonly label?: string\n readonly entryId?: string\n }\n\n/**\n * Reset the user's passphrase using a recovery proof. v0.1.0-pre.5\n * supports the `'paper'` profile via `@noy-db/on-recovery` entries\n * persisted in `_meta/recovery-paper`. The other three profiles throw\n * {@link RecoveryProfileNotImplementedError}.\n *\n * On success, the used recovery entry is burned (deleted from the\n * stored set).\n */\nexport async function recoverPassphrase(\n provider: ShamirRecoveryProvider | undefined,\n store: NoydbStore,\n vault: string,\n userId: string,\n input: RecoverPassphraseInput,\n): Promise<UnlockedKeyring> {\n if (!input.allowWeakPassphrase) {\n assertStrongPassphrase(input.newPassphrase, input.passphrasePolicy)\n }\n\n // Runtime defense-in-depth: the type narrows to 'paper' | 'shamir'\n // (#86 + #196), but a consumer bypassing TS via\n // `as unknown as RecoveryProof` should still hit a clear error\n // rather than silently fall into a handler with a malformed payload.\n const profile = (input.recoveryProof as { profile: string }).profile\n if (profile === 'paper') {\n return recoverViaPaperCode(store, vault, userId, input)\n }\n if (profile === 'shamir') {\n return recoverViaShamir(provider, store, vault, userId, input)\n }\n throw new RecoveryProfileNotImplementedError(\n profile,\n 'https://github.com/vLannaAi/noy-db/issues/196',\n )\n}\n\nasync function recoverViaPaperCode(\n store: NoydbStore,\n vault: string,\n userId: string,\n input: RecoverPassphraseInput,\n): Promise<UnlockedKeyring> {\n if (input.recoveryProof.profile !== 'paper') throw new Error('unreachable')\n const { code } = input.recoveryProof.payload\n\n const env = await store.get(vault, '_keyring', userId)\n if (!env) {\n throw new NoAccessError(`No keyring found for user \"${userId}\" in vault \"${vault}\".`)\n }\n const file = JSON.parse(env._data) as KeyringFile\n\n const entries = await loadPaperRecoveryEntries(store, vault)\n if (entries.length === 0) {\n throw new NoAccessError(\n `No paper-recovery entries enrolled for vault \"${vault}\". ` +\n 'Enroll via `db.enrollRecovery({ profile: \"paper\", entries })` before relying on recovery.',\n )\n }\n\n const normalized = normalizePaperCode(code)\n let recovered: { deks: Map<string, CryptoKey>; entry: PaperRecoveryEntry } | undefined\n for (const entry of entries) {\n try {\n const deks = await unwrapDeksFromPaperEntry(entry, normalized)\n recovered = { deks, entry }\n break\n } catch {\n // wrong code for this entry — try the next one\n }\n }\n if (!recovered) {\n throw new InvalidKeyError(\n 'Recovery code does not match any enrolled paper entry. The code may have been ' +\n 'previously used (single-use) or typed incorrectly.',\n )\n }\n\n const deks = recovered.deks\n\n // Fresh salt + KEK from the new passphrase, rewrap.\n const newSalt = generateSalt()\n const newKek = await deriveKey(input.newPassphrase, newSalt)\n const wrappedDeks: Record<string, string> = {}\n for (const [coll, dek] of deks) {\n wrappedDeks[coll] = await wrapKey(dek, newKek)\n }\n\n const canary = await mintKeyringCanary(newKek)\n const next: KeyringFile = {\n ...file,\n _noydb_keyring: NOYDB_KEYRING_VERSION,\n deks: wrappedDeks,\n salt: bufferToBase64(newSalt),\n authenticators: [], // tier-2 slots wrap old KEK, drop them\n canary,\n }\n\n // Burn first, then rewrite the keyring. The two writes are not\n // atomic — if the second fails (#84), the safer ordering is:\n //\n // 1. Code burned, keyring untouched: user keeps their old passphrase\n // and loses one recovery code (recoverable: contact admin / use\n // another code).\n //\n // 2. Keyring rewritten, code unburned: user has rotated, but the\n // consumed code REMAINS VALID. Anyone with access to the paper\n // sheet can use it again. Security regression.\n //\n // Burning first picks (1) over (2).\n await burnPaperRecoveryEntry(store, vault, recovered.entry.codeId)\n await writeKeyringFile(store, vault, userId, next)\n\n return {\n userId: file.user_id,\n displayName: file.display_name,\n role: file.role,\n permissions: file.permissions,\n deks,\n kek: newKek,\n salt: newSalt,\n authenticators: [],\n ...(file.export_capability !== undefined && { exportCapability: file.export_capability }),\n ...(file.import_capability !== undefined && { importCapability: file.import_capability }),\n }\n}\n\n/**\n * Mirror of `@noy-db/on-recovery/parseRecoveryCode`. Inlined so the\n * hub does not gain a peer dep on on-recovery — both implementations\n * follow the same RFC 4648 Base32 + checksum format and round-trip\n * through the same KDF.\n *\n * Accepts hyphenated, lowercase, or whitespace-padded input.\n */\nfunction normalizePaperCode(input: string): string {\n return input.toUpperCase().replace(/[\\s\\-_]/g, '')\n}\n\n/**\n * Recover the user's keyring via the Shamir profile.\n *\n * 1. Decode each supplied share string into a {@link RawShare}.\n * 2. Load `_meta/recovery-shamir` entries.\n * 3. If `payload.entryId` is supplied, restrict to that entry; else\n * iterate over all entries and try each until one combines.\n * 4. For each candidate: filter shares to those whose `(k, n)`\n * match the entry's parameters, then attempt\n * `unwrapDeksFromShamirEntry`. AES-GCM auth-tag failure means\n * the combined secret doesn't match — try the next entry.\n * 5. With unwrapped DEKs: derive fresh KEK from `newPassphrase` +\n * fresh salt, rewrap, write the keyring.\n * 6. Shamir entries are NOT burned on recovery (shares reusable);\n * explicit {@link Noydb.rotateRecovery} is the refresh ceremony.\n */\nasync function recoverViaShamir(\n provider: ShamirRecoveryProvider | undefined,\n store: NoydbStore,\n vault: string,\n userId: string,\n input: RecoverPassphraseInput,\n): Promise<UnlockedKeyring> {\n if (input.recoveryProof.profile !== 'shamir') throw new Error('unreachable')\n const { entryId: requestedEntryId, shares: shareStrings } = input.recoveryProof.payload\n\n if (shareStrings.length === 0) {\n throw new ValidationError(\n 'Shamir recovery requires at least one share; received an empty array.',\n )\n }\n\n const env = await store.get(vault, '_keyring', userId)\n if (!env) {\n throw new NoAccessError(`No keyring found for user \"${userId}\" in vault \"${vault}\".`)\n }\n const file = JSON.parse(env._data) as KeyringFile\n\n const allEntries = await loadShamirRecoveryEntries(store, vault)\n if (allEntries.length === 0) {\n throw new NoAccessError(\n `No Shamir-recovery entries enrolled for vault \"${vault}\". `\n + 'Enroll via `db.enrollRecovery({ profile: \"shamir\", k, n })` before relying on recovery.',\n )\n }\n\n if (!provider) {\n throw new Error(\n \"shamir recovery requires a ShamirRecoveryProvider — pass \"\n + \"shamirRecovery: shamirRecoveryProvider() from '@noy-db/on-shamir' to createNoydb()\",\n )\n }\n\n // Restrict to a specific entry when entryId supplied.\n let candidates: ReadonlyArray<ShamirRecoveryEntry>\n if (requestedEntryId !== undefined) {\n candidates = allEntries.filter(e => e.entryId === requestedEntryId)\n if (candidates.length === 0) {\n throw new NoAccessError(\n `No Shamir-recovery entry with entryId=\"${requestedEntryId}\" found `\n + `in vault \"${vault}\". Available entries: `\n + allEntries.map(e => `\"${e.entryId}\"`).join(', '),\n )\n }\n } else {\n candidates = allEntries\n }\n\n // Try each candidate entry. Pass all share strings to the provider;\n // provider.combineShares validates and throws on mismatch — the\n // AES-GCM auth-tag is an additional guard.\n let recoveredDeks: Map<string, CryptoKey> | undefined\n for (const entry of candidates) {\n if (shareStrings.length < entry.k) {\n // Not enough shares for this entry — could still match another.\n continue\n }\n try {\n const deks = await unwrapDeksFromShamirEntry(provider, entry, shareStrings)\n recoveredDeks = deks\n break\n } catch {\n // provider.combineShares threw (malformed/mismatched shares) or\n // AES-GCM auth-tag failure → try the next entry.\n }\n }\n\n if (!recoveredDeks) {\n // Distinguish \"below-threshold\" from \"no entry matches\" so the\n // error message is actionable.\n const minK = Math.min(...candidates.map(e => e.k))\n if (shareStrings.length < minK) {\n throw new InvalidKeyError(\n `Insufficient Shamir shares to combine: the smallest enrolled threshold is ${minK}, `\n + `but only ${shareStrings.length} share${shareStrings.length === 1 ? ' was' : 's were'} provided.`,\n )\n }\n throw new InvalidKeyError(\n 'Shamir shares do not match any enrolled entry. Possible causes: '\n + 'shares were tampered with, came from a different enrollment, '\n + 'or the entry was rotated after these shares were distributed.',\n )\n }\n\n // Mint fresh KEK from new passphrase, rewrap DEKs (mirrors paper).\n const newSalt = generateSalt()\n const newKek = await deriveKey(input.newPassphrase, newSalt)\n const wrappedDeks: Record<string, string> = {}\n for (const [coll, dek] of recoveredDeks) {\n wrappedDeks[coll] = await wrapKey(dek, newKek)\n }\n\n const canary = await mintKeyringCanary(newKek)\n const next: KeyringFile = {\n ...file,\n _noydb_keyring: NOYDB_KEYRING_VERSION,\n deks: wrappedDeks,\n salt: bufferToBase64(newSalt),\n authenticators: [], // tier-2 slots wrap old KEK, drop them on recovery\n canary,\n }\n\n // No burn: Shamir entries persist across recoveries. Explicit\n // rotateRecovery is the refresh ceremony.\n await writeKeyringFile(store, vault, userId, next)\n\n return {\n userId: file.user_id,\n displayName: file.display_name,\n role: file.role,\n permissions: file.permissions,\n deks: recoveredDeks,\n kek: newKek,\n salt: newSalt,\n authenticators: [],\n ...(file.export_capability !== undefined && { exportCapability: file.export_capability }),\n ...(file.import_capability !== undefined && { importCapability: file.import_capability }),\n }\n}\n\nasync function writeKeyringFile(\n store: NoydbStore,\n vault: string,\n userId: string,\n file: KeyringFile,\n): Promise<void> {\n const envelope = {\n _noydb: 1 as const,\n _v: 1,\n _ts: new Date().toISOString(),\n _iv: '',\n _data: JSON.stringify(file),\n }\n await store.put(vault, '_keyring', userId, envelope)\n}\n","/**\n * Atomic peer-recovery primitive — issues #33 + #34.\n *\n * `recoverUser` is a SEPARATE operation from `revoke + grant`. It\n * exists because peer-recovery has different semantics than account\n * removal-then-reissue:\n *\n * 1. **Same identity preserved.** `userId`, `role`, `permissions`,\n * capability bits, user envelope (if any), policy override (if\n * any) all survive. Only the wrapping changes.\n * 2. **No key rotation.** The existing DEKs stay valid — every\n * OTHER principal in the vault keeps their access. Rotating\n * keys would invalidate every co-user's wrapping.\n * 3. **Atomic by construction.** A single `store.put` overwrites\n * `_keyring/<userId>` with the recovered file. No revoke step\n * means no partial-failure window.\n * 4. **Owner→owner natively allowed.** Two co-owners recovering\n * each other is the explicitly-intentional case (a partner\n * forgot the master phrase). The existing `canRevoke` rule that\n * blocks owner→owner is correct for `revoke` (which is account\n * *removal*) and intentionally NOT replicated here. The policy\n * gate `peer-recover-user` carries the freshness requirement.\n * 5. **Tier-2 slots dropped.** The slots wrap the OLD KEK under\n * method-derived keys; after recovery the KEK is re-derived\n * from the new temp passphrase. Match `rotatePassphrase`'s\n * precedent — the recovered user re-enrols slots after picking\n * their own phrase.\n *\n * Caller must be at least as privileged as the target. The hub\n * `db.recoverUser` method gates this with the `peer-recover-user`\n * policy gate (#33's factor-proof requirement); the function below\n * enforces only the role + anti-privilege-escalation invariants.\n *\n * @module\n */\nimport type { NoydbStore, KeyringFile, Role } from '../types.js'\nimport { NOYDB_KEYRING_VERSION } from '../types.js'\nimport { deriveKey, generateSalt, wrapKey, bufferToBase64 } from '../crypto.js'\nimport { NoAccessError, PermissionDeniedError, PrivilegeEscalationError } from '../errors.js'\nimport { assertStrongPassphrase, type PassphrasePolicy } from '../validation.js'\nimport type { UnlockedKeyring } from './keyring.js'\nimport { mintKeyringCanary } from './keyring.js'\n\nconst ADMIN_RECOVERABLE_TARGETS: readonly Role[] = ['operator', 'viewer', 'client', 'admin']\n\n/**\n * Whether `callerRole` may recover `targetRole`.\n *\n * Differs from `canRevoke` (in `keyring.ts`) in one critical place:\n * **owner→owner IS allowed**. Peer recovery is the explicitly\n * intentional case (a co-owner forgot their phrase); the freshness\n * binding lives in the `peer-recover-user` policy gate, not in the\n * permission predicate.\n *\n * Admins can recover everyone they could grant (operator / viewer /\n * client / admin) but NOT owners — that boundary stays as a hard\n * structural rule even under recovery.\n */\nfunction canRecover(callerRole: Role, targetRole: Role): boolean {\n if (callerRole === 'owner') return true\n if (callerRole === 'admin') return ADMIN_RECOVERABLE_TARGETS.includes(targetRole)\n return false\n}\n\n/** Input shape for {@link recoverUser}. */\nexport interface RecoverUserOptions {\n /** Target user id whose keyring is being recovered. */\n readonly userId: string\n /**\n * Temporary passphrase under which the new keyring is wrapped.\n * The recipient should call `db.rotatePassphrase` immediately on\n * acceptance to choose their own phrase — this temp acts as a\n * single-use bridge in invite / peer-recovery flows.\n */\n readonly passphrase: string\n /** Override the target's role. Defaults to the existing target's role. */\n readonly role?: Role\n /** Override the target's display name. Defaults to existing. */\n readonly displayName?: string\n /** Validate phrase strength against the configured policy. */\n readonly validatePassphrase?: boolean\n /**\n * Skip phrase strength validation even when `validatePassphrase` is\n * set. The escape hatch matches `grant`'s shape — used when the\n * temp phrase is a high-entropy one-shot string that doesn't need\n * to satisfy the human-typeable rules.\n */\n readonly allowWeakPassphrase?: boolean\n /**\n * Optional explicit phrase policy override (passed through to\n * `assertStrongPassphrase`). Mirrors how `grant` accepts a custom\n * `PassphrasePolicy` for app-specific tightening.\n */\n readonly passphrasePolicy?: PassphrasePolicy\n}\n\n/**\n * Atomically rewrap the target user's keyring under a fresh temp\n * passphrase. Single store write; no revoke step; no key rotation.\n *\n * Caller's responsibilities (NOT enforced here):\n * - Run the `peer-recover-user` policy gate first via\n * `Noydb.checkGate` to enforce the freshness factor proof.\n * - Communicate the temp passphrase to the recipient via a secure\n * channel (URL fragment, in-person, etc.) — the hub does not\n * transport secrets.\n */\nexport async function recoverUser(\n store: NoydbStore,\n vault: string,\n callerKeyring: UnlockedKeyring,\n options: RecoverUserOptions,\n): Promise<void> {\n // 1. Load the target's existing keyring file (plaintext header).\n const env = await store.get(vault, '_keyring', options.userId)\n if (!env) {\n throw new NoAccessError(\n `recoverUser: user \"${options.userId}\" has no keyring in vault \"${vault}\".`,\n )\n }\n const target = JSON.parse(env._data) as KeyringFile\n const targetRole = options.role ?? target.role\n\n // 2. Permission check — caller must be allowed to recover this role.\n // Owner→owner natively allowed; admin→admin allowed; admin→owner blocked.\n if (!canRecover(callerKeyring.role, targetRole)) {\n throw new PermissionDeniedError(\n `Role \"${callerKeyring.role}\" cannot recover role \"${targetRole}\"`,\n )\n }\n // Also guard against role-uplift via the override — admin cannot\n // promote a target to owner under cover of recovery.\n if (!canRecover(callerKeyring.role, target.role)) {\n throw new PermissionDeniedError(\n `Role \"${callerKeyring.role}\" cannot recover role \"${target.role}\"`,\n )\n }\n\n // 3. Anti-privilege-escalation. Every collection the target had\n // access to must be in the caller's DEK set — the recoverer\n // cannot give the recovered user access to a collection the\n // recoverer themselves can't read. Mirrors `grant()`'s check.\n for (const coll of Object.keys(target.deks)) {\n if (!callerKeyring.deks.has(coll)) {\n throw new PrivilegeEscalationError(coll)\n }\n }\n\n // 4. Optional phrase strength validation (mirrors `grant` opt-in).\n if (options.validatePassphrase && !options.allowWeakPassphrase) {\n assertStrongPassphrase(options.passphrase, options.passphrasePolicy)\n }\n\n // 5. Mint a fresh salt + KEK from the temp passphrase. The DEKs\n // themselves are unchanged — only the wrapping is replaced.\n const newSalt = generateSalt()\n const newKek = await deriveKey(options.passphrase, newSalt)\n\n const wrappedDeks: Record<string, string> = {}\n for (const coll of Object.keys(target.deks)) {\n const callerDek = callerKeyring.deks.get(coll)\n if (!callerDek) {\n // Already caught by the anti-privilege-escalation loop above.\n // This branch is defensive belt-and-braces; if it ever fires,\n // the target had a collection the caller's deks Map disagrees\n // with — fail loud rather than silently dropping access.\n throw new PrivilegeEscalationError(coll)\n }\n wrappedDeks[coll] = await wrapKey(callerDek, newKek)\n }\n\n // 6. Build the recovered keyring file. Identity preserved; wrapping\n // refreshed; tier-2 slots dropped (they wrap the OLD KEK and\n // can't survive a tier-1 phrase change — same precedent as\n // rotatePassphrase). Mint a fresh canary under newKek (#113); the\n // OLD canary on the spread `...target` would fail to verify against\n // the new KEK and trip KeyringCorruptError on next load.\n const canary = await mintKeyringCanary(newKek)\n const next: KeyringFile = {\n ...target,\n _noydb_keyring: NOYDB_KEYRING_VERSION,\n role: targetRole,\n display_name: options.displayName ?? target.display_name,\n deks: wrappedDeks,\n salt: bufferToBase64(newSalt),\n granted_by: callerKeyring.userId,\n authenticators: [],\n canary,\n }\n\n // 7. Single atomic write — overwrites the existing envelope.\n // Backend `put` is the canonical write primitive across every\n // `to-*` store; no partial-failure window between revoke + grant.\n const envelope = {\n _noydb: 1 as const,\n _v: 1,\n _ts: new Date().toISOString(),\n _iv: '',\n _data: JSON.stringify(next),\n }\n await store.put(vault, '_keyring', options.userId, envelope)\n}\n","/**\n * Persistence helpers for the vault-level policy document\n * (`_meta/policy`). Mirrors the bypass-AES pattern used by\n * `_meta/handle` — the policy document is plain JSON, the envelope's\n * `_iv` field is left empty.\n *\n * @see docs/subsystems/session-tiers.md → Storage location\n *\n * @module\n */\nimport type { NoydbStore, EncryptedEnvelope } from '../types.js'\nimport { NOYDB_FORMAT_VERSION } from '../types.js'\nimport type { VaultPolicy } from './types.js'\n\n/** Reserved collection name for vault-level metadata documents. */\nexport const META_COLLECTION = '_meta'\n/** Reserved id for the vault-level policy document. */\nexport const POLICY_RECORD_ID = 'policy'\n\n/**\n * Read the vault-level policy from `_meta/policy`. Returns `undefined`\n * when no policy has been persisted (fresh vault, or a vault written\n * before the policy module landed). The caller falls back to the\n * default preset.\n *\n * Tolerates corrupted documents the same way `_meta/handle` does: a\n * JSON parse failure surfaces as `undefined`, not a thrown error, so\n * a bad write never permanently locks a vault.\n */\nexport async function loadVaultPolicy(\n store: NoydbStore,\n vault: string,\n): Promise<VaultPolicy | undefined> {\n const envelope = await store.get(vault, META_COLLECTION, POLICY_RECORD_ID)\n if (!envelope) return undefined\n try {\n const parsed = JSON.parse(envelope._data) as unknown\n if (!isVaultPolicy(parsed)) return undefined\n return parsed\n } catch {\n return undefined\n }\n}\n\n/**\n * Persist the vault-level policy at `_meta/policy`. Idempotent — call\n * once at vault creation and again on `db.updatePolicy()` invocations.\n */\nexport async function saveVaultPolicy(\n store: NoydbStore,\n vault: string,\n policy: VaultPolicy,\n): Promise<void> {\n const envelope: EncryptedEnvelope = {\n _noydb: NOYDB_FORMAT_VERSION,\n _v: 1,\n _ts: new Date().toISOString(),\n _iv: '',\n _data: JSON.stringify(policy),\n }\n await store.put(vault, META_COLLECTION, POLICY_RECORD_ID, envelope)\n}\n\nfunction isVaultPolicy(x: unknown): x is VaultPolicy {\n if (x === null || typeof x !== 'object') return false\n if (!('gates' in x)) return false\n const gates = (x as { gates: unknown }).gates\n return gates !== null && typeof gates === 'object'\n}\n","/**\n * Authentication introspection — issue #13.\n *\n * Three surfaces over the configured tier model and the actual\n * per-user enrollment state:\n *\n * 1. **Vault-wide English summary** — {@link describeAuthConfig}.\n * 2. **Vault-wide Mermaid diagram** — {@link diagramAuthConfig}.\n * 3. **Per-user introspection** — {@link describeUserAuth}, gated by\n * the `view-user-auth` policy gate (off by default).\n *\n * The per-user surface is held to a strict allowlist — fields not on\n * the allowlist are dropped, never rendered. The negative test in\n * `auth-introspection.test.ts` exercises the allowlist by feeding a\n * contrived keyring with fake \"secret\" fields and asserting that none\n * of them appear in the output.\n *\n * @module\n */\nimport type { NoydbStore, KeyringFile, KeyringAuthenticator } from '../types.js'\nimport type { VaultPolicy, GatePolicy } from '../policy/types.js'\nimport { loadVaultPolicy } from '../policy/storage.js'\nimport { loadPaperRecoveryEntries } from '../team/recovery.js'\n\n/** Vault-wide English summary of the configured authentication graph. */\nexport async function describeAuthConfig(\n store: NoydbStore,\n vault: string,\n): Promise<string> {\n const policy = (await loadVaultPolicy(store, vault)) ?? defaultPolicySnapshot()\n const recoveryProfiles = await listRecoveryProfilesEnrolled(store, vault)\n\n const lines: string[] = []\n lines.push(`Vault \"${vault}\" — three-tier authentication`)\n lines.push('')\n lines.push('Tier 1 — Passphrase (master)')\n lines.push(` Phrase format: ${policy.passphrase?.minWords ?? 6}+ words, lowercase letters, ≥${policy.passphrase?.minWordLength ?? 3} chars/word`)\n lines.push(' Strength validator: enforced (override available for tests only)')\n lines.push('')\n lines.push('Tier 2 — Authenticate (routine login)')\n lines.push(' Allowed methods: WebAuthn (passkey), OIDC, Password')\n lines.push(' Slots per user: unlimited')\n lines.push('')\n lines.push('Tier 3 — Unlock (quick resume)')\n lines.push(' Method: PIN (per-app configurable)')\n lines.push('')\n lines.push(`Recovery profiles enrolled: ${recoveryProfiles.length === 0 ? 'none' : recoveryProfiles.join(', ')}`)\n lines.push('Managed-passphrase mode: off (post-1.0)')\n lines.push('')\n lines.push('Sensitive-action gates:')\n for (const [gate, gp] of Object.entries(policy.gates) as Array<[string, GatePolicy]>) {\n lines.push(` ${gate} — ${describeGatePolicy(gp)}`)\n }\n return lines.join('\\n')\n}\n\n/**\n * Render the vault's auth graph as Mermaid `flowchart TB` source. The\n * caller pipes this through Mermaid (CLI or browser) to get an SVG.\n */\nexport async function diagramAuthConfig(\n store: NoydbStore,\n vault: string,\n): Promise<string> {\n const policy = (await loadVaultPolicy(store, vault)) ?? defaultPolicySnapshot()\n const lines: string[] = []\n lines.push('flowchart TB')\n lines.push(` vault[\"Vault: ${escapeMermaid(vault)}\"]`)\n lines.push(' tier1[\"Tier 1<br/>Passphrase\"]')\n lines.push(' tier2[\"Tier 2<br/>Multi-slot Authenticate\"]')\n lines.push(' tier3[\"Tier 3<br/>PIN / Quick-resume\"]')\n lines.push(' vault --> tier1')\n lines.push(' tier1 --> tier2')\n lines.push(' tier2 --> tier3')\n for (const [gateName, gp] of Object.entries(policy.gates) as Array<[string, GatePolicy]>) {\n if (gp.enabled === false) continue\n const id = sanitizeId(gateName)\n const label = `${gateName}<br/>tier ≥ ${gp.minTier}`\n lines.push(` ${id}[\"${escapeMermaid(label)}\"]`)\n const tierNode = gp.minTier === 1 ? 'tier1' : gp.minTier === 2 ? 'tier2' : 'tier3'\n lines.push(` ${tierNode} --> ${id}`)\n }\n return lines.join('\\n')\n}\n\n/**\n * Render the per-user enrollment summary. Returns an empty\n * (non-throwing) string when the user has no keyring file — never\n * confirms or denies the existence of the user from the document\n * alone.\n *\n * Sanitization is strict: only the slot list, enrollment dates, and\n * recovery-profile counts are rendered. WebAuthn cred ids, OIDC\n * subject ids, password hashes, recovery codes, TOTP secrets — all\n * dropped at the allowlist boundary, not redacted.\n */\nexport async function describeUserAuth(\n store: NoydbStore,\n vault: string,\n userId: string,\n): Promise<string> {\n const env = await store.get(vault, '_keyring', userId)\n if (!env) return ''\n const file = JSON.parse(env._data) as KeyringFile\n\n const lines: string[] = []\n lines.push(\n `User: ${file.user_id} (joined ${file.created_at.slice(0, 10)}, role: ${file.role})`,\n )\n lines.push('')\n lines.push('Tier 2 enrollments:')\n if (!file.authenticators || file.authenticators.length === 0) {\n lines.push(' (none enrolled)')\n } else {\n for (const slot of file.authenticators) {\n lines.push(` - ${describeSlot(slot)}`)\n }\n }\n return lines.join('\\n')\n}\n\n/** Bulk variant for owner dashboards. */\nexport async function describeAllUsersAuth(\n store: NoydbStore,\n vault: string,\n): Promise<Array<{ userId: string; description: string }>> {\n const ids = await store.list(vault, '_keyring')\n const results: Array<{ userId: string; description: string }> = []\n for (const userId of ids) {\n const description = await describeUserAuth(store, vault, userId)\n if (description !== '') results.push({ userId, description })\n }\n return results\n}\n\n// ─── Helpers ───────────────────────────────────────────────────────────\n\nconst SLOT_FIELD_ALLOWLIST: ReadonlyArray<keyof KeyringAuthenticator> = [\n 'id',\n 'method',\n 'enrolled_at',\n 'enrolled_via_tier',\n] as const\n\nfunction describeSlot(slot: KeyringAuthenticator): string {\n // Project to the allowlist FIRST — never read meta/wrapped_kek into\n // any user-facing string. The allowlist is the only path values\n // take to the renderer; off-allowlist fields are dropped, not redacted.\n const sanitized: Partial<KeyringAuthenticator> = {}\n for (const key of SLOT_FIELD_ALLOWLIST) {\n if (key in slot) {\n // @ts-expect-error narrow assignment from allowlist iteration\n sanitized[key] = slot[key]\n }\n }\n const date = (sanitized.enrolled_at ?? '').slice(0, 10)\n return `${sanitized.method ?? '?'} (id=${sanitized.id ?? '?'}, enrolled ${date}, via tier ${sanitized.enrolled_via_tier ?? '?'})`\n}\n\nfunction describeGatePolicy(gp: GatePolicy): string {\n if (gp.enabled === false) return 'disabled'\n const parts: string[] = []\n parts.push(`tier ${gp.minTier}`)\n if (gp.factors && gp.factors.length > 0) {\n for (const f of gp.factors) {\n parts.push(`+ ${f.count ?? 1}× ${f.anyOf.join('|')}`)\n }\n }\n if (gp.warn?.sharedDevice === 'block') parts.push('block-on-shared-device')\n return parts.join(' ')\n}\n\nfunction defaultPolicySnapshot(): VaultPolicy {\n return {\n passphrase: { minWords: 6, minWordLength: 3, rejectRepeatedAdjacent: true },\n gates: {},\n }\n}\n\nasync function listRecoveryProfilesEnrolled(\n store: NoydbStore,\n vault: string,\n): Promise<ReadonlyArray<string>> {\n const enrolled: string[] = []\n const paper = await loadPaperRecoveryEntries(store, vault)\n if (paper.length > 0) enrolled.push(`paper (${paper.length} codes)`)\n return enrolled\n}\n\nfunction escapeMermaid(s: string): string {\n return s.replace(/\"/g, '\\\\\"').replace(/\\n/g, ' ')\n}\n\nfunction sanitizeId(s: string): string {\n return s.replace(/[^a-zA-Z0-9]/g, '_')\n}\n","/**\n * Public envelope — owner-curated plaintext metadata, readable\n * before vault unlock or bundle decryption.\n *\n * @see docs/subsystems/public-envelope.md\n *\n * @module\n */\n\n/**\n * Either a single string (used when the developer's app is\n * single-locale) or a locale → string map for i18n. Mirrors the\n * shape `@noy-db/hub/i18n` already uses for record fields, so the\n * existing `resolveI18nText` resolver applies.\n */\nexport type PublicEnvelopeText = string | Record<string, string>\n\n/**\n * Persisted shape — both `_meta/public-envelope` and the bundle\n * header carry this. The version number is monotonic per vault and\n * helps cache invalidators detect change without hashing the JSON.\n */\nexport interface PublicEnvelope {\n readonly _noydb_public: 1\n readonly version: number\n readonly name?: PublicEnvelopeText\n readonly description?: PublicEnvelopeText\n /** Inline `data:` URL (`data:image/png;base64,…` or `data:image/svg+xml;base64,…`). */\n readonly icon?: string\n /** ISO-8601 timestamp; auto-set on first envelope write, immutable thereafter. */\n readonly createdAt?: string\n /** ISO-8601 timestamp; auto-updated on every `setPublicEnvelope` call. */\n readonly updatedAt?: string\n /** BCP-47 fallback locale for renderers when the user's locale isn't covered. */\n readonly defaultLocale?: string\n}\n\n/** Field names the developer can allow in `PublicEnvelopeSchema.fields`. */\nexport const PUBLIC_ENVELOPE_FIELDS = [\n 'name',\n 'description',\n 'icon',\n 'createdAt',\n 'updatedAt',\n 'defaultLocale',\n] as const\n\nexport type PublicEnvelopeField = (typeof PUBLIC_ENVELOPE_FIELDS)[number]\n\n/**\n * Build-time schema. The developer enables the feature and bounds\n * what the owner can set. `true` is shorthand for \"all defaults\" —\n * gives the owner the full field set with the standard caps.\n */\nexport interface PublicEnvelopeSchema {\n /**\n * Allowed field names. Setting `name`/`description`/`icon`/`defaultLocale` is\n * gated on the field being listed here. `createdAt` / `updatedAt` are managed\n * by the hub; including them is a no-op (the owner cannot set them\n * directly). Default: every field above.\n */\n readonly fields?: ReadonlyArray<PublicEnvelopeField>\n /**\n * Maximum icon size — measured as the length of the data-URL\n * string. Default 256 KB.\n */\n readonly maxIconBytes?: number\n /** Allowed icon MIME types. Default ['image/png', 'image/svg+xml']. */\n readonly iconMimeTypes?: ReadonlyArray<string>\n /** Maximum length of `name` / `description` per locale. Default 200. */\n readonly maxStringChars?: number\n}\n\n/** Default schema values; merged onto every developer-supplied schema. */\nexport const DEFAULT_PUBLIC_ENVELOPE_SCHEMA = {\n fields: PUBLIC_ENVELOPE_FIELDS,\n maxIconBytes: 256 * 1024,\n iconMimeTypes: ['image/png', 'image/svg+xml'] as const,\n maxStringChars: 200,\n} as const satisfies Required<PublicEnvelopeSchema>\n\n/** Resolved schema after merging developer override onto defaults. */\nexport interface ResolvedPublicEnvelopeSchema {\n readonly fields: ReadonlyArray<PublicEnvelopeField>\n readonly maxIconBytes: number\n readonly iconMimeTypes: ReadonlyArray<string>\n readonly maxStringChars: number\n}\n\n/**\n * Merge developer schema onto the defaults. The shorthand `true`\n * resolves to the full default schema; an explicit object only\n * overrides the keys it provides.\n */\nexport function resolveSchema(\n schema: true | PublicEnvelopeSchema | undefined,\n): ResolvedPublicEnvelopeSchema | undefined {\n if (!schema) return undefined\n if (schema === true) {\n return {\n fields: DEFAULT_PUBLIC_ENVELOPE_SCHEMA.fields,\n maxIconBytes: DEFAULT_PUBLIC_ENVELOPE_SCHEMA.maxIconBytes,\n iconMimeTypes: DEFAULT_PUBLIC_ENVELOPE_SCHEMA.iconMimeTypes,\n maxStringChars: DEFAULT_PUBLIC_ENVELOPE_SCHEMA.maxStringChars,\n }\n }\n return {\n fields: schema.fields ?? DEFAULT_PUBLIC_ENVELOPE_SCHEMA.fields,\n maxIconBytes: schema.maxIconBytes ?? DEFAULT_PUBLIC_ENVELOPE_SCHEMA.maxIconBytes,\n iconMimeTypes: schema.iconMimeTypes ?? DEFAULT_PUBLIC_ENVELOPE_SCHEMA.iconMimeTypes,\n maxStringChars: schema.maxStringChars ?? DEFAULT_PUBLIC_ENVELOPE_SCHEMA.maxStringChars,\n }\n}\n","/**\n * Public envelope subsystem — barrel export.\n *\n * @see docs/subsystems/public-envelope.md\n *\n * @module\n */\nexport type {\n PublicEnvelope,\n PublicEnvelopeText,\n PublicEnvelopeSchema,\n PublicEnvelopeField,\n ResolvedPublicEnvelopeSchema,\n} from './types.js'\nexport {\n PUBLIC_ENVELOPE_FIELDS,\n DEFAULT_PUBLIC_ENVELOPE_SCHEMA,\n resolveSchema,\n} from './types.js'\n\nexport type { SetPublicEnvelopeInput } from './schema.js'\nexport { validatePublicEnvelopeInput, isPublicEnvelope } from './schema.js'\n\nexport {\n loadPublicEnvelope,\n savePublicEnvelope,\n readPublicEnvelope,\n resolveLocale,\n pickLocale,\n PUBLIC_ENVELOPE_RECORD_ID,\n} from './storage.js'\n","/**\n * Strategy seam between core Collection and the optional CRDT\n * subsystem. Core imports `CrdtStrategy` as a TYPE-ONLY symbol and\n * `NO_CRDT` as a minimal runtime stub.\n *\n * The state-construction / merge / snapshot-resolution helpers —\n * `buildLwwMapState`, `buildRgaState`, `mergeCrdtStates`,\n * `resolveCrdtSnapshot` — are only reachable from `withCrdt()` in\n * `./active.ts`, which is only exported through the `@noy-db/hub/crdt`\n * subpath. Consumers without CRDT mode configured never pull the\n * ~221 LOC into their bundle.\n *\n * @internal\n */\n\nimport type { CrdtState, LwwMapState, RgaState } from './crdt.js'\n\n/**\n * Seam interface. `@internal`.\n *\n * @internal\n */\nexport interface CrdtStrategy {\n buildLwwMapState(\n record: Record<string, unknown>,\n previous: LwwMapState | undefined,\n now: string,\n ): LwwMapState\n buildRgaState(\n items: readonly unknown[],\n previous: RgaState | undefined,\n idGen: () => string,\n ): RgaState\n mergeCrdtStates(local: CrdtState, remote: CrdtState): CrdtState\n resolveCrdtSnapshot(state: CrdtState): unknown\n}\n\nconst NOT_ENABLED = new Error(\n 'CRDT mode requires the CRDT strategy. Import `{ withCrdt }` from ' +\n '\"@noy-db/hub/crdt\" and pass it to `createNoydb({ crdtStrategy: withCrdt() })`.',\n)\n\n/**\n * No-CRDT stub. Every method throws with a pointer at the subpath.\n * If a Collection declares `crdt: '...'` without this strategy wired,\n * the first put/sync-merge/read that hits the CRDT path surfaces the\n * error immediately.\n *\n * @internal\n */\nexport const NO_CRDT: CrdtStrategy = {\n buildLwwMapState() { throw NOT_ENABLED },\n buildRgaState() { throw NOT_ENABLED },\n mergeCrdtStates() { throw NOT_ENABLED },\n resolveCrdtSnapshot() { throw NOT_ENABLED },\n}\n","/**\n * Strategy seam for the optional i18n (multi-locale + dictionary)\n * subsystem. Core imports `I18nStrategy` type-only + `NO_I18N` stub;\n * real `applyI18nLocale` / `validateI18nTextValue` /\n * `DictionaryHandle` are only reachable via `withI18n()` in\n * `./active.ts`.\n *\n * Solo apps that don't use `i18nText()` fields, don't declare\n * `dictKey()` fields, and don't open a `vault.dictionary(...)` handle\n * ship none of the ~854 LOC behind this seam.\n *\n * Behavior under NO_I18N:\n *\n * - **applyI18nLocale** — returns the record unchanged. Apps without\n * any i18n descriptors never observe a difference; apps that\n * *did* declare i18nText/dictKey fields without opting into the\n * strategy still get raw values back (locale resolution silently\n * skipped). The validators below ensure the misconfiguration is\n * caught at write time instead.\n * - **validateI18nTextValue** — throws when called. Only fires when\n * a collection declared `i18nFields`; if you declared the field,\n * you must opt in.\n * - **buildDictionaryHandle** — throws when called. Only fires when\n * user code calls `vault.dictionary(...)`.\n *\n * @internal\n */\n\nimport type { NoydbStore } from '../types.js'\nimport type { LedgerStore } from '../history/ledger/store.js'\nimport type { UnlockedKeyring } from '../team/keyring.js'\nimport type { NoydbEventEmitter } from '../events.js'\nimport type { I18nTextDescriptor } from './core.js'\nimport type { DictionaryHandle, DictionaryOptions } from './dictionary.js'\n\n/**\n * Options accepted by `I18nStrategy.buildDictionaryHandle`. Mirrors\n * the `DictionaryHandle` constructor verbatim — kept here so core\n * code never imports the dictionary module at runtime.\n *\n * @internal\n */\nexport interface BuildDictionaryHandleOptions<Keys extends string = string> {\n adapter: NoydbStore\n compartmentName: string\n dictionaryName: string\n keyring: UnlockedKeyring\n getDEK: (collectionName: string) => Promise<CryptoKey>\n encrypted: boolean\n ledger: LedgerStore | undefined\n options: DictionaryOptions\n findAndUpdateReferences:\n | ((\n dictionaryName: string,\n oldKey: string,\n newKey: string,\n ) => Promise<void>)\n | undefined\n emitter: NoydbEventEmitter\n /**\n * Used by the active strategy to satisfy the generic-key parameter\n * on the returned handle. The NO_I18N stub never reads it.\n */\n // marker generic — runtime sees no value\n _keyMarker?: Keys\n}\n\n/**\n * @internal\n */\nexport interface I18nStrategy {\n /**\n * Resolve `i18nText` fields on a record to the requested locale and\n * return a new object. Returns the input unchanged under\n * `NO_I18N`.\n */\n applyI18nLocale(\n record: Record<string, unknown>,\n fields: Record<string, I18nTextDescriptor>,\n locale: string,\n fallback?: string | readonly string[],\n ): Record<string, unknown>\n\n /**\n * Validate that an i18nText field's value satisfies its descriptor\n * (required locales present, etc.). Throws under `NO_I18N` —\n * declaring i18nFields without opting in is a misconfiguration.\n */\n validateI18nTextValue(\n value: unknown,\n field: string,\n descriptor: I18nTextDescriptor,\n ): void\n\n /**\n * Construct a typed `DictionaryHandle` for the named dictionary.\n * Throws under `NO_I18N`.\n */\n buildDictionaryHandle<Keys extends string = string>(\n opts: BuildDictionaryHandleOptions<Keys>,\n ): DictionaryHandle<Keys>\n}\n\nfunction notEnabled(op: string): Error {\n return new Error(\n `${op} requires the i18n strategy. Import ` +\n '`{ withI18n }` from \"@noy-db/hub/i18n\" and pass it to ' +\n '`createNoydb({ i18nStrategy: withI18n() })`.',\n )\n}\n\n/**\n * No-i18n stub. Locale resolution is the identity; validation and\n * dictionary construction throw with an actionable pointer.\n *\n * @internal\n */\nexport const NO_I18N: I18nStrategy = {\n applyI18nLocale(record) { return record },\n validateI18nTextValue() { throw notEnabled('i18nText field validation') },\n buildDictionaryHandle() { throw notEnabled('vault.dictionary()') },\n}\n","/**\n * Hierarchical access — tier-aware keyring helpers.\n *\n * The keyring's existing `deks: Map<string, CryptoKey>` is keyed by\n * collection name. extends the key space:\n *\n * `'invoices'` — tier-0 DEK (unchanged from v0.x)\n * `'invoices#1'` — tier-1 DEK\n * `'invoices#2'` — tier-2 DEK\n *\n * Tier 0 keeps the bare collection name so any keyring written\n * before tiers existed loads without migration. Tiers ≥ 1 use `#N`\n * suffixes that\n * would be invalid as user-supplied collection names (see\n * `ReservedCollectionNameError` — `#` is reserved).\n *\n * @module\n */\n\nimport type { UnlockedKeyring } from './keyring.js'\nimport { TierNotGrantedError } from '../errors.js'\n\n/** Canonical DEK key for a given collection + tier. Tier 0 → bare name. */\nexport function dekKey(collection: string, tier: number): string {\n if (tier <= 0) return collection\n return `${collection}#${tier}`\n}\n\n/**\n * Returns the user's effective clearance for a given collection: the\n * maximum tier for which their keyring holds a DEK. Falls back to 0\n * when the user has only the tier-0 DEK (or none — the getDEK caller\n * will raise separately).\n */\nexport function effectiveClearance(keyring: UnlockedKeyring, collection: string): number {\n let max = 0\n const prefix = `${collection}#`\n for (const key of keyring.deks.keys()) {\n if (!key.startsWith(prefix)) continue\n const suffix = key.slice(prefix.length)\n const n = Number.parseInt(suffix, 10)\n if (Number.isFinite(n) && n > max) max = n\n }\n return max\n}\n\n/**\n * Assert the caller is cleared for the requested tier. Owners and\n * admins always pass (they can mint any new tier DEK on demand);\n * other roles must already hold the tier DEK — via a prior grant or\n * an active delegation — otherwise this throws `TierNotGrantedError`.\n *\n * This gate runs BEFORE `getDEK()` on the mutation path so a\n * non-cleared operator never has the opportunity to silently\n * auto-create a tier DEK they shouldn't have.\n */\nexport function assertTierAccess(\n keyring: UnlockedKeyring,\n collection: string,\n tier: number,\n): void {\n if (tier <= 0) return\n if (keyring.role === 'owner' || keyring.role === 'admin') return\n if (!keyring.deks.has(dekKey(collection, tier))) {\n throw new TierNotGrantedError(collection, tier)\n }\n}\n","/**\n * Standard Schema v1 integration.\n *\n * This file is the entry point for **schema validation**. Any\n * validator that implements the [Standard Schema v1\n * protocol](https://standardschema.dev) — Zod, Valibot, ArkType, Effect\n * Schema, etc. — can be attached to a `Collection` or `defineNoydbStore`\n * and will:\n *\n * 1. Validate the record BEFORE encryption on `put()` — bad data is\n * rejected at the store boundary with a rich issue list.\n * 2. Validate the record AFTER decryption on `get()`/`list()`/`query()`\n * — stored data that has drifted from the current schema throws\n * loudly instead of silently propagating garbage to the UI.\n *\n * ## Why vendor the types?\n *\n * Standard Schema is a protocol, not a library. The spec is <200 lines of\n * TypeScript and has no runtime. There's an official `@standard-schema/spec`\n * types package on npm, but pulling it in would add a dependency edge\n * purely for type definitions. Vendoring the minimal surface keeps\n * `@noy-db/core` at **zero runtime dependencies** and gives us freedom to\n * evolve the helpers without a version-lock on the spec package.\n *\n * If the spec changes in a breaking way (unlikely — it's frozen at v1),\n * we update this file and bump our minor.\n *\n * ## Why not just run `schema.parse(value)` directly?\n *\n * Because then we'd be locked to whichever validator happens to have\n * `.parse`. Standard Schema's `'~standard'.validate` contract is the same\n * across every implementation and includes a structured issues list,\n * which is much more useful than a thrown error for programmatic error\n * handling (e.g., rendering field-level messages in a Vue component).\n */\n\nimport { SchemaValidationError } from './errors.js'\n\n/**\n * The Standard Schema v1 protocol. A schema is any object that exposes a\n * `'~standard'` property with `version: 1` and a `validate` function.\n *\n * The type parameters are:\n * - `Input` — the type accepted by `validate` (what the user passes in)\n * - `Output` — the type produced by `validate` (what we store/return,\n * may differ from Input if the schema transforms or coerces)\n *\n * In most cases `Input === Output`, but validators that transform\n * (Zod's `.transform`, Valibot's `transform`, etc.) can narrow or widen.\n *\n * We intentionally keep the `types` field `readonly` and optional — the\n * spec marks it as optional because it's only used for inference, and\n * not every implementation bothers populating it at runtime.\n */\nexport interface StandardSchemaV1<Input = unknown, Output = Input> {\n readonly '~standard': {\n readonly version: 1\n readonly vendor: string\n readonly validate: (\n value: unknown,\n ) =>\n | StandardSchemaV1SyncResult<Output>\n | Promise<StandardSchemaV1SyncResult<Output>>\n readonly types?:\n | {\n readonly input: Input\n readonly output: Output\n }\n | undefined\n }\n}\n\n/**\n * The result of a single call to `schema['~standard'].validate`. Either\n * `{ value }` on success or `{ issues }` on failure — never both.\n *\n * The spec allows `issues` to be undefined on success (and some\n * validators leave it that way), so consumers should discriminate on\n * `issues?.length` rather than on truthiness of `value`.\n */\nexport type StandardSchemaV1SyncResult<Output> =\n | { readonly value: Output; readonly issues?: undefined }\n | {\n readonly value?: undefined\n readonly issues: readonly StandardSchemaV1Issue[]\n }\n\n/**\n * A single validation issue. The `message` is always present; the `path`\n * is optional and points at the offending field when the schema tracks\n * it (virtually every validator does for object types).\n *\n * The path is deliberately permissive — both a plain `PropertyKey` and a\n * `{ key }` wrapper are allowed so validators that wrap path segments in\n * objects (Zod does this in some modes) don't need special handling.\n */\nexport interface StandardSchemaV1Issue {\n readonly message: string\n readonly path?:\n | ReadonlyArray<PropertyKey | { readonly key: PropertyKey }>\n | undefined\n}\n\n/**\n * Infer the output type of a Standard Schema. Consumers use this to\n * pull the type out of a schema instance when they want to declare a\n * Collection<T> or defineNoydbStore<T> with `T` derived from the schema.\n *\n * Example:\n * ```ts\n * const InvoiceSchema = z.object({ id: z.string(), amount: z.number() })\n * type Invoice = InferOutput<typeof InvoiceSchema>\n * ```\n */\nexport type InferOutput<T extends StandardSchemaV1> =\n T extends StandardSchemaV1<unknown, infer O> ? O : never\n\n/**\n * Validate an input value against a schema. Throws\n * `SchemaValidationError` if the schema rejects, with the rich issue\n * list attached. Otherwise returns the (possibly transformed) output\n * value.\n *\n * The `context` string is included in the thrown error's message so the\n * caller knows where the failure happened (e.g. `\"put(inv-001)\"`) without\n * every caller having to wrap the throw in a try/catch.\n *\n * This function is ALWAYS async because some validators (notably Effect\n * Schema and Zod's `.refine` with async predicates) can return a\n * Promise. We `await` the result unconditionally to normalize the\n * contract — the extra microtask is free compared to the cost of an\n * encrypt/decrypt round-trip.\n */\nexport async function validateSchemaInput<Output>(\n schema: StandardSchemaV1<unknown, Output>,\n value: unknown,\n context: string,\n): Promise<Output> {\n const result = await schema['~standard'].validate(value)\n if (result.issues !== undefined && result.issues.length > 0) {\n throw new SchemaValidationError(\n `Schema validation failed on ${context}: ${summarizeIssues(result.issues)}`,\n result.issues,\n 'input',\n )\n }\n // Safe: the spec guarantees `value` is present when `issues` is absent.\n return result.value as Output\n}\n\n/**\n * Validate an already-stored value coming OUT of the collection. This\n * is a distinct helper from `validateSchemaInput` because the error\n * semantics differ: an output-validation failure means the data in\n * storage has drifted from the current schema (an unexpected state),\n * whereas an input-validation failure means the user passed bad data\n * (an expected state for a UI that isn't guarding its inputs).\n *\n * We still throw — silently returning bad data would be worse — but\n * the error carries `direction: 'output'` so upstream code (and a\n * potential migrate hook) can distinguish the two cases.\n */\nexport async function validateSchemaOutput<Output>(\n schema: StandardSchemaV1<unknown, Output>,\n value: unknown,\n context: string,\n): Promise<Output> {\n const result = await schema['~standard'].validate(value)\n if (result.issues !== undefined && result.issues.length > 0) {\n throw new SchemaValidationError(\n `Stored data for ${context} does not match the current schema — ` +\n `schema drift? ${summarizeIssues(result.issues)}`,\n result.issues,\n 'output',\n )\n }\n return result.value as Output\n}\n\n/**\n * Produce a short human-readable summary of an issue list for the\n * thrown error's message. The full issue array is still attached to the\n * error as a property — this is only for the `.message` string that\n * shows up in console.error / stack traces.\n *\n * Format: `field: message; field2: message2` (up to 3 issues, then `…`).\n * Issues without a path are shown as `root: message`.\n */\nfunction summarizeIssues(\n issues: readonly StandardSchemaV1Issue[],\n): string {\n const shown = issues.slice(0, 3).map((issue) => {\n const pathStr = formatPath(issue.path)\n return `${pathStr}: ${issue.message}`\n })\n const suffix = issues.length > 3 ? ` (+${issues.length - 3} more)` : ''\n return shown.join('; ') + suffix\n}\n\nfunction formatPath(\n path: StandardSchemaV1Issue['path'],\n): string {\n if (!path || path.length === 0) return 'root'\n return path\n .map((segment) =>\n typeof segment === 'object' && segment !== null\n ? String(segment.key)\n : String(segment),\n )\n .join('.')\n}\n","/**\n * Strategy seam for the optional history + ledger + time-machine\n * subsystem. Core imports `HistoryStrategy` type-only + `NO_HISTORY`\n * stub; real implementations of `saveHistory`, `LedgerStore`,\n * `VaultInstant`, `computePatch`, `diff` etc. are only reachable via\n * `withHistory()` in `./active.ts`.\n *\n * Applications that don't track per-record versioning, don't need the\n * hash-chained audit ledger, and don't restore to past instants ship\n * none of the ~1,880 LOC behind this seam.\n *\n * Strategy contract:\n *\n * - **saveHistory / pruneHistory / clearHistory** — no-ops under\n * NO_HISTORY. Writes still succeed; no snapshot is captured.\n * - **getHistoryEntries / getVersionEnvelope / diff** — throw under\n * NO_HISTORY. These are read APIs the consumer would only call\n * after explicitly asking for history; the throw guides them to\n * `@noy-db/hub/history`.\n * - **envelopePayloadHash / computePatch** — return empty / `[]`\n * under NO_HISTORY. These are only used inside the\n * `if (this.ledger)` branch, which is itself gated by\n * `buildLedger()` returning null.\n * - **buildLedger** — returns `null` under NO_HISTORY. The Vault's\n * public `vault.ledger()` accessor throws when null.\n * - **buildVaultInstant** — throws under NO_HISTORY. `vault.at()`\n * propagates the throw.\n *\n * @internal\n */\n\nimport type {\n EncryptedEnvelope,\n NoydbStore,\n HistoryOptions,\n PruneOptions,\n} from '../types.js'\nimport type { LedgerStore } from './ledger/store.js'\nimport type { JsonPatch } from './ledger/patch.js'\nimport type { DiffEntry } from './diff.js'\nimport type { VaultInstant, VaultEngine } from './time-machine.js'\n\n/**\n * Options accepted by `HistoryStrategy.buildLedger`. Mirrors the\n * `LedgerStore` constructor verbatim — kept in this file so `core`\n * code never imports the LedgerStore module at runtime.\n *\n * @internal\n */\nexport interface BuildLedgerOptions {\n adapter: NoydbStore\n vault: string\n encrypted: boolean\n getDEK: (collectionName: string) => Promise<CryptoKey>\n actor: string\n}\n\n/**\n * @internal\n */\nexport interface HistoryStrategy {\n /**\n * Persist a full encrypted envelope snapshot of the prior version\n * under `_history/{collection}:{id}:{paddedVersion}`. No-op under\n * `NO_HISTORY`.\n */\n saveHistory(\n adapter: NoydbStore,\n vault: string,\n collection: string,\n recordId: string,\n envelope: EncryptedEnvelope,\n ): Promise<void>\n\n /**\n * List history envelopes for a record, newest first. Throws under\n * `NO_HISTORY` — callers reach this via `collection.history()` /\n * `collection.getVersion()` / `collection.diff()`, which only work\n * with the strategy enabled.\n */\n getHistoryEntries(\n adapter: NoydbStore,\n vault: string,\n collection: string,\n recordId: string,\n options?: HistoryOptions,\n ): Promise<EncryptedEnvelope[]>\n\n /**\n * Fetch a specific version's envelope. Throws under `NO_HISTORY`.\n */\n getVersionEnvelope(\n adapter: NoydbStore,\n vault: string,\n collection: string,\n recordId: string,\n version: number,\n ): Promise<EncryptedEnvelope | null>\n\n /**\n * Prune history entries by retention rule. Returns `0` under\n * `NO_HISTORY`.\n */\n pruneHistory(\n adapter: NoydbStore,\n vault: string,\n collection: string,\n recordId: string | undefined,\n options: PruneOptions,\n ): Promise<number>\n\n /**\n * Clear all history for vault/collection/record. Returns `0` under\n * `NO_HISTORY`.\n */\n clearHistory(\n adapter: NoydbStore,\n vault: string,\n collection?: string,\n recordId?: string,\n ): Promise<number>\n\n /**\n * Compute the SHA-256 hash of an envelope's encrypted payload, used\n * by `LedgerStore.append` to track tamper-evidence. Returns the\n * empty string under `NO_HISTORY` (the call site is gated by\n * `if (this.ledger)`, so the value is never observed).\n */\n envelopePayloadHash(envelope: EncryptedEnvelope | null): Promise<string>\n\n /**\n * Compute the JSON patch from `from` → `to`. Returns `[]` under\n * `NO_HISTORY`.\n */\n computePatch(from: unknown, to: unknown): JsonPatch\n\n /**\n * Compute the typed diff between two records. Throws under\n * `NO_HISTORY` — `collection.diff()` is a history-read API.\n */\n diff(recordA: unknown, recordB: unknown): DiffEntry[]\n\n /**\n * Construct (or return null) a `LedgerStore` for the vault. Returns\n * `null` under `NO_HISTORY`; the Vault treats null as \"no ledger\n * attached\" — collection write paths skip the append branch and the\n * public `vault.ledger()` accessor throws.\n */\n buildLedger(opts: BuildLedgerOptions): LedgerStore | null\n\n /**\n * Construct a `VaultInstant` for time-machine reads. Throws under\n * `NO_HISTORY`.\n */\n buildVaultInstant(engine: VaultEngine, timestamp: string): VaultInstant\n}\n\n/**\n * Error thrown when the consumer reaches a history-gated surface\n * without opting into the strategy. The message names the offending\n * operation and points to the subpath import.\n *\n * @internal\n */\nfunction notEnabled(op: string): Error {\n return new Error(\n `${op} requires the history strategy. Import ` +\n '`{ withHistory }` from \"@noy-db/hub/history\" and pass it to ' +\n '`createNoydb({ historyStrategy: withHistory() })`.',\n )\n}\n\n/**\n * No-history stub. Snapshots and prune/clear are no-ops; reads and\n * time-machine throw with an actionable message; ledger construction\n * returns null so the write-path's `if (this.ledger)` branch is dead\n * code in the bundle.\n *\n * @internal\n */\nexport const NO_HISTORY: HistoryStrategy = {\n async saveHistory() {},\n async getHistoryEntries() { throw notEnabled('collection.history()') },\n async getVersionEnvelope() { throw notEnabled('collection.getVersion()') },\n async pruneHistory() { return 0 },\n async clearHistory() { return 0 },\n async envelopePayloadHash() { return '' },\n computePatch() { return [] },\n diff() { throw notEnabled('collection.diff()') },\n buildLedger() { return null },\n buildVaultInstant() { throw notEnabled('vault.at() / vault.timeMachine()') },\n}\n","/**\n * Operator implementations for the query DSL.\n *\n * All predicates run client-side, AFTER decryption — they never see ciphertext.\n * This file is dependency-free and tree-shakeable.\n */\n\n/** Comparison operators supported by the where() builder. */\nexport type Operator =\n | '=='\n | '!='\n | '<'\n | '<='\n | '>'\n | '>='\n | 'in'\n | 'contains'\n | 'startsWith'\n | 'between'\n\n/**\n * A single field comparison clause inside a query plan.\n * Plans are JSON-serializable, so this type uses primitives only.\n */\nexport interface FieldClause {\n readonly type: 'field'\n readonly field: string\n readonly op: Operator\n readonly value: unknown\n}\n\n/**\n * A user-supplied predicate function escape hatch. Not serializable.\n *\n * The predicate accepts `unknown` at the type level so the surrounding\n * Clause type can stay non-parametric — this keeps Collection<T> covariant\n * in T at the public API surface. Builder methods cast user predicates\n * (typed `(record: T) => boolean`) into this shape on the way in.\n */\nexport interface FilterClause {\n readonly type: 'filter'\n readonly fn: (record: unknown) => boolean\n}\n\n/**\n * A declared deterministic predicate reference (#153). The query\n * builder produces this via `.wherePredicate(name, ctx?)` when a\n * Query has been augmented with a predicates map (typically by the\n * materialized-view registry — see MV v2 spec § Function-based\n * source-row predicates).\n *\n * `predicateHash` is the consumer-supplied stable hash for the\n * function body; `ctxHash` is the canonical-JSON SHA-256 of `ctx`.\n * Both fold into the MV's `queryHash` so a function or ctx change\n * forces refresh on next visit.\n *\n * `fn` is resolved at builder time from the predicates map and\n * embedded directly — so `evaluateClause` can fire it without a\n * runtime lookup.\n */\nexport interface WherePredicateClause {\n readonly type: 'wherePredicate'\n readonly name: string\n readonly ctx: unknown\n readonly predicateHash: string\n readonly ctxHash: string\n readonly fn: (record: unknown, ctx?: unknown) => boolean\n}\n\n/** A logical group of clauses combined by AND or OR. */\nexport interface GroupClause {\n readonly type: 'group'\n readonly op: 'and' | 'or'\n readonly clauses: readonly Clause[]\n}\n\nexport type Clause = FieldClause | FilterClause | WherePredicateClause | GroupClause\n\n/**\n * Read a possibly nested field path like \"address.city\" from a record.\n * Returns undefined if any segment is missing.\n */\nexport function readPath(record: unknown, path: string): unknown {\n if (record === null || record === undefined) return undefined\n if (!path.includes('.')) {\n return (record as Record<string, unknown>)[path]\n }\n const segments = path.split('.')\n let cursor: unknown = record\n for (const segment of segments) {\n if (cursor === null || cursor === undefined) return undefined\n cursor = (cursor as Record<string, unknown>)[segment]\n }\n return cursor\n}\n\n/**\n * Evaluate a single field clause against a record.\n * Returns false on type mismatches rather than throwing — query results\n * exclude non-matching records by definition.\n */\nexport function evaluateFieldClause(record: unknown, clause: FieldClause): boolean {\n const actual = readPath(record, clause.field)\n const { op, value } = clause\n\n switch (op) {\n case '==':\n return actual === value\n case '!=':\n return actual !== value\n case '<':\n return isComparable(actual, value) && (actual as number) < (value as number)\n case '<=':\n return isComparable(actual, value) && (actual as number) <= (value as number)\n case '>':\n return isComparable(actual, value) && (actual as number) > (value as number)\n case '>=':\n return isComparable(actual, value) && (actual as number) >= (value as number)\n case 'in':\n return Array.isArray(value) && value.includes(actual)\n case 'contains':\n if (typeof actual === 'string') return typeof value === 'string' && actual.includes(value)\n if (Array.isArray(actual)) return actual.includes(value)\n return false\n case 'startsWith':\n return typeof actual === 'string' && typeof value === 'string' && actual.startsWith(value)\n case 'between': {\n if (!Array.isArray(value) || value.length !== 2) return false\n const [lo, hi] = value\n if (!isComparable(actual, lo) || !isComparable(actual, hi)) return false\n return (actual as number) >= (lo as number) && (actual as number) <= (hi as number)\n }\n default: {\n // Exhaustiveness — TS will error if a new operator is added without a case.\n const _exhaustive: never = op\n void _exhaustive\n return false\n }\n }\n}\n\n/**\n * Two values are \"comparable\" if they share an order-defined runtime type.\n * Strings compare lexicographically; numbers and Dates numerically; otherwise false.\n */\nfunction isComparable(a: unknown, b: unknown): boolean {\n if (typeof a === 'number' && typeof b === 'number') return true\n if (typeof a === 'string' && typeof b === 'string') return true\n if (a instanceof Date && b instanceof Date) return true\n return false\n}\n\n/**\n * Evaluate any clause (field / filter / group) against a record.\n * The recursion depth is bounded by the user's query expression — no risk of\n * blowing the stack on a 50K-record collection.\n */\nexport function evaluateClause(record: unknown, clause: Clause): boolean {\n switch (clause.type) {\n case 'field':\n return evaluateFieldClause(record, clause)\n case 'filter':\n return clause.fn(record)\n case 'wherePredicate':\n return clause.fn(record, clause.ctx)\n case 'group':\n if (clause.op === 'and') {\n for (const child of clause.clauses) {\n if (!evaluateClause(record, child)) return false\n }\n return true\n } else {\n for (const child of clause.clauses) {\n if (evaluateClause(record, child)) return true\n }\n return false\n }\n }\n}\n","/**\n * Query DSL `.join()` — eager, single-FK, intra-vault joins.\n *\n * resolves a ref()-declared foreign key into an attached\n * right-side record under an alias, using one of two planner paths\n * selected automatically:\n *\n * - **nested-loop** — right-side source exposes `lookupById`, so\n * each left row costs O(1). This is the common path for joins\n * against a Collection, which backs `lookupById` with a Map\n * lookup.\n * - **hash** — right-side has only `snapshot()`. Build a\n * `Map<id, record>` once, probe per left row. Same asymptotic\n * cost for our collections, but the path exists as a fallback\n * for custom QuerySource implementations and as an explicit\n * test-only override via `{ strategy: 'hash' }`.\n *\n * Scope:\n *\n * - Equi-joins on declared `ref()` fields only. Joins on\n * undeclared fields throw at plan time with an actionable error\n * naming the field and collection.\n * - Same-vault only. Cross-vault correlation goes\n * through `queryAcross`; this is an architectural\n * invariant, not a limitation we plan to lift.\n * - Hard row ceiling via `JoinTooLargeError` — default 50k per\n * side, override via `{ maxRows }`. Warns at 80% of the ceiling\n * on the existing warn channel.\n * - Three ref-mode behaviors on dangling refs:\n * strict → `DanglingReferenceError`,\n * warn → attach `null` with a one-shot warning,\n * cascade → attach `null` silently (cascade is a delete-time\n * mode; any dangling refs still present at read time are\n * mid-flight cascades or orphans from earlier, not a DSL error).\n *\n * Partition-awareness seam:\n *\n * Every `JoinLeg` carries a `partitionScope` field that is always\n * `'all'` in. The executor never reads this field.\n * partition-aware joins will start populating it from `where()`\n * predicates on the partition key without changing the planner's\n * external shape — this is the whole reason it exists now.\n *\n * Joins stay OUT of the ledger: reads don't touch `_ledger/`,\n * including joined reads.\n */\n\nimport type { RefDescriptor, RefMode } from '../refs.js'\nimport { readPath } from './predicate.js'\nimport { JoinTooLargeError, DanglingReferenceError } from '../errors.js'\n\n/** Planner strategy for a single join leg. Auto-selected unless overridden. */\nexport type JoinStrategy = 'hash' | 'nested'\n\n/** Default per-side row ceiling before `.join()` throws `JoinTooLargeError`. */\nexport const DEFAULT_JOIN_MAX_ROWS = 50_000\n\n/**\n * Fraction of the row ceiling at which a one-shot warning is emitted.\n * At 80% we warn; at 100% we throw. The warn gives consumers a\n * heads-up before the hard error so they can raise the ceiling or\n * filter further without first hitting a broken query.\n */\nconst JOIN_WARN_FRACTION = 0.8\n\n/**\n * Internal representation of a single join leg in the query plan.\n *\n * This is the primary place where constraint #1 is honored:\n * every leg carries a `partitionScope` field that is always `'all'`\n * in and is never read by the executor. partition-aware\n * joins will start populating it from `where()` predicates on the\n * partition key without changing the planner's external shape.\n */\nexport interface JoinLeg {\n /** Field on the left-side record holding the foreign key value. */\n readonly field: string\n /** Alias key under which the joined right-side record attaches. */\n readonly as: string\n /** Target collection name, resolved from the `ref()` declaration. */\n readonly target: string\n /** Ref mode controlling behavior on dangling refs at read time. */\n readonly mode: RefMode\n /** Manual planner strategy override. `undefined` → auto-select. */\n readonly strategy: JoinStrategy | undefined\n /** Per-side row ceiling override. `undefined` → DEFAULT_JOIN_MAX_ROWS. */\n readonly maxRows: number | undefined\n /**\n * Partition scope for future partition-aware joins. Always `'all'`\n * today — the executor never reads this field. Future versions will\n * populate it from `where()` predicates without breaking the\n * planner's external shape. Do not remove even though it looks\n * unused today — that's the whole point of having it.\n */\n readonly partitionScope: 'all' | readonly string[]\n /**\n * When `true`, this is a dictionary join. The executor\n * resolves the left-field value against the dict snapshot and\n * attaches `{ ...labels, key }` rather than a right-side record.\n * `target` holds the dictionary name (not a collection name).\n */\n readonly isDictJoin?: true\n}\n\n/**\n * Minimal shape of a joinable right-side record source.\n *\n * Collections implement this structurally via their `QuerySource`;\n * sources without `lookupById` force the hash-join fallback. Kept as\n * a thin interface so tests can wire up plain-object sources without\n * pulling in the full Collection class.\n *\n * The optional `subscribe` is used by `Query.live()` to merge\n * right-side change streams into the live re-run trigger. Sources\n * that omit `subscribe` still work for live joins — they just\n * don't drive re-fires when their right side mutates. Collection\n * implements `subscribe` by hooking into the existing per-\n * vault event emitter.\n */\nexport interface JoinableSource {\n snapshot(): readonly unknown[]\n lookupById?(id: string): unknown\n /**\n * Subscribe to mutations on this source. The callback fires\n * AFTER the underlying record set has been updated. Returns an\n * unsubscribe function. Optional — sources without this method\n * cannot trigger live-join re-fires from their side.\n */\n subscribe?(cb: () => void): () => void\n}\n\n/**\n * Join resolution context attached to a `Query` when it's constructed\n * from a `Collection`. Holds everything the `.join()` method needs to\n * translate a field name into a target collection + ref mode, and\n * everything the executor needs to read the right side.\n *\n * Kept as a structural interface so `Vault` can implement it\n * without `Query` needing to import `Vault` (circular-import\n * avoid). The Collection wires this up in its `query()` method using\n * the `joinResolver` back-reference the Vault passes in.\n */\nexport interface JoinContext {\n /** Name of the left-side (owning) collection. */\n readonly leftCollection: string\n /** Look up a `RefDescriptor` by field name on the left collection. */\n resolveRef(field: string): RefDescriptor | null\n /** Resolve a right-side source by target collection name. */\n resolveSource(collectionName: string): JoinableSource | null\n /**\n * Resolve a dictKey join source. Returns a `JoinableSource`\n * whose snapshot exposes `{ key, ...labels }` records, keyed by the\n * stable dictionary key. `null` when the field is not a dictKey.\n *\n * The source is built from the compartment's in-memory dictionary\n * snapshot — same data as `DictionaryHandle.list()`, O(1) per lookup.\n */\n resolveDictSource?(field: string): JoinableSource | null\n}\n\n/**\n * Coerce an unknown FK value into a lookup key string.\n *\n * Legitimate ref values are strings or numbers — the same narrowing\n * the write-time `enforceRefsOnPut` path applies. Anything else\n * (objects, arrays, booleans, null, undefined) is treated as \"no\n * ref\" and returns `null`, so the join attaches `null` instead of\n * running `String({})` and producing `'[object Object]'` as a\n * bucket key. This matches the lint rule guidance and keeps\n * bizarre FK values from producing silently-wrong lookups.\n */\nfunction coerceRefKey(value: unknown): string | null {\n if (value === null || value === undefined) return null\n if (typeof value === 'string') return value\n if (typeof value === 'number' || typeof value === 'bigint') return String(value)\n return null\n}\n\n/**\n * Warn-channel deduplication for dangling-ref `'warn'` mode. Keyed\n * by `field → target:refId` so the same dangling ref only produces\n * one warning even across many rows or repeated queries.\n */\nconst warnedDanglingKeys = new Set<string>()\nfunction warnOnceDangling(field: string, target: string, refId: string): void {\n const key = `${field}→${target}:${refId}`\n if (warnedDanglingKeys.has(key)) return\n warnedDanglingKeys.add(key)\n console.warn(\n `[noy-db] .join() encountered dangling ref in 'warn' mode: ` +\n `field \"${field}\" → \"${target}:${refId}\" not found. Attaching null.`,\n )\n}\n\n/**\n * Track row-ceiling warnings to fire only once per (target, side).\n * Prevents per-query spam when a consumer is running the same query\n * repeatedly (e.g. in a reactive loop).\n */\nconst warnedCeilingKeys = new Set<string>()\nfunction warnCeilingApproaching(\n target: string,\n side: 'left' | 'right',\n rows: number,\n maxRows: number,\n): void {\n const key = `${target}:${side}`\n if (warnedCeilingKeys.has(key)) return\n warnedCeilingKeys.add(key)\n const pct = Math.round((rows / maxRows) * 100)\n console.warn(\n `[noy-db] .join() ${side} side is at ${pct}% of the ${maxRows}-row ` +\n `ceiling for target \"${target}\" (${rows} rows). Streaming joins over ` +\n `scan() are not yet supported for collections that need to exceed this.`,\n )\n}\n\n/**\n * Apply every join leg in the plan against a base set of left-side\n * rows. Called by the query executor after `where` / `orderBy` /\n * `offset` / `limit` have narrowed the left set.\n *\n * Each leg attaches a `leg.as` field to every row. Returns a new\n * array of plain objects — the original left rows are not mutated\n * (structural sharing is fine for the inner fields, but the\n * top-level object is a fresh clone so consumers can further mutate\n * safely).\n *\n * **Ordering:** joins run AFTER orderBy / limit / offset in v1.\n * This keeps the planner simple and means queries like \"top 10\n * invoices with client\" sort and paginate the left side first, then\n * join. Sorting *by* a joined field is out of scope for — users\n * can post-sort the result array in userland or wait for \n * (multi-FK chaining) which can be layered on top.\n *\n * **Multi-FK chaining:** each leg's `maxRows` is enforced\n * against the current left-row count independently. Because\n * joins are equi-joins on the target's primary key (one-to-one or\n * one-to-null), the left row count is constant across legs — no\n * cartesian blowup. The per-leg left-side check is still necessary\n * so that a later leg with a tighter ceiling correctly fires on a\n * query like `.join('a', { maxRows: 100_000 }).join('b', { maxRows: 50 })`,\n * which should throw on the second leg if the left set exceeds 50.\n */\nexport function applyJoins(\n rows: readonly unknown[],\n joins: readonly JoinLeg[],\n context: JoinContext,\n): unknown[] {\n if (joins.length === 0) return [...rows]\n\n let result: unknown[] = [...rows]\n for (const leg of joins) {\n result = applyOneJoin(result, leg, context)\n }\n return result\n}\n\nfunction applyOneJoin(\n leftRows: readonly unknown[],\n leg: JoinLeg,\n context: JoinContext,\n): unknown[] {\n // Dict join path — resolve left-field value against the\n // dictionary snapshot and attach { key, ...labels } under leg.as.\n if (leg.isDictJoin) {\n const dictSource = context.resolveDictSource?.(leg.field)\n if (!dictSource) {\n throw new Error(\n `.join() field \"${leg.field}\" on \"${context.leftCollection}\" is declared as a ` +\n `dictKey join but the dict source could not be resolved. ` +\n `Ensure the dictionary has at least one entry.`,\n )\n }\n const out: unknown[] = []\n const snapshot = dictSource.snapshot()\n const dictMap = new Map<string, unknown>()\n for (const entry of snapshot) {\n const k = readPath(entry, 'key')\n if (typeof k === 'string') dictMap.set(k, entry)\n }\n for (const left of leftRows) {\n const rawId = readPath(left, leg.field)\n const key = coerceRefKey(rawId)\n const dictEntry = key === null ? undefined : dictMap.get(key)\n out.push({ ...(left as Record<string, unknown>), [leg.as]: dictEntry ?? null })\n }\n return out\n }\n\n const source = context.resolveSource(leg.target)\n if (!source) {\n throw new Error(\n `.join() cannot resolve target collection \"${leg.target}\" ` +\n `(referenced from field \"${leg.field}\" on \"${context.leftCollection}\"). ` +\n `Make sure the target collection has been opened via vault.collection() ` +\n `at least once before running the query.`,\n )\n }\n\n const maxRows = leg.maxRows ?? DEFAULT_JOIN_MAX_ROWS\n\n // Per-leg left-side ceiling check. In a\n // multi-FK chain, each leg's `maxRows` is enforced independently\n // against the current left-row count, so\n // `.join('a', { maxRows: 100_000 }).join('b', { maxRows: 50 })`\n // correctly throws on the second leg if the left set exceeds 50.\n if (leftRows.length > maxRows) {\n throw new JoinTooLargeError({\n leftRows: leftRows.length,\n rightRows: -1,\n maxRows,\n side: 'left',\n message:\n `.join() left side has ${leftRows.length} rows, exceeding the ${maxRows}-row ` +\n `ceiling for target \"${leg.target}\". Filter the left side further with ` +\n `where()/limit() before joining, or raise the ceiling via { maxRows }. ` +\n `Streaming joins over scan() are not yet supported.`,\n })\n }\n if (leftRows.length > maxRows * JOIN_WARN_FRACTION) {\n warnCeilingApproaching(leg.target, 'left', leftRows.length, maxRows)\n }\n\n const rightSnapshot = source.snapshot()\n if (rightSnapshot.length > maxRows) {\n throw new JoinTooLargeError({\n leftRows: leftRows.length,\n rightRows: rightSnapshot.length,\n maxRows,\n side: 'right',\n message:\n `.join() right side \"${leg.target}\" has ${rightSnapshot.length} rows, ` +\n `exceeding the ${maxRows}-row ceiling. Raise the ceiling via { maxRows } ` +\n `if the data genuinely fits in memory, or track for streaming joins.`,\n })\n }\n if (rightSnapshot.length > maxRows * JOIN_WARN_FRACTION) {\n warnCeilingApproaching(leg.target, 'right', rightSnapshot.length, maxRows)\n }\n\n // Strategy selection: explicit override wins; otherwise prefer\n // nested-loop when the source exposes lookupById (O(1) per row),\n // falling back to hash join when it doesn't.\n const strategy: JoinStrategy =\n leg.strategy ?? (source.lookupById ? 'nested' : 'hash')\n\n if (strategy === 'nested' && source.lookupById) {\n // Bind through an arrow so the `this` context of lookupById\n // doesn't drift — same pattern as the existing candidateRecords\n // helper in builder.ts.\n const lookup = (id: string): unknown => source.lookupById?.(id)\n return nestedLoopJoin(leftRows, leg, lookup)\n }\n return hashJoin(leftRows, leg, rightSnapshot)\n}\n\nfunction nestedLoopJoin(\n leftRows: readonly unknown[],\n leg: JoinLeg,\n lookupById: (id: string) => unknown,\n): unknown[] {\n const out: unknown[] = []\n for (const left of leftRows) {\n const rawId = readPath(left, leg.field)\n const key = coerceRefKey(rawId)\n const right = key === null ? undefined : lookupById(key)\n out.push(attachJoin(left, leg, right, rawId))\n }\n return out\n}\n\nfunction hashJoin(\n leftRows: readonly unknown[],\n leg: JoinLeg,\n rightSnapshot: readonly unknown[],\n): unknown[] {\n // Build the right-side hash once per query execution. We key on\n // the `id` field because ref() always points to a target's primary\n // key — non-equi and non-id joins are out of scope for.\n const rightMap = new Map<string, unknown>()\n for (const record of rightSnapshot) {\n const rawId = readPath(record, 'id')\n const key = coerceRefKey(rawId)\n if (key !== null) {\n rightMap.set(key, record)\n }\n }\n const out: unknown[] = []\n for (const left of leftRows) {\n const rawId = readPath(left, leg.field)\n const key = coerceRefKey(rawId)\n const right = key === null ? undefined : rightMap.get(key)\n out.push(attachJoin(left, leg, right, rawId))\n }\n return out\n}\n\n/**\n * Attach the resolved right-side record (or null) to the left row\n * under the alias, applying ref-mode semantics for the dangling\n * case.\n *\n * A left-side record whose FK field is null/undefined is NOT a\n * dangling ref — it's \"no reference at all\", which is always\n * allowed regardless of mode. This matches the write-time\n * `enforceRefsOnPut` behavior: \"Nullish ref values are allowed —\n * treat them as 'no reference'.\"\n *\n * Only non-null FKs pointing at non-existent targets trigger the\n * mode behavior.\n */\nfunction attachJoin(\n left: unknown,\n leg: JoinLeg,\n right: unknown,\n rawId: unknown,\n): unknown {\n if (left === null || typeof left !== 'object') {\n // Pathological input — return as-is. Shouldn't happen in\n // practice because QuerySource yields objects, but defensive\n // because plan execution is untyped at this layer.\n return left\n }\n const merged: Record<string, unknown> = { ...(left as Record<string, unknown>) }\n\n // \"No ref at all\" — null/undefined FK value, or a non-string/non-\n // number FK that coerceRefKey treated as no-ref. Never throws\n // regardless of mode; matches the write-time policy that nullish\n // refs are allowed.\n const refKey = coerceRefKey(rawId)\n if (right === undefined) {\n if (refKey !== null && leg.mode === 'strict') {\n throw new DanglingReferenceError({\n field: leg.field,\n target: leg.target,\n refId: refKey,\n message:\n `.join() strict dangling: record references \"${leg.target}:${refKey}\" ` +\n `via field \"${leg.field}\", but no such record exists. Use ref() mode 'warn' ` +\n `or 'cascade' if dangling refs are acceptable, or run ` +\n `vault.checkIntegrity() to find and fix the orphans.`,\n })\n }\n if (refKey !== null && leg.mode === 'warn') {\n warnOnceDangling(leg.field, leg.target, refKey)\n }\n // For 'cascade' and null refs we attach null silently. Cascade\n // is a delete-time mode; any dangling refs visible at read time\n // are either mid-flight or pre-existing orphans, not a DSL error.\n merged[leg.as] = null\n } else {\n merged[leg.as] = right\n }\n return merged\n}\n\n/**\n * Test-only: reset the join warning deduplication state between\n * tests. Production code never calls this — the dedup state is\n * intentionally process-scoped so a noisy query doesn't spam the\n * console once per component render.\n */\nexport function resetJoinWarnings(): void {\n warnedDanglingKeys.clear()\n warnedCeilingKeys.clear()\n}\n","/**\n * Reactive query primitive — `query.live()`.\n *\n * produces a `LiveQuery<T>` that re-runs the query and\n * updates its `value` whenever any source feeding it (the left\n * collection AND every right-side collection a join leg points at)\n * mutates.\n *\n * Framework-agnostic by design. The Vue layer wraps a `LiveQuery`\n * in a Vue `Ref<T[]>` by subscribing once and copying `value` into\n * the ref on every notification. React/Solid/Svelte adapters do the\n * same with their own primitives. Core never depends on a UI\n * framework.\n *\n * **Error semantics.** A `.live()` query may throw at re-run time —\n * a strict-mode `DanglingReferenceError` is the most common case\n * (a right-side record was deleted out-of-band, leaving a left\n * row's FK pointing at nothing). When the re-run throws, the\n * `LiveQuery` catches the error and stores it in the `error`\n * field; it does NOT propagate the throw out of the source's\n * change handler, because doing so would tear down whatever\n * upstream emitter is dispatching. Listeners check `error` after\n * each notification and render an error state in the UI.\n *\n * **Dedup of right-side subscriptions.** A multi-FK chain that\n * joins the same target twice (e.g.\n * `.join('billingClientId').join('shippingClientId')`, both\n * pointing at `clients`) only subscribes to that target once. We\n * dedup by target collection name, on the assumption that\n * `resolveSource(name)` returns a single subscribable source per\n * vault + name. Vault's `resolveSource` reads from\n * `collectionCache` so this assumption holds.\n *\n * **What .live() does NOT do in v1:**\n * - No granular delta updates — the whole query re-runs on every\n * change. Granular delta tracking is a v2 optimization once\n * the API is stable.\n * - No batching of bursty changes — one event in, one re-run\n * out. Batching with microtask coalescing is a v2 enhancement.\n * - No async notifications — every notification is synchronous\n * within the source's change handler.\n * - No re-planning under live mutations — the planner picks once\n * at subscription time and reuses the same plan for every\n * re-run.\n */\n\n/**\n * The reactive primitive returned by `Query.live()`.\n *\n * Listeners can read the current `value` snapshot at any time and\n * subscribe to changes via `.subscribe(cb)`. The `error` field\n * carries the most recent re-run error, if any — read it after\n * each notification to render error state.\n *\n * Always call `stop()` when the live query is no longer needed.\n * Without it, the upstream change-stream subscriptions stay live\n * forever and the query keeps re-running on every mutation.\n */\nexport interface LiveQuery<T> {\n /**\n * Current snapshot of the query result. Updated in place on\n * every upstream change. The reference returned is the same\n * `readonly T[]` array — consumers that want change detection by\n * reference should copy: `const arr = [...live.value]`.\n */\n readonly value: readonly T[]\n /**\n * Most recent re-run error, or `null` on success. Set when the\n * executor throws (e.g. `DanglingReferenceError` in strict mode\n * after a right-side delete). Cleared on the next successful\n * re-run.\n */\n readonly error: Error | null\n /**\n * Register a notification callback. Fires AFTER `value` and\n * `error` have been updated for a given upstream change.\n * Returns an unsubscribe function.\n *\n * The first call to `subscribe` does NOT fire the callback\n * immediately — call sites that want the initial value should\n * read `live.value` directly before subscribing.\n */\n subscribe(cb: () => void): () => void\n /**\n * Tear down every upstream subscription and clear the listener\n * set. Idempotent — calling twice is safe. After `stop()`, the\n * query no longer re-runs and `subscribe()` becomes a no-op\n * (the returned unsubscribe is still callable and is also a\n * no-op).\n */\n stop(): void\n}\n\n/**\n * Internal subscription handle for an upstream source — left or\n * right side. The contract is just `subscribe(cb): unsubscribe`,\n * matching the existing `QuerySource.subscribe` and the new\n * `JoinableSource.subscribe` (added in ).\n */\nexport interface LiveUpstream {\n subscribe(cb: () => void): () => void\n}\n\n/**\n * Build a LiveQuery from a `recompute` callback (typically the\n * Query's bound `toArray`) and a list of upstream sources to\n * subscribe to.\n *\n * The recompute fires once synchronously to populate the initial\n * value, then re-fires every time any upstream notifies. Errors\n * thrown by recompute are caught and stored in `error` instead of\n * propagating — see the file docstring for the rationale.\n */\nexport function buildLiveQuery<T>(\n recompute: () => T[],\n upstreams: readonly LiveUpstream[],\n): LiveQuery<T> {\n return new LiveQueryImpl<T>(recompute, upstreams)\n}\n\nclass LiveQueryImpl<T> implements LiveQuery<T> {\n private _value: readonly T[] = []\n private _error: Error | null = null\n private readonly listeners = new Set<() => void>()\n private readonly unsubs: Array<() => void> = []\n private stopped = false\n\n constructor(\n private readonly recompute: () => T[],\n upstreams: readonly LiveUpstream[],\n ) {\n // Initial compute. If this throws, the constructor still\n // succeeds — we want consumers to be able to render an error\n // state from `live.error` rather than wrapping every\n // `query.live()` call in a try/catch.\n this.refresh()\n for (const upstream of upstreams) {\n try {\n this.unsubs.push(upstream.subscribe(this.onUpstreamChange))\n } catch (err) {\n // Upstream subscription failed — record it as the live\n // error and continue with the upstreams that did work.\n // The LiveQuery is now degraded (won't re-fire on this\n // upstream's changes) but isn't broken; consumers can\n // detect this via `live.error`.\n this._error = err instanceof Error ? err : new Error(String(err))\n }\n }\n }\n\n get value(): readonly T[] {\n return this._value\n }\n\n get error(): Error | null {\n return this._error\n }\n\n /**\n * Bound change handler — used as the callback passed to every\n * upstream's subscribe. Bound via class field so the `this`\n * context survives the indirect call from arbitrary upstreams.\n */\n private readonly onUpstreamChange = (): void => {\n this.refresh()\n for (const cb of this.listeners) {\n try {\n cb()\n } catch {\n // Listener errors are isolated — one buggy consumer\n // doesn't break the others or tear down the live query.\n }\n }\n }\n\n private refresh(): void {\n if (this.stopped) return\n try {\n this._value = this.recompute()\n this._error = null\n } catch (err) {\n this._error = err instanceof Error ? err : new Error(String(err))\n // Don't clobber the previous value on error — consumers\n // typically want to keep showing the last known good state\n // alongside the error message rather than flashing to an\n // empty list.\n }\n }\n\n subscribe(cb: () => void): () => void {\n if (this.stopped) return () => {}\n this.listeners.add(cb)\n return () => this.listeners.delete(cb)\n }\n\n stop(): void {\n if (this.stopped) return\n this.stopped = true\n for (const unsub of this.unsubs) {\n try {\n unsub()\n } catch {\n // Unsub errors are swallowed — at this point we're tearing\n // down anyway and the failure is noise.\n }\n }\n this.unsubs.length = 0\n this.listeners.clear()\n }\n}\n","/**\n * Strategy seam between the core Query / ScanBuilder chain and the\n * optional aggregate / groupBy subsystem. Core imports\n * `AggregateStrategy` as a TYPE-ONLY symbol and `NO_AGGREGATE` as a\n * tiny runtime stub.\n *\n * The heavy machinery — `Aggregation`, `GroupedQuery`, the\n * reducer-step logic — is only reachable from `withAggregate()` in\n * `./active.ts`, which is only exported through the\n * `@noy-db/hub/aggregate` subpath. Consumers that don't import the\n * subpath ship none of the ~886 LOC.\n *\n * @internal\n */\n\nimport type {\n Aggregation,\n AggregateSpec,\n AggregateResult,\n AggregationUpstream,\n} from './aggregation.js'\nimport type { GroupedQuery, GroupedQueryN } from './groupby.js'\n\n/**\n * Seam interface. `@internal` — will promote to public only when the\n * aggregate subsystem is extracted into its own package.\n *\n * @internal\n */\nexport interface AggregateStrategy {\n /**\n * Build an `Aggregation<R>` for `Query.aggregate(spec)`. `executeRecords`\n * is a closure that produces the matching record set when the\n * aggregation runs. NO_AGGREGATE throws; the active strategy\n * constructs a real `Aggregation`.\n */\n aggregate<Spec extends AggregateSpec>(\n executeRecords: () => readonly unknown[],\n spec: Spec,\n upstreams: readonly AggregationUpstream[],\n ): Aggregation<AggregateResult<Spec>>\n\n /**\n * Build a `GroupedQuery<T, F>` for `Query.groupBy(field)`. Same\n * closure / upstream inputs as `aggregate` plus the group key field.\n */\n groupBy<T, F extends string>(\n executeRecords: () => readonly unknown[],\n field: F,\n upstreams: readonly AggregationUpstream[],\n dictLabelResolver?: (\n key: string,\n locale: string,\n fallback?: string | readonly string[],\n ) => Promise<string | undefined>,\n ): GroupedQuery<T, F>\n\n /**\n * Variadic-keyed sibling — builds a `GroupedQueryN<T, F>` for\n * `Query.groupBy(...fields)`. No dictLabelResolver — `<field>Label`\n * projection only applies to single-field groupings, which dispatch\n * through `groupBy` above.\n */\n groupByN<T, F extends readonly string[]>(\n executeRecords: () => readonly unknown[],\n fields: F,\n upstreams: readonly AggregationUpstream[],\n ): GroupedQueryN<T, F>\n\n /**\n * Terminal streaming aggregator for `ScanBuilder.aggregate(spec)`.\n * Takes an async iterable of decrypted records + the spec and\n * returns the reduced result.\n */\n scanAggregate<Spec extends AggregateSpec>(\n iter: AsyncIterable<unknown>,\n spec: Spec,\n ): Promise<AggregateResult<Spec>>\n}\n\nconst NOT_ENABLED = new Error(\n 'Aggregate / groupBy is not enabled on this Noydb instance. ' +\n 'Import `{ withAggregate }` from \"@noy-db/hub/aggregate\" and pass it to ' +\n '`createNoydb({ aggregateStrategy: withAggregate() })`.',\n)\n\n/**\n * No-aggregate stub. Every `.aggregate()` / `.groupBy()` / streaming\n * `scan().aggregate()` call throws with a pointer at the subpath. The\n * real `Aggregation` / `GroupedQuery` classes are never referenced at\n * runtime, so the bundler drops the ~886 LOC.\n *\n * @internal\n */\nexport const NO_AGGREGATE: AggregateStrategy = {\n aggregate() { throw NOT_ENABLED },\n groupBy() { throw NOT_ENABLED },\n groupByN() { throw NOT_ENABLED },\n scanAggregate() { throw NOT_ENABLED },\n}\n","/**\n * Chainable, immutable query builder.\n *\n * Each builder operation returns a NEW Query — the underlying plan is never\n * mutated. This makes plans safe to share, cache, and serialize.\n */\n\nimport type { Clause, FieldClause, FilterClause, GroupClause, Operator, WherePredicateClause } from './predicate.js'\nimport { evaluateClause } from './predicate.js'\nimport type { CollectionIndexes } from '../indexing/eager-indexes.js'\nimport type { JoinContext, JoinLeg, JoinStrategy } from './join.js'\nimport { applyJoins } from './join.js'\nimport type { LiveQuery, LiveUpstream } from './live.js'\nimport { buildLiveQuery } from './live.js'\nimport type { AggregateSpec, AggregateResult, AggregationUpstream, Aggregation } from '../aggregate/aggregation.js'\nimport type { GroupedQuery, GroupedQueryN } from '../aggregate/groupby.js'\nimport { NO_AGGREGATE, type AggregateStrategy } from '../aggregate/strategy.js'\n\nexport interface OrderBy {\n readonly field: string\n readonly direction: 'asc' | 'desc'\n}\n\n/**\n * A complete query plan: zero-or-more clauses, optional ordering, pagination,\n * and optional joins.\n *\n * Plans are JSON-serializable as long as no FilterClause is present and no\n * join leg carries a manual `strategy` override (JoinLeg itself is plain\n * data, so it serializes cleanly).\n *\n * Plans are intentionally NOT parametric on T — see `predicate.ts` FilterClause\n * for the variance reasoning. The public `Query<T>` API attaches the type tag.\n */\nexport interface QueryPlan {\n readonly clauses: readonly Clause[]\n readonly orderBy: readonly OrderBy[]\n readonly limit: number | undefined\n readonly offset: number\n /**\n * Zero-or-more join legs to apply after where/orderBy/limit/offset.\n * Each leg attaches a resolved right-side record (or null) under its\n * alias. See `query/join.ts` for the full semantics.\n */\n readonly joins: readonly JoinLeg[]\n}\n\nconst EMPTY_PLAN: QueryPlan = {\n clauses: [],\n orderBy: [],\n limit: undefined,\n offset: 0,\n joins: [],\n}\n\n/**\n * Source of records that a query executes against.\n *\n * The interface is non-parametric to keep variance friendly: callers cast\n * their typed source (e.g. `QuerySource<Invoice>`) into this opaque shape.\n *\n * `getIndexes` and `lookupById` are optional fast-path hooks. When both are\n * present and a where clause matches an indexed field, the executor uses\n * the index to skip a linear scan. Sources without these methods (or with\n * `getIndexes` returning `null`) always fall back to a linear scan.\n */\nexport interface QuerySource<T> {\n /** Snapshot of all current records. The query never mutates this array. */\n snapshot(): readonly T[]\n /** Subscribe to mutations; returns an unsubscribe function. */\n subscribe?(cb: () => void): () => void\n /** Index store for the indexed-fast-path. Optional. */\n getIndexes?(): CollectionIndexes | null\n /** O(1) record lookup by id, used to materialize index hits. */\n lookupById?(id: string): T | undefined\n}\n\ninterface InternalSource {\n snapshot(): readonly unknown[]\n subscribe?(cb: () => void): () => void\n getIndexes?(): CollectionIndexes | null\n lookupById?(id: string): unknown\n}\n\n/**\n * The chainable builder. All methods return a new Query — the original\n * remains unchanged. Terminal methods (`toArray`, `first`, `count`,\n * `subscribe`) execute the plan against the source.\n *\n * Type parameter T flows through the public API for ergonomics, but the\n * internal storage uses `unknown` so Collection<T> stays covariant.\n *\n * The optional `joinContext` is attached when the Query is constructed\n * via `Collection.query()` (Collection passes in a context built from\n * the Vault's join resolver). A Query constructed via `new Query`\n * directly — e.g. from tests with a plain-object source — has no\n * joinContext, and calling `.join()` on it throws with an actionable\n * error. See `query/join.ts` for the full design.\n */\n/**\n * Declared deterministic predicate (#153). Carries the consumer's\n * stable `hash` (for function-body identity), the function itself,\n * and is keyed by name when registered on a `Query<T>` via\n * `_withPredicates()`.\n */\nexport interface DeclaredPredicate {\n hash: string\n fn: (record: unknown, ctx?: unknown) => boolean\n}\n\nexport class Query<T> {\n private readonly source: InternalSource\n private readonly plan: QueryPlan\n private readonly joinContext: JoinContext | undefined\n private readonly aggregateStrategy: AggregateStrategy\n private readonly predicates: ReadonlyMap<string, DeclaredPredicate> | undefined\n\n constructor(\n source: QuerySource<T>,\n plan: QueryPlan = EMPTY_PLAN,\n joinContext?: JoinContext,\n aggregateStrategy: AggregateStrategy = NO_AGGREGATE,\n predicates?: ReadonlyMap<string, DeclaredPredicate>,\n ) {\n this.source = source as InternalSource\n this.plan = plan\n this.joinContext = joinContext\n this.aggregateStrategy = aggregateStrategy\n this.predicates = predicates\n }\n\n /**\n * @internal — accessor for the materialized-view dependency\n * analyzer. Not part of the public API; consumers should use the\n * builder methods, not inspect the plan directly.\n */\n _plan(): QueryPlan {\n return this.plan\n }\n\n /**\n * @internal — accessor for the materialized-view dependency\n * analyzer. Returns the join resolution context (or `undefined` for\n * queries constructed without a Collection backing).\n */\n _joinContext(): JoinContext | undefined {\n return this.joinContext\n }\n\n /**\n * @internal — clone this Query with a declared-predicate map\n * attached. Used by the materialized-view registry to enable\n * `.wherePredicate(name, ctx?)` for the MV's query callback (#153).\n * Consumers don't call this directly.\n */\n _withPredicates(predicates: ReadonlyMap<string, DeclaredPredicate>): Query<T> {\n return new Query<T>(\n this.source as QuerySource<T>,\n this.plan,\n this.joinContext,\n this.aggregateStrategy,\n predicates,\n )\n }\n\n /**\n * Filter by a registered deterministic predicate (#153). Requires\n * the Query to have been augmented with a predicates map (typically\n * via the materialized-view registry — bare Queries constructed\n * outside an MV throw on `.wherePredicate()`).\n *\n * `ctx` is an optional opaque value passed verbatim to the predicate\n * function. Both `predicateHash` (from the registration) and a\n * canonical-JSON hash of `ctx` fold into the MV's `queryHash`, so\n * either changing forces refresh on next visit.\n */\n wherePredicate(name: string, ctx?: unknown): Query<T> {\n if (!this.predicates) {\n throw new Error(\n `.wherePredicate(\"${name}\"): no predicates registered on this Query. ` +\n `Function-based predicates require the Query to be obtained from ` +\n `inside a materialized-view query() callback whose strategy declares ` +\n `\\`predicates: { ${name}: { hash, fn } }\\`.`,\n )\n }\n const decl = this.predicates.get(name)\n if (!decl) {\n throw new Error(\n `.wherePredicate(\"${name}\"): predicate not registered. ` +\n `Available: ${[...this.predicates.keys()].join(', ') || '(none)'}.`,\n )\n }\n const clause: WherePredicateClause = {\n type: 'wherePredicate',\n name,\n ctx,\n predicateHash: decl.hash,\n ctxHash: canonicalCtxHash(ctx),\n fn: decl.fn,\n }\n return new Query<T>(\n this.source as QuerySource<T>,\n { ...this.plan, clauses: [...this.plan.clauses, clause] },\n this.joinContext,\n this.aggregateStrategy,\n this.predicates,\n )\n }\n\n /** Add a field comparison. Multiple where() calls are AND-combined. */\n where(field: string, op: Operator, value: unknown): Query<T> {\n const clause: FieldClause = { type: 'field', field, op, value }\n return new Query<T>(\n this.source as QuerySource<T>,\n { ...this.plan, clauses: [...this.plan.clauses, clause] },\n this.joinContext,\n this.aggregateStrategy,\n this.predicates,\n )\n }\n\n /**\n * Logical OR group. Pass a callback that builds a sub-query.\n * Each clause inside the callback is OR-combined; the group itself\n * joins the parent plan with AND.\n */\n or(builder: (q: Query<T>) => Query<T>): Query<T> {\n const sub = builder(\n new Query<T>(this.source as QuerySource<T>, EMPTY_PLAN, this.joinContext, this.aggregateStrategy, this.predicates),\n )\n const group: GroupClause = {\n type: 'group',\n op: 'or',\n clauses: sub.plan.clauses,\n }\n return new Query<T>(\n this.source as QuerySource<T>,\n { ...this.plan, clauses: [...this.plan.clauses, group] },\n this.joinContext,\n this.aggregateStrategy,\n this.predicates,\n )\n }\n\n /**\n * Logical AND group. Same shape as `or()` but every clause inside the group\n * must match. Useful for explicit grouping inside a larger OR.\n */\n and(builder: (q: Query<T>) => Query<T>): Query<T> {\n const sub = builder(\n new Query<T>(this.source as QuerySource<T>, EMPTY_PLAN, this.joinContext, this.aggregateStrategy, this.predicates),\n )\n const group: GroupClause = {\n type: 'group',\n op: 'and',\n clauses: sub.plan.clauses,\n }\n return new Query<T>(\n this.source as QuerySource<T>,\n { ...this.plan, clauses: [...this.plan.clauses, group] },\n this.joinContext,\n this.aggregateStrategy,\n this.predicates,\n )\n }\n\n /** Escape hatch: add an arbitrary predicate function. Not serializable. */\n filter(fn: (record: T) => boolean): Query<T> {\n const clause: FilterClause = {\n type: 'filter',\n fn: fn as (record: unknown) => boolean,\n }\n return new Query<T>(\n this.source as QuerySource<T>,\n { ...this.plan, clauses: [...this.plan.clauses, clause] },\n this.joinContext,\n this.aggregateStrategy,\n this.predicates,\n )\n }\n\n /** Sort by a field. Subsequent calls are tie-breakers. */\n orderBy(field: string, direction: 'asc' | 'desc' = 'asc'): Query<T> {\n return new Query<T>(\n this.source as QuerySource<T>,\n { ...this.plan, orderBy: [...this.plan.orderBy, { field, direction }] },\n this.joinContext,\n this.aggregateStrategy,\n this.predicates,\n )\n }\n\n /** Cap the result size. */\n limit(n: number): Query<T> {\n return new Query<T>(\n this.source as QuerySource<T>,\n { ...this.plan, limit: n },\n this.joinContext,\n this.aggregateStrategy,\n this.predicates,\n )\n }\n\n /** Skip the first N matching records (after ordering). */\n offset(n: number): Query<T> {\n return new Query<T>(\n this.source as QuerySource<T>,\n { ...this.plan, offset: n },\n this.joinContext,\n this.aggregateStrategy,\n this.predicates,\n )\n }\n\n /**\n * Resolve a `ref()`-declared foreign key and attach the right-side\n * record under `opts.as`. — eager, single-FK, intra-\n * vault joins.\n *\n * ```ts\n * const rows = invoices.query()\n * .where('status', '==', 'open')\n * .join('clientId', { as: 'client' })\n * .toArray()\n * // → [{ id, amount, client: { id, name, ... } }, ...]\n * ```\n *\n * Preconditions:\n * - The Query must have a `joinContext` (constructed via\n * `Collection.query()`, not `new Query`).\n * - `field` must have a matching `refs: { [field]: ref('<target>') }`\n * declaration on the left collection.\n * - The target collection must be reachable via the vault\n * (either currently open or openable on demand).\n *\n * Strategy:\n * - Nested-loop against `lookupById` when the target source\n * provides it (the common path for Collection targets).\n * - Hash join otherwise, or when `{ strategy: 'hash' }` is\n * explicitly passed for test purposes.\n *\n * Ref-mode semantics on dangling refs (left record has a non-null\n * FK value pointing at a right-side id that doesn't exist):\n * - `strict` → throws `DanglingReferenceError` with the full\n * field / target / refId context.\n * - `warn` → attaches `null` and emits a one-shot warning per\n * unique dangling pair.\n * - `cascade` → attaches `null` silently. Cascade is a\n * delete-time mode; dangling refs visible at read time are\n * either mid-flight cascades or pre-existing orphans, not a\n * DSL-level error.\n *\n * A left-side record whose FK field is `null` / `undefined` is NOT\n * a dangling ref — it's \"no reference at all\", always allowed\n * regardless of mode.\n *\n * The return type widens `T` with `Record<As, R | null>`. The `R`\n * parameter is optional — supply it explicitly for type-checked\n * access to the joined fields:\n *\n * ```ts\n * invoices.query().join<'client', Client>('clientId', { as: 'client' })\n * // ^^^^^^^^^^^^^^^^^^^ alias literal + right-side type\n * ```\n *\n * Without the generic, the joined field is typed as `unknown`, which\n * still works but requires a cast to access its properties.\n *\n * Joins stay intra-vault by construction — cross-vault\n * correlation goes through `Noydb.queryAcross`, not\n * `.join()`.\n */\n join<As extends string, R = unknown>(\n field: string,\n opts: { as: As; strategy?: JoinStrategy; maxRows?: number },\n ): Query<T & Record<As, R | null>> {\n if (!this.joinContext) {\n throw new Error(\n `Query.join() requires a join context. Use collection.query() ` +\n `to construct a join-capable Query instead of the Query constructor ` +\n `directly (the direct constructor is only used for tests with ` +\n `plain-object sources).`,\n )\n }\n const descriptor = this.joinContext.resolveRef(field)\n // Check for dictKey join when no ref() is declared\n const isDictJoinField = !descriptor && this.joinContext.resolveDictSource?.(field) != null\n if (!descriptor && !isDictJoinField) {\n throw new Error(\n `Query.join(): no ref() declared for field \"${field}\" on collection ` +\n `\"${this.joinContext.leftCollection}\". Add ` +\n `refs: { ${field}: ref('<target-collection>') } to the collection ` +\n `options, then retry. See the ref() docs for the full list of modes.`,\n )\n }\n const leg: JoinLeg = descriptor\n ? {\n field,\n as: opts.as,\n target: descriptor.target,\n mode: descriptor.mode,\n strategy: opts.strategy,\n maxRows: opts.maxRows,\n // constraint #1 — always 'all' in. Do not remove.\n partitionScope: 'all',\n }\n : {\n // Dict join leg\n field,\n as: opts.as,\n target: field, // dict name = field name for dictKey\n mode: 'strict',\n strategy: opts.strategy,\n maxRows: opts.maxRows,\n partitionScope: 'all',\n isDictJoin: true,\n }\n return new Query<T & Record<As, R | null>>(\n this.source as unknown as QuerySource<T & Record<As, R | null>>,\n { ...this.plan, joins: [...this.plan.joins, leg] },\n this.joinContext,\n this.aggregateStrategy,\n this.predicates,\n )\n }\n\n /**\n * Execute the plan and return the matching records. When the plan\n * carries any join legs, they are applied after `where` / `orderBy`\n * / `limit` / `offset` narrow the left set. See the `.join()` doc\n * for the ordering rationale.\n */\n toArray(): T[] {\n const base = executePlanWithSource(this.source, this.plan)\n if (this.plan.joins.length === 0) return base as T[]\n if (!this.joinContext) {\n // Unreachable in practice — .join() throws if joinContext is\n // missing — but belt-and-braces for direct plan construction.\n throw new Error(\n `Query.toArray(): plan carries ${this.plan.joins.length} join leg(s) ` +\n `but no JoinContext is attached. This usually means the Query was ` +\n `constructed via the raw Query constructor with a plan that had joins ` +\n `pre-populated. Use collection.query().join(...) instead.`,\n )\n }\n return applyJoins(base, this.plan.joins, this.joinContext) as T[]\n }\n\n /** Return the first matching record, or null. Joins are applied. */\n first(): T | null {\n const arr = this.limit(1).toArray()\n return arr[0] ?? null\n }\n\n /**\n * Return the number of matching records (after where/filter,\n * before limit). **Joins are NOT applied** — count() reports the\n * left-side cardinality, because joins in are projection-only\n * (they attach an aliased field; they never filter). Running joins\n * here just to discard the aliases would be wasteful, and in strict\n * mode it could throw `DanglingReferenceError` for a call whose\n * intent is purely to count.\n */\n count(): number {\n // Use the same index-aware candidate machinery as toArray(); skip the\n // index-driving clause from re-evaluation. The length BEFORE limit/offset\n // is what `count()` documents.\n const { candidates, remainingClauses } = candidateRecords(this.source, this.plan.clauses)\n if (remainingClauses.length === 0) return candidates.length\n return filterRecords(candidates, remainingClauses).length\n }\n\n /**\n * Reduce the matching records through a named set of reducers.\n * the aggregation terminal.\n *\n * ```ts\n * const { total, n, avgAmount } = invoices.query()\n * .where('status', '==', 'open')\n * .aggregate({\n * total: sum('amount'),\n * n: count(),\n * avgAmount: avg('amount'),\n * })\n * .run()\n * ```\n *\n * Returns an `Aggregation<R>` wrapper with two terminals:\n * - `.run(): R` — synchronous one-shot reduction\n * - `.live(): LiveAggregation<R>` — reactive primitive that\n * re-runs the reduction whenever the source notifies of a\n * change. Always call `live.stop()` when finished.\n *\n * The reducer spec is bound here once and reused by both\n * terminals — this is why `.aggregate()` returns a wrapper instead\n * of being a direct terminal. Consumers who only need the static\n * value read `.run()`; consumers wiring a reactive UI read\n * `.live()`.\n *\n * Joins are intentionally NOT applied to aggregations in —\n * the same logic as `.count()`. Joins in are projection-only\n * (they attach an aliased field and never filter), so running\n * them just to throw the aliases away would be wasteful. If you\n * need a reducer that reads a joined field, open an issue —\n * aggregations-across-joins is explicitly out of scope for v1.\n *\n * Every reducer factory accepts an optional `{ seed }` parameter\n * that is plumbed through the protocol but unused by the\n * executor — that's constraint #2. When partition-aware\n * aggregation lands, the seed will carry running state across\n * partition boundaries without an API break.\n */\n aggregate<Spec extends AggregateSpec>(\n spec: Spec,\n ): Aggregation<AggregateResult<Spec>> {\n // Closure over the current query. Produces the record set that\n // the aggregation reduces — same pipeline as `count()`, skipping\n // limit/offset because aggregation is over the full match set,\n // not a paginated slice. (A paginated aggregation would be a\n // different operation; see docs for rationale.)\n const source = this.source\n const clauses = this.plan.clauses\n const executeRecords = (): readonly unknown[] => {\n const { candidates, remainingClauses } = candidateRecords(source, clauses)\n return remainingClauses.length === 0\n ? candidates\n : filterRecords(candidates, remainingClauses)\n }\n\n // Upstream for live mode — only the left source subscribes.\n // Joined aggregations are out of scope for (see above), so\n // there are no right-side change streams to merge in.\n const upstreams: AggregationUpstream[] = []\n if (source.subscribe) {\n const subscribe = source.subscribe.bind(source)\n upstreams.push({ subscribe: (cb: () => void) => subscribe(cb) })\n }\n\n return this.aggregateStrategy.aggregate<Spec>(executeRecords, spec, upstreams)\n }\n\n /**\n * Partition matching records into buckets keyed by a field, then\n * terminate with `.aggregate(spec)` to compute per-bucket\n * reducers..\n *\n * ```ts\n * const byClient = invoices.query()\n * .where('status', '==', 'open')\n * .groupBy('clientId')\n * .aggregate({ total: sum('amount'), n: count() })\n * .run()\n * // → [ { clientId: 'c1', total: 5250, n: 3 }, … ]\n * ```\n *\n * Result rows carry the group key value under the grouping field\n * name plus every reducer output from the spec. Buckets are\n * emitted in first-seen order — consumers who want a specific\n * ordering should `.sort()` downstream.\n *\n * **Cardinality caps:** a one-shot warning fires at 10_000\n * distinct groups; `GroupCardinalityError` throws at 100_000.\n * Grouping on a high-uniqueness field like `id` or `createdAt` is\n * almost always a query mistake — the error message names the\n * field and observed cardinality and suggests narrowing with\n * `.where()` first.\n *\n * **Null / undefined keys:** records with a missing or explicitly\n * `null` group field get their own buckets. `Map`-based\n * partitioning distinguishes `undefined` from `null`, so the two\n * cases do NOT merge. Consumers who want them merged should\n * coalesce upstream with `.filter()`.\n *\n * **Joins are not applied** — same rationale as `.count()` and\n * `.aggregate()`. Joined fields in are projection-only, so\n * running a join inside a grouping pipeline would be wasteful and\n * could trigger `DanglingReferenceError` in strict mode for a\n * call whose intent is purely to bucket-and-reduce. Grouping by\n * a joined field is explicitly out of scope for — file an\n * issue if a real consumer needs it.\n *\n * **Filter clauses (`.filter(fn)`):** grouped queries still\n * support filter clauses in the underlying plan — they run in\n * the same candidate/filter pipeline that `.aggregate()` uses.\n * The performance caveat is the same: filter clauses cost O(N)\n * per record and can't be index-accelerated.\n */\n groupBy<F extends string>(field: F): GroupedQuery<T, F>\n groupBy<F extends readonly [string, string, ...string[]]>(\n ...fields: F\n ): GroupedQueryN<T, F>\n groupBy(...fields: readonly string[]): GroupedQuery<T, string> | GroupedQueryN<T, readonly string[]> {\n if (fields.length === 0) {\n throw new Error('.groupBy() requires at least one field')\n }\n // Same record-producing closure as .aggregate() — grouped and\n // non-grouped aggregations execute over the same candidate set.\n // We inline the closure here instead of sharing a helper so the\n // builder stays allocation-friendly for the hot path.\n const source = this.source\n const clauses = this.plan.clauses\n const executeRecords = (): readonly unknown[] => {\n const { candidates, remainingClauses } = candidateRecords(source, clauses)\n return remainingClauses.length === 0\n ? candidates\n : filterRecords(candidates, remainingClauses)\n }\n\n const upstreams: AggregationUpstream[] = []\n if (source.subscribe) {\n const subscribe = source.subscribe.bind(source)\n upstreams.push({ subscribe: (cb: () => void) => subscribe(cb) })\n }\n\n // Dict-label resolution is single-field only — the <field>Label\n // projection has no meaningful shape for composite keys.\n if (fields.length === 1) {\n const field = fields[0]!\n const dictLabelResolver = buildDictLabelResolver(this.joinContext, field)\n return this.aggregateStrategy.groupBy<T, string>(\n executeRecords,\n field,\n upstreams,\n dictLabelResolver,\n )\n }\n return this.aggregateStrategy.groupByN<T, readonly string[]>(\n executeRecords,\n fields,\n upstreams,\n )\n }\n\n /**\n * Re-run the query whenever the source notifies of changes.\n * Returns an unsubscribe function. The callback receives the latest result.\n * Throws if the source does not support subscriptions.\n *\n * **For joined queries, prefer `.live()`** — `subscribe()`\n * only re-fires on LEFT-side changes, so joined data can be\n * stale if the right side mutates between emissions. `.live()`\n * merges change streams from every join target.\n */\n subscribe(cb: (result: T[]) => void): () => void {\n if (!this.source.subscribe) {\n throw new Error('Query source does not support subscriptions. Pass a source with a subscribe() method.')\n }\n cb(this.toArray())\n return this.source.subscribe(() => cb(this.toArray()))\n }\n\n /**\n * Reactive terminal — returns a `LiveQuery<T>` that re-runs the\n * query and updates its `value` whenever any source feeding it\n * mutates..\n *\n * For non-joined queries, `.live()` is a convenience over the\n * existing `.subscribe()` callback shape: a hand-rolled reactive\n * primitive with `value` / `error` fields and a `subscribe(cb)`\n * notification channel. Frame-agnostic — Vue / React / Solid\n * adapters wrap it in their own primitive.\n *\n * For joined queries, `.live()` additionally subscribes to every\n * join target's change stream. Mutations on a right-side\n * collection (insert / update / delete of a client referenced by\n * an invoice) re-fire the live query and re-evaluate every\n * dependent left row. Right-side targets are deduped by\n * collection name, so a chain that joins the same target twice\n * (e.g. billing client + shipping client → both 'clients') only\n * subscribes once.\n *\n * **Ref-mode behavior on right-side disappearance** — matches the\n * eager `.toArray()` contract from :\n * - `strict` → re-run throws `DanglingReferenceError`. The\n * LiveQuery catches the throw, stores it in `live.error`, and\n * notifies listeners (the throw does NOT propagate out of\n * the source's change handler — that would tear down the\n * emitter). Consumers check `live.error` after each\n * notification and render an error state in the UI.\n * - `warn` → joined value flips to `null`; the existing\n * warn-channel deduplication keeps repeated re-runs from\n * spamming the console.\n * - `cascade` → no special handling needed; the cascade-\n * delete mechanism propagates the right-side delete into the\n * left collection on the next tick, and the live query\n * naturally re-fires with the orphaned left rows gone.\n *\n * Always call `live.stop()` when finished — it tears down every\n * upstream subscription. The Vue layer's `onUnmounted` hook\n * should call `stop()` automatically; raw consumers must do it\n * themselves.\n *\n * **Limitations:**\n * - No granular delta updates — the whole query re-runs on\n * every change.\n * - No microtask batching — bursty changes produce one re-run\n * per change.\n * - No re-planning under live mutations — the planner picks\n * once at subscription time and reuses the same plan.\n * - Streaming live joins are deferred.\n */\n live(): LiveQuery<T> {\n const upstreams: LiveUpstream[] = []\n\n // Left-side change stream — every live query subscribes to\n // its source if the source supports subscriptions.\n if (this.source.subscribe) {\n const leftSubscribe = this.source.subscribe.bind(this.source)\n upstreams.push({\n subscribe: (cb: () => void) => leftSubscribe(cb),\n })\n }\n\n // Right-side change streams — only for joined queries. Dedup\n // by target name so a chain joining the same target twice\n // doesn't double-subscribe and double-fire on every right-side\n // mutation.\n if (this.plan.joins.length > 0 && this.joinContext) {\n const subscribed = new Set<string>()\n for (const leg of this.plan.joins) {\n if (subscribed.has(leg.target)) continue\n subscribed.add(leg.target)\n const rightSource = this.joinContext.resolveSource(leg.target)\n if (rightSource?.subscribe) {\n const rightSubscribe = rightSource.subscribe.bind(rightSource)\n upstreams.push({\n subscribe: (cb: () => void) => rightSubscribe(cb),\n })\n }\n }\n }\n\n // The recompute is just toArray bound to this query — same\n // pipeline as eager execution, including join application.\n return buildLiveQuery<T>(() => this.toArray(), upstreams)\n }\n\n /**\n * Return the plan as a JSON-friendly object. FilterClause entries are\n * stripped (their `fn` cannot be serialized) and replaced with\n * { type: 'filter', fn: '[function]' } so devtools can still see them.\n */\n toPlan(): unknown {\n return serializePlan(this.plan)\n }\n}\n\n/**\n * Index-aware execution: try the indexed fast path first, fall back to a\n * full scan otherwise. Mirrors `executePlan` for the public surface but\n * takes a `QuerySource` so it can consult `getIndexes()` and `lookupById()`.\n */\nfunction executePlanWithSource(source: InternalSource, plan: QueryPlan): unknown[] {\n const { candidates, remainingClauses } = candidateRecords(source, plan.clauses)\n // Only the clauses NOT consumed by the index need re-evaluation. This is\n // the key optimization that makes indexed queries dominate linear scans:\n // for a single-clause query against an indexed field, `remainingClauses`\n // is empty and we skip the per-record predicate evaluation entirely.\n let result = remainingClauses.length === 0\n ? [...candidates]\n : filterRecords(candidates, remainingClauses)\n if (plan.orderBy.length > 0) {\n result = sortRecords(result, plan.orderBy)\n }\n if (plan.offset > 0) {\n result = result.slice(plan.offset)\n }\n if (plan.limit !== undefined) {\n result = result.slice(0, plan.limit)\n }\n return result\n}\n\ninterface CandidateResult {\n /** The reduced candidate set, materialized to record objects. */\n readonly candidates: readonly unknown[]\n /** The clauses that the index could not satisfy and must still be evaluated. */\n readonly remainingClauses: readonly Clause[]\n}\n\n/**\n * Pick a candidate record set using the index store when possible.\n *\n * Strategy: scan the top-level clauses for the FIRST `==` or `in` clause\n * against an indexed field. If found, use the index to materialize a\n * candidate set and return the OTHER clauses as `remainingClauses`. The\n * caller skips re-evaluating the index-driving clause because the index\n * is authoritative for that field.\n *\n * This is a deliberately simple planner. A future optimizer could pick\n * the most selective index, intersect multiple indexes, or push composite\n * keys through. For the single-index fast path is good enough.\n */\nfunction candidateRecords(source: InternalSource, clauses: readonly Clause[]): CandidateResult {\n const indexes = source.getIndexes?.()\n if (!indexes || !source.lookupById || clauses.length === 0) {\n return { candidates: source.snapshot(), remainingClauses: clauses }\n }\n // Bind the lookup method through an arrow so it doesn't drift from\n // its `this` context — keeps the unbound-method lint rule happy.\n const lookupById = (id: string): unknown => source.lookupById?.(id)\n\n for (let i = 0; i < clauses.length; i++) {\n const clause = clauses[i]!\n if (clause.type !== 'field') continue\n if (!indexes.has(clause.field)) continue\n\n let ids: ReadonlySet<string> | null = null\n if (clause.op === '==') {\n ids = indexes.lookupEqual(clause.field, clause.value)\n } else if (clause.op === 'in' && Array.isArray(clause.value)) {\n ids = indexes.lookupIn(clause.field, clause.value)\n }\n\n if (ids !== null) {\n // Found an index-eligible clause: materialize the candidate set and\n // remove this clause from the remaining list.\n const remaining: Clause[] = []\n for (let j = 0; j < clauses.length; j++) {\n if (j !== i) remaining.push(clauses[j]!)\n }\n return {\n candidates: materializeIds(ids, lookupById),\n remainingClauses: remaining,\n }\n }\n // Not index-eligible — keep scanning in case a later clause is a\n // better candidate.\n }\n\n // No clause was index-eligible — fall back to a full scan.\n return { candidates: source.snapshot(), remainingClauses: clauses }\n}\n\nfunction materializeIds(\n ids: ReadonlySet<string>,\n lookupById: (id: string) => unknown,\n): unknown[] {\n const out: unknown[] = []\n for (const id of ids) {\n const record = lookupById(id)\n if (record !== undefined) out.push(record)\n }\n return out\n}\n\n/**\n * Execute a plan against a snapshot of records.\n * Pure function — same input, same output, no side effects.\n *\n * Records are typed as `unknown` because plans are non-parametric; callers\n * cast the return type at the API surface (see `Query.toArray()`).\n */\nexport function executePlan(records: readonly unknown[], plan: QueryPlan): unknown[] {\n let result = filterRecords(records, plan.clauses)\n if (plan.orderBy.length > 0) {\n result = sortRecords(result, plan.orderBy)\n }\n if (plan.offset > 0) {\n result = result.slice(plan.offset)\n }\n if (plan.limit !== undefined) {\n result = result.slice(0, plan.limit)\n }\n return result\n}\n\nfunction filterRecords(records: readonly unknown[], clauses: readonly Clause[]): unknown[] {\n if (clauses.length === 0) return [...records]\n const out: unknown[] = []\n for (const r of records) {\n let matches = true\n for (const clause of clauses) {\n if (!evaluateClause(r, clause)) {\n matches = false\n break\n }\n }\n if (matches) out.push(r)\n }\n return out\n}\n\nfunction sortRecords(records: unknown[], orderBy: readonly OrderBy[]): unknown[] {\n // Stable sort: Array.prototype.sort is required to be stable since ES2019.\n return [...records].sort((a, b) => {\n for (const { field, direction } of orderBy) {\n const av = readField(a, field)\n const bv = readField(b, field)\n const cmp = compareValues(av, bv)\n if (cmp !== 0) return direction === 'asc' ? cmp : -cmp\n }\n return 0\n })\n}\n\nfunction readField(record: unknown, field: string): unknown {\n if (record === null || record === undefined) return undefined\n if (!field.includes('.')) {\n return (record as Record<string, unknown>)[field]\n }\n const segments = field.split('.')\n let cursor: unknown = record\n for (const segment of segments) {\n if (cursor === null || cursor === undefined) return undefined\n cursor = (cursor as Record<string, unknown>)[segment]\n }\n return cursor\n}\n\nfunction compareValues(a: unknown, b: unknown): number {\n // Nullish goes last in asc order.\n if (a === undefined || a === null) return b === undefined || b === null ? 0 : 1\n if (b === undefined || b === null) return -1\n if (typeof a === 'number' && typeof b === 'number') return a - b\n if (typeof a === 'string' && typeof b === 'string') return a < b ? -1 : a > b ? 1 : 0\n if (a instanceof Date && b instanceof Date) return a.getTime() - b.getTime()\n // Mixed/unsupported types: treat as equal so the sort stays stable.\n // (Deliberate choice — we don't try to coerce arbitrary objects to strings.)\n return 0\n}\n\nfunction serializePlan(plan: QueryPlan): unknown {\n return {\n clauses: plan.clauses.map(serializeClause),\n orderBy: plan.orderBy,\n limit: plan.limit,\n offset: plan.offset,\n joins: plan.joins,\n }\n}\n\nfunction serializeClause(clause: Clause): unknown {\n if (clause.type === 'filter') {\n return { type: 'filter', fn: '[function]' }\n }\n if (clause.type === 'wherePredicate') {\n // Strip the live `fn` reference (non-serializable) but keep the\n // identity-carrying fields so distinct predicates still serialize\n // distinctly. `predicateHash` + `ctxHash` are the hash identity;\n // `name` is the named predicate reference. This matters because\n // niwat-review of #159 caught that the previous fall-through\n // (return clause) exposed the live fn and produced identical\n // serializations for distinct predicates with different ctx\n // values.\n return {\n type: 'wherePredicate',\n name: clause.name,\n ctx: clause.ctx,\n predicateHash: clause.predicateHash,\n ctxHash: clause.ctxHash,\n fn: '[function]',\n }\n }\n if (clause.type === 'group') {\n return {\n type: 'group',\n op: clause.op,\n clauses: clause.clauses.map(serializeClause),\n }\n }\n return clause\n}\n\n/**\n * Compute a stable hash of a `ctx` value supplied to\n * `.wherePredicate(name, ctx)`. Canonical-JSON: keys sorted at each\n * level so `{a, b}` and `{b, a}` hash to the same value. Undefined ctx\n * hashes to the empty string. The hash is sync because it just runs\n * a cheap djb2-style fold — used at builder time, not security-sensitive.\n *\n * @internal\n */\nfunction canonicalCtxHash(ctx: unknown): string {\n if (ctx === undefined) return \"\"\n const canonical = JSON.stringify(ctx, (_key, value) => {\n if (value && typeof value === \"object\" && !Array.isArray(value)) {\n const sorted: Record<string, unknown> = {}\n for (const k of Object.keys(value as Record<string, unknown>).sort()) {\n sorted[k] = (value as Record<string, unknown>)[k]\n }\n return sorted\n }\n return value\n })\n // djb2 fold over the canonical string; converted to hex.\n let h = 5381\n for (let i = 0; i < canonical.length; i++) {\n h = ((h << 5) + h) ^ canonical.charCodeAt(i)\n }\n return (h >>> 0).toString(16).padStart(8, \"0\")\n}\n\n/**\n * Build a dict-label resolver for `Query.groupBy(field)` when the\n * grouping field is a `dictKey`. Extracted from the inline closure\n * inside `groupBy` so the multi-key path (which has no meaningful\n * `<field>Label` shape) can skip it cleanly. Pure refactor — no\n * behaviour change for the single-field path.\n *\n * Returns `undefined` when:\n * - the join context lacks a `resolveDictSource` hook, or\n * - no dictionary source is registered for `field`.\n *\n * @internal\n */\nfunction buildDictLabelResolver(\n joinCtx: JoinContext | undefined,\n field: string,\n):\n | ((key: string, locale: string, fallback?: string | readonly string[]) => Promise<string | undefined>)\n | undefined {\n if (!joinCtx?.resolveDictSource) return undefined\n const dictSource = joinCtx.resolveDictSource(field)\n if (!dictSource) return undefined\n const snapshot = dictSource.snapshot()\n const dictMap = new Map<string, Record<string, string>>()\n for (const entry of snapshot) {\n const k = (entry as Record<string, unknown>)['key']\n const labels = (entry as Record<string, unknown>)['labels']\n if (typeof k === 'string' && labels && typeof labels === 'object') {\n dictMap.set(k, labels as Record<string, string>)\n }\n }\n return async (\n key: string,\n locale: string,\n fallback?: string | readonly string[],\n ): Promise<string | undefined> => {\n const labels = dictMap.get(key)\n if (!labels) return undefined\n if (labels[locale] !== undefined) return labels[locale]\n const chain = Array.isArray(fallback)\n ? (fallback as readonly string[])\n : fallback\n ? [fallback as string]\n : []\n for (const fb of chain) {\n if (fb === 'any') {\n const any = Object.values(labels)[0]\n if (any !== undefined) return any\n } else if (labels[fb] !== undefined) {\n return labels[fb]\n }\n }\n return undefined\n }\n}\n","/**\n * Canonicalise a group-key tuple to a stable string for dedup hashing.\n *\n * Sorts field names lexicographically before serialising, so that\n * `.groupBy('a', 'b')` and `.groupBy('b', 'a')` produce identical\n * keys for the same logical group. Values are JSON-stringified;\n * `undefined` and `null` are distinguished (matching the Map-key\n * semantics in `groupAndReduce`).\n *\n * NOT part of the public API. Used by:\n * - `groupAndReduce` for the dedup Map's key\n * - `materialized-views/query-hash` for UNION MV cross-arm row-key dedup (PR 2)\n *\n * Pure: same input → same output, no side effects.\n */\nexport function canonicalGroupKey(\n fields: readonly string[],\n row: Record<string, unknown>,\n): string {\n const sorted = [...fields].sort()\n const parts: string[] = []\n for (const name of sorted) {\n const v = row[name]\n const serialised =\n v === undefined ? 'undefined' : JSON.stringify(v)\n parts.push(`${name}=${serialised}`)\n }\n return parts.join('|')\n}\n","/**\n * Query DSL `.groupBy()` —.\n *\n * Chains after `.where()` / `.filter()` / `.or()` / `.and()` on a\n * Query and before a reducer spec, so consumers can compute\n * per-bucket aggregates without folding in userland:\n *\n * ```ts\n * const byClient = invoices.query()\n * .where('status', '==', 'open')\n * .groupBy('clientId')\n * .aggregate({ total: sum('amount'), n: count() })\n * .run()\n * // → [ { clientId: 'c1', total: 5250, n: 3 }, … ]\n * ```\n *\n * Execution pipeline:\n *\n * 1. Run the query's where/filter clauses (same candidate /\n * filter pipeline as `.aggregate()` directly on Query).\n * 2. Partition the matching records into buckets keyed by\n * `readPath(record, field)`. JS `Map` preserves insertion\n * order, so the first-seen key for a bucket determines its\n * position in the result array — consumers who want a\n * specific ordering should `.sort()` downstream.\n * 3. Enforce cardinality: warn once per field at 10% of the cap\n * (10_000 buckets), throw `GroupCardinalityError` at 100% of\n * the cap (100_000 buckets).\n * 4. For each bucket, build a per-group reducer state and\n * step every record in the bucket through it.\n * 5. Emit one result row per bucket, shaped as\n * `{ [field]: key, ...reduced }`.\n *\n * **Null / undefined keys:** `Map` distinguishes `null` from\n * `undefined`, so records with a missing group field get their own\n * bucket, and records with an explicit `null` value get a separate\n * bucket from that. Consumers who want them merged can coalesce\n * upstream with `.filter()`.\n *\n * **Live mode:** `.groupBy().aggregate().live()` re-runs the full\n * grouping pipeline on every source change. Per-bucket incremental\n * delta maintenance is a future optimization — the reducer\n * protocol's `remove()` hook admits it, but ships naive\n * re-grouping for simplicity.\n *\n * **Type-level stable-key narrowing:** when\n * `dictKey` lands, `groupBy<DictField>()` will narrow the group key\n * type to the stable dictionary key rather than the resolved locale\n * label. That prevents grouping by the locale-resolved label,\n * which would produce different buckets per reader. types the\n * key as `unknown` at the result shape; the dictKey narrowing\n * layers on top without an API break.\n *\n * Partition-awareness seam: when partitioned collections land,\n * per-partition grouping will need to merge sub-results across\n * partitions. The reducer protocol's `{ seed }` parameter\n * (already plumbed through in `reducers.ts`) is the mechanism —\n * groupBy doesn't need its own seam for the moment, because it\n * delegates to the reducer protocol for all per-bucket state.\n */\n\nimport { readPath } from '../query/predicate.js'\nimport type {\n AggregateSpec,\n AggregateResult,\n AggregationUpstream,\n LiveAggregation,\n} from './aggregation.js'\nimport { buildLiveAggregation } from './aggregation.js'\nimport { canonicalGroupKey } from './canonical-key.js'\nimport { GroupCardinalityError } from '../errors.js'\n\n/**\n * Cardinality thresholds for `.groupBy()`. The warn threshold gives\n * consumers a heads-up before the hard error; the cap is a fixed\n * constant in (not overridable). A `{ maxGroups }` override\n * can be added later without a break if a real consumer asks.\n */\nexport const GROUPBY_WARN_CARDINALITY = 10_000\nexport const GROUPBY_MAX_CARDINALITY = 100_000\n\n/**\n * One-shot warning dedup per-field-set — reactive dashboards\n * re-executing the same grouped query should produce the warning\n * once, not once per re-fire. Keyed on the sorted JSON of grouping\n * field names so `.groupBy('a', 'b')` and `.groupBy('b', 'a')`\n * share the same dedup slot (their result tuples are isomorphic).\n */\nconst warnedCardinalityFields = new Set<string>()\nfunction warnCardinalityApproaching(\n fields: readonly string[],\n observed: number,\n): void {\n const key = JSON.stringify([...fields].sort())\n if (warnedCardinalityFields.has(key)) return\n warnedCardinalityFields.add(key)\n const label = `[${fields.join(', ')}]`\n console.warn(\n `[noy-db] .groupBy(${label}) produced ${observed} distinct groups, ` +\n `${Math.round((observed / GROUPBY_MAX_CARDINALITY) * 100)}% of the ` +\n `${GROUPBY_MAX_CARDINALITY}-group ceiling. Narrow the query with ` +\n `.where() before grouping, or switch to a lower-cardinality field.`,\n )\n}\n\n/**\n * Test-only: clear the per-field cardinality warning dedup between\n * tests. Production code never calls this — matching the\n * `resetJoinWarnings` pattern in `join.ts`.\n */\nexport function resetGroupByWarnings(): void {\n warnedCardinalityFields.clear()\n}\n\n/**\n * Result row shape for a grouped aggregation. Each row carries the\n * group key value under the grouping field name plus every reducer\n * output from the spec.\n *\n * types the group key as `unknown` at the result shape — the\n * runtime read via `readPath` can return any value, and narrowing\n * to a specific type would require the caller to assert at the\n * call site. `dictKey` narrowing layers on top of this by\n * adding an overload that constrains `F` when the grouping field\n * is a `dictKey`.\n */\nexport type GroupedRow<F extends string, R> = { [K in F]: unknown } & R\n\n/**\n * Multi-key variant — result-row shape for variadic\n * `.groupBy(...fields)`. Every grouped field name appears on the row\n * (typed as `unknown` for the same reason as `GroupedRow`), plus the\n * reducer outputs from the spec.\n */\nexport type GroupedRowN<F extends readonly string[], R> =\n { [K in F[number]]: unknown } & R\n\n/**\n * Shared base class for the chainable grouped-query wrappers. Holds\n * the constructor + protected fields that both single-key\n * `GroupedQuery<T, F>` and variadic `GroupedQueryN<T, F>` need; each\n * subclass only overrides `aggregate()` with its own result-row\n * generic.\n *\n * Not exported — implementation detail. Adding `.having()` /\n * `.live()` / `.orderByGroup()` etc. in the future lands here once\n * and both subclasses pick it up automatically.\n *\n * @internal\n */\nabstract class GroupedQueryBase {\n /**\n * Field set this grouped query buckets on. Stored in declaration\n * order — the same order is preserved on every result row by\n * `groupAndReduce`. For the single-field constructor, this is\n * `[field]`.\n */\n protected readonly fields: readonly string[]\n\n constructor(\n protected readonly executeRecords: () => readonly unknown[],\n fieldOrFields: string | readonly string[],\n protected readonly upstreams: readonly AggregationUpstream[],\n /**\n * Optional dict label resolver attached by the query builder when\n * the grouping field is a dictKey. Variadic groupings always pass\n * `undefined` — `<field>Label` projection has no meaningful shape\n * for composite keys.\n */\n protected readonly dictLabelResolver?: (\n key: string,\n locale: string,\n fallback?: string | readonly string[],\n ) => Promise<string | undefined>,\n ) {\n this.fields =\n typeof fieldOrFields === 'string' ? [fieldOrFields] : [...fieldOrFields]\n }\n}\n\n/**\n * Chainable wrapper returned by `Query.groupBy(field)`. Terminates\n * with `.aggregate(spec)` which returns a `GroupedAggregation`.\n *\n * Kept minimal — the only operation on a grouped query is\n * aggregation. Ordering, limiting, and further filtering belong on\n * the underlying `Query` before `.groupBy()` is called; applying\n * them post-group would be a different operation (`having` /\n * `groupOrderBy`), out of scope for.\n */\nexport class GroupedQuery<T, F extends string> extends GroupedQueryBase {\n /**\n * Build a grouped aggregation. Returns a `GroupedAggregation`\n * with `.run()`, `.runAsync()`, and `.live()` terminals — same shape\n * as the non-grouped `.aggregate()` wrapper, just with an array\n * result (one row per bucket) instead of a single reduced object.\n */\n aggregate<Spec extends AggregateSpec>(\n spec: Spec,\n ): GroupedAggregation<GroupedRow<F, AggregateResult<Spec>>> {\n // T is phantom on the wrapper so consumers can still see the\n // source row type on hover. Reference it to keep lint quiet.\n void undefined as T | undefined\n return new GroupedAggregation<GroupedRow<F, AggregateResult<Spec>>>(\n this.executeRecords,\n this.fields,\n spec,\n this.upstreams,\n this.dictLabelResolver,\n )\n }\n}\n\n/**\n * Variadic-keyed sibling of `GroupedQuery<T, F>`. Constructed by the\n * multi-arg `Query.groupBy(...fields)` overload. The runtime shape is\n * identical — only the type-level result-row narrowing differs.\n */\nexport class GroupedQueryN<T, F extends readonly string[]> extends GroupedQueryBase {\n aggregate<Spec extends AggregateSpec>(\n spec: Spec,\n ): GroupedAggregation<GroupedRowN<F, AggregateResult<Spec>>> {\n void undefined as T | undefined\n return new GroupedAggregation<GroupedRowN<F, AggregateResult<Spec>>>(\n this.executeRecords,\n this.fields,\n spec,\n this.upstreams,\n this.dictLabelResolver,\n )\n }\n}\n\n/**\n * Execute the group-and-reduce pipeline. Pure function over a\n * record array and a spec — shared by `GroupedAggregation.run()`\n * and the live-mode refresh path. Exported for tests and for any\n * future `scan().groupBy().aggregate()` reuse.\n *\n * Enforces the cardinality cap incrementally during the partition\n * loop, so a runaway grouping throws at the moment the 100_001st\n * bucket would be created — the consumer doesn't have to wait for\n * the full partition to materialize before the error fires.\n */\nexport function groupAndReduce<R>(\n records: readonly unknown[],\n fieldOrFields: string | readonly string[],\n spec: AggregateSpec,\n): R[] {\n const fields: readonly string[] =\n typeof fieldOrFields === 'string' ? [fieldOrFields] : fieldOrFields\n if (fields.length === 0) {\n throw new Error('.groupBy() requires at least one field')\n }\n\n // Bucket value is { keyValues, records } so the output row can stamp\n // every grouped field in DECLARATION ORDER. Map preserves insertion\n // order natively (ES2015), so first-seen keys determine ordering.\n interface Bucket {\n keyValues: Record<string, unknown>\n records: unknown[]\n }\n const buckets = new Map<string, Bucket>()\n // Field-label string for error messages — matches the variadic\n // surface (`[a, b]` for multi-key, `\"k\"` for single-key back-compat).\n const fieldLabel = fields.length === 1 ? fields[0]! : `[${fields.join(', ')}]`\n\n for (const record of records) {\n // Read each field's value into a row object, then canonicalise.\n const keyValues: Record<string, unknown> = {}\n for (const f of fields) {\n keyValues[f] = readPath(record, f)\n }\n const dedupKey = canonicalGroupKey(fields, keyValues)\n let bucket = buckets.get(dedupKey)\n if (bucket === undefined) {\n if (buckets.size >= GROUPBY_MAX_CARDINALITY) {\n throw new GroupCardinalityError(\n fieldLabel,\n buckets.size + 1,\n GROUPBY_MAX_CARDINALITY,\n )\n }\n bucket = { keyValues, records: [] }\n buckets.set(dedupKey, bucket)\n }\n bucket.records.push(record)\n }\n\n if (buckets.size >= GROUPBY_WARN_CARDINALITY) {\n warnCardinalityApproaching(fields, buckets.size)\n }\n\n // Reduce each bucket through the spec. Same init/step/finalize\n // pipeline as `reduceRecords` in aggregate.ts, but one state per\n // bucket. Inlining the loop here keeps the per-bucket path tight\n // — calling `reduceRecords` per bucket would recompute\n // `Object.keys(spec)` once per bucket unnecessarily.\n const reducerKeys = Object.keys(spec)\n const out: R[] = []\n for (const bucket of buckets.values()) {\n const state: Record<string, unknown> = {}\n for (const rk of reducerKeys) {\n state[rk] = spec[rk]!.init()\n }\n for (const record of bucket.records) {\n for (const rk of reducerKeys) {\n state[rk] = spec[rk]!.step(state[rk], record)\n }\n }\n // Stamp grouped fields FIRST, in declaration order — this is\n // tested via `Object.keys(row).slice(0, fields.length)`.\n const row: Record<string, unknown> = {}\n for (const f of fields) {\n row[f] = bucket.keyValues[f]\n }\n for (const rk of reducerKeys) {\n row[rk] = spec[rk]!.finalize(state[rk])\n }\n out.push(row as unknown as R)\n }\n return out\n}\n\n/**\n * Grouped aggregation wrapper — the `.groupBy(field).aggregate(spec)`\n * terminal. Shape mirrors `Aggregation<R>` from aggregate.ts: two\n * terminals (`.run()` and `.live()`), spec bound at construction\n * time, upstreams collected for live mode.\n *\n * The generic `R` is the per-row result shape (i.e. a single\n * grouped row), and the terminals return `R[]` — one row per\n * bucket.\n */\nexport class GroupedAggregation<R> {\n private readonly fields: readonly string[]\n\n constructor(\n private readonly executeRecords: () => readonly unknown[],\n fields: string | readonly string[],\n private readonly spec: AggregateSpec,\n private readonly upstreams: readonly AggregationUpstream[],\n /**\n * Optional dict label resolver for `<field>Label` projection\n *. Present when the grouping field is a dictKey.\n */\n private readonly dictLabelResolver?: (\n key: string,\n locale: string,\n fallback?: string | readonly string[],\n ) => Promise<string | undefined>,\n ) {\n this.fields = typeof fields === 'string' ? [fields] : [...fields]\n }\n\n /** Execute the query, group, reduce, and return an array of rows. */\n run(): R[] {\n return groupAndReduce<R>(this.executeRecords(), this.fields, this.spec)\n }\n\n /**\n * Execute the query, group, reduce, and resolve `<field>Label` for\n * each result row when the grouping field is a `dictKey` and a\n * `locale` is provided. Returns `R[]` synchronously when\n * no locale is specified (identical to `.run()`).\n *\n * The `<field>Label` field is appended to each row. Rows whose group\n * key has no dictionary entry get `<field>Label: undefined`.\n *\n * Dict-label resolution is single-field only — multi-key groupings\n * do not produce a `<field>Label`. The resolver is only attached\n * by the builder when `fields.length === 1`.\n */\n async runAsync(opts?: {\n locale?: string\n fallback?: string | readonly string[]\n }): Promise<R[]> {\n const rows = groupAndReduce<R>(this.executeRecords(), this.fields, this.spec)\n if (!opts?.locale || !this.dictLabelResolver || this.fields.length !== 1) return rows\n\n const resolve = this.dictLabelResolver\n const locale = opts.locale\n const fallback = opts.fallback\n const field = this.fields[0]!\n const labelKey = `${field}Label`\n\n return Promise.all(\n rows.map(async (row) => {\n const key = (row as Record<string, unknown>)[field]\n if (typeof key !== 'string') return row\n const label = await resolve(key, locale, fallback)\n return { ...(row as Record<string, unknown>), [labelKey]: label } as unknown as R\n }),\n )\n }\n\n /**\n * Build a reactive `LiveAggregation<R[]>` that re-runs the full\n * group-and-reduce pipeline whenever any upstream source notifies\n * of a change. Same error-isolation and idempotent-stop contract\n * as `Aggregation.live()` — the implementation delegates to the\n * same `LiveAggregationImpl` class by threading a fresh\n * recompute closure through the existing constructor.\n *\n * uses naive full re-run on every change. Incremental\n * per-bucket maintenance (apply `step` on inserted records,\n * `remove` on deleted records, route by bucket key) is a future\n * optimization — the reducer protocol admits it, but wiring\n * delta-aware source subscriptions is a separate PR.\n *\n * Always call `live.stop()` when finished.\n */\n live(): LiveAggregation<R[]> {\n const recompute = (): R[] =>\n groupAndReduce<R>(this.executeRecords(), this.fields, this.spec)\n return buildLiveAggregation<R[]>(recompute, this.upstreams)\n }\n}\n","/**\n * Streaming scan builder with filter + aggregate support.\n *\n * `Collection.scan()` now returns a `ScanBuilder<T>` that\n * implements `AsyncIterable<T>` (for existing `for await … of`\n * consumers) AND exposes chainable `.where()` / `.filter()` clauses\n * plus a `.aggregate(spec)` async terminal that reduces the scan\n * stream through the same reducer protocol as `Query.aggregate()`\n *.\n *\n * **Memory model:** O(reducers), not O(records). The aggregate\n * terminal initializes one state per reducer, iterates through the\n * scan one record at a time via `for await`, applies every reducer's\n * `step` per record, and never collects the stream into an array.\n * This is what makes `scan().aggregate()` suitable for collections\n * that don't fit in memory — the bound is a code-level invariant\n * visible in the function body, not a runtime assertion.\n *\n * **Paginated iteration:** the builder holds a `pageProvider`\n * closure that maps `(cursor, limit) → Promise<page>`, plumbed by\n * `Collection.scan()` to `collection.listPage(...)`. The page\n * iterator walks cursors forward until exhaustion, same as the\n * previous async-generator `scan()` did.\n *\n * **Backward compatibility:** existing `for await (const rec of\n * collection.scan()) { … }` code continues to work because\n * `ScanBuilder` implements `[Symbol.asyncIterator]`. The previous\n * signature returned an `AsyncIterableIterator<T>` (which has both\n * `[Symbol.asyncIterator]` and `.next()`). We verified at grep time\n * that no call sites use `.next()` on the scan result directly, so\n * the narrowed interface is safe.\n *\n * **Immutability:** each `.where()` / `.filter()` call returns a\n * fresh builder sharing the same page provider and page size. This\n * lets a base scan be reused for multiple parallel aggregations:\n *\n * ```ts\n * const scan = invoices.scan()\n * const [open, paid] = await Promise.all([\n * scan.where('status', '==', 'open').aggregate({ n: count() }),\n * scan.where('status', '==', 'paid').aggregate({ n: count() }),\n * ])\n * ```\n *\n * Note that each aggregation pays a full scan — there's no shared\n * iteration across the two. Multi-way aggregation in a single pass\n * is out of scope; consumers who need it should build a compound spec\n * and run a single `.aggregate({ openN, paidN })` at the DSL level.\n *\n * **Out of scope for (tracked separately):**\n * - `scan().aggregate().live()` — unbounded scan + change-stream\n * reconciliation is a design problem, not just a code one\n * - `scan().groupBy().aggregate()` — high-cardinality grouping on\n * huge collections would re-introduce the O(groups) memory\n * problem that aggregate fixes\n * - Parallel scan across pages — race-safe page cursor contracts\n * are not in the adapter API yet\n * - `scan().join(...)` — tracked under (streaming join)\n */\n\nimport type { Clause, FieldClause, Operator } from './predicate.js'\nimport { evaluateClause, readPath } from './predicate.js'\nimport type {\n AggregateSpec,\n AggregateResult,\n} from '../aggregate/aggregation.js'\nimport type { JoinContext, JoinLeg, JoinableSource } from './join.js'\nimport { DanglingReferenceError } from '../errors.js'\n\n/**\n * Page provider — the Collection-shaped hook the builder calls to\n * walk cursors forward. Kept as a structural interface so tests can\n * wire up a synthetic provider without pulling in the full\n * Collection class. Collection's `listPage` matches this shape\n * exactly.\n */\nexport interface ScanPageProvider<T> {\n listPage(opts: {\n cursor?: string\n limit?: number\n }): Promise<{ items: T[]; nextCursor: string | null }>\n}\n\nconst DEFAULT_SCAN_PAGE_SIZE = 100\n\n/**\n * Chainable streaming scan. Implements `AsyncIterable<T>` for\n * drop-in use with `for await … of`; adds `.where()` / `.filter()`\n * chainable clauses and a `.aggregate(spec)` async terminal.\n *\n * The builder is immutable per operation — each chained call\n * returns a fresh `ScanBuilder` sharing the same page provider and\n * page size. The original builder is never mutated, so it's safe\n * to reuse across multiple parallel consumers.\n */\nexport class ScanBuilder<T> implements AsyncIterable<T> {\n private readonly pageProvider: ScanPageProvider<T>\n private readonly pageSize: number\n private readonly clauses: readonly Clause[]\n /**\n * Zero-or-more join legs to apply per record as the stream flows.\n * Each leg attaches the resolved right-side record (or null) under\n * its alias. — streaming joins.\n *\n * Joins are evaluated AFTER clauses, so a `where()` filtered-out\n * record never triggers a right-side lookup. This is the same\n * ordering as `Query.toArray()` (clauses first, joins after) and\n * keeps the streaming path from doing wasted work.\n */\n private readonly joins: readonly JoinLeg[]\n /**\n * Join resolution context. Required for `.join()` to translate a\n * field name into a target collection + ref mode and to resolve\n * the right-side `JoinableSource`. Optional because tests\n * construct ScanBuilder directly with synthetic page providers\n * that don't know about ref() — calling `.join()` without a\n * context throws with an actionable error.\n */\n private readonly joinContext: JoinContext | undefined\n\n constructor(\n pageProvider: ScanPageProvider<T>,\n pageSize: number = DEFAULT_SCAN_PAGE_SIZE,\n clauses: readonly Clause[] = [],\n joins: readonly JoinLeg[] = [],\n joinContext?: JoinContext,\n ) {\n this.pageProvider = pageProvider\n this.pageSize = pageSize\n this.clauses = clauses\n this.joins = joins\n this.joinContext = joinContext\n }\n\n /**\n * Add a field comparison. Runs per record as the scan stream\n * flows through, so non-matching records are dropped before they\n * reach `.aggregate()` or the iteration consumer. Multiple\n * `.where()` calls are AND-combined — same semantics as\n * `Query.where()`.\n *\n * Clauses cannot use the secondary-index fast path here because\n * the scan sources records from the adapter's paginator, not from\n * the in-memory cache where indexes live. Index-accelerated scans\n * are a future optimization — the current implementation\n * evaluates clauses per record in O(1) per clause.\n */\n where(field: string, op: Operator, value: unknown): ScanBuilder<T> {\n const clause: FieldClause = { type: 'field', field, op, value }\n return new ScanBuilder<T>(\n this.pageProvider,\n this.pageSize,\n [...this.clauses, clause],\n this.joins,\n this.joinContext,\n )\n }\n\n /**\n * Escape hatch: add an arbitrary predicate function. Same\n * non-serializable caveat as `Query.filter()` — filter clauses\n * don't round-trip through `toPlan()`. Prefer `.where()` when\n * possible.\n */\n filter(fn: (record: T) => boolean): ScanBuilder<T> {\n const clause: Clause = {\n type: 'filter',\n fn: fn as (record: unknown) => boolean,\n }\n return new ScanBuilder<T>(\n this.pageProvider,\n this.pageSize,\n [...this.clauses, clause],\n this.joins,\n this.joinContext,\n )\n }\n\n /**\n * Resolve a `ref()`-declared foreign key per record as the scan\n * stream flows, attaching the right-side record (or null) under\n * `opts.as`. — streaming joins over `scan()`.\n *\n * ```ts\n * for await (const inv of invoices.scan().join('clientId', { as: 'client' })) {\n * await processInvoice(inv) // inv.client is attached\n * }\n *\n * // Or terminate with .aggregate() for streaming joined aggregation\n * const { total } = await invoices.scan()\n * .where('status', '==', 'open')\n * .join('clientId', { as: 'client' })\n * .aggregate({ total: sum('amount') })\n * ```\n *\n * **The key difference from eager `.join()`:** the LEFT\n * side streams page-by-page from the adapter and is never\n * materialized. Memory ceiling on the left is O(pageSize), not\n * O(rowCount). This is what makes streaming joins suitable for\n * collections that exceed the eager join's 50_000-row ceiling.\n *\n * **Right-side strategy** is auto-selected per leg:\n * - **Indexed** — right source exposes `lookupById`, so each\n * left row costs O(1). This is the common path for\n * Collection right sides, which back `lookupById` with a Map\n * lookup over the in-memory cache. The right collection must\n * be in eager mode (the same constraint as eager join's\n * `querySourceForJoin` from ).\n * - **Hash** — right source has only `snapshot()`. Build a\n * `Map<id, record>` once at iteration start, probe per left\n * row. Same correctness, same per-row cost as the indexed\n * path; the difference is the upfront cost of materializing\n * the right side once.\n *\n * Both strategies hold the right side in memory for the duration\n * of the iteration. The \"streaming\" property applies to the LEFT\n * side only — true left-and-right streaming joins (where neither\n * side fits in memory) require a sort-merge join planner that's\n * out of scope for.\n *\n * **Ref-mode semantics** match eager `.join()` exactly:\n * - `strict` → throws `DanglingReferenceError` mid-stream\n * when a left record points at a non-existent right id.\n * The throw aborts the async iterator — consumers should\n * wrap the `for await` in try/catch if they want to recover.\n * - `warn` → attaches `null` and emits a one-shot warning\n * per unique dangling pair (deduped via the same warn\n * channel as eager join).\n * - `cascade` → attaches `null` silently. A delete-time mode;\n * dangling refs at read time are mid-flight or pre-existing\n * orphans, not a DSL error.\n *\n * Left records with null/undefined FK values attach `null`\n * regardless of mode — same \"no reference at all\" policy as\n * eager join and write-time `enforceRefsOnPut`.\n *\n * **Multi-FK chaining** is supported via repeated `.join()`\n * calls: each leg resolves an independent ref. Each leg\n * independently picks its right-side strategy and applies its\n * own ref mode.\n *\n * **Joins are NOT applied** to a `.aggregate()` terminal that\n * doesn't reference joined fields — wait, that's not quite\n * right. The streaming path actually DOES apply joins before\n * `.aggregate()` because the join attaches a field that the\n * spec might reference. Unlike `Query.aggregate()` (which skips\n * joins entirely as a projection-only short-circuit), the\n * streaming aggregation can't know whether the spec touches a\n * joined field, so it always applies joins. Consumers who want\n * unjoined streaming aggregation should leave `.join()` off the\n * chain — the chain is composable for a reason.\n *\n * constraint #1 — every JoinLeg carries `partitionScope:\n * 'all'` plumbed through but never read by. Same seam as\n * eager join.\n */\n join<As extends string, R = unknown>(\n field: string,\n opts: { as: As },\n ): ScanBuilder<T & Record<As, R | null>> {\n if (!this.joinContext) {\n throw new Error(\n `ScanBuilder.join() requires a join context. Use ` +\n `collection.scan() to construct a join-capable scan instead ` +\n `of the ScanBuilder constructor directly (the direct ` +\n `constructor is only used for tests with synthetic page ` +\n `providers).`,\n )\n }\n const descriptor = this.joinContext.resolveRef(field)\n if (!descriptor) {\n throw new Error(\n `ScanBuilder.join(): no ref() declared for field \"${field}\" on ` +\n `collection \"${this.joinContext.leftCollection}\". Add ` +\n `refs: { ${field}: ref('<target-collection>') } to the ` +\n `collection options, then retry.`,\n )\n }\n const leg: JoinLeg = {\n field,\n as: opts.as,\n target: descriptor.target,\n mode: descriptor.mode,\n strategy: undefined,\n maxRows: undefined,\n // constraint #1 — always 'all' in, never read by\n // the streaming executor. partition-aware scan joins\n // will populate this from where() predicates without\n // changing the planner shape.\n partitionScope: 'all',\n }\n return new ScanBuilder<T & Record<As, R | null>>(\n this.pageProvider as unknown as ScanPageProvider<T & Record<As, R | null>>,\n this.pageSize,\n this.clauses,\n [...this.joins, leg],\n this.joinContext,\n )\n }\n\n /**\n * Iterate the scan as an async iterable. Walks the page\n * provider's cursors forward until exhaustion, applying every\n * clause per record — only matching records are yielded.\n *\n * Backward-compatible with the previous async-generator `scan()`\n * return type for `for await … of` consumers.\n */\n async *[Symbol.asyncIterator](): AsyncIterator<T> {\n // One-time setup: resolve every join leg's right-side source\n // and pick its strategy (lookupById per row vs hash from\n // snapshot once). Both are O(left) per record after setup; the\n // difference is the upfront cost of hashing the right side\n // when there's no lookupById.\n //\n // Hash maps live for the lifetime of the iteration, so memory\n // for the right side is O(rightRowCount) per leg. Memory for\n // the left side stays O(pageSize) regardless — that's the\n // streaming property we're after.\n const joinResolvers = this.joins.length === 0 ? null : this.buildJoinResolvers()\n\n let page = await this.pageProvider.listPage({ limit: this.pageSize })\n while (true) {\n for (const record of page.items) {\n if (!this.recordMatches(record)) continue\n if (joinResolvers === null) {\n yield record\n } else {\n // Apply every join leg in declaration order. Each\n // leg attaches a field — the result of one leg becomes\n // the input to the next. Multi-FK chaining is\n // supported by construction.\n let attached: unknown = record\n for (const resolver of joinResolvers) {\n attached = this.applyOneJoinStreaming(attached, resolver)\n }\n yield attached as T\n }\n }\n if (page.nextCursor === null) return\n page = await this.pageProvider.listPage({\n cursor: page.nextCursor,\n limit: this.pageSize,\n })\n }\n }\n\n /**\n * Per-leg right-side resolution state. Built once at iteration\n * start and reused for every left record. Two strategies:\n *\n * - `lookupById`: present when the right source exposes the\n * hook directly (typical Collection right side). Per-row\n * cost is O(1).\n * - `hashByPrimaryKey`: built from `snapshot()` when no\n * lookupById. Per-row cost is O(1) after the upfront O(N)\n * materialization. Same as eager join's hash strategy.\n *\n * `warnedKeys` is the per-leg dedup set for ref-mode 'warn'. We\n * key on `field→target:refId` so the same dangling pair only\n * warns once per iteration. The dedup is per-iteration, not\n * per-process — a long-running scan that re-iterates would warn\n * again, which is the desired behavior (the data may have\n * changed between iterations).\n */\n private buildJoinResolvers(): Array<{\n leg: JoinLeg\n source: JoinableSource\n lookupById: ((id: string) => unknown) | null\n hashByPrimaryKey: ReadonlyMap<string, unknown> | null\n warnedKeys: Set<string>\n }> {\n if (!this.joinContext) {\n // Unreachable — .join() throws if joinContext is missing.\n // Belt-and-braces because the iterator is invoked via\n // Symbol.asyncIterator on a builder that may have been\n // constructed via the direct constructor with pre-populated\n // joins.\n throw new Error(\n `ScanBuilder iterator: ${this.joins.length} join leg(s) ` +\n `present but no JoinContext attached. Use collection.scan() ` +\n `to construct a join-capable scan.`,\n )\n }\n const resolvers: Array<{\n leg: JoinLeg\n source: JoinableSource\n lookupById: ((id: string) => unknown) | null\n hashByPrimaryKey: ReadonlyMap<string, unknown> | null\n warnedKeys: Set<string>\n }> = []\n for (const leg of this.joins) {\n const source = this.joinContext.resolveSource(leg.target)\n if (!source) {\n throw new Error(\n `ScanBuilder.join() cannot resolve target collection ` +\n `\"${leg.target}\" (referenced from field \"${leg.field}\" on ` +\n `\"${this.joinContext.leftCollection}\"). Make sure the target ` +\n `collection has been opened via vault.collection() ` +\n `at least once before iterating the scan.`,\n )\n }\n // Strategy selection: prefer lookupById when available\n // (O(1) per row, no upfront cost), fall back to hashing\n // snapshot() once otherwise.\n let lookupById: ((id: string) => unknown) | null = null\n let hashByPrimaryKey: ReadonlyMap<string, unknown> | null = null\n if (source.lookupById) {\n // Bind through an arrow so the lookupById's `this`\n // doesn't drift — same pattern as the eager join's\n // strategy resolver.\n const fn = source.lookupById.bind(source)\n lookupById = (id: string): unknown => fn(id)\n } else {\n const map = new Map<string, unknown>()\n for (const record of source.snapshot()) {\n const rawId = readPath(record, 'id')\n const key = coerceRefKey(rawId)\n if (key !== null) map.set(key, record)\n }\n hashByPrimaryKey = map\n }\n resolvers.push({\n leg,\n source,\n lookupById,\n hashByPrimaryKey,\n warnedKeys: new Set<string>(),\n })\n }\n return resolvers\n }\n\n /**\n * Resolve a single join leg for one left record and return the\n * left record with the joined field attached under\n * `leg.as`. Pure function over `(left, resolver)`; never\n * mutates the input.\n *\n * Ref-mode dispatch matches eager `applyJoins` from :\n * - null/undefined FK → attach null silently (always allowed)\n * - dangling FK + strict → throw `DanglingReferenceError`\n * - dangling FK + warn → attach null, warn-once per pair\n * - dangling FK + cascade → attach null silently\n */\n private applyOneJoinStreaming(\n left: unknown,\n resolver: {\n leg: JoinLeg\n source: JoinableSource\n lookupById: ((id: string) => unknown) | null\n hashByPrimaryKey: ReadonlyMap<string, unknown> | null\n warnedKeys: Set<string>\n },\n ): unknown {\n if (left === null || typeof left !== 'object') {\n // Pathological input; matches eager join's defensive return.\n return left\n }\n const { leg } = resolver\n const rawId = readPath(left, leg.field)\n const refKey = coerceRefKey(rawId)\n let right: unknown = undefined\n if (refKey !== null) {\n if (resolver.lookupById !== null) {\n right = resolver.lookupById(refKey)\n } else if (resolver.hashByPrimaryKey !== null) {\n right = resolver.hashByPrimaryKey.get(refKey)\n }\n }\n\n const merged: Record<string, unknown> = {\n ...(left as Record<string, unknown>),\n }\n if (right === undefined) {\n // No matching record. Distinguish \"no ref at all\" (null FK)\n // from \"dangling ref\" (FK pointed at nothing).\n if (refKey !== null && leg.mode === 'strict') {\n throw new DanglingReferenceError({\n field: leg.field,\n target: leg.target,\n refId: refKey,\n message:\n `ScanBuilder.join() strict dangling: record references ` +\n `\"${leg.target}:${refKey}\" via field \"${leg.field}\", but no ` +\n `such record exists. Use ref() mode 'warn' or 'cascade' if ` +\n `dangling refs are acceptable, or run ` +\n `vault.checkIntegrity() to find and fix the orphans.`,\n })\n }\n if (refKey !== null && leg.mode === 'warn') {\n const dedupKey = `${leg.field}→${leg.target}:${refKey}`\n if (!resolver.warnedKeys.has(dedupKey)) {\n resolver.warnedKeys.add(dedupKey)\n console.warn(\n `[noy-db] ScanBuilder.join() encountered dangling ref in ` +\n `'warn' mode: field \"${leg.field}\" → \"${leg.target}:` +\n `${refKey}\" not found. Attaching null.`,\n )\n }\n }\n // strict already threw above; warn falls through here; cascade\n // hits this path silently.\n merged[leg.as] = null\n } else {\n merged[leg.as] = right\n }\n return merged\n }\n\n /**\n * Reduce the scan stream through a named set of reducers and\n * return the final aggregated shape.\n *\n * Memory is O(reducers): one mutable state slot per spec key.\n * Records flow through the pipeline one at a time via\n * `for await` and are discarded after their `step()` is applied\n * — never collected into an array. This is the distinguishing\n * property from `Query.aggregate()`, which materializes the full\n * match set first.\n *\n * Reuses the same reducer protocol as `Query.aggregate()`,\n * so `count()`, `sum(field)`, `avg(field)`, `min(field)`,\n * `max(field)` all work unchanged. The `{ seed }` parameter\n * plumbing from constraint #2 is honored transparently — the\n * factories ignore it in and the scan executor never\n * touches the per-reducer state construction.\n *\n * **Returns a Promise**, unlike `Query.aggregate().run()` which\n * is synchronous. The scan is inherently async because it walks\n * adapter pages, so the terminal has to be too. Consumers\n * destructure with await:\n *\n * ```ts\n * const { total, n } = await invoices.scan()\n * .where('year', '==', 2025)\n * .aggregate({ total: sum('amount'), n: count() })\n * ```\n *\n * **No `.live()` in.** `scan().aggregate().live()` would\n * require reconciling an unbounded streaming iteration with a\n * change-stream subscription — a design problem, not just a code\n * one. Consumers with huge collections and live needs should\n * narrow with `.where()` enough to fit in the 50k `query()`\n * limit and use `query().aggregate().live()` instead.\n */\n async aggregate<Spec extends AggregateSpec>(\n spec: Spec,\n ): Promise<AggregateResult<Spec>> {\n const keys = Object.keys(spec)\n // Per-reducer state. Exactly |keys| entries, never grows with\n // the record count — that's the O(reducers) memory guarantee.\n const state: Record<string, unknown> = {}\n for (const key of keys) {\n state[key] = spec[key]!.init()\n }\n\n // Record-by-record streaming step. `for await (… of this)`\n // invokes the Symbol.asyncIterator above, which honors the\n // clause list, so filtered-out records never reach the step\n // loop — they're dropped at the iterator boundary.\n for await (const record of this) {\n for (const key of keys) {\n state[key] = spec[key]!.step(state[key], record)\n }\n }\n\n const result: Record<string, unknown> = {}\n for (const key of keys) {\n result[key] = spec[key]!.finalize(state[key])\n }\n return result as AggregateResult<Spec>\n }\n\n /**\n * Evaluate the clause list against a single record. Linear in\n * the clause count; short-circuits on first false. Clauses on a\n * scan are always re-evaluated per record — no index-accelerated\n * path, because the stream sources records from the adapter\n * paginator, not from the in-memory cache where indexes live.\n */\n private recordMatches(record: T): boolean {\n if (this.clauses.length === 0) return true\n for (const clause of this.clauses) {\n if (!evaluateClause(record, clause)) return false\n }\n return true\n }\n}\n\n/**\n * Coerce an unknown FK value into a lookup key string.\n *\n * Mirror of the same helper in `query/join.ts` — kept local to\n * `scan-builder.ts` to avoid pulling the eager join executor's\n * surface area into this file. Strings and numbers convert to\n * string keys; everything else (objects, arrays, booleans, null,\n * undefined) returns null and is treated as \"no ref at all\".\n *\n * Matches the write-time `enforceRefsOnPut` policy: nullish ref\n * values are never dangling, regardless of mode.\n */\nfunction coerceRefKey(value: unknown): string | null {\n if (value === null || value === undefined) return null\n if (typeof value === 'string') return value\n if (typeof value === 'number' || typeof value === 'bigint') return String(value)\n return null\n}\n","/**\n * Query DSL — barrel export.\n *\n * Public API:\n * - `Query<T>` — chainable, immutable builder\n * - `QuerySource<T>` — interface for record sources (Collection implements it)\n * - `Operator` — comparison operators accepted by `where()`\n * - `QueryPlan<T>` — serializable plan object\n *\n * Tree-shakeable: importing this barrel without calling `query()` does not\n * pull in the executor's runtime, since `Query` is the only entry point.\n */\n\nexport { Query, executePlan } from './builder.js'\nexport type { QueryPlan, QuerySource, OrderBy } from './builder.js'\nexport type { Operator, Clause, FieldClause, FilterClause, GroupClause } from './predicate.js'\nexport { evaluateClause, evaluateFieldClause, readPath } from './predicate.js'\n// Indexing relocated to `../indexing/` as part of the capability-\n// subpath refactor. Re-export from the new home for backward compat\n// with consumers reaching into `@noy-db/hub/query`; `@noy-db/hub/indexing`\n// is now the preferred import path.\nexport { CollectionIndexes } from '../indexing/eager-indexes.js'\nexport type { IndexDef, HashIndex } from '../indexing/eager-indexes.js'\nexport { applyJoins, DEFAULT_JOIN_MAX_ROWS, resetJoinWarnings } from './join.js'\nexport type { JoinLeg, JoinContext, JoinableSource, JoinStrategy } from './join.js'\nexport { buildLiveQuery } from './live.js'\nexport type { LiveQuery, LiveUpstream } from './live.js'\nexport { count, sum, avg, min, max } from '../aggregate/reducers.js'\nexport type { Reducer, ReducerOptions } from '../aggregate/reducers.js'\nexport { Aggregation, reduceRecords, buildLiveAggregation } from '../aggregate/aggregation.js'\nexport type {\n AggregateSpec,\n AggregateResult,\n AggregationUpstream,\n LiveAggregation,\n} from '../aggregate/aggregation.js'\nexport {\n GroupedQuery,\n GroupedQueryN,\n GroupedAggregation,\n groupAndReduce,\n resetGroupByWarnings,\n GROUPBY_WARN_CARDINALITY,\n GROUPBY_MAX_CARDINALITY,\n} from '../aggregate/groupby.js'\nexport type { GroupedRow, GroupedRowN } from '../aggregate/groupby.js'\nexport { ScanBuilder } from './scan-builder.js'\nexport type { ScanPageProvider } from './scan-builder.js'\n\n// Re-export note: QueryPlan, Clause, FilterClause, GroupClause are intentionally\n// non-parametric — their `T` was removed for variance reasons. The Query<T> type\n// at the public API surface still flows the record type through generic methods.\n\n// ─── Query / aggregate errors ────────────────────────\n// Re-exported from the central errors module so subpath consumers can\n// `instanceof JoinTooLargeError` without falling back to the root barrel.\nexport {\n GroupCardinalityError,\n IndexRequiredError,\n IndexWriteFailureError,\n JoinTooLargeError,\n DanglingReferenceError,\n} from '../errors.js'\n","/**\n * Persistent, encrypted secondary indexes for lazy-mode collections.\n *\n * Parallel to the in-memory `CollectionIndexes` used by eager mode (see\n * `packages/hub/src/query/indexes.ts`): same logical surface, but entries\n * are materialised as encrypted side-car records (`_idx/<field>/<recordId>`)\n * and bulk-loaded into an in-memory mirror on first query.\n *\n * This module only owns the id-namespace convention, the in-memory mirror,\n * and the typed errors. Write-path integration (PR 2 / ), query-planner\n * dispatch (PR 3 / , PR 4 / ), and the rebuild/reconcile utilities\n * (PR 5 / ) live in other files.\n *\n * See the design spec for the full architecture + threat model.\n */\n\n/**\n * Reserved id prefix for encrypted index side-car records.\n * Matches the existing `_keyring`, `_ledger_deltas/…`, `_meta/handle`\n * conventions inside a collection's id namespace.\n */\nexport const IDX_PREFIX = '_idx/' as const\n\n/**\n * Encode the side-car record id for a (field, recordId) pair.\n *\n * Format: `_idx/<field>/<recordId>` — no escaping. Field names may contain\n * dots (for dotted-path access consistent with eager-mode `readPath`);\n * record ids may contain slashes. The first two slash-separated segments\n * are `_idx` and the field; everything after the *second* slash is the\n * record id verbatim.\n */\nexport function encodeIdxId(field: string, recordId: string): string {\n return `${IDX_PREFIX}${field}/${recordId}`\n}\n\n/**\n * Decode a side-car id back into `{ field, recordId }`, or `null` if the\n * input is not a well-formed idx id. A well-formed id is:\n * - prefixed with `_idx/`\n * - contains a field segment (non-empty, no slashes)\n * - contains a record-id segment (non-empty, may contain slashes)\n */\nexport function decodeIdxId(id: string): { field: string; recordId: string } | null {\n if (!id.startsWith(IDX_PREFIX)) return null\n const rest = id.slice(IDX_PREFIX.length)\n const firstSlash = rest.indexOf('/')\n if (firstSlash <= 0) return null\n const field = rest.slice(0, firstSlash)\n const recordId = rest.slice(firstSlash + 1)\n if (recordId.length === 0) return null\n return { field, recordId }\n}\n\n/**\n * Fast-path predicate for discriminating side-car ids from regular record\n * ids and other reserved namespaces. Used by the hub to filter `list()`\n * results during bulk-load of the in-memory mirror.\n */\nexport function isIdxId(id: string): boolean {\n return decodeIdxId(id) !== null\n}\n\n/**\n * Sorted-value entry returned by `orderedBy()`. Mirrors the body shape\n * used by the write path — but `orderedBy` emits them already sorted by\n * `value` in the requested direction. Consumers (PR 4 / ) treat the\n * array as immutable and paginate via a numeric offset.\n *\n * **Note on `value`:** as of, this is the ORIGINAL TYPED\n * value (number, Date, boolean, etc.), not the stringified bucket key.\n * That's what lets range predicates and `orderedBy` compare numerically\n * instead of stumbling into `'10' < '2'` on `String(n)`.\n */\nexport interface OrderedEntry {\n readonly recordId: string\n readonly value: unknown\n}\n\n/**\n * Bulk-load row shape accepted by `ingest()`. The `value` field is the\n * decrypted index body's `value` field verbatim.\n */\nexport interface IngestRow {\n readonly recordId: string\n readonly value: unknown\n}\n\n/**\n * In-memory mirror of the persisted index side-car records for a single\n * collection. Populated by bulk-loading `_idx/<field>/*` ids on first\n * query and maintained incrementally by `Collection.put()` / `.delete()`\n * via `upsert()` / `remove()`.\n *\n * API surface is deliberately parallel to `CollectionIndexes` (eager mode)\n * so the query planner in PR 3/4 can dispatch to either polymorphically.\n *\n * Lifecycle:\n * - `declare(field)` — accept the field as indexable (idempotent)\n * - `ingest(field, rows[])` — bulk-load from decrypted index bodies\n * - `upsert(recordId, field, newValue, previousValue)` — incremental update\n * - `remove(recordId, field, value)` — incremental remove\n * - `lookupEqual(field, value)` / `lookupIn(field, values)` — equality reads\n * - `orderedBy(field, dir)` — sorted iteration for orderBy\n * - `clear()` — drop all buckets (invalidation / rotation)\n */\n/**\n * Per-field storage: the equality bucket map AND a parallel table of typed\n * values keyed by recordId. The typed table exists so range predicates\n * and `orderedBy` can compare on the original typed value rather\n * than the stringified bucket key — String(10) < String(2) is the classic\n * landmine `stringifyKey` introduces for numeric fields.\n */\ninterface PersistedFieldState {\n readonly buckets: Map<string, Set<string>>\n readonly values: Map<string, unknown>\n}\n\n/**\n * Structured index definition. Single-field indexes carry just a field\n * name; composite indexes carry the ordered list of fields and\n * the synthetic `key` (= fields joined by `COMPOSITE_DELIMITER`) used\n * as the bucket-map key and side-car envelope id segment.\n */\nexport type PersistedIndexDef =\n | { readonly kind: 'single'; readonly field: string; readonly key: string }\n | { readonly kind: 'composite'; readonly fields: readonly string[]; readonly key: string }\n\n/**\n * Delimiter used to synthesize a composite-index key from an ordered\n * field list. Intentionally a character that is extremely unusual in\n * JavaScript object keys (`|`) so collision with a literal field name\n * is vanishingly rare in practice. Composite declarations whose field\n * names contain `|` are rejected at declare-time with an explicit\n * error.\n */\nexport const COMPOSITE_DELIMITER = '|'\n\nexport function compositeKey(fields: readonly string[]): string {\n return fields.join(COMPOSITE_DELIMITER)\n}\n\nexport class PersistedCollectionIndex {\n private readonly indexes = new Map<string, PersistedFieldState>()\n private readonly defs = new Map<string, PersistedIndexDef>()\n\n /**\n * Declare a single-field index. Subsequent `upsert` / `ingest` calls\n * populate the in-memory mirror; calls before `declare` are no-ops\n * (tolerant bulk-load ordering). Idempotent.\n */\n declare(field: string): void {\n if (this.indexes.has(field)) return\n this.indexes.set(field, { buckets: new Map(), values: new Map() })\n this.defs.set(field, { kind: 'single', field, key: field })\n }\n\n /**\n * Declare a composite (multi-field) index. The synthetic\n * key is `fields.join('|')`; it doubles as the in-memory map key and\n * the `_idx/<key>/<recordId>` side-car field segment. Callers upsert\n * and lookup via the same `key` as single-field indexes, just with a\n * tuple value (JSON-stringified for bucketing).\n */\n declareComposite(fields: readonly string[]): void {\n if (fields.length === 0) {\n throw new Error('declareComposite: fields array must be non-empty')\n }\n for (const f of fields) {\n if (f.includes(COMPOSITE_DELIMITER)) {\n throw new Error(\n `declareComposite: field \"${f}\" contains the composite delimiter ` +\n `\"${COMPOSITE_DELIMITER}\" — pick a different field name or open an ` +\n `issue to add hash-based composite keys.`,\n )\n }\n }\n const key = compositeKey(fields)\n if (this.indexes.has(key)) return\n this.indexes.set(key, { buckets: new Map(), values: new Map() })\n this.defs.set(key, { kind: 'composite', fields: [...fields], key })\n }\n\n /**\n * Every declared index's structured definition. Collection walks this\n * when materialising side-cars on put/delete so it can extract a\n * single-field value or a composite tuple appropriately.\n */\n definitions(): PersistedIndexDef[] {\n return [...this.defs.values()]\n }\n\n /** True if `field` has been declared as indexable on this mirror. */\n has(field: string): boolean {\n return this.indexes.has(field)\n }\n\n /** All declared field names, in declaration order. */\n fields(): string[] {\n return [...this.indexes.keys()]\n }\n\n /**\n * Bulk-load the mirror from decrypted index bodies. Intended to be\n * called once per field after reading the collection's `_idx/<field>/*`\n * side-cars. Safe to call twice with the same rows — bucket Sets\n * deduplicate recordIds. If `field` is not declared, this is a no-op\n * (tolerates the case where bulk-load runs before `declare()` lands).\n */\n ingest(field: string, rows: readonly IngestRow[]): void {\n const state = this.indexes.get(field)\n if (!state) return\n for (const row of rows) {\n addToState(state, row.recordId, row.value)\n }\n }\n\n /**\n * Incrementally update a record's index entry for one field. Called by\n * `Collection.put()` after the main write succeeds. If\n * `previousValue` is non-null, the record is removed from the old\n * bucket first — this is the update path. Pass `null` for fresh adds.\n * No-op if the field is not declared.\n */\n upsert(recordId: string, field: string, newValue: unknown, previousValue: unknown): void {\n const state = this.indexes.get(field)\n if (!state) return\n if (previousValue !== null && previousValue !== undefined) {\n removeFromState(state, recordId, previousValue)\n }\n addToState(state, recordId, newValue)\n }\n\n /**\n * Remove a record from the index for one field. Called by\n * `Collection.delete()`. No-op if the field is not declared or\n * the record isn't in the bucket. Empty buckets are dropped to keep\n * the Map clean.\n */\n remove(recordId: string, field: string, value: unknown): void {\n const state = this.indexes.get(field)\n if (!state) return\n removeFromState(state, recordId, value)\n }\n\n /**\n * Drop all bucket data while preserving field declarations. Called on\n * invalidation (incoming sync changes, keyring rotation) — the next\n * query re-populates via `ingest`.\n */\n clear(): void {\n for (const state of this.indexes.values()) {\n state.buckets.clear()\n state.values.clear()\n }\n }\n\n /**\n * Equality lookup — return the set of record ids whose `field` matches\n * `value`. Returns `null` if the field is not declared (caller falls\n * back to scan or throws `IndexRequiredError`). Returns a shared empty\n * set if the field is declared but no record matches — that set MUST\n * NOT be mutated by the caller.\n */\n lookupEqual(field: string, value: unknown): ReadonlySet<string> | null {\n const state = this.indexes.get(field)\n if (!state) return null\n const key = stringifyKey(value)\n return state.buckets.get(key) ?? EMPTY_SET\n }\n\n /**\n * Set lookup — return the union of record ids whose `field` matches any\n * of `values`. Returns `null` if the field is not declared. Returns a\n * fresh (non-shared) Set — safe for the caller to mutate.\n */\n lookupIn(field: string, values: readonly unknown[]): ReadonlySet<string> | null {\n const state = this.indexes.get(field)\n if (!state) return null\n const out = new Set<string>()\n for (const value of values) {\n const bucket = state.buckets.get(stringifyKey(value))\n if (bucket) for (const id of bucket) out.add(id)\n }\n return out\n }\n\n /**\n * Range lookup. Return record ids whose indexed value\n * satisfies the predicate. Comparison happens on the ORIGINAL TYPED\n * value carried in `state.values` — so numeric `<` sorts numerically,\n * not lexicographically on `String(n)`. Returns `null` if the field\n * is not declared.\n *\n * Supported ops: `'<'`, `'<='`, `'>'`, `'>='`, `'between'`. For\n * `'between'`, `value` is `[lo, hi]` and both bounds are inclusive\n * (matches the eager-mode operator contract in `predicate.ts`).\n */\n lookupRange(\n field: string,\n op: '<' | '<=' | '>' | '>=' | 'between',\n value: unknown,\n ): ReadonlySet<string> | null {\n const state = this.indexes.get(field)\n if (!state) return null\n const out = new Set<string>()\n for (const [recordId, live] of state.values) {\n if (live === undefined || live === null) continue\n if (matchesRange(live, op, value)) out.add(recordId)\n }\n return out\n }\n\n /**\n * Sorted iteration — return every entry on `field` as an\n * `OrderedEntry[]`, sorted by the ORIGINAL TYPED value (#275: no more\n * `'10' < '2'` surprises on numeric fields). Consumers paginate with\n * a numeric offset. `OrderedEntry.value` is the typed value.\n */\n orderedBy(field: string, dir: 'asc' | 'desc'): readonly OrderedEntry[] | null {\n const state = this.indexes.get(field)\n if (!state) return null\n const entries: OrderedEntry[] = []\n for (const [recordId, value] of state.values) {\n entries.push({ recordId, value })\n }\n entries.sort((a, b) => compareTyped(a.value, b.value))\n if (dir === 'desc') entries.reverse()\n return entries\n }\n}\n\nconst EMPTY_SET: ReadonlySet<string> = new Set()\n\n/**\n * Canonicalize a value into a bucket key. Deliberately identical to the\n * eager-mode `stringifyKey` in `query/indexes.ts` so semantics match. When\n * `query/indexes.ts` changes its coercion rules, update this in lockstep.\n *\n * null / undefined values are NOT indexed — callers who pass them to\n * `upsert` / `remove` short-circuit before reaching this function; the\n * sentinel here exists only to make `lookupEqual(field, null)` return\n * an empty bucket (rather than matching some arbitrary record).\n */\nfunction stringifyKey(value: unknown): string {\n if (value === null || value === undefined) return '\\0NULL\\0'\n if (typeof value === 'string') return value\n if (typeof value === 'number' || typeof value === 'boolean') return String(value)\n if (value instanceof Date) return value.toISOString()\n // composite index values are tuple arrays. JSON.stringify\n // gives a delimiter-safe, order-preserving canonical form so buckets\n // for `['c-A', '2026-Q1']` and `['c-A', '2026-Q2']` never collide.\n if (Array.isArray(value)) {\n const parts: string[] = []\n for (const el of value) parts.push(stringifyKey(el))\n return JSON.stringify(parts)\n }\n return '\\0OBJECT\\0'\n}\n\nfunction addToState(state: PersistedFieldState, recordId: string, value: unknown): void {\n if (value === null || value === undefined) return\n const key = stringifyKey(value)\n let bucket = state.buckets.get(key)\n if (!bucket) {\n bucket = new Set()\n state.buckets.set(key, bucket)\n }\n bucket.add(recordId)\n state.values.set(recordId, value)\n}\n\nfunction removeFromState(state: PersistedFieldState, recordId: string, value: unknown): void {\n if (value === null || value === undefined) return\n const key = stringifyKey(value)\n const bucket = state.buckets.get(key)\n if (bucket) {\n bucket.delete(recordId)\n if (bucket.size === 0) state.buckets.delete(key)\n }\n state.values.delete(recordId)\n}\n\n/**\n * Range-predicate comparator. Runs on the ORIGINAL TYPED value so numeric\n * fields sort numerically (not lexicographically on `String(n)`). ISO-8601\n * date strings already sort correctly lexicographically; Date instances\n * compare via `getTime()` before the string branch to keep the contract\n * honest regardless of which form survived serialization.\n */\nfunction matchesRange(\n live: unknown,\n op: '<' | '<=' | '>' | '>=' | 'between',\n bound: unknown,\n): boolean {\n if (op === 'between') {\n if (!Array.isArray(bound) || bound.length !== 2) return false\n return compareTyped(live, bound[0]) >= 0 && compareTyped(live, bound[1]) <= 0\n }\n const cmp = compareTyped(live, bound)\n switch (op) {\n case '<': return cmp < 0\n case '<=': return cmp <= 0\n case '>': return cmp > 0\n case '>=': return cmp >= 0\n }\n}\n\nfunction compareTyped(a: unknown, b: unknown): number {\n if (a === undefined || a === null) return b === undefined || b === null ? 0 : 1\n if (b === undefined || b === null) return -1\n if (typeof a === 'number' && typeof b === 'number') return a - b\n if (a instanceof Date && b instanceof Date) return a.getTime() - b.getTime()\n if (typeof a === 'string' && typeof b === 'string') return a < b ? -1 : a > b ? 1 : 0\n if (typeof a === 'boolean' && typeof b === 'boolean') {\n return a === b ? 0 : a ? 1 : -1\n }\n // Mixed/unsupported types: deliberately treat as equal so sort stays\n // stable. Matches the eager-mode `compareValues` contract in\n // builder.ts — we don't silently coerce arbitrary objects to strings\n // (which would be meaningless) nor throw (which would be hostile).\n return 0\n}\n","/**\n * Lazy-mode query builder.\n *\n * Companion to `Query<T>` in `builder.ts`, but built for collections in lazy\n * mode where `snapshot()` is unavailable — records live in the adapter and\n * are pulled on demand. Dispatches through `PersistedCollectionIndex` to\n * resolve a candidate record-id set, then decrypts only those records.\n *\n * Scope:\n * - `.where(field, '==' | 'in', value)` — dispatched through the index\n * - `.where(field, other-op, value)` — evaluated against the decrypted\n * candidate set (non-indexed ops still require the field to be indexed\n * — we need SOMETHING to scope the candidate set)\n * - `.orderBy(field, dir?)` — dispatched through `orderedBy` when no\n * `==`/`in` clause is present; otherwise applied as an in-memory sort\n * over the candidate set\n * - `.limit(n)` / `.offset(n)` — page slice after filtering\n * - `.toArray()` / `.first()` / `.count()` — terminals\n *\n * Every field referenced by a where or orderBy clause MUST be indexed;\n * otherwise `toArray()` throws `IndexRequiredError`. This is deliberate:\n * silent scan-fallback would hide the very performance cliff that lazy-mode\n * indexes exist to prevent (see `docs/architecture.md` §indexes).\n */\n\nimport type { Clause, FieldClause, Operator } from '../query/predicate.js'\nimport { evaluateClause, readPath } from '../query/predicate.js'\nimport type { PersistedCollectionIndex } from './persisted-indexes.js'\nimport { IndexRequiredError } from '../errors.js'\n\nexport interface LazyOrderBy {\n readonly field: string\n readonly direction: 'asc' | 'desc'\n}\n\n/**\n * Source abstraction the LazyQuery runs against. Collection implements it.\n * Kept minimal so the builder stays test-friendly.\n */\nexport interface LazyQuerySource<T> {\n readonly collectionName: string\n readonly persistedIndexes: PersistedCollectionIndex\n /** Ensure `_idx/<field>/*` side-cars have been bulk-loaded into the mirror. */\n ensurePersistedIndexesLoaded(): Promise<void>\n /** Decrypt one record by id, or return null if it's gone. */\n getRecord(id: string): Promise<T | null>\n}\n\ninterface LazyPlan {\n readonly clauses: readonly FieldClause[]\n readonly orderBy: readonly LazyOrderBy[]\n readonly limit: number | undefined\n readonly offset: number\n}\n\nconst EMPTY_PLAN: LazyPlan = {\n clauses: [],\n orderBy: [],\n limit: undefined,\n offset: 0,\n}\n\nexport class LazyQuery<T> {\n private readonly source: LazyQuerySource<T>\n private readonly plan: LazyPlan\n\n constructor(source: LazyQuerySource<T>, plan: LazyPlan = EMPTY_PLAN) {\n this.source = source\n this.plan = plan\n }\n\n where<V>(field: string, op: Operator, value: V): LazyQuery<T> {\n const clause: FieldClause = { type: 'field', field, op, value }\n return new LazyQuery<T>(this.source, {\n ...this.plan,\n clauses: [...this.plan.clauses, clause],\n })\n }\n\n orderBy(field: string, direction: 'asc' | 'desc' = 'asc'): LazyQuery<T> {\n return new LazyQuery<T>(this.source, {\n ...this.plan,\n orderBy: [...this.plan.orderBy, { field, direction }],\n })\n }\n\n limit(n: number): LazyQuery<T> {\n return new LazyQuery<T>(this.source, { ...this.plan, limit: n })\n }\n\n offset(n: number): LazyQuery<T> {\n return new LazyQuery<T>(this.source, { ...this.plan, offset: n })\n }\n\n async toArray(): Promise<T[]> {\n await this.source.ensurePersistedIndexesLoaded()\n\n const touchedFields = collectTouchedFields(this.plan)\n const missingFields = touchedFields.filter(f => !isFieldIndexed(f, this.source.persistedIndexes))\n if (missingFields.length > 0) {\n throw new IndexRequiredError({\n collection: this.source.collectionName,\n touchedFields,\n missingFields,\n })\n }\n\n const candidateIds = this.resolveCandidateIds()\n if (candidateIds === null) {\n // No usable driver — every touched field is indexed but no clause\n // pins the candidate set. This happens when a query only uses\n // operators other than `==`/`in` and no `orderBy` clause is\n // present — we refuse to enumerate the whole index, because that\n // defeats the purpose of lazy mode.\n throw new IndexRequiredError({\n collection: this.source.collectionName,\n touchedFields,\n missingFields: touchedFields,\n })\n }\n\n const records: T[] = []\n for (const id of candidateIds) {\n const record = await this.source.getRecord(id)\n if (record === null) continue\n if (!matchesAll(record, this.plan.clauses)) continue\n records.push(record)\n }\n\n const sorted = this.plan.orderBy.length > 0\n ? sortRecords(records, this.plan.orderBy)\n : records\n\n const offset = this.plan.offset > 0 ? this.plan.offset : 0\n const limited = this.plan.limit === undefined\n ? sorted.slice(offset)\n : sorted.slice(offset, offset + this.plan.limit)\n\n return limited\n }\n\n async first(): Promise<T | null> {\n const out = await this.limit(1).toArray()\n return out.length > 0 ? out[0]! : null\n }\n\n async count(): Promise<number> {\n const out = await this.toArray()\n return out.length\n }\n\n /**\n * Resolve the candidate record-id set to decrypt. Returns null when the\n * query has no usable driver — no `==`/`in` clause and no `orderBy`\n * clause that can scope the scan. Callers interpret null as\n * IndexRequiredError (see `toArray`).\n */\n private resolveCandidateIds(): readonly string[] | null {\n const idx = this.source.persistedIndexes\n\n // prefer a composite index when the query's `==`\n // clauses cover every field of one declared composite. The\n // composite mirror lookup is O(matches) vs single-field +\n // post-filter on the decrypted candidate set.\n const eqMap = new Map<string, unknown>()\n for (const clause of this.plan.clauses) {\n if (clause.op === '==') eqMap.set(clause.field, clause.value)\n }\n if (eqMap.size >= 2) {\n for (const def of idx.definitions()) {\n if (def.kind !== 'composite') continue\n if (def.fields.every(f => eqMap.has(f))) {\n const tuple = def.fields.map(f => eqMap.get(f))\n const ids = idx.lookupEqual(def.key, tuple)\n if (ids) return [...ids]\n }\n }\n }\n\n for (const clause of this.plan.clauses) {\n if (clause.op === '==') {\n const ids = idx.lookupEqual(clause.field, clause.value)\n if (ids) return [...ids]\n } else if (clause.op === 'in' && Array.isArray(clause.value)) {\n const ids = idx.lookupIn(clause.field, clause.value as readonly unknown[])\n if (ids) return [...ids]\n } else if (isRangeOp(clause.op)) {\n // range predicates on an indexed field dispatch\n // through `lookupRange`, which compares on the original typed\n // value (no numeric-lexicographic landmines).\n const ids = idx.lookupRange(clause.field, clause.op, clause.value)\n if (ids) return [...ids]\n }\n }\n\n // No equality/range driver — try to scope via orderBy.\n if (this.plan.orderBy.length > 0) {\n const primary = this.plan.orderBy[0]!\n const entries = idx.orderedBy(primary.field, primary.direction)\n if (entries) return entries.map(e => e.recordId)\n }\n\n return null\n }\n}\n\n/**\n * True if the given field name is covered by either a single-field\n * index or appears as a component of a declared composite index.\n * Composite coverage is sufficient for the missing-field check because\n * composite writes also maintain the in-memory mirror — the range /\n * orderBy / single-equality lookup paths fall through to decrypted\n * candidates that still get post-filtered by the composite clause.\n */\nfunction isFieldIndexed(field: string, idx: PersistedCollectionIndex): boolean {\n if (idx.has(field)) return true\n for (const def of idx.definitions()) {\n if (def.kind === 'composite' && def.fields.includes(field)) return true\n }\n return false\n}\n\nfunction isRangeOp(op: Operator): op is '<' | '<=' | '>' | '>=' | 'between' {\n return op === '<' || op === '<=' || op === '>' || op === '>=' || op === 'between'\n}\n\nfunction collectTouchedFields(plan: LazyPlan): string[] {\n const seen = new Set<string>()\n for (const c of plan.clauses) seen.add(c.field)\n for (const o of plan.orderBy) seen.add(o.field)\n return [...seen]\n}\n\nfunction matchesAll(record: unknown, clauses: readonly Clause[]): boolean {\n for (const c of clauses) {\n if (!evaluateClause(record, c)) return false\n }\n return true\n}\n\nfunction sortRecords<T>(records: T[], orderBy: readonly LazyOrderBy[]): T[] {\n return [...records].sort((a, b) => {\n for (const { field, direction } of orderBy) {\n const av = readPath(a, field)\n const bv = readPath(b, field)\n const cmp = compareValues(av, bv)\n if (cmp !== 0) return direction === 'asc' ? cmp : -cmp\n }\n return 0\n })\n}\n\nfunction compareValues(a: unknown, b: unknown): number {\n if (a === undefined || a === null) return b === undefined || b === null ? 0 : 1\n if (b === undefined || b === null) return -1\n if (typeof a === 'number' && typeof b === 'number') return a - b\n if (typeof a === 'string' && typeof b === 'string') return a < b ? -1 : a > b ? 1 : 0\n if (a instanceof Date && b instanceof Date) return a.getTime() - b.getTime()\n return 0\n}\n","/**\n * Strategy seam between core Collection and the optional indexing\n * subsystem. Core imports `IndexStrategy` and `IndexState` as\n * TYPE-ONLY symbols and `NO_INDEXING` as a tiny runtime stub.\n *\n * The heavy classes — `CollectionIndexes`, `PersistedCollectionIndex`,\n * `LazyQuery` — are only instantiated inside the `withIndexing()`\n * factory under `./active.ts`, which in turn is only reachable through\n * the `@noy-db/hub/indexing` subpath export. A consumer that never\n * imports the subpath ships none of those classes in their bundle\n * (ESM tree-shaking + hub's `\"sideEffects\": false`).\n *\n * @internal\n */\n\nimport type { CollectionIndexes, IndexDef } from './eager-indexes.js'\nimport type { PersistedCollectionIndex } from './persisted-indexes.js'\n\n/**\n * Per-collection container for whatever mirrors the active strategy\n * decided to materialize. Both accessors may return `null` — they do\n * for `NO_INDEXING`, and `getEagerIndexes` returns null in a\n * lazy-mode collection even when indexing is active (lazy uses the\n * persisted mirror instead).\n *\n * `isEnabled` is a cheap guard so collection code can short-circuit\n * the full indexing path without inspecting either mirror.\n *\n * @internal\n */\nexport interface IndexState {\n readonly isEnabled: boolean\n getEagerIndexes(): CollectionIndexes | null\n getPersistedIndexes(): PersistedCollectionIndex | null\n}\n\n/**\n * Factory that builds one `IndexState` per Collection. Called exactly\n * once inside each Collection constructor with the declared\n * `IndexDef[]` and the lazy-mode flag (so lazy collections get the\n * persisted mirror and eager collections get the in-memory one).\n *\n * @internal\n */\nexport interface IndexStrategy {\n createState(args: {\n readonly defs: readonly IndexDef[]\n readonly lazy: boolean\n }): IndexState\n}\n\n/**\n * No-indexing stub. Every Collection defaults to this; it returns a\n * cheap `IndexState` whose mirrors are both `null`. Collection code\n * null-checks both accessors and short-circuits, so no indexing code\n * path runs and the heavy classes never arrive in the bundle.\n *\n * @internal\n */\nexport const NO_INDEXING: IndexStrategy = {\n createState() {\n return DISABLED_STATE\n },\n}\n\nconst DISABLED_STATE: IndexState = {\n isEnabled: false,\n getEagerIndexes: () => null,\n getPersistedIndexes: () => null,\n}\n","/**\n * Generic LRU cache for `Collection`'s lazy hydration mode.\n *\n * Backed by a JavaScript `Map`, which preserves insertion order. Promotion\n * is implemented as `delete()` + `set()` — O(1) on `Map` since both\n * operations are constant-time. Eviction walks the iterator from the front\n * (least recently used) until both budgets are satisfied.\n *\n * ships in-memory only. The cache is never persisted; on collection\n * close every entry is dropped. Persisting cache state is a follow-up\n * once the access patterns from real consumers tell us whether it would\n * pay back the complexity.\n */\n\nexport interface LruEntry<V> {\n /** The cached value. */\n readonly value: V\n /**\n * Approximate decrypted byte size of the entry. Used by the byte-budget\n * eviction path. Callers compute this once at insert time and pass it\n * in — recomputing on every access would dominate the per-record cost.\n */\n readonly size: number\n}\n\nexport interface LruOptions {\n /** Maximum number of entries before eviction. Required if `maxBytes` is unset. */\n maxRecords?: number\n /** Maximum total bytes before eviction. Computed from per-entry `size`. */\n maxBytes?: number\n}\n\nexport interface LruStats {\n /** Total cache hits since construction (or `resetStats()`). */\n hits: number\n /** Total cache misses since construction (or `resetStats()`). */\n misses: number\n /** Total entries evicted since construction (or `resetStats()`). */\n evictions: number\n /** Current number of cached entries. */\n size: number\n /** Current sum of cached entry sizes (in bytes, approximate). */\n bytes: number\n}\n\n/**\n * O(1) LRU cache. Both `get()` and `set()` promote the touched entry to\n * the most-recently-used end. Eviction happens after every insert and\n * walks the front of the Map iterator dropping entries until both\n * budgets are satisfied.\n */\nexport class Lru<K, V> {\n private readonly entries = new Map<K, LruEntry<V>>()\n private readonly maxRecords: number | undefined\n private readonly maxBytes: number | undefined\n private currentBytes = 0\n private hits = 0\n private misses = 0\n private evictions = 0\n\n constructor(options: LruOptions) {\n if (options.maxRecords === undefined && options.maxBytes === undefined) {\n throw new Error('Lru: must specify maxRecords, maxBytes, or both')\n }\n this.maxRecords = options.maxRecords\n this.maxBytes = options.maxBytes\n }\n\n /**\n * Look up a key. Hits promote the entry to most-recently-used; misses\n * return undefined. Both update the running stats counters.\n */\n get(key: K): V | undefined {\n const entry = this.entries.get(key)\n if (!entry) {\n this.misses++\n return undefined\n }\n // Promote: re-insert moves the entry to the end of the iteration order.\n this.entries.delete(key)\n this.entries.set(key, entry)\n this.hits++\n return entry.value\n }\n\n /**\n * Insert or update a key. If the key already exists, its size is\n * accounted for and the entry is promoted to MRU. After insertion,\n * eviction runs to maintain both budgets.\n */\n set(key: K, value: V, size: number): void {\n const existing = this.entries.get(key)\n if (existing) {\n // Update path: subtract the old size before adding the new one.\n this.currentBytes -= existing.size\n this.entries.delete(key)\n }\n this.entries.set(key, { value, size })\n this.currentBytes += size\n this.evictUntilUnderBudget()\n }\n\n /**\n * Remove a key without affecting hit/miss stats. Used by `Collection.delete()`.\n * Returns true if the key was present.\n */\n remove(key: K): boolean {\n const existing = this.entries.get(key)\n if (!existing) return false\n this.currentBytes -= existing.size\n this.entries.delete(key)\n return true\n }\n\n /** True if the cache currently holds an entry for the given key. */\n has(key: K): boolean {\n return this.entries.has(key)\n }\n\n /**\n * Drop every entry. Stats counters survive — call `resetStats()` if you\n * want a clean slate. Used by `Collection.invalidate()` on key rotation.\n */\n clear(): void {\n this.entries.clear()\n this.currentBytes = 0\n }\n\n /** Reset hit/miss/eviction counters to zero. Does NOT touch entries. */\n resetStats(): void {\n this.hits = 0\n this.misses = 0\n this.evictions = 0\n }\n\n /** Snapshot of current cache statistics. Cheap — no copying. */\n stats(): LruStats {\n return {\n hits: this.hits,\n misses: this.misses,\n evictions: this.evictions,\n size: this.entries.size,\n bytes: this.currentBytes,\n }\n }\n\n /**\n * Iterate over all currently-cached values. Order is least-recently-used\n * first. Used by tests and devtools — production callers should use\n * `Collection.scan()` instead.\n */\n *values(): IterableIterator<V> {\n for (const entry of this.entries.values()) yield entry.value\n }\n\n /**\n * Walk the cache from the LRU end and drop entries until both budgets\n * are satisfied. Called after every `set()`. Single pass — entries are\n * never re-promoted during eviction.\n */\n private evictUntilUnderBudget(): void {\n while (this.overBudget()) {\n const oldest = this.entries.keys().next()\n if (oldest.done) return // empty cache; nothing more to evict\n const key = oldest.value\n const entry = this.entries.get(key)\n if (entry) this.currentBytes -= entry.size\n this.entries.delete(key)\n this.evictions++\n }\n }\n\n private overBudget(): boolean {\n if (this.maxRecords !== undefined && this.entries.size > this.maxRecords) return true\n if (this.maxBytes !== undefined && this.currentBytes > this.maxBytes) return true\n return false\n }\n}\n","/**\n * Cache policy helpers — parse human-friendly byte budgets into raw numbers.\n *\n * Accepted shapes (case-insensitive on suffix):\n * number — interpreted as raw bytes\n * '1024' — string of digits, raw bytes\n * '50KB' — kilobytes (×1024)\n * '50MB' — megabytes (×1024²)\n * '1GB' — gigabytes (×1024³)\n *\n * Decimals are accepted (`'1.5GB'` → 1610612736 bytes).\n *\n * Anything else throws — better to fail loud at construction time than\n * to silently treat a typo as 0 bytes (which would evict everything).\n */\n\nconst UNITS: Record<string, number> = {\n '': 1,\n 'B': 1,\n 'KB': 1024,\n 'MB': 1024 * 1024,\n 'GB': 1024 * 1024 * 1024,\n // 'TB' deliberately not supported — if you need it, you're not using NOYDB.\n}\n\n/** Parse a byte budget into a positive integer number of bytes. */\nexport function parseBytes(input: number | string): number {\n if (typeof input === 'number') {\n if (!Number.isFinite(input) || input <= 0) {\n throw new Error(`parseBytes: numeric input must be a positive finite number, got ${String(input)}`)\n }\n return Math.floor(input)\n }\n\n const trimmed = input.trim()\n if (trimmed === '') {\n throw new Error('parseBytes: empty string is not a valid byte budget')\n }\n\n // Accept either a bare number or a number followed by a unit suffix.\n // Regex: optional sign, digits with optional decimal, optional unit.\n const match = /^([0-9]+(?:\\.[0-9]+)?)\\s*([A-Za-z]*)$/.exec(trimmed)\n if (!match) {\n throw new Error(`parseBytes: invalid byte budget \"${input}\". Expected format: \"1024\", \"50KB\", \"50MB\", \"1GB\"`)\n }\n\n const value = parseFloat(match[1]!)\n const unit = (match[2] ?? '').toUpperCase()\n\n if (!(unit in UNITS)) {\n throw new Error(`parseBytes: unknown unit \"${match[2]}\" in \"${input}\". Supported: B, KB, MB, GB`)\n }\n\n const bytes = Math.floor(value * UNITS[unit]!)\n if (bytes <= 0) {\n throw new Error(`parseBytes: byte budget must be > 0, got ${bytes} from \"${input}\"`)\n }\n return bytes\n}\n\n/**\n * Estimate the in-memory byte size of a decrypted record.\n *\n * Uses `JSON.stringify().length` as a stand-in for actual heap usage.\n * It's a deliberate approximation: real V8 heap size includes pointer\n * overhead, hidden classes, and string interning that we can't measure\n * from JavaScript. The JSON length is a stable, monotonic proxy that\n * costs O(record size) per insert — fine when records are typically\n * < 1 KB and the cache eviction is the slow path anyway.\n *\n * Returns `0` (and the caller must treat it as 1 for accounting) if\n * stringification throws on circular references; this is documented\n * but in practice records always come from JSON-decoded envelopes.\n */\nexport function estimateRecordBytes(record: unknown): number {\n try {\n return JSON.stringify(record).length\n } catch {\n return 0\n }\n}\n","/**\n * Cache module — barrel export.\n *\n * Tree-shakeable: importing this barrel without setting `cache:` on a\n * Collection does not pull in the LRU runtime, since `Lru` is the only\n * entry point.\n */\n\nexport { Lru } from './lru.js'\nexport type { LruEntry, LruOptions, LruStats } from './lru.js'\nexport { parseBytes, estimateRecordBytes } from './policy.js'\n","/**\n * Strategy seam for the optional sync engine + presence subsystem.\n * Core imports `SyncStrategy` type-only + `NO_SYNC` stub; the real\n * `SyncEngine`, `SyncTransaction`, and `PresenceHandle` constructors\n * are only reachable via `withSync()` in `./sync-active.ts`.\n *\n * Solo apps that never configure `sync` and never call\n * `collection.presence()` ship none of the ~856 LOC behind this seam\n * (`sync.ts` + `sync-transaction.ts` + `presence.ts`).\n *\n * Note: `keyring.ts` (~746 LOC) stays in core because it's required\n * for any multi-user vault — even single-owner vaults use a keyring\n * to wrap the DEK. The team package's grant/revoke/magic-link/\n * delegation modules tree-shake naturally via direct named imports.\n *\n * Behavior under NO_SYNC:\n *\n * - **buildSyncEngine** — throws. Only fires when `createNoydb({ sync })`\n * passes a remote target.\n * - **buildSyncTransaction** — throws. Only fires when `db.transaction(vault)`\n * is called on a vault with sync configured.\n * - **buildPresence** — throws. Only fires when user code calls\n * `collection.presence()`.\n *\n * @internal\n */\n\nimport type {\n NoydbStore,\n ConflictStrategy,\n SyncTargetRole,\n} from '../types.js'\nimport type { NoydbEventEmitter } from '../events.js'\nimport type { SyncPolicy } from '../store/sync-policy.js'\nimport type { SyncEngine } from './sync.js'\nimport type { SyncTransaction } from './sync-transaction.js'\nimport type { PresenceHandle, PresenceHandleOpts } from './presence.js'\nimport type { Vault } from '../vault.js'\n\n/**\n * Options accepted by `SyncStrategy.buildSyncEngine`. Mirrors the\n * `SyncEngine` constructor verbatim — kept here so core code never\n * imports the sync module at runtime.\n *\n * @internal\n */\nexport interface BuildSyncEngineOptions {\n local: NoydbStore\n remote: NoydbStore\n vault: string\n strategy: ConflictStrategy\n emitter: NoydbEventEmitter\n syncPolicy?: SyncPolicy\n role?: SyncTargetRole\n label?: string\n}\n\n/**\n * @internal\n */\nexport interface SyncStrategy {\n buildSyncEngine(opts: BuildSyncEngineOptions): SyncEngine\n buildSyncTransaction(vault: Vault, engine: SyncEngine): SyncTransaction\n buildPresence<P>(opts: PresenceHandleOpts): PresenceHandle<P>\n}\n\nfunction notEnabled(op: string): Error {\n return new Error(\n `${op} requires the sync strategy. Import ` +\n '`{ withSync }` from \"@noy-db/hub/sync\" and pass it to ' +\n '`createNoydb({ syncStrategy: withSync() })`.',\n )\n}\n\n/**\n * No-sync stub. Every constructor throws with an actionable pointer\n * — there is no useful \"off\" mode for sync engine / presence /\n * sync-transaction; if the consumer reached one of these surfaces,\n * they intended to use it.\n *\n * @internal\n */\nexport const NO_SYNC: SyncStrategy = {\n buildSyncEngine() { throw notEnabled('SyncEngine') },\n buildSyncTransaction() { throw notEnabled('SyncTransaction') },\n buildPresence() { throw notEnabled('collection.presence()') },\n}\n","/**\n * Strategy seam between core Collection and the optional blob subsystem.\n *\n * Core imports `BlobStrategy` as a TYPE-ONLY symbol and `NO_BLOBS` as a\n * minimal runtime stub. Neither pulls in the heavy `BlobSet` / chunk /\n * MIME machinery — those only arrive when the consumer explicitly\n * imports `@noy-db/hub/blobs` (see `./index.ts` → `withBlobs()` factory).\n *\n * This file is intentionally tiny and free of side effects so the\n * bundler keeps it in the graph without dragging everything else in.\n *\n * @internal\n */\n\nimport type { BlobSet } from './blob-set.js'\nimport type { NoydbStore } from '../types.js'\n\n/**\n * Args forwarded by `Collection.blob(id)` to the active strategy's\n * `openSlot`. The strategy is responsible for returning a live\n * `BlobSet` bound to the given record.\n *\n * @internal\n */\nexport interface BlobStrategyOpenArgs {\n readonly store: NoydbStore\n readonly vault: string\n readonly collection: string\n readonly recordId: string\n readonly getDEK: (collectionName: string) => Promise<CryptoKey>\n readonly encrypted: boolean\n readonly userId: string\n}\n\n/**\n * The seam interface. `@internal` — do not build public APIs on this\n * shape; it can evolve freely until blobs are extracted into their\n * own package, at which point it will be promoted to public.\n *\n * @internal\n */\nexport interface BlobStrategy {\n openSlot(args: BlobStrategyOpenArgs): BlobSet\n}\n\n/**\n * Default strategy for collections that did not opt into blob storage.\n * Every operation surfaces an actionable error that points the caller\n * at the opt-in path.\n *\n * @internal\n */\nexport const NO_BLOBS: BlobStrategy = {\n openSlot() {\n throw new Error(\n 'Blob storage is not enabled on this Noydb instance. ' +\n 'Import `{ withBlobs }` from \"@noy-db/hub/blobs\" and pass `withBlobs()` to `createNoydb({ blobStrategy: withBlobs() })`.',\n )\n },\n}\n","/**\n * Multi-record atomic transactions.\n *\n * Lets an application stage writes across two or more collections (or\n * vaults) and commit them all-or-nothing.\n *\n * ```ts\n * await db.transaction(async (tx) => {\n * const inv = tx.vault('acme').collection<Invoice>('invoices')\n * const pay = tx.vault('acme').collection<Payment>('payments')\n * await inv.put(invoiceId, { ...invoice, status: 'paid' })\n * await pay.put(paymentId, { invoiceId, amount, paidAt })\n * })\n * // If the body throws before returning: nothing persisted.\n * // If the body returns: all puts committed; any CAS mismatch rolls\n * // the batch back and surfaces as ConflictError.\n * ```\n *\n * ## Atomicity semantics\n *\n * Ops are buffered during the body. On body-return the hub:\n *\n * 1. **Pre-flight** — re-reads every touched envelope and enforces\n * any caller-supplied `expectedVersion`. A mismatch throws\n * `ConflictError` with *no* writes performed.\n * 2. **Execute** — calls `Collection.put()` / `.delete()` for each\n * staged op in declaration order. History snapshots, ledger\n * appends, and change events fire as normal per op.\n * 3. **Unwind on failure** — if step 2 throws mid-batch, each\n * already-committed op is reverted via the raw store (restoring\n * the captured prior envelope, or deleting if none existed). The\n * ledger is NOT rewritten — audit history preserves the partial\n * commit and the revert.\n *\n * **Crash window.** Steps 2–3 are not a storage-layer transaction —\n * if the process dies between two executed ops, the on-disk state is\n * partial. True all-or-nothing atomicity requires a store that\n * implements `NoydbStore.tx()` (DynamoDB `TransactWriteItems`,\n * IndexedDB `readwrite` transaction, …). This executor declares\n * that future integration point via the `tx?()` method + the\n * `StoreCapabilities.txAtomic` bit, but does not yet delegate\n * to it — the cascade into `Fork · Stores` tracks the per-adapter\n * wire-up.\n *\n * ## Not covered\n *\n * - Cross-sync-peer atomicity. Transactions commit against the\n * primary store only; the sync engine pushes on its normal\n * schedule. For cross-peer two-phase commit use `SyncTransaction`\n * via `db.transaction(vaultName)`.\n * - Read-your-writes within the body. `tx.collection().get(id)`\n * returns the most-recently-staged value for that id when one\n * exists; if no staged op has touched the id, it reads the current\n * committed state. Version numbers returned by `get` reflect the\n * pre-transaction state (staged puts have no version yet).\n *\n * @module\n */\n\nimport type { Noydb } from '../noydb.js'\nimport type { Vault } from '../vault.js'\nimport type { Collection } from '../collection.js'\nimport type { EncryptedEnvelope } from '../types.js'\nimport {\n AmendmentForbiddenError,\n ConflictError,\n InvariantError,\n ValidationError,\n} from '../errors.js'\nimport type { GuardExecutor as GuardExecutorModule } from '../guards/executor.js'\nimport type { LedgerEntry } from '../history/ledger/entry.js'\n\n/** One op buffered inside a running `TxContext`. @internal */\nexport interface StagedOp {\n type: 'put' | 'delete'\n vaultName: string\n collectionName: string\n id: string\n record?: unknown\n expectedVersion?: number\n /**\n * Optional human-readable tag forwarded to the resulting ledger\n * entry's `reason` field (#1). Set by callers via\n * `tx.vault(v).collection(c).put(id, record, { reason })`.\n */\n reason?: string\n}\n\n/**\n * One executed op (main staged op or recursive side-effect like a\n * derivation output) paired with the envelope captured before the write.\n * `revertExecuted` walks this array in reverse on rollback.\n * @internal\n */\nexport interface ExecutedOp {\n op: StagedOp\n priorEnvelope: EncryptedEnvelope | null\n}\n\n/**\n * Options accepted by `db.transaction({ amendment, reason }, fn)`.\n * Only the amendment variant uses these — a plain `db.transaction(fn)`\n * never sees this shape.\n */\nexport interface AmendmentTxOptions {\n /** Opt into amendment mode. Required to be `true`. */\n readonly amendment: true\n /** Human-readable rationale recorded in the ledger entry. Required. */\n readonly reason: string\n}\n\n/**\n * Transaction handle passed to the user's body. Use\n * `tx.vault(name).collection<T>(name)` to get a per-collection\n * facade; its `put`/`delete`/`get` calls stage ops against the tx.\n */\nexport class TxContext {\n /** @internal */\n readonly _ops: StagedOp[] = []\n /**\n * @internal — write log built up in Phase 2. Each entry records the\n * envelope captured BEFORE the write so a mid-batch failure can\n * restore prior state via `revertExecuted`. Side-effect writes (e.g.\n * recursive derivation outputs fired inside `Collection.put`) are\n * appended here in execution order so they roll back alongside the\n * main staged ops (#133).\n */\n readonly _executed: ExecutedOp[] = []\n /** @internal */\n readonly _db: Noydb\n /**\n * @internal — true when this TxContext was opened in amendment\n * mode. Toggles the lazy-`beginAmendment` + role-check path on first\n * `tx.vault(name)` and unlocks the post-Phase-2 invariant + audit run.\n */\n readonly _amendment: boolean\n /** @internal — vaults that have already had `beginAmendment` called. */\n readonly _amendmentVaults = new Map<string, Vault>()\n\n /** @internal */\n constructor(db: Noydb, amendment = false) {\n this._db = db\n this._amendment = amendment\n }\n\n /** Scope subsequent `collection()` calls to the named vault. */\n vault(name: string): TxVault {\n const v = this._db.vault(name)\n if (this._amendment && !this._amendmentVaults.has(name)) {\n // Role check is per-vault. The task spec (\"only admin or owner\n // can open an amendment\") is implemented lazy-on-first-touch\n // because the role lives on the vault's keyring, and `tx.vault()`\n // is the first place we know which vault we're addressing. The\n // observable effect is identical to an eager check in the single-\n // vault case the tests exercise; multi-vault amendments check\n // each touched vault as they first appear.\n const role = v.role\n if (role !== 'admin' && role !== 'owner') {\n throw new AmendmentForbiddenError(v.userId, role)\n }\n // Amendments require an initialised guard registry — they\n // produce a structured invariant + change-set audit. A vault\n // opened without `guardStrategies` (or via the sync fallback\n // path) has a null registry and cannot run an amendment.\n const reg = v._getGuardRegistry()\n if (reg === null) {\n throw new ValidationError(\n `Vault \"${name}\": amendment mode requires at least one ` +\n `guardStrategy registered via createNoydb({ guardStrategies }). ` +\n `Open the vault with guardStrategies before calling ` +\n `db.transaction({ amendment: true }).`,\n )\n }\n reg.beginAmendment()\n this._amendmentVaults.set(name, v)\n }\n return new TxVault(this, v)\n }\n}\n\n/** Per-vault facade inside a running transaction. */\nexport class TxVault {\n /** @internal */\n readonly _ctx: TxContext\n /** @internal */\n readonly _vault: Vault\n\n /** @internal */\n constructor(ctx: TxContext, vault: Vault) {\n this._ctx = ctx\n this._vault = vault\n }\n\n /** Scope subsequent op calls to the named collection. */\n collection<T>(name: string): TxCollection<T> {\n const c = this._vault.collection<T>(name)\n return new TxCollection<T>(this._ctx, this._vault, c, name)\n }\n}\n\n/** Per-collection facade inside a running transaction. */\nexport class TxCollection<T> {\n /** @internal */\n readonly _ctx: TxContext\n /** @internal */\n readonly _vault: Vault\n /** @internal */\n readonly _coll: Collection<T>\n /** @internal */\n readonly _name: string\n\n /** @internal */\n constructor(ctx: TxContext, vault: Vault, coll: Collection<T>, name: string) {\n this._ctx = ctx\n this._vault = vault\n this._coll = coll\n this._name = name\n }\n\n /**\n * Read the current committed value, or the most-recently-staged\n * value from the same transaction if one exists.\n */\n async get(id: string): Promise<T | null> {\n for (let i = this._ctx._ops.length - 1; i >= 0; i--) {\n const op = this._ctx._ops[i]!\n if (\n op.vaultName === this._vault.name &&\n op.collectionName === this._name &&\n op.id === id\n ) {\n if (op.type === 'delete') return null\n return op.record as T\n }\n }\n return this._coll.get(id)\n }\n\n /**\n * Stage a put. Does not write until the transaction body returns.\n * Supply `{ expectedVersion }` to enforce optimistic concurrency\n * during the commit pre-flight.\n */\n put(id: string, record: T, options?: { expectedVersion?: number; reason?: string }): void {\n const op: StagedOp = {\n type: 'put',\n vaultName: this._vault.name,\n collectionName: this._name,\n id,\n record,\n }\n if (options?.expectedVersion !== undefined) op.expectedVersion = options.expectedVersion\n if (options?.reason !== undefined) op.reason = options.reason\n this._ctx._ops.push(op)\n }\n\n /**\n * Stage a delete. Does not write until the transaction body returns.\n * Supply `{ expectedVersion }` to enforce optimistic concurrency\n * during the commit pre-flight.\n */\n delete(id: string, options?: { expectedVersion?: number }): void {\n const op: StagedOp = {\n type: 'delete',\n vaultName: this._vault.name,\n collectionName: this._name,\n id,\n }\n if (options?.expectedVersion !== undefined) op.expectedVersion = options.expectedVersion\n this._ctx._ops.push(op)\n }\n}\n\n/**\n * Commit plan: pre-flight check + execution + revert plan.\n *\n * @internal — driven by `withTransactions()` (via `tx/active.ts`) for\n * user-facing `db.transaction(...)` calls and by the `amendment` path\n * in `noydb.ts`. `Collection.putManyAtomic` runs its own Phase 2 loop\n * but shares the `_activeTxContext` mechanism (and the `revertExecuted`\n * helper) so nested side-effect derivation writes get registered for\n * revert alongside the bulk-put source ops (#133).\n */\nexport async function runTransaction<T>(\n db: Noydb,\n fn: (tx: TxContext) => Promise<T> | T,\n options?: AmendmentTxOptions,\n): Promise<T> {\n // ─── Amendment-mode pre-flight ───────────────────────────────\n // `reason` is the only thing we can validate before the body runs;\n // the per-vault role check happens lazily on first `tx.vault(name)`\n // because we don't know which vaults the body will touch ahead of\n // time. Throwing here keeps the failure mode close to the call site\n // so the developer doesn't have to walk an async stack to find the\n // missing-reason mistake.\n if (options?.amendment) {\n if (typeof options.reason !== 'string' || options.reason.trim().length === 0) {\n throw new ValidationError(\n 'db.transaction({ amendment: true }) requires a non-empty `reason` string.',\n )\n }\n }\n\n const ctx = new TxContext(db, options?.amendment === true)\n const bodyResult = await fn(ctx)\n\n if (ctx._ops.length === 0) {\n // Body produced no ops. If amendment mode was active we still\n // need to close any opened windows so a subsequent (unrelated)\n // write doesn't surprise-collect into a stale change-set. Each\n // `beginAmendment` is matched by exactly one `consumeChanges`.\n if (ctx._amendment) {\n for (const v of ctx._amendmentVaults.values()) {\n // Registry is guaranteed non-null here — `tx.vault(name)`\n // threw above if it was null before adding to\n // `_amendmentVaults`.\n const reg = v._getGuardRegistry()\n if (reg !== null) {\n reg.consumeChanges()\n reg.consumeMeta()\n }\n }\n }\n return bodyResult\n }\n\n // Phase 1 — pre-flight: snapshot every touched envelope and enforce\n // any caller-supplied expectedVersion. Same (vault, coll, id) touched\n // more than once in one tx snapshots only the *initial* committed\n // state; the in-order replay in Phase 2 takes care of successor ops.\n const priorEnvelopes = new Map<string, EncryptedEnvelope | null>()\n const store = db._store\n for (const op of ctx._ops) {\n const key = keyOf(op)\n if (!priorEnvelopes.has(key)) {\n const env = await store.get(op.vaultName, op.collectionName, op.id)\n priorEnvelopes.set(key, env)\n }\n if (op.expectedVersion !== undefined) {\n const env = priorEnvelopes.get(key) ?? null\n const actual = env?._v ?? 0\n if (actual !== op.expectedVersion) {\n throw new ConflictError(\n actual,\n `Transaction pre-flight: ${op.vaultName}/${op.collectionName}/${op.id} ` +\n `expected v${op.expectedVersion}, found v${actual}`,\n )\n }\n }\n }\n\n // Phase 2 — execute via the Collection layer so history snapshots,\n // ledger entries, and change events fire normally. We capture each\n // successful op so a mid-batch throw can revert in Phase 3.\n //\n // `_activeTxContext` is published on the Noydb instance for the\n // duration of Phase 2 so recursive writes triggered inside\n // `Collection.put` (today: eager derivation outputs) can register\n // their own envelopes onto `ctx._executed` and roll back alongside\n // the main staged ops (#133). The `finally` clears it before the\n // amendment commit phase runs.\n db._setActiveTxContext(ctx)\n try {\n try {\n for (const op of ctx._ops) {\n const coll = db.vault(op.vaultName).collection(op.collectionName)\n const key = keyOf(op)\n const prior = priorEnvelopes.get(key) ?? null\n // Record the revert plan BEFORE the call so a mid-`coll.put` throw\n // (e.g. strict-mode derivation failure firing after `store.put`\n // has already committed the envelope) still has its source write\n // reverted. `revertExecuted` is best-effort: putting prior back is\n // idempotent when the failing op never actually wrote, and\n // `_invalidateCacheEntry` is a no-op when the collection isn't\n // hydrated.\n ctx._executed.push({ op, priorEnvelope: prior })\n if (op.type === 'put') {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n await coll.put(op.id, op.record as any, op.reason !== undefined ? { reason: op.reason } : undefined)\n } else {\n await coll.delete(op.id)\n }\n }\n } catch (err) {\n // Phase 3 — best-effort revert. See helper docstring.\n await revertExecuted(ctx._executed, store, db)\n // Drain amendment windows so the next transaction starts clean.\n if (ctx._amendment) {\n for (const v of ctx._amendmentVaults.values()) {\n const reg = v._getGuardRegistry()\n if (reg !== null) {\n reg.consumeChanges()\n reg.consumeMeta()\n }\n }\n }\n throw err\n }\n } finally {\n db._clearActiveTxContext(ctx)\n }\n\n // ─── Amendment commit phase (only if amendment === true) ────\n // Body succeeded — now run each touched vault's invariants over the\n // collected change-set, then append a structured ledger entry. If\n // any invariant throws, treat it exactly like a mid-Phase-2 failure:\n // revert every executed op and re-throw the InvariantError.\n if (ctx._amendment) {\n // Lazy-load GuardExecutor at the dispatch site — keeps the floor\n // bundle free of the guards subsystem when amendments aren't used.\n // Mirrors the deferred-load pattern from #130 elsewhere in this PR.\n const { GuardExecutor } = (await import('../guards/executor.js')) as {\n GuardExecutor: typeof GuardExecutorModule\n }\n try {\n for (const [vaultName, v] of ctx._amendmentVaults) {\n const registry = v._getGuardRegistry()\n // Registry is guaranteed non-null at this point — the\n // `tx.vault(name)` path that populates `_amendmentVaults`\n // throws if the registry is null. The defensive check here\n // is for TypeScript's narrowing.\n if (registry === null) continue\n const changesByCollection = registry.consumeChanges()\n const meta = registry.consumeMeta()\n if (changesByCollection.size === 0) continue\n\n const readOnlyVault = v._getReadOnlyFacade()\n if (readOnlyVault === null) continue\n\n // Build the invariant ctx once per vault — it's the same shape\n // every guard sees on the normal `check` path, just with a\n // synthetic `existing: null` (invariants get the full change\n // set in their first parameter; `existing` is a per-record\n // concept that doesn't apply here).\n const invariantsPassed: string[] = []\n for (const [collection, changes] of changesByCollection) {\n const guards = registry.guardsFor(collection).filter(g => g.amendment !== undefined)\n for (const guard of guards) {\n await GuardExecutor.runInvariant(guard, changes, {\n existing: null,\n vault: readOnlyVault,\n userId: v.userId,\n role: v.role,\n })\n }\n if (guards.length > 0) invariantsPassed.push(collection)\n }\n\n // Append the audit ledger entry. Silent no-op when the\n // history strategy isn't configured — the records still\n // committed, only the multi-record summary is unavailable.\n const ledger = v._getLedgerOrNull()\n if (ledger) {\n const role = v.role as 'admin' | 'owner'\n const amendment: NonNullable<LedgerEntry['amendment']> = {\n reason: options!.reason,\n role,\n changes: meta,\n invariantsPassed,\n }\n await ledger.append({\n op: 'amendment',\n collection: '',\n id: '',\n version: 0,\n actor: v.userId,\n // No payload to hash — the per-record entries already\n // captured `payloadHash` at their own append time. We use\n // a sha256 of the canonical reason string so the field is\n // populated with something deterministic and non-empty.\n payloadHash: '',\n amendment,\n })\n }\n void vaultName\n }\n } catch (err) {\n await revertExecuted(ctx._executed, store, db)\n throw err instanceof InvariantError ? err : new InvariantError(\n err instanceof Error ? err.message : `invariant violated: ${String(err)}`,\n )\n }\n }\n\n return bodyResult\n}\n\n/**\n * Phase 3 helper — restore captured prior envelopes via the raw store\n * to avoid re-firing Collection-level side effects (we don't want a\n * cascade of change events undoing themselves). The ledger is left\n * as-is: each committed op appended an entry; the revert is\n * deliberately NOT recorded as a compensating entry because the\n * caller-facing contract is \"atomic or not at all,\" not \"every write\n * visible in the audit trail.\" Auditors who need the intermediate\n * state can still reconstruct it by walking the ledger through the\n * failed-tx timestamp.\n *\n * @internal — shared between `runTransaction` and\n * `Collection.putManyAtomic`. Both register source ops + nested\n * derivation side-effect ops onto `_executed`; this helper unwinds the\n * combined list in reverse on rollback.\n */\nexport async function revertExecuted(\n executed: ReadonlyArray<ExecutedOp>,\n store: Noydb['_store'],\n db?: Noydb,\n): Promise<void> {\n for (const { op, priorEnvelope } of executed.slice().reverse()) {\n try {\n if (priorEnvelope) {\n await store.put(op.vaultName, op.collectionName, op.id, priorEnvelope)\n } else {\n await store.delete(op.vaultName, op.collectionName, op.id)\n }\n // Sync the Collection-layer cache with what we just wrote at\n // the raw store. Without this, eager-mode `get` would still\n // return the rolled-back record from its in-memory map. The\n // Collection's `_invalidateCacheEntry` is a no-op when the\n // collection hasn't yet been hydrated.\n if (db) {\n const coll = db.vault(op.vaultName).collection(op.collectionName)\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n await (coll as any)._invalidateCacheEntry(op.id)\n }\n } catch {\n // swallow — best-effort. Surfacing the revert error would mask\n // the original one that triggered the rollback.\n }\n }\n}\n\nfunction keyOf(op: StagedOp): string {\n return `${op.vaultName}\\x00${op.collectionName}\\x00${op.id}`\n}\n","import { DerivationCapExceededError, DerivationOutputShapeError } from '../errors.js'\nimport type { DerivationContext, DerivationStrategy, DerivedFromMeta } from './types.js'\n\nexport interface RunResult {\n outputs: Record<string, OutputResult>\n failed: boolean\n}\n\n/**\n * Per-output result of a strategy invocation. Discriminated by\n * `kind`:\n *\n * - `record` — the existing v1 shape: one value (or a \"skipped\"\n * marker if the output was optional and `derive` returned null).\n * - `array` — the #200 shape: a list of `(key, value)` entries.\n * The caller diffs these against the previously-emitted key set\n * (loaded from the fanout sidecar) to compute deletes + upserts.\n */\nexport type OutputResult =\n | RecordOutputResult\n | ArrayOutputResult\n | FailedOutputResult\n\nexport interface RecordOutputResult {\n kind: 'record'\n value: Record<string, unknown>\n ok: true\n /**\n * `true` when an optional output (#144) returned `null` /\n * `undefined`. The caller deletes any previously-emitted output at\n * the same id (mirrors \"tombstone for derived data\"); a never-emitted\n * output is a silent no-op. `ok: true` because skipping is a\n * successful outcome, not a failure.\n */\n skipped?: boolean\n}\n\nexport interface ArrayOutputResult {\n kind: 'array'\n ok: true\n /** One `(key, value)` per derived row. Empty array means \"all prior outputs for this source go.\" */\n entries: ReadonlyArray<{ readonly key: string; readonly value: Record<string, unknown> }>\n}\n\nexport interface FailedOutputResult {\n kind: 'failed'\n ok: false\n error: Error\n /** Always empty on failure; present so consumers don't have to narrow. */\n value: Record<string, unknown>\n}\n\n/**\n * Stateless functions that execute a derivation strategy. Persistence\n * (encrypt + store.put) is the caller's job — typically\n * `DerivationRegistry.onSourceWrite` which iterates run() results and\n * writes each output via `Collection.put`.\n */\nexport const DerivationExecutor = {\n /**\n * Run `derive` once, validate output shape against the spec, stamp\n * `_derivedFrom` onto every output. Returns per-output success or\n * failure; throws only for shape mismatches (a contract violation).\n */\n async run<\n TSource extends Record<string, unknown>,\n TOutputs extends Record<string, Record<string, unknown>>,\n >(\n strategy: DerivationStrategy<TSource, TOutputs>,\n source: TSource & { id: string },\n sourceVersion: number,\n strategyHash: string,\n ctx: DerivationContext,\n ): Promise<RunResult> {\n const outputs: Record<string, OutputResult> = {}\n let derived: Partial<TOutputs>\n\n try {\n derived = await Promise.resolve(strategy.derive(source as TSource, ctx))\n } catch (err) {\n for (const key of Object.keys(strategy.outputs)) {\n outputs[key] = {\n kind: 'failed',\n value: {},\n ok: false,\n error: err instanceof Error ? err : new Error(String(err)),\n }\n }\n return { outputs, failed: true }\n }\n\n const meta: DerivedFromMeta = {\n source: strategy.source,\n sourceId: source.id,\n sourceVersion,\n derivedAt: new Date().toISOString(),\n strategyHash,\n }\n\n for (const key of Object.keys(strategy.outputs)) {\n const outSpec = strategy.outputs[key]\n if (!outSpec) continue\n const value = (derived as Record<string, unknown>)[key]\n\n // ── Array-shape branch (#200 slice 1) ──────────────────────\n if (outSpec.shape === 'array') {\n if (value === undefined || value === null) {\n // Treat null/undefined as \"empty array\" — clears all prior\n // outputs for this (source, output) pair. The caller's\n // diff turns that into deletes.\n outputs[key] = { kind: 'array', ok: true, entries: [] }\n continue\n }\n if (!Array.isArray(value)) {\n throw new DerivationOutputShapeError(\n key,\n `shape 'array' expects an array, got ${typeof value}`,\n )\n }\n const maxFanout = outSpec.maxFanout ?? 64\n if (value.length > maxFanout) {\n throw new DerivationCapExceededError(key, value.length, maxFanout)\n }\n const entries: Array<{ key: string; value: Record<string, unknown> }> = []\n const seenKeys = new Set<string>()\n for (let i = 0; i < value.length; i++) {\n const row = value[i] as unknown\n if (row === null || typeof row !== 'object') {\n throw new DerivationOutputShapeError(\n key,\n `array member at index ${i} must be a non-null object (got ${row === null ? 'null' : typeof row})`,\n )\n }\n let derivedKey: string\n try {\n derivedKey = outSpec.key(row as Record<string, unknown>)\n } catch (err) {\n throw new DerivationOutputShapeError(\n key,\n `key extractor threw on array member at index ${i}: `\n + (err instanceof Error ? err.message : String(err)),\n )\n }\n if (typeof derivedKey !== 'string' || derivedKey.length === 0) {\n throw new DerivationOutputShapeError(\n key,\n `key extractor returned ${typeof derivedKey === 'string' ? 'empty string' : typeof derivedKey} at index ${i}; expected non-empty string`,\n )\n }\n if (seenKeys.has(derivedKey)) {\n throw new DerivationOutputShapeError(\n key,\n `duplicate key \"${derivedKey}\" in array output (index ${i}); each derived row must have a unique key within a single derive() invocation`,\n )\n }\n seenKeys.add(derivedKey)\n entries.push({\n key: derivedKey,\n value: { ...(row as Record<string, unknown>), _derivedFrom: meta },\n })\n }\n outputs[key] = { kind: 'array', ok: true, entries }\n continue\n }\n\n // ── Record-shape branch (existing v1 behavior) ─────────────\n if (value === undefined || value === null) {\n if (outSpec.optional === true) {\n // #144: optional output explicitly skipped. Mark for caller\n // so any prior-emitted output at this id can be deleted.\n outputs[key] = { kind: 'record', value: {}, ok: true, skipped: true }\n continue\n }\n throw new DerivationOutputShapeError(\n key,\n `expected object, got ${value === undefined ? 'undefined' : 'null'}`,\n )\n }\n if (typeof value !== 'object') {\n throw new DerivationOutputShapeError(\n key,\n `expected object, got ${typeof value}`,\n )\n }\n outputs[key] = {\n kind: 'record',\n value: { ...(value as Record<string, unknown>), _derivedFrom: meta },\n ok: true,\n }\n }\n return { outputs, failed: false }\n },\n}\n","import type { Collection } from '../collection.js'\nimport type { ReadOnlyVaultFacade } from '../guards/types.js'\nimport type { TxContext } from '../tx/transaction.js'\nimport type { DerivationRegistry } from './registry.js'\n// Type-only — runtime class loaded via dynamic import in\n// `resolveStaleOnRead` only when a stale flag actually fires. Keeps\n// the executor chunk out of the floor bundle (#130).\nimport type { DerivationExecutor as DerivationExecutorType } from './executor.js'\nimport type { DerivationStrategy } from './types.js'\n\n/**\n * Accessor shape passed through from the owning Vault. Provides the\n * registry (used to look up strategies and as the WeakMap key) and a\n * resolver from collection name to the live `Collection` instance.\n * Same shape as `Collection.derivationSource` so callers can pass it\n * through directly.\n */\nexport interface DerivationStaleAccessor {\n registry(): DerivationRegistry\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n getCollection(name: string): Collection<any>\n /**\n * Read-only vault facade handed to `derive(source, ctx)` on the lazy\n * resolve-on-read path. Same instance/shape as the eager path uses\n * (#147).\n */\n getReadOnlyFacade(): ReadOnlyVaultFacade\n /**\n * Active multi-record TxContext or `null`. The lazy resolve-on-read\n * path uses this to register tombstone deletes on `_executed` so a\n * later rollback restores the prior emission. Mirrors the eager\n * path's #133-style tracking; the lazy `put` was historically\n * unregistered but #144's tombstone delete (a NEW write path)\n * matches the eager registration for symmetry.\n */\n getActiveTxContext(): TxContext | null\n}\n\n/**\n * In-memory stale map keyed by `DerivationRegistry` instance (stable\n * per vault). Maps `${source}/${sourceId}` → set of pending strategies.\n *\n * Persistence across vault close is NOT implemented in v1 (concern\n * flagged in the dim14 spec). On vault re-open, derived records'\n * `_derivedFrom.strategyHash` will still match the registered\n * strategies' hash, so an unset stale flag is interpreted as \"fresh.\"\n * `vault.deriveAll()` (Task D13) is the explicit recompute escape\n * hatch.\n *\n * @internal\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nconst _staleByRegistry = new WeakMap<DerivationRegistry, Map<string, Set<DerivationStrategy<any, any>>>>()\n\nconst keyFor = (source: string, sourceId: string): string => `${source}/${sourceId}`\n\n/** Mark every output of (strategy, sourceId) as stale. */\nexport async function markStale(\n registry: DerivationRegistry,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n strategy: DerivationStrategy<any, any>,\n sourceId: string,\n): Promise<void> {\n let map = _staleByRegistry.get(registry)\n if (!map) {\n map = new Map()\n _staleByRegistry.set(registry, map)\n }\n const k = keyFor(strategy.source, sourceId)\n let set = map.get(k)\n if (!set) {\n set = new Set()\n map.set(k, set)\n }\n set.add(strategy)\n}\n\n/**\n * Called from `Collection.get` on lazy-mode output collections. If the\n * id has a pending stale flag for any strategy producing this output\n * collection, re-derive before returning the record. No-op when there\n * is no pending work — keeps the read fast path negligible.\n */\nexport async function resolveStaleOnRead(\n accessor: DerivationStaleAccessor,\n outputCollection: string,\n id: string,\n): Promise<void> {\n const registry = accessor.registry()\n const producers = registry.strategiesProducingOutput(outputCollection)\n if (producers.length === 0) return\n\n const map = _staleByRegistry.get(registry)\n if (!map) return\n\n // Dynamic-import the executor only when at least one stale flag\n // actually fires. Vaults with no derivation strategies never call\n // this function (gated on `derivationSource` in `Collection.get`);\n // vaults with strategies but no pending stale ids reach the\n // `pending.has(spec)` short-circuit below without ever touching\n // the executor chunk. See #130.\n let DerivationExecutor: typeof DerivationExecutorType | null = null\n\n for (const { spec, strategyHash } of producers) {\n const k = keyFor(spec.source, id)\n const pending = map.get(k)\n if (!pending || !pending.has(spec)) continue\n\n // Read the source record from the source collection and re-derive.\n // We use the same getCollection accessor that eager dispatch uses\n // — it returns the live `Collection<any>` instance with full\n // crypto / keyring wiring.\n const sourceColl = accessor.getCollection(spec.source)\n const source = await sourceColl.get(id)\n if (!source) {\n pending.delete(spec)\n continue\n }\n const sourceWithId = { ...(source as Record<string, unknown>), id } as Record<string, unknown> & { id: string }\n // sourceVersion: not tracked in v1 stale map; pass 0 — matches the\n // forthcoming v0 semantics, `_derivedFrom.sourceVersion` is\n // informational, not load-bearing for correctness.\n if (DerivationExecutor === null) {\n ({ DerivationExecutor } = (await import('./executor.js')) as { DerivationExecutor: typeof DerivationExecutorType })\n }\n const ctx = { vault: accessor.getReadOnlyFacade() }\n const result = await DerivationExecutor.run(spec, sourceWithId, 0, strategyHash, ctx)\n for (const key of Object.keys(spec.outputs)) {\n const out = result.outputs[key]\n if (!out) continue\n if (out.kind === 'failed') {\n const err = out.error\n if (spec.strict) {\n // Leave the stale flag set so a future read retries.\n throw err\n }\n console.warn(\n `[derivation] lazy output \"${key}\" for source \"${spec.source}\" id=\"${id}\" failed:`,\n err,\n )\n continue\n }\n if (out.kind === 'array') {\n // Defensive — array-shape requires `lifecycle: 'eager'`\n // (validated at withDerivation registration, #200 slice 1).\n // Reaching the lazy-resolve path for array-shape would mean\n // a registration check was bypassed. Log and skip.\n console.warn(\n `[derivation] unexpected array-shape output \"${key}\" in lazy resolve path; `\n + 'array-shape derivations require lifecycle: \"eager\" (#200 slice 1).',\n )\n continue\n }\n const outSpec = spec.outputs[key]\n if (!outSpec) continue\n const outputColl = accessor.getCollection(outSpec.collection)\n if (out.skipped === true) {\n // #144: optional output skipped on lazy resolve — delete any\n // prior emission so the read returns null (matches eager-path\n // tombstone semantics). Routed through `_internalDelete` so a\n // user-registered `onDelete` (#145) on the output collection\n // does NOT fire. The active TxContext (if any) is forwarded:\n // `resolveStaleOnRead` is reachable from `Collection.get()`\n // which can be called from inside a transaction, so the\n // tombstone must be observable to `revertExecuted` on\n // rollback. Closes the #133-asymmetry surfaced in PR #148\n // review.\n await outputColl._internalDelete(id, accessor.getActiveTxContext())\n continue\n }\n await outputColl.put(id, out.value)\n }\n pending.delete(spec)\n }\n}\n","import type { Query, QueryPlan } from '../query/builder.js'\nimport type { JoinContext } from '../query/join.js'\nimport type { MaterializedViewStrategy } from './types.js'\n\n/**\n * Walks a `Query<T>` plan and returns the set of source collection\n * names that any source-write should trigger a refresh on.\n *\n * Foundation sub-issue (#150) handles:\n * - root collection (the one the query was built from)\n * - FK join targets (`.join(field, { as })`)\n *\n * Deferred to later sub-issues:\n * - `.crossJoin()` — v3 cross-join spec (separate primitive)\n * - `.wherePredicate(name)` — v2 predicate primitive, sub-issue #153\n * - Overlay-name expansion to {base, overlay} — sub-issue #154\n *\n * The set is materialized at MV registration time. The MV registry\n * uses it to (a) dispatch `onSourceWrite` only to MVs that actually\n * care, and (b) contribute edges to the shared cycle-detection graph.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function analyzeDependencies(query: Query<any>): Set<string> {\n const deps = new Set<string>()\n const plan = query._plan()\n const ctx = query._joinContext()\n\n // The root collection is always a dependency.\n if (ctx?.leftCollection) {\n deps.add(ctx.leftCollection)\n }\n\n // FK join targets contribute additional sources.\n for (const leg of plan.joins) {\n deps.add(leg.target)\n }\n\n // Sub-plans inside OR clauses can carry nested joins. Walk them.\n // (Today only top-level `.join()` populates `plan.joins`, but the\n // OR-group machinery permits sub-plans, so we recurse defensively.)\n walkClausesForJoins(plan, deps, ctx)\n\n return deps\n}\n\nfunction walkClausesForJoins(\n plan: QueryPlan,\n deps: Set<string>,\n ctx: JoinContext | undefined,\n): void {\n void ctx\n // Today `plan.joins` carries all join legs at top level. Sub-plans\n // inside OR groups don't currently support nested joins, so the loop\n // below is a no-op safety net for future builder extensions.\n for (const clause of plan.clauses) {\n if (clause.type === 'group') {\n // Group clauses don't (yet) carry their own joins; this is a\n // forward-compat anchor for when OR-groups support nested\n // sources.\n }\n }\n}\n\n/**\n * Convenience: produce a stable string summary of the query plan\n * suitable for `queryHash` derivation. Captures everything the\n * dependency analyzer reads + the where/orderBy/limit/offset\n * structure that affects materialized rows.\n *\n * `joinContext` is intentionally NOT included — the join-resolution\n * function references would defeat hash determinism. The set of join\n * TARGETS (collection names) IS included via the plan.joins legs.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function summarizeQueryPlan(query: Query<any>): string {\n const plan = query._plan()\n const ctx = query._joinContext()\n return JSON.stringify({\n root: ctx?.leftCollection ?? null,\n clauses: plan.clauses,\n orderBy: plan.orderBy,\n limit: plan.limit ?? null,\n offset: plan.offset,\n joins: plan.joins.map(j => ({ field: j.field, as: j.as, target: j.target, mode: j.mode })),\n })\n}\n\n/**\n * Canonical string description of a UNION MV's plan, used as input to\n * `computeQueryHash`.\n *\n * Asymmetry note (#165 niwat review):\n * - Arm collection names are NOT sorted. Declaration order is\n * semantically meaningful for the dedup-only UNION path —\n * `materializeUnionResult` iterates `spec.unionSources` in\n * declaration order and keeps the first-seen row per composite key\n * (tie-break precedence). If we sorted arms here, a consumer who\n * reordered `unionSources` to change precedence would compute the\n * same `queryHash`, refresh would be a no-op, and stale MV rows\n * would persist. Hashing in declaration order makes any reorder\n * trigger a refresh.\n * - `groupBy` fields ARE sorted. Multi-key groupBy buckets are\n * commutative (`canonicalGroupKey` produces the same composite key\n * regardless of field order in the input spec).\n * - `aggregate` keys ARE sorted. Reducer-spec keys are independent\n * of each other — order of declaration doesn't change output.\n *\n * Per-arm `map` functions are NOT fingerprinted; consumers must bump\n * the MV's `name` (or rely on application-level cache busting) when\n * `map` semantics change non-equivalently.\n */\nexport function summarizeUnionPlan<T extends Record<string, unknown>>(\n spec: MaterializedViewStrategy<T>,\n): string {\n const arms = (spec.unionSources ?? [])\n .map(s => s.collection)\n .join(',')\n const groupBy: string = Array.isArray(spec.groupBy)\n ? [...spec.groupBy].sort().join(',')\n : typeof spec.groupBy === 'string'\n ? spec.groupBy\n : ''\n const aggKeys = spec.aggregate ? Object.keys(spec.aggregate).sort().join(',') : ''\n return `union(${arms})|groupBy(${groupBy})|aggregate(${aggKeys})`\n}\n","/**\n * Deterministic hash of a materialized view strategy's \"shape\": MV\n * name + canonical query-plan summary + sorted dependency-set.\n *\n * Used to detect strategy drift: a row whose `_materializedFrom.queryHash`\n * doesn't match the current strategy is considered stale.\n *\n * Web Crypto SHA-256 — no extra deps. Mirrors the v1\n * `computeStrategyHash` pattern.\n */\nexport async function computeQueryHash(\n mvName: string,\n /**\n * Source-collection set the query depends on. Sorted before\n * canonicalization so set iteration order doesn't affect the hash.\n */\n dependencies: ReadonlySet<string>,\n /**\n * Stringified query-plan summary. The caller produces this from the\n * `Query<T>` builder — concretely: a JSON serialization of clauses +\n * orderBy + limit + offset + joins. Function bodies inside\n * `wherePredicate` are NOT included here (those carry their own\n * `predicateHash` to be folded in by a later sub-issue).\n */\n queryPlanSummary: string,\n): Promise<string> {\n const canonical = JSON.stringify({\n mvName,\n dependencies: [...dependencies].sort(),\n queryPlanSummary,\n })\n const bytes = new TextEncoder().encode(canonical)\n const digest = await crypto.subtle.digest('SHA-256', bytes)\n return Array.from(new Uint8Array(digest))\n .map(b => b.toString(16).padStart(2, '0'))\n .join('')\n}\n\n/**\n * Canonicalize a query plan for hashing. Walks the plan structure\n * with sorted keys so insertion order doesn't perturb the result.\n * Lives here rather than in `query/builder.ts` to keep that module\n * stable across MV-specific evolutions.\n *\n * @internal exported for testing\n */\nexport function canonicalizeQueryPlan(plan: unknown): string {\n return JSON.stringify(plan, (_key, value) => {\n if (value && typeof value === 'object' && !Array.isArray(value)) {\n const sorted: Record<string, unknown> = {}\n for (const k of Object.keys(value as Record<string, unknown>).sort()) {\n sorted[k] = (value as Record<string, unknown>)[k]\n }\n return sorted\n }\n return value\n })\n}\n","import { MaterializedViewCycleError, MaterializedViewSourceUnknownError } from '../errors.js'\nimport type { DerivationRegistry } from '../derivations/registry.js'\nimport type { Clause, FieldClause } from '../query/predicate.js'\nimport type { DeclaredPredicate } from '../query/builder.js'\nimport { analyzeDependencies, summarizeQueryPlan, summarizeUnionPlan } from './dependency-analyzer.js'\nimport { computeQueryHash } from './query-hash.js'\nimport type { MaterializedViewStrategy, MVQueryContext } from './types.js'\n\n/**\n * One registered MV strategy alongside its derived metadata. Stored\n * type-erased on `TRow` so the registry can hold heterogeneous MVs.\n */\nexport interface RegisteredMV {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n readonly spec: MaterializedViewStrategy<any>\n /** Output collection name (`spec.output?.collection ?? spec.name`). */\n readonly outputCollection: string\n /** Set of source collections; populated at registration via the analyzer. */\n readonly dependencies: ReadonlySet<string>\n /** Canonical `queryHash` — `_materializedFrom.queryHash` for every emitted row. */\n readonly queryHash: string\n /**\n * Top-level FieldClauses on the partition field, captured at\n * registration time. Used by the cycle detector to resolve\n * same-collection-as-source edges via the partition-discriminator\n * check (#152). Empty when `spec.output?.partition` is undefined.\n */\n readonly partitionClauses: readonly FieldClause[]\n}\n\n/**\n * Vault-internal registry of MV strategies. Owned by `Vault`; not\n * exported. Parallel to v1's `DerivationRegistry`; the two graphs share\n * a single cycle-detection pass at vault open (see `validate`).\n *\n * @internal\n */\nexport class MaterializedViewRegistry {\n /** Keyed by `spec.name`. */\n private readonly _byName = new Map<string, RegisteredMV>()\n /** Keyed by dependency source-collection → MVs that depend on it. */\n private readonly _bySource = new Map<string, RegisteredMV[]>()\n\n /**\n * Register an MV. Invokes `spec.query()` once at registration time to\n * read the plan + join context; the resulting `Query<T>` is discarded\n * after dependency extraction. `vault.collection(...)` must therefore\n * be functional by the time this runs — typically wired from\n * `Vault._initMaterializedViews` after collection bootstrap.\n *\n * Throws `MaterializedViewSourceUnknownError` if the analyzer\n * surfaces a dependency the vault doesn't know about (when a\n * `knownCollections` checker is supplied).\n */\n async register(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n spec: MaterializedViewStrategy<any>,\n db: MVQueryContext,\n options?: { knownCollections?: (name: string) => boolean },\n ): Promise<void> {\n // Build a predicate-aware db wrapper (#153). If `spec.predicates` is\n // declared, the wrapper intercepts `.collection().query()` and\n // attaches the predicates map to the resulting Query<T>. With no\n // predicates declared, the wrapper is the original db unchanged.\n const dbForQuery = spec.predicates ? wrapDbWithPredicates(db, spec.predicates) : db\n\n // Invoke the query callback once to inspect its plan / dependencies.\n // For Query<T> shapes the analyzer extracts deps + plan summary\n // automatically. Aggregation / GroupedAggregation shapes don't\n // expose the underlying Query, so the spec must declare `sources`\n // explicitly. `partitionClauses` are only populated for Query<T>\n // since same-collection-partition is a non-aggregate concern.\n // UNION-form strategies (#165): dependencies and plan summary come\n // straight off the strategy — no `query` callback to introspect.\n // The dependency-analyzer + summarizer are bypassed entirely; the\n // executor handles materialization via `materializeUnionResult`.\n let dependencies: Set<string>\n let queryPlanSummary: string\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let qAny: any = null\n let isQuery = false\n if (spec.unionSources) {\n dependencies = new Set(spec.unionSources.map(s => s.collection))\n queryPlanSummary = summarizeUnionPlan(spec)\n } else {\n const q = spec.query!(dbForQuery)\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n qAny = q as any\n isQuery = typeof qAny._plan === 'function'\n if (isQuery) {\n dependencies = analyzeDependencies(q)\n queryPlanSummary = summarizeQueryPlan(q)\n // Fold `.wherePredicate(name, ctx)` references into the plan\n // summary so predicate function or ctx changes (signalled by\n // bumping `hash` or supplying a different ctx) propagate into\n // `queryHash` and force refresh on next visit.\n const predicateRefs = extractPredicateRefs(qAny._plan())\n if (predicateRefs.length > 0) {\n queryPlanSummary = JSON.stringify({ plan: queryPlanSummary, predicates: predicateRefs })\n }\n // If `sources` is ALSO declared, take the union (consumer's\n // explicit list extends the auto-analyzed set).\n if (spec.sources) for (const s of spec.sources) dependencies.add(s)\n } else {\n // Aggregate shape: require explicit `sources`.\n if (!spec.sources || spec.sources.length === 0) {\n throw new Error(\n `withMaterializedView \"${spec.name}\": query() returned an aggregate ` +\n `(Aggregation or GroupedAggregation) but no \\`sources\\` field is declared. ` +\n `The dependency analyzer cannot walk through groupBy().aggregate() ` +\n `back to the source — declare sources: [...] explicitly.`,\n )\n }\n dependencies = new Set(spec.sources)\n // Aggregate plans don't carry a chainable query plan for summary\n // purposes; the dep-set + spec.name serve as the queryHash inputs.\n queryPlanSummary = JSON.stringify({ aggregate: true, sources: [...spec.sources].sort() })\n }\n }\n\n // Sanity-check declared dependencies against the vault's known\n // collections. Optional — when the checker isn't supplied (test\n // wiring, in-process composition) the registration succeeds and\n // any typo surfaces at first onSourceWrite as a no-op.\n if (options?.knownCollections) {\n for (const dep of dependencies) {\n if (!options.knownCollections(dep)) {\n throw new MaterializedViewSourceUnknownError(spec.name, dep)\n }\n }\n }\n\n const outputCollection = spec.output?.collection ?? spec.name\n const queryHash = await computeQueryHash(spec.name, dependencies, queryPlanSummary)\n // For same-collection-as-source MVs, capture the where-clauses on\n // the partition field so cycle detection can prove disjointness.\n // Only applicable to Query<T> shapes — aggregate MVs don't carry\n // a chainable plan to inspect (and same-collection aggregation\n // doesn't make sense in the niwat use cases that motivated #152).\n const partitionClauses: FieldClause[] = []\n const partitionField = spec.output?.partition?.field\n if (partitionField !== undefined && isQuery) {\n const plan = qAny._plan()\n for (const clause of plan.clauses) {\n if (isFieldClauseOnField(clause, partitionField)) partitionClauses.push(clause)\n }\n }\n const reg: RegisteredMV = { spec, outputCollection, dependencies, queryHash, partitionClauses }\n\n this._byName.set(spec.name, reg)\n for (const dep of dependencies) {\n const arr = this._bySource.get(dep)\n if (arr) arr.push(reg)\n else this._bySource.set(dep, [reg])\n }\n }\n\n /** All MVs that depend on `source`, in registration order. */\n mvsForSource(source: string): ReadonlyArray<RegisteredMV> {\n return this._bySource.get(source) ?? []\n }\n\n /** Single MV by name, or `undefined`. */\n byName(name: string): RegisteredMV | undefined {\n return this._byName.get(name)\n }\n\n /** Iterate over every registered MV. */\n all(): ReadonlyArray<RegisteredMV> {\n return [...this._byName.values()]\n }\n\n /**\n * Cycle detection over the combined derivation + MV graph. Edges:\n * - Derivation: derivation.source → output.collection (each output)\n * - MV: every dep in MV.dependencies → MV.outputCollection\n *\n * Throws `MaterializedViewCycleError` if the cycle's terminal node\n * is an MV output collection; otherwise (a pure-derivation cycle)\n * the caller's `DerivationRegistry.validate()` will surface\n * `DerivationCycleError` separately at vault open.\n *\n * Call AFTER all `register()` calls complete.\n */\n validate(derivationRegistry?: DerivationRegistry | null): void {\n const visited = new Set<string>()\n const stack: string[] = []\n const mvOutputs = new Set<string>()\n for (const reg of this._byName.values()) mvOutputs.add(reg.outputCollection)\n\n const edges = new Map<string, string[]>()\n\n // MV edges: every dep → output. Same-collection edges (dep ===\n // outputCollection) are skipped IFF the MV declares an\n // `output.partition` discriminator AND the query has a where-clause\n // that provably excludes the partition value. Otherwise the cycle\n // detector treats the edge as real — naïve same-collection MVs\n // surface as `MaterializedViewCycleError`.\n for (const reg of this._byName.values()) {\n for (const dep of reg.dependencies) {\n if (dep === reg.outputCollection && partitionDisjoint(reg)) continue\n const arr = edges.get(dep)\n if (arr) arr.push(reg.outputCollection)\n else edges.set(dep, [reg.outputCollection])\n }\n }\n\n // Derivation edges: source → output collections\n if (derivationRegistry) {\n // The shared DerivationRegistry exposes its edges via the same\n // `strategiesForSource` API its own `validate()` uses. We don't\n // duplicate cycle detection — we add MV nodes to the graph and\n // run the unified DFS, attributing cycles that touch an MV\n // output to `MaterializedViewCycleError`.\n for (const reg of this._byName.values()) {\n // Walk every dependency through derivation edges too: a\n // derivation whose output we depend on is itself a source.\n void reg\n }\n // Pull derivation edges by scanning every MV dep + every MV\n // output as potential derivation sources.\n const sourcesToScan = new Set<string>()\n for (const reg of this._byName.values()) {\n for (const dep of reg.dependencies) sourcesToScan.add(dep)\n sourcesToScan.add(reg.outputCollection)\n }\n for (const src of sourcesToScan) {\n const strategies = derivationRegistry.strategiesForSource(src)\n if (strategies.length === 0) continue\n for (const s of strategies) {\n for (const key of Object.keys(s.spec.outputs)) {\n const o = s.spec.outputs[key]\n if (!o) continue\n const arr = edges.get(src)\n if (arr) arr.push(o.collection)\n else edges.set(src, [o.collection])\n }\n }\n }\n }\n\n const visit = (node: string): void => {\n if (stack.includes(node)) {\n const cycle = stack.slice(stack.indexOf(node)).concat(node)\n // If any node on the cycle is an MV output, attribute as MV\n // cycle. Otherwise let DerivationRegistry.validate() surface it.\n if (cycle.some(n => mvOutputs.has(n))) {\n throw new MaterializedViewCycleError(cycle)\n }\n // Pure-derivation cycle — caller's DerivationRegistry.validate()\n // will catch it separately. Don't double-report.\n return\n }\n if (visited.has(node)) return\n stack.push(node)\n const outs = edges.get(node)\n if (outs) for (const o of outs) visit(o)\n stack.pop()\n visited.add(node)\n }\n\n for (const node of edges.keys()) visit(node)\n }\n}\n\n/**\n * Type guard: is the clause a top-level `FieldClause` on the given\n * field? Used by the partition-disjoint check.\n *\n * @internal\n */\nfunction isFieldClauseOnField(clause: Clause, field: string): clause is FieldClause {\n return clause.type === 'field' && clause.field === field\n}\n\n/**\n * Wrap an `MVQueryContext` so its `.collection().query()` returns a\n * Query<T> with the MV's declared predicates attached. Bare Queries\n * (outside of any MV) don't gain `.wherePredicate()` — only Queries\n * obtained through this wrapped db do.\n *\n * @internal\n */\nexport function wrapDbWithPredicates(\n db: MVQueryContext,\n predicates: NonNullable<MaterializedViewStrategy<Record<string, unknown>>['predicates']>,\n): MVQueryContext {\n // Build the predicate map once — the fn signature in the MV spec\n // is row-typed but the QueryBuilder casts to unknown, so we widen\n // here for the Map.\n const map = new Map<string, DeclaredPredicate>()\n for (const [name, decl] of Object.entries(predicates)) {\n map.set(name, {\n hash: decl.hash,\n fn: decl.fn as (record: unknown, ctx?: unknown) => boolean,\n })\n }\n return {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n collection<T extends Record<string, unknown>>(name: string): any {\n const c = db.collection<T>(name)\n // Return an object that delegates everything to `c` but\n // overrides `.query()` to attach predicates via the new\n // `Query._withPredicates()` accessor.\n return new Proxy(c, {\n get(target, prop, receiver) {\n if (prop === 'query') {\n return (...args: unknown[]) => {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const q = (target.query as any)(...args)\n // For non-aggregate Query<T>, attach predicates. For\n // legacy predicate-arg overload that returns T[] (sync\n // filter), pass through unchanged.\n \n if (q && typeof q._withPredicates === 'function') {\n return q._withPredicates(map)\n }\n return q\n }\n }\n return Reflect.get(target, prop, receiver)\n },\n })\n },\n }\n}\n\n/**\n * Walk a QueryPlan's clauses and collect predicate-reference markers\n * for `queryHash` derivation. Returns a sorted array (deterministic\n * order) of `{ name, predicateHash, ctxHash }` tuples — these are the\n * hashable identity of each `.wherePredicate()` call site.\n *\n * @internal\n */\nfunction extractPredicateRefs(\n plan: { clauses: readonly Clause[] },\n): Array<{ name: string; predicateHash: string; ctxHash: string }> {\n const refs: Array<{ name: string; predicateHash: string; ctxHash: string }> = []\n const walk = (clauses: readonly Clause[]): void => {\n for (const c of clauses) {\n if (c.type === 'wherePredicate') {\n refs.push({ name: c.name, predicateHash: c.predicateHash, ctxHash: c.ctxHash })\n } else if (c.type === 'group') {\n walk(c.clauses)\n }\n }\n }\n walk(plan.clauses)\n // Stable-sort by (name, predicateHash, ctxHash) — same predicate\n // appearing twice with different ctx hashes both flow through.\n refs.sort((a, b) => {\n if (a.name !== b.name) return a.name < b.name ? -1 : 1\n if (a.predicateHash !== b.predicateHash) return a.predicateHash < b.predicateHash ? -1 : 1\n return a.ctxHash < b.ctxHash ? -1 : a.ctxHash > b.ctxHash ? 1 : 0\n })\n return refs\n}\n\n/**\n * Provability check for the same-collection partition-discriminator\n * (#152, spec § Same-collection-as-source MV). Returns `true` when\n * the captured partition clauses on the MV's query provably exclude\n * the partition's value — meaning the input filter and the output\n * partition are disjoint and the same-collection edge isn't really a\n * cycle.\n *\n * Supported provability shapes (narrow on purpose — niwat's DERIV-\n * PP30-001 is the load-bearing case):\n *\n * - `.where(field, '==', X)` where X !== partition.value → disjoint\n * - `.where(field, '!=', partition.value)` → disjoint\n * - `.where(field, 'in', [...])` where partition.value NOT in list → disjoint\n *\n * Anything else (no clause on the partition field, an 'in' list that\n * contains partition.value, unsupported operators) → not disjoint,\n * the cycle detector surfaces `MaterializedViewCycleError`.\n *\n * @internal\n */\nfunction partitionDisjoint(reg: RegisteredMV): boolean {\n const partition = reg.spec.output?.partition\n if (partition === undefined) return false\n const value = partition.value\n // The OR-semantics of multiple where-clauses on the same field\n // would muddy this check. v2 only treats AND-chained clauses;\n // any clause that proves disjoint is sufficient.\n for (const c of reg.partitionClauses) {\n if (c.op === '==' && c.value !== value) return true\n if (c.op === '!=' && c.value === value) return true\n if (c.op === 'in' && Array.isArray(c.value)) {\n const list = c.value as readonly unknown[]\n if (!list.includes(value)) return true\n }\n }\n return false\n}\n","import type { Collection } from '../collection.js'\nimport type { TxContext } from '../tx/transaction.js'\nimport type { EncryptedEnvelope } from '../types.js'\nimport { MaterializedViewTooLargeError } from '../errors.js'\nimport type { MaterializedFromMeta, MVQueryContext, MaterializedViewStrategy } from './types.js'\nimport type { RegisteredMV } from './registry.js'\nimport { wrapDbWithPredicates } from './registry.js'\nimport { groupAndReduce } from '../aggregate/groupby.js'\nimport { canonicalGroupKey } from '../aggregate/canonical-key.js'\n\n/**\n * Accessor shape passed in from the owning Vault. Mirrors v1's\n * `DerivationStaleAccessor` — provides the per-collection resolver\n * and the active TxContext so refresh writes/tombstones register on\n * `_executed` for #133-style rollback symmetry.\n */\nexport interface MVExecutorAccessor {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n getCollection(name: string): Collection<any>\n getActiveTxContext(): TxContext | null\n /**\n * Vault-shaped accessor passed to the MV's `query()` callback at\n * each refresh. Same instance the registry used at registration\n * time; threading through the executor lets the refresh path\n * re-evaluate the closure against the live vault state.\n */\n getQueryContext(): MVQueryContext\n}\n\nexport interface RefreshResult {\n /** Rows newly written / overwritten. */\n written: number\n /** Rows tombstoned via `_internalDelete` (only when `onEmpty: 'delete'`). */\n deleted: number\n /** Failed row writes (non-strict mode). */\n failed: number\n}\n\n/** Default cost ceiling — overridable per-MV via `spec.maxRows`. */\nconst DEFAULT_MAX_ROWS = 100_000\n\n/**\n * Materialize a query terminal that may be a `Query<T>` (call\n * `.toArray()`), an `Aggregation<R>` (call `.run()` returning a\n * single object — wrap as a one-row array), or a `GroupedAggregation<R>`\n * (call `.run()` returning an array of grouped rows). Branches on\n * available terminal at runtime — no type-discrimination at registration.\n */\nasync function materializeQueryResult(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n q: any,\n mvName: string,\n): Promise<ReadonlyArray<Record<string, unknown>>> {\n if (typeof q?.toArray === 'function') {\n // Query<T> — non-aggregate path. `.toArray()` returns Promise<T[]>.\n return await q.toArray()\n }\n if (typeof q?.run === 'function') {\n // Aggregation<R> or GroupedAggregation<R>. `.run()` is synchronous\n // and returns either a single object (Aggregation) or an array of\n // rows (GroupedAggregation). Promise.resolve() normalizes both\n // sync and async (future) variants.\n const result: unknown = await Promise.resolve(q.run())\n if (Array.isArray(result)) {\n return result as ReadonlyArray<Record<string, unknown>>\n }\n // Single-aggregate result — wrap as one-row array. The consumer's\n // `rowKey()` should return a stable identity (often a literal\n // constant like `'total'`) since there's only one row.\n return [result as Record<string, unknown>]\n }\n throw new Error(\n `MV \"${mvName}\": query() must return a Query<T>, Aggregation, or GroupedAggregation. ` +\n `Got something without a .toArray() or .run() terminal.`,\n )\n}\n\n/**\n * Materialize a UNION-form MV (#165): read every arm's source\n * collection, apply each arm's `map` to project rows into the unified\n * MV row shape, concatenate the mapped streams, then optionally run\n * `groupBy` + `aggregate` over the result.\n *\n * Modes (driven by `spec.groupBy` / `spec.aggregate`):\n *\n * - No `groupBy` → return the concatenated mapped rows unchanged.\n * - `groupBy` without `aggregate` → dedupe by composite group key,\n * keep the first row seen per key (later arms don't overwrite\n * earlier arms — Map insertion order rules).\n * - `groupBy` + `aggregate` → delegate to the shared `groupAndReduce`\n * pipeline used by `Query.groupBy().aggregate()`.\n *\n * Per-arm `map` is the schema-unification boundary; the strategy's\n * `TRow` type parameter enforces that every arm projects into the\n * same shape at compile time.\n *\n * @internal\n */\nasync function materializeUnionResult<TRow extends Record<string, unknown>>(\n spec: MaterializedViewStrategy<TRow>,\n db: MVQueryContext,\n): Promise<ReadonlyArray<Record<string, unknown>>> {\n const unified: TRow[] = []\n for (const arm of spec.unionSources!) {\n const coll = db.collection<Record<string, unknown>>(arm.collection)\n const sourceRows = coll.query().toArray()\n for (const r of sourceRows) {\n unified.push(arm.map(r))\n }\n }\n\n if (!spec.groupBy) return unified\n\n const groupFields: readonly string[] =\n typeof spec.groupBy === 'string' ? [spec.groupBy] : spec.groupBy\n\n // groupBy without aggregate — dedupe by composite key, keep first\n // seen row per key. Useful for cross-arm uniqueness (e.g. unify two\n // sibling collections, keeping one row per natural key).\n if (!spec.aggregate) {\n const seen = new Map<string, TRow>()\n for (const row of unified) {\n const k = canonicalGroupKey(groupFields, row as Record<string, unknown>)\n if (!seen.has(k)) seen.set(k, row)\n }\n return [...seen.values()]\n }\n\n // groupBy + aggregate — delegate to the shared pipeline used by\n // `Query.groupBy().aggregate()`. Result rows carry each grouped\n // field in declaration order followed by the spec's reducer outputs.\n return groupAndReduce<Record<string, unknown>>(unified, groupFields, spec.aggregate)\n}\n\n/**\n * Run an MV's `query()` and write the result rows to the output\n * collection. Same-DEK encryption: routes through the standard\n * `Collection.put` pipeline, so the output collection's DEK is what\n * gets used (matches the v2 spec's \"same DEK as the left-most source\"\n * invariant — `Collection.put` looks up the DEK by collection name,\n * and the output collection IS the MV's owned collection).\n *\n * Stamps `_materializedFrom` onto every emitted row.\n *\n * **Tombstoning** (#152): when `spec.onEmpty: 'delete'` (default), rows\n * that existed in a prior refresh but no longer appear in the new\n * materialized result are deleted via `Collection._internalDelete` —\n * the housekeeping bypass primitive added in PR #148 prevents user\n * `onDelete` guards on the output collection from firing on these\n * system-internal deletes. `onEmpty: 'keep'` opts out (rows from\n * prior refreshes linger even when the new result lacks them).\n *\n * **Cost ceiling** (#152): if the materialized row count exceeds\n * `spec.maxRows` (default 100k), throws `MaterializedViewTooLargeError`\n * before any writes hit the store — so strict-mode rollback is\n * clean.\n *\n * **Strict mode** (#152): `spec.strict === true` re-throws on any\n * row-write failure; the active TxContext registration means the\n * source-write rolls back atomically via `revertExecuted` (#133).\n *\n * @internal\n */\nexport const MaterializedViewExecutor = {\n async refresh(\n reg: RegisteredMV,\n accessor: MVExecutorAccessor,\n ): Promise<RefreshResult> {\n const spec = reg.spec\n const outputColl = accessor.getCollection(reg.outputCollection)\n const maxRows = spec.maxRows ?? DEFAULT_MAX_ROWS\n const onEmpty = spec.onEmpty ?? 'delete'\n const strict = spec.strict ?? false\n\n // 1. Materialize the query (branches on terminal shape). If the\n // MV declared predicates, wrap the query context the same way\n // the registry did at registration time so `.wherePredicate()`\n // calls resolve to the registered functions.\n const baseCtx = accessor.getQueryContext()\n const ctxForQuery: MVQueryContext = spec.predicates\n ? wrapDbWithPredicates(baseCtx, spec.predicates)\n : baseCtx\n // UNION-form strategies (#165): read every arm, map to the unified\n // row shape, concatenate, then optionally groupBy + aggregate. The\n // single-source `query()` path is untouched.\n let rows: ReadonlyArray<Record<string, unknown>>\n if (spec.unionSources) {\n rows = await materializeUnionResult(spec, ctxForQuery)\n } else {\n const q = spec.query!(ctxForQuery)\n rows = await materializeQueryResult(q, spec.name)\n }\n\n // 2. Cost ceiling check BEFORE any writes — keeps the rollback\n // clean if the source-write is wrapped in a transaction.\n if (rows.length > maxRows) {\n throw new MaterializedViewTooLargeError(spec.name, rows.length, maxRows)\n }\n\n const txCtx = accessor.getActiveTxContext()\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const adapter = (outputColl as any).adapter as {\n get(v: string, c: string, i: string): Promise<EncryptedEnvelope | null>\n }\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const vaultName = (outputColl as any).vault as string\n\n // 3. Compute the post-refresh id set so we can diff against the\n // prior-emitted id set for tombstoning (when onEmpty === 'delete').\n const newIds = new Set<string>()\n const enrichedRows: Array<{ id: string; record: Record<string, unknown> }> = []\n for (const row of rows) {\n const id = spec.rowKey(row)\n newIds.add(id)\n const meta: MaterializedFromMeta = {\n mvName: spec.name,\n queryHash: reg.queryHash,\n sourceVersions: {},\n materializedAt: new Date().toISOString(),\n }\n enrichedRows.push({ id, record: { ...row, _materializedFrom: meta } })\n }\n\n // 4. Write the new rows.\n let written = 0\n let failed = 0\n for (const { id, record } of enrichedRows) {\n try {\n if (txCtx !== null) {\n const prior = await adapter.get(vaultName, reg.outputCollection, id)\n txCtx._executed.push({\n op: { type: 'put', vaultName, collectionName: reg.outputCollection, id },\n priorEnvelope: prior,\n })\n }\n await outputColl.put(id, record)\n written++\n } catch (err) {\n failed++\n if (strict) throw err\n \n console.warn(`[mv] \"${spec.name}\" row write failed:`, err)\n }\n }\n\n // 5. Tombstone rows that existed before but don't appear now.\n // `onEmpty: 'keep'` skips this pass entirely. Uses\n // `_internalDelete` so a user-registered `onDelete` on the\n // output collection does NOT fire on housekeeping (the #145\n // composition fix).\n let deleted = 0\n if (onEmpty === 'delete') {\n const priorIds = await listOutputIds(outputColl)\n for (const priorId of priorIds) {\n if (newIds.has(priorId)) continue\n try {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const outAny = outputColl as any\n if (typeof outAny._internalDelete === 'function') {\n await outAny._internalDelete(priorId, txCtx)\n deleted++\n } else {\n // Defensive fallback — should never hit in real flow since\n // every Collection has `_internalDelete`.\n await outputColl.delete(priorId)\n deleted++\n }\n } catch (err) {\n failed++\n if (strict) throw err\n \n console.warn(`[mv] \"${spec.name}\" tombstone failed for id=\"${priorId}\":`, err)\n }\n }\n }\n\n return { written, deleted, failed }\n },\n}\n\n/**\n * List ids currently present in the MV's output collection via the\n * adapter directly (avoids triggering the lazy resolve-on-read path\n * we're INSIDE). Returns an empty array if the collection doesn't\n * exist or the adapter doesn't surface a list method.\n *\n * @internal\n */\nasync function listOutputIds(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n outputColl: Collection<any>,\n): Promise<string[]> {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const cAny = outputColl as any\n const adapter = cAny.adapter as { list?: (v: string, c: string) => Promise<readonly string[]> }\n const vault = cAny.vault as string\n const name = cAny.name as string\n if (typeof adapter?.list !== 'function') return []\n try {\n const ids = await adapter.list(vault, name)\n return [...ids]\n } catch {\n return []\n }\n}\n","import type { Collection } from '../collection.js'\nimport type { TxContext } from '../tx/transaction.js'\nimport type { MaterializedViewRegistry } from './registry.js'\n// Type-only — runtime class loaded via dynamic import in\n// `resolveStaleMVOnRead` only when a stale flag actually fires.\n// Keeps the executor chunk out of the floor bundle (mirrors v1 #130).\nimport type { MaterializedViewExecutor as MVExecutorType } from './executor.js'\nimport type { MVQueryContext } from './types.js'\n\n/**\n * Accessor shape passed in from the owning Vault. Provides the\n * registry (used as a stable WeakMap key + to look up MVs by output\n * collection) and the runtime context the lazy refresh needs.\n * Mirrors v1's `DerivationStaleAccessor`.\n */\nexport interface MVStaleAccessor {\n registry(): MaterializedViewRegistry\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n getCollection(name: string): Collection<any>\n getActiveTxContext(): TxContext | null\n getQueryContext(): MVQueryContext\n}\n\n/**\n * In-memory stale map keyed by `MaterializedViewRegistry` instance\n * (stable per vault). Each registry maps to a set of MV names that\n * have at least one pending source-change requiring a re-materialize.\n *\n * Persistence across vault close is NOT implemented in this iteration\n * (concern flagged in the v2 spec, mirrors v1 derivation behavior).\n * On vault re-open, the unset stale flag is interpreted as \"fresh\" —\n * `vault.refreshView(name)` is the explicit recompute escape hatch.\n *\n * @internal\n */\nconst _staleByRegistry = new WeakMap<MaterializedViewRegistry, Set<string>>()\n\n/**\n * Mark an MV as stale. Called from `Collection.dispatchMaterializedViews`\n * when a source-write fires for a `refresh: 'lazy'` MV.\n *\n * @internal\n */\nexport function markMVStale(registry: MaterializedViewRegistry, mvName: string): void {\n let set = _staleByRegistry.get(registry)\n if (!set) {\n set = new Set()\n _staleByRegistry.set(registry, set)\n }\n set.add(mvName)\n}\n\n/**\n * Test-only: check whether a given MV name is currently flagged stale\n * against a registry. Exported so the regression suite can pin the\n * stale-bit lifecycle without touching the internal `WeakMap`.\n *\n * @internal\n */\nexport function isMVStale(registry: MaterializedViewRegistry, mvName: string): boolean {\n return _staleByRegistry.get(registry)?.has(mvName) ?? false\n}\n\n/**\n * Called from `Collection.get` (and any reader that materializes the\n * MV's output collection). If any MV producing `outputCollection` is\n * flagged stale, runs the executor against the live source state\n * before returning. No-op when there is no pending work — keeps the\n * read fast path negligible.\n *\n * Dynamic-imports the executor only when a stale flag actually fires\n * (the floor-bundle isolation pattern v1 derivations established in\n * #130).\n */\nexport async function resolveStaleMVOnRead(\n accessor: MVStaleAccessor,\n outputCollection: string,\n): Promise<void> {\n const registry = accessor.registry()\n const pending = _staleByRegistry.get(registry)\n if (!pending || pending.size === 0) return\n\n // Find every MV that writes to this output collection AND is\n // currently flagged stale. Multiple MVs CAN share an output\n // collection in theory; in practice the registration validation +\n // cycle detection make this unusual.\n const candidates: string[] = []\n for (const mv of registry.all()) {\n if (mv.outputCollection !== outputCollection) continue\n if (!pending.has(mv.spec.name)) continue\n candidates.push(mv.spec.name)\n }\n if (candidates.length === 0) return\n\n let executor: typeof MVExecutorType | null = null\n for (const name of candidates) {\n const reg = registry.byName(name)\n if (!reg) {\n pending.delete(name)\n continue\n }\n if (executor === null) {\n ({ MaterializedViewExecutor: executor } = (await import('./executor.js')) as {\n MaterializedViewExecutor: typeof MVExecutorType\n })\n }\n await executor.refresh(reg, {\n getCollection: (n) => accessor.getCollection(n),\n getActiveTxContext: () => accessor.getActiveTxContext(),\n getQueryContext: () => accessor.getQueryContext(),\n })\n pending.delete(name)\n }\n}\n\n/**\n * Drop every stale flag for a registry. Used after a manual\n * `vault.refreshView(name)` runs the executor explicitly — the\n * post-refresh state matches the registered strategies, so\n * lingering stale bits would force a redundant refresh on the next\n * read.\n *\n * @internal\n */\nexport function clearMVStale(registry: MaterializedViewRegistry, mvName: string): void {\n _staleByRegistry.get(registry)?.delete(mvName)\n}\n","import { FieldFrozenError, InvariantError } from '../errors.js'\nimport type { GuardStrategy, GuardContext, GuardChange } from './types.js'\n\n/**\n * Pure functions that execute the work declared by a `GuardStrategy`.\n * Stateless — `GuardRegistry` decides when to call these.\n *\n * @internal\n */\nexport const GuardExecutor = {\n /**\n * Compare existing vs incoming for each `frozenFields.fields` entry\n * when `frozenFields.when(existing)` is true. Throws\n * `FieldFrozenError` listing every changed frozen field.\n */\n async checkFrozenFields<T extends Record<string, unknown>>(\n guard: GuardStrategy<T>,\n id: string,\n existing: T | null,\n incoming: T,\n ): Promise<void> {\n const ff = guard.frozenFields\n if (!ff) return\n if (existing === null) return // insert — nothing to freeze\n if (!ff.when(existing)) return\n\n const changed: string[] = []\n for (const f of ff.fields) {\n // Strict equality first, then deep-equality fallback for objects.\n if (existing[f] !== incoming[f]) {\n if (!deepEqual(existing[f], incoming[f])) changed.push(String(f))\n }\n }\n if (changed.length > 0) {\n throw new FieldFrozenError(guard.collection, id, changed)\n }\n },\n\n /**\n * Run a single guard's invariant over its slice of the change-set.\n * Any throw is converted to `InvariantError` unless it already is one.\n */\n async runInvariant<T extends Record<string, unknown>>(\n guard: GuardStrategy<T>,\n changes: ReadonlyArray<GuardChange<T>>,\n ctx: GuardContext<T>,\n ): Promise<void> {\n const amendment = guard.amendment\n if (!amendment) return\n try {\n await amendment.invariant(changes, ctx)\n } catch (err) {\n if (err instanceof InvariantError) throw err\n throw new InvariantError(\n err instanceof Error ? err.message : `invariant violated: ${String(err)}`,\n )\n }\n },\n}\n\n/**\n * Minimal deep-equality for guarded field diff. Handles arrays, plain\n * objects, primitives. Not for cyclic structures.\n *\n * @internal\n */\nfunction deepEqual(a: unknown, b: unknown): boolean {\n if (a === b) return true\n if (a === null || b === null) return a === b\n if (typeof a !== typeof b) return false\n if (typeof a !== 'object') return a === b\n if (Array.isArray(a) !== Array.isArray(b)) return false\n if (Array.isArray(a)) {\n const aa = a as unknown[]\n const bb = b as unknown[]\n if (aa.length !== bb.length) return false\n for (let i = 0; i < aa.length; i++) if (!deepEqual(aa[i], bb[i])) return false\n return true\n }\n const ao = a as Record<string, unknown>\n const bo = b as Record<string, unknown>\n const ak = Object.keys(ao)\n const bk = Object.keys(bo)\n if (ak.length !== bk.length) return false\n for (const k of ak) {\n if (!Object.prototype.hasOwnProperty.call(bo, k)) return false\n if (!deepEqual(ao[k], bo[k])) return false\n }\n return true\n}\n","/**\n * Per-source-row fanout sidecar for `shape: 'array'` derivations (#200).\n *\n * Each `(sourceCollection, sourceId, outputKey)` triple gets its own\n * envelope at:\n *\n * _meta/derivations-fanout/<sourceCollection>/<sourceId>/<outputKey>\n *\n * The envelope records the last-emitted derived row ids so the\n * dispatcher can compute the diff on every source-row update in O(1):\n * read prior keys, compute `toDelete = prev \\ new`, write new, persist\n * back.\n *\n * Stored as plain JSON with AES-GCM bypassed (same pattern as\n * `_meta/policy`, `_meta/recovery-paper`, `_meta/sealed-passphrase`,\n * etc.): the sidecar is system metadata, not user data, and the\n * derived outputs themselves carry their own encryption envelopes.\n *\n * @module\n */\nimport type { NoydbStore, EncryptedEnvelope } from '../types.js'\nimport { NOYDB_FORMAT_VERSION } from '../types.js'\n\n/** Magic-prefixed JSON payload at `_meta/<recordId>`. */\nexport interface FanoutSidecar {\n readonly _noydb_fanout: 1\n /** Source collection name. */\n readonly source: string\n /** Source record id. */\n readonly sourceId: string\n /** Strategy output key (the key in `strategy.outputs`). */\n readonly outputKey: string\n /** Output collection name (audit / forensics). */\n readonly outputCollection: string\n /** Derived-row ids last emitted for this (source, output) pair. */\n readonly keys: ReadonlyArray<string>\n /** ISO timestamp of last dispatch. */\n readonly emittedAt: string\n}\n\n/**\n * Build the canonical `_meta` record id for a fanout sidecar.\n *\n * The full path inside the store is `_meta/<this-string>`. We pack the\n * full triple into the id so a single `_meta` collection holds all\n * sidecars (collection-per-source would proliferate names; the\n * existing `_meta` flat namespace is cheaper).\n */\nfunction recordId(source: string, sourceId: string, outputKey: string): string {\n // Use `/` as a separator. None of the components is supposed to\n // contain it (collection names + output keys are bare identifiers;\n // source ids are typically ULIDs / UUIDs / app-chosen strings\n // without slashes). If a future source carries `/` in its id, we'd\n // need an escape; deferred until that's a real case.\n return `derivations-fanout/${source}/${sourceId}/${outputKey}`\n}\n\n/** Read the sidecar; returns empty if absent. */\nexport async function loadFanoutSidecar(\n store: NoydbStore,\n vault: string,\n source: string,\n sourceId: string,\n outputKey: string,\n): Promise<FanoutSidecar | undefined> {\n const envelope = await store.get(vault, '_meta', recordId(source, sourceId, outputKey))\n if (!envelope) return undefined\n try {\n const parsed = JSON.parse(envelope._data) as FanoutSidecar\n if (parsed._noydb_fanout !== 1) return undefined\n if (!Array.isArray(parsed.keys)) return undefined\n return parsed\n } catch {\n return undefined\n }\n}\n\n/** Persist (insert/replace) the sidecar with a fresh key set. */\nexport async function saveFanoutSidecar(\n store: NoydbStore,\n vault: string,\n payload: {\n readonly source: string\n readonly sourceId: string\n readonly outputKey: string\n readonly outputCollection: string\n readonly keys: ReadonlyArray<string>\n },\n): Promise<void> {\n const doc: FanoutSidecar = {\n _noydb_fanout: 1,\n source: payload.source,\n sourceId: payload.sourceId,\n outputKey: payload.outputKey,\n outputCollection: payload.outputCollection,\n keys: payload.keys,\n emittedAt: new Date().toISOString(),\n }\n const id = recordId(payload.source, payload.sourceId, payload.outputKey)\n const prior = await store.get(vault, '_meta', id)\n const envelope: EncryptedEnvelope = {\n _noydb: NOYDB_FORMAT_VERSION,\n _v: (prior?._v ?? 0) + 1,\n _ts: new Date().toISOString(),\n // AES-GCM bypassed — sidecar is system metadata, no user data inside.\n _iv: '',\n _data: JSON.stringify(doc),\n }\n await store.put(vault, '_meta', id, envelope)\n}\n\n/** Delete the sidecar (used on source-row delete cascade). */\nexport async function deleteFanoutSidecar(\n store: NoydbStore,\n vault: string,\n source: string,\n sourceId: string,\n outputKey: string,\n): Promise<void> {\n await store.delete(vault, '_meta', recordId(source, sourceId, outputKey))\n}\n","import type { NoydbStore, EncryptedEnvelope, ChangeEvent, HistoryConfig, HistoryOptions, HistoryEntry, PruneOptions, ListPageResult, LocaleReadOptions, ConflictPolicy, CollectionConflictResolver, PutManyItemOptions, PutManyOptions, PutManyResult, DeleteManyResult } from './types.js'\nimport { NOYDB_FORMAT_VERSION } from './types.js'\nimport type { CrdtMode, CrdtState, LwwMapState, RgaState } from './crdt/crdt.js'\nimport { NO_CRDT, type CrdtStrategy } from './crdt/strategy.js'\nimport type { I18nTextDescriptor } from './i18n/core.js'\nimport type { DictKeyDescriptor } from './i18n/dictionary.js'\nimport { NO_I18N, type I18nStrategy } from './i18n/strategy.js'\nimport { encrypt, decrypt, encryptDeterministic } from './crypto.js'\nimport { ConflictError, ReadOnlyError, TranslatorNotConfiguredError, TierDemoteDeniedError } from './errors.js'\nimport { dekKey, assertTierAccess } from './team/tiers.js'\nimport type { GhostRecord, TierMode, CrossTierAccessEvent } from './types.js'\nimport type { UnlockedKeyring } from './team/keyring.js'\nimport { hasWritePermission } from './team/keyring.js'\nimport type { NoydbEventEmitter } from './events.js'\nimport type { StandardSchemaV1 } from './schema.js'\nimport { validateSchemaInput, validateSchemaOutput } from './schema.js'\nimport type { LedgerStore } from './history/ledger/index.js'\nimport type { DiffEntry } from './history/diff.js'\nimport { NO_HISTORY, type HistoryStrategy } from './history/strategy.js'\nimport { Query, ScanBuilder } from './query/index.js'\nimport type { QuerySource, JoinContext, JoinableSource } from './query/index.js'\nimport type { CollectionIndexes, IndexDef } from './indexing/eager-indexes.js'\nimport { encodeIdxId, decodeIdxId } from './indexing/persisted-indexes.js'\nimport type { PersistedCollectionIndex, PersistedIndexDef } from './indexing/persisted-indexes.js'\nimport { LazyQuery } from './indexing/lazy-builder.js'\nimport type { LazyQuerySource } from './indexing/lazy-builder.js'\nimport { NO_INDEXING, type IndexStrategy, type IndexState } from './indexing/strategy.js'\nimport { IndexWriteFailureError } from './errors.js'\nimport type { RefDescriptor } from './refs.js'\nimport { Lru, parseBytes, estimateRecordBytes, type LruStats } from './cache/index.js'\nimport { generateULID } from './bundle/ulid.js'\nimport type { PresenceHandle, PresenceHandleOpts } from './team/presence.js'\nimport { NO_SYNC, type SyncStrategy } from './team/sync-strategy.js'\nimport type { BlobSet } from './blobs/blob-set.js'\nimport { NO_BLOBS, type BlobStrategy } from './blobs/strategy.js'\nimport { NO_AGGREGATE, type AggregateStrategy } from './aggregate/strategy.js'\nimport type { GuardRegistry } from './guards/registry.js'\nimport type { ReadOnlyVaultFacade } from './guards/types.js'\n// Type-only — runtime class loaded via dynamic import in the\n// frozen-field branch of `put()` / amendment paths. Keeps the guard\n// executor chunk out of the floor bundle (#130).\nimport type { GuardExecutor as GuardExecutorType } from './guards/executor.js'\nimport type { DerivationRegistry } from './derivations/registry.js'\nimport type { TxContext, ExecutedOp } from './tx/transaction.js'\nimport { revertExecuted } from './tx/transaction.js'\n// Type-only — runtime class loaded via dynamic import in\n// `dispatchDerivations` when an eager-mode strategy fires. Keeps the\n// derivation executor chunk out of the floor bundle (#130).\nimport type { DerivationExecutor as DerivationExecutorType } from './derivations/executor.js'\nimport type {\n loadFanoutSidecar as LoadFanoutSidecarType,\n deleteFanoutSidecar as DeleteFanoutSidecarType,\n saveFanoutSidecar as SaveFanoutSidecarType,\n} from './derivations/fanout-sidecar.js'\nimport { markStale, resolveStaleOnRead } from './derivations/stale.js'\nimport type { MaterializedViewRegistry } from './materialized-views/registry.js'\nimport type { MVQueryContext } from './materialized-views/types.js'\nimport type { MaterializedViewExecutor as MVExecutorType } from './materialized-views/executor.js'\nimport type * as MVStaleModule from './materialized-views/stale.js'\n\n/** Callback for dirty tracking (sync engine integration). */\nexport type OnDirtyCallback = (collection: string, id: string, action: 'put' | 'delete', version: number) => Promise<void>\n\n/**\n * Event delivered to a `collection.subscribe()` callback. Distinct\n * from the hub-level `ChangeEvent` — this one is bound to a single\n * collection's type `T` and hydrates the record from cache on put.\n *\n * - `type: 'put'` — `record` is the current decrypted value, or\n * `null` in the rare case where another op deleted the record\n * between the emit and the handler firing.\n * - `type: 'delete'` — `record` is always `null`; the deletion is\n * the only information.\n */\nexport interface CollectionChangeEvent<T> {\n readonly type: 'put' | 'delete'\n readonly id: string\n readonly record: T | null\n}\n\n/**\n * Per-collection cache configuration. Only meaningful when paired with\n * `prefetch: false` (lazy mode); eager mode keeps the entire decrypted\n * cache in memory and ignores these bounds.\n */\nexport interface CacheOptions {\n /** Maximum number of records to keep in memory before LRU eviction. */\n maxRecords?: number\n /**\n * Maximum total decrypted byte size before LRU eviction. Accepts a raw\n * number or a human-friendly string: `'50KB'`, `'50MB'`, `'1GB'`.\n * Eviction picks the least-recently-used entry until both budgets\n * (maxRecords AND maxBytes, if both are set) are satisfied.\n */\n maxBytes?: number | string\n}\n\n/** Statistics exposed via `Collection.cacheStats()`. */\nexport interface CacheStats extends LruStats {\n /** True if this collection is in lazy mode. */\n lazy: boolean\n}\n\n/**\n * Track which adapter names have already triggered the listPage fallback\n * warning. We only emit once per adapter per process so consumers see the\n * heads-up without log spam.\n */\nconst fallbackWarned = new Set<string>()\nfunction warnOnceFallback(adapterName: string): void {\n if (fallbackWarned.has(adapterName)) return\n fallbackWarned.add(adapterName)\n // Only warn in non-test environments — vitest runs are noisy enough.\n if (typeof process !== 'undefined' && process.env['NODE_ENV'] === 'test') return\n console.warn(\n `[noy-db] Store \"${adapterName}\" does not implement listPage(); ` +\n `Collection.scan()/listPage() are using a synthetic fallback (slower). ` +\n `Add a listPage method to opt into the streaming fast path.`,\n )\n}\n\n/** A typed collection of records within a vault. */\nexport class Collection<T> {\n private readonly adapter: NoydbStore\n private readonly vault: string\n private readonly name: string\n private readonly keyring: UnlockedKeyring\n private readonly encrypted: boolean\n private readonly emitter: NoydbEventEmitter\n private readonly getDEK: (collectionName: string) => Promise<CryptoKey>\n private readonly onDirty: OnDirtyCallback | undefined\n private readonly historyConfig: HistoryConfig\n\n /**\n * tree-shake seam — the strategy that backs `collection.blob(id)`.\n * Defaults to `NO_BLOBS`, a ~10-line stub that throws with an actionable\n * message. Consumers opt into real blob storage by importing\n * `{ blobs }` from `@noy-db/hub/blobs` and passing the returned\n * strategy to `createNoydb({ blobStrategy: blobs() })`. With the\n * default stub, none of the BlobSet / chunk / MIME-magic machinery\n * reaches the bundle.\n */\n private readonly blobStrategy: BlobStrategy\n private readonly aggregateStrategy: AggregateStrategy\n private readonly crdtStrategy: CrdtStrategy\n private readonly historyStrategy: HistoryStrategy\n private readonly i18nStrategy: I18nStrategy\n private readonly syncStrategy: SyncStrategy\n\n // In-memory cache of decrypted records (eager mode only). Lazy mode\n // uses `lru` instead. Both fields exist so a single Collection instance\n // doesn't need a runtime branch on every cache access.\n private readonly cache = new Map<string, { record: T; version: number }>()\n private hydrated = false\n\n /**\n * Lazy mode flag. `true` when constructed with `prefetch: false`.\n * In lazy mode the cache is bounded by an LRU and `list()`/`query()`\n * throw — callers must use `scan()` or per-id `get()` instead.\n */\n private readonly lazy: boolean\n\n /**\n * LRU cache for lazy mode. Only allocated when `prefetch: false` is set.\n * Stores `{ record, version }` entries the same shape as `this.cache`.\n * Tree-shaking note: importing Collection without setting `prefetch:false`\n * still pulls in the Lru class today; future bundle-size work could\n * lazy-import the cache module.\n */\n private readonly lru: Lru<string, { record: T; version: number }> | null\n\n /**\n * tree-shake seam — per-Collection indexing state. Owned by the\n * `IndexStrategy` passed through from `createNoydb({ indexStrategy })`.\n * Defaults to a disabled state (both accessors return null) so the\n * `CollectionIndexes` / `PersistedCollectionIndex` / `LazyQuery`\n * classes never reach the bundle when indexing is unused.\n *\n * Accessor helpers below (`get indexes()`, `get persistedIndexes()`)\n * preserve the field-access ergonomics without changing every\n * caller site.\n */\n private readonly indexState: IndexState\n\n /**\n * True once `_idx/*` side-cars have been bulk-loaded into\n * `persistedIndexes`. Flipped by `ensurePersistedIndexesLoaded()` on\n * first lazy-mode query so subsequent queries skip the adapter round\n * trip. Invalidation (remote sync, rotation) resets it alongside\n * `persistedIndexes.clear()`.\n */\n private persistedIndexesLoaded = false\n\n /**\n * Accessor for the in-memory eager-mode index mirror. Returns `null`\n * when indexing is disabled on this Noydb instance (the\n * `NO_INDEXING` default) or when the collection is in lazy mode\n * (which uses the persisted mirror instead).\n */\n private get indexes(): CollectionIndexes | null {\n return this.indexState.getEagerIndexes()\n }\n\n /**\n * Accessor for the persisted-mirror (lazy-mode) index. Returns `null`\n * when indexing is disabled or the collection is in eager mode.\n */\n private get persistedIndexes(): PersistedCollectionIndex | null {\n return this.indexState.getPersistedIndexes()\n }\n\n /**\n * per-collection reconcile-on-open policy. Read once\n * from `CollectionOptions.reconcileOnOpen` and applied by\n * `ensurePersistedIndexesLoaded()` on the first lazy-mode query.\n */\n private readonly reconcileOnOpen: 'off' | 'dry-run' | 'auto'\n\n /**\n * Re-entrancy guard for the auto-reconcile path. `reconcileIndex`\n * reloads the mirror after applying fixes, which re-enters\n * `ensurePersistedIndexesLoaded`; without this flag we'd trigger a\n * second auto-reconcile pass and potentially infinite recursion.\n */\n private autoReconciling = false\n\n /**\n * Optional Standard Schema v1 validator. When set, every `put()` runs\n * the input through `validateSchemaInput` before encryption, and every\n * record coming OUT of `decryptRecord` runs through\n * `validateSchemaOutput`. A rejected input throws\n * `SchemaValidationError` with `direction: 'input'`; drifted stored\n * data throws with `direction: 'output'`. Both carry the rich issue\n * list from the validator so UI code can render field-level messages.\n *\n * The schema is stored as `StandardSchemaV1<unknown, T>` because the\n * collection type parameter `T` is the OUTPUT type — whatever the\n * validator produces after transforms and coercion. Users who pass a\n * schema to `defineNoydbStore` (or `Collection.constructor`) get their\n * `T` inferred automatically via `InferOutput<Schema>`.\n */\n private readonly schema: StandardSchemaV1<unknown, T> | undefined\n\n /**\n * Vault-default locale. Used as the fallback when no per-call\n * locale option is passed to `get()`/`list()`. Provided by Vault\n * at collection construction time via the `collection({ locale })` or\n * `openVault(name, { locale })` path.\n *\n * `undefined` means \"no default locale set\" — i18nText fields will\n * throw `LocaleNotSpecifiedError` unless a per-call locale is passed.\n */\n private readonly defaultLocale: string | undefined\n\n /**\n * Map of field name → `I18nTextDescriptor` for fields declared with\n * `i18nText()`. Used by:\n * - `put()` via `i18nPutValidator` to enforce required translations\n * - `get()`/`list()` to apply locale resolution after decryption\n *\n * Declared via the `i18nFields` collection option.\n */\n private readonly i18nFields: Record<string, I18nTextDescriptor> | undefined\n\n /**\n * Map of field name → `DictKeyDescriptor` for fields declared with\n * `dictKey()`. Used by `get()`/`list()` to add `<field>Label` virtual\n * fields when a locale is requested.\n */\n private readonly dictKeyFields: Record<string, DictKeyDescriptor> | undefined\n\n /**\n * Async callback provided by the Vault that resolves a dict key\n * to its label for a given locale. Used by the locale-read path for\n * dictKey fields.\n *\n * Signature: `(dictName, key, locale, fallback?) => Promise<string | undefined>`\n */\n private readonly dictLabelResolver:\n | ((\n dictName: string,\n key: string,\n locale: string,\n fallback?: string | readonly string[],\n ) => Promise<string | undefined>)\n | undefined\n\n /**\n * Synchronous callback provided by the Vault that validates\n * i18nText fields on `put()`. Throws `MissingTranslationError` when\n * a required translation is absent. Called after schema validation,\n * before encryption.\n */\n private readonly i18nPutValidator: ((record: unknown) => void) | undefined\n\n /**\n * declared deterministic fields. `null` when the feature\n * is inactive for this collection; a frozen `Set` otherwise.\n */\n private readonly deterministicFields: ReadonlySet<string> | null\n\n /**\n * declared tiers for this collection. `null` when\n * tier-aware methods are disabled. Tier 0 is implicit and never\n * stored here.\n */\n private readonly tiers: ReadonlySet<number> | null\n private readonly tierMode: TierMode\n private readonly onCrossTierAccess: ((event: CrossTierAccessEvent) => void) | undefined\n\n /**\n * Async translator callback provided by Noydb via Vault for\n * `i18nText` fields with `autoTranslate: true`. Called\n * before i18n validation so translated values are present when the\n * validator runs. `undefined` when no `plaintextTranslator` was\n * configured on `createNoydb()`.\n */\n private readonly autoTranslateHook:\n | ((text: string, from: string, to: string, field: string, collection: string) => Promise<string>)\n | undefined\n\n /**\n * Optional reference to the vault-level hash-chained audit\n * log. When present, every successful `put()` and `delete()` appends\n * an entry to the ledger AFTER the adapter write succeeds (so a\n * failed adapter write never produces an orphan ledger entry).\n *\n * The ledger is always a vault-wide singleton — all\n * collections in the same vault share the same LedgerStore.\n * Vault.ledger() does the lazy init; this field just holds\n * the reference so Collection doesn't need to reach back up to the\n * vault on every mutation.\n *\n * `undefined` means \"no ledger attached\" — supported for tests that\n * construct a Collection directly without a vault, and for\n * future backwards-compat scenarios. Production usage always has a\n * ledger because Vault.collection() passes one through.\n */\n private readonly ledger: LedgerStore | undefined\n\n /** — per-collection CRDT mode, or undefined for normal LWW-at-record-level. */\n private readonly crdtMode: CrdtMode | undefined\n\n /** — optional remote/sync adapter for presence broadcasting. */\n private readonly syncAdapter: NoydbStore | undefined\n\n /** — consent-audit hook, no-op when no scope is active. */\n private readonly onAccess:\n | ((op: 'get' | 'put' | 'delete', id: string) => Promise<void>)\n | undefined\n\n /**\n * accounting-period write guard. Called BEFORE any\n * adapter write with:\n * - `existing` — the prior envelope's `_ts` and decrypted record\n * (or `null` if no prior envelope exists)\n * - `incoming` — the record being written (or `null` for delete)\n *\n * Throws `PeriodClosedError` if either side falls inside a closed\n * period. Installed by Vault; no-op when no period has been closed.\n * Async so the Vault can lazy-load the period list from the\n * adapter on first use.\n */\n private readonly periodGuard:\n | ((\n existing: { ts: string | null; record: Record<string, unknown> | null } | null,\n incoming: Record<string, unknown> | null,\n ) => Promise<void>)\n | undefined\n\n /**\n * Optional back-reference to the owning vault's guard registry + a\n * read-only vault facade. When present, `Collection.put` and\n * `Collection.delete` consult the registry for guards declared\n * against this collection and run their `check` + `frozenFields`\n * before the adapter write. Absent in unit tests that construct\n * a Collection directly; production code always sets it via\n * `Vault.collection()`.\n *\n * Typed structurally rather than as `Vault` to avoid a circular\n * import (mirrors the `refEnforcer` / `joinResolver` pattern).\n */\n private readonly guardSource:\n | {\n registry(): GuardRegistry\n readOnlyVault(): ReadOnlyVaultFacade\n }\n | undefined\n\n /**\n * Vault-internal hook for derivation dispatch. When set,\n * `Collection.put` consults the registry after the source-write\n * commits and writes derived outputs through `getCollection(name).put`.\n * Same structural-interface pattern as `guardSource` to avoid a\n * circular Vault import.\n */\n private readonly derivationSource:\n | {\n registry(): DerivationRegistry\n getCollection(name: string): Collection<Record<string, unknown>>\n getReadOnlyFacade(): ReadOnlyVaultFacade\n getActiveTxContext(): TxContext | null\n /**\n * Construct a fresh transient TxContext bound to the owning\n * Noydb. Used by `Collection.putManyAtomic` to publish an\n * `_activeTxContext` for the duration of its Phase 2 loop so\n * recursive derived-output writes register their pre-write\n * envelopes on `ctx._executed` and roll back alongside the\n * bulk-put source ops (#133).\n */\n createTxContext(): TxContext\n /** Publish a TxContext for the duration of a bulk-atomic loop. */\n setActiveTxContext(ctx: TxContext): void\n /** Drop a previously-published TxContext (defensive no-op if mismatched). */\n clearActiveTxContext(ctx: TxContext): void\n }\n | undefined\n\n /**\n * Vault-internal hook for materialized-view dispatch (#143/#150).\n * Parallel to `derivationSource` — when set, `Collection.put` fires\n * `MaterializedViewRegistry.onSourceWrite` after the source-write\n * commits + after `dispatchDerivations` has run.\n */\n private readonly materializedViewSource:\n | {\n \n registry(): MaterializedViewRegistry\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n getCollection(name: string): Collection<any>\n getActiveTxContext(): TxContext | null\n getQueryContext(): MVQueryContext\n }\n | undefined\n\n /**\n * Optional back-reference to the owning compartment's ref\n * enforcer. When present, `Collection.put` calls\n * `refEnforcer.enforceRefsOnPut(name, record)` before the adapter\n * write, and `Collection.delete` calls\n * `refEnforcer.enforceRefsOnDelete(name, id)` before its own\n * adapter delete. The Vault handles the actual registry\n * lookup and cross-collection enforcement — Collection just\n * notifies it at the right points in the lifecycle.\n *\n * Typed as a structural interface rather than `Vault`\n * directly to avoid a circular import. Vault implements\n * these two methods; any other object with the same shape would\n * work too (used only in unit tests).\n */\n private readonly refEnforcer:\n | {\n enforceRefsOnPut(collectionName: string, record: unknown): Promise<void>\n enforceRefsOnDelete(collectionName: string, id: string): Promise<void>\n }\n | undefined\n\n /**\n * Optional back-reference to the owning compartment's join resolver\n *`). When present,\n * `Collection.query()` builds a `JoinContext` that lets the Query\n * resolve `.join(field)` calls into target collections via this\n * resolver.\n *\n * Two methods:\n * - `resolveSource(name)` — fetch a `JoinableSource` for the\n * right-side collection by name. Returning `null` means \"no\n * such collection in this compartment\" — the executor then\n * throws an actionable error naming the missing target.\n * - `resolveRef(leftCollection, field)` — look up the ref\n * descriptor the left collection declared for this field.\n * `null` when the field has no ref, which makes `.join()`\n * throw at plan time before any records are touched.\n *\n * Typed structurally rather than as `Vault` to avoid a\n * circular import. Vault implements these two methods; any\n * other object with the same shape works too (used only in unit\n * tests against a plain object).\n */\n private readonly joinResolver:\n | {\n resolveSource(collectionName: string): JoinableSource | null\n resolveRef(leftCollection: string, field: string): RefDescriptor | null\n resolveDictSource?: (leftCollection: string, field: string) => JoinableSource | null\n }\n | undefined\n\n constructor(opts: {\n adapter: NoydbStore\n vault: string\n name: string\n keyring: UnlockedKeyring\n encrypted: boolean\n emitter: NoydbEventEmitter\n getDEK: (collectionName: string) => Promise<CryptoKey>\n historyConfig?: HistoryConfig | undefined\n onDirty?: OnDirtyCallback | undefined\n /**\n * tree-shake seam. When omitted, `collection.blob(id)` throws\n * with a pointer at the `@noy-db/hub/blobs` subpath. When set (via\n * `createNoydb({ blobStrategy: blobs() })`), blob storage is live.\n * `@internal` by virtue of `BlobStrategy` being `@internal`.\n */\n blobStrategy?: BlobStrategy | undefined\n aggregateStrategy?: AggregateStrategy | undefined\n crdtStrategy?: CrdtStrategy | undefined\n /**\n * tree-shake seam — strategy for optional history/ledger/\n * time-machine. When omitted, history snapshots and ledger appends\n * become silent no-ops (data still writes); the read APIs\n * (`history`, `getVersion`, `revert`, `diff`, `clearHistory`,\n * `pruneRecordHistory`) throw with a pointer at `@noy-db/hub/history`.\n */\n historyStrategy?: HistoryStrategy | undefined\n i18nStrategy?: I18nStrategy | undefined\n syncStrategy?: SyncStrategy | undefined\n /**\n * tree-shake seam. When omitted, indexing is off for this\n * collection — every `.lazyQuery()` call throws, `.rebuildIndexes()`\n * is a no-op, and `indexes: [...]` declarations are ignored. Enable\n * by passing `withIndexing()` from `@noy-db/hub/indexing` at\n * `createNoydb` time.\n */\n indexStrategy?: IndexStrategy | undefined\n indexes?: IndexDef[] | undefined\n /**\n * Auto-reconcile behavior for persisted-index drift on lazy-mode\n * collections. Defaults to `'off'` — operators call\n * `collection.reconcileIndex(field)` explicitly.\n *\n * - `'off'` (default): no implicit work. Same semantics as.\n * - `'dry-run'`: on first lazy-mode query, run\n * `reconcileIndex(field, { dryRun: true })` per declared field\n * and emit `index:reconciled` with the diff. Nothing is written.\n * - `'auto'`: same walk as `'dry-run'` but with `dryRun: false`.\n * Drift is repaired in-place and the fix count surfaces on the\n * event.\n *\n * Unattended long-lived processes (Workers, Node services with no\n * human operator) should set `'auto'`. Attended desktop apps should\n * leave it `'off'` and surface a manual \"rebuild indexes\" button.\n */\n reconcileOnOpen?: 'off' | 'dry-run' | 'auto'\n /**\n * Hydration mode. `'eager'` (default) loads everything into memory on\n * first access — matches behavior exactly. `'lazy'` defers loads\n * to per-id `get()` calls and bounds memory via the `cache` option.\n */\n prefetch?: boolean\n /**\n * LRU cache options. Only meaningful when `prefetch: false`. At least\n * one of `maxRecords` or `maxBytes` must be set in lazy mode — an\n * unbounded lazy cache defeats the purpose.\n */\n cache?: CacheOptions | undefined\n /**\n * Optional Standard Schema v1 validator (Zod, Valibot, ArkType,\n * Effect Schema, etc.). When set, every `put()` is validated before\n * encryption and every read is validated after decryption. See the\n * `schema` field docstring for the error semantics.\n */\n schema?: StandardSchemaV1<unknown, T> | undefined\n /**\n * Optional reference to the compartment's hash-chained ledger.\n * When present, successful mutations append a ledger entry via\n * `LedgerStore.append()`. Constructed at the Vault level and\n * threaded through — see the Vault.collection() source for\n * the wiring.\n */\n ledger?: LedgerStore | undefined\n /**\n * Optional back-reference to the owning compartment's ref\n * enforcer`).\n * Collection.put calls `enforceRefsOnPut` before the adapter\n * write; Collection.delete calls `enforceRefsOnDelete` before\n * its own adapter delete. See the `refEnforcer` field docstring\n * for the full protocol.\n */\n refEnforcer?:\n | {\n enforceRefsOnPut(collectionName: string, record: unknown): Promise<void>\n enforceRefsOnDelete(collectionName: string, id: string): Promise<void>\n }\n | undefined\n /**\n * Optional back-reference to the owning compartment's join\n * resolver. When present, `query()` builds a\n * `JoinContext` so `.join(field)` can resolve through the\n * existing `ref()` declaration into the target collection.\n * Absent in tests that construct a Collection directly without\n * a vault; production usage always has one because\n * Vault.collection() passes `this` through.\n */\n joinResolver?:\n | {\n resolveSource(collectionName: string): JoinableSource | null\n resolveRef(leftCollection: string, field: string): RefDescriptor | null\n }\n | undefined\n /** — i18nText field descriptors for locale-aware reads. */\n i18nFields?: Record<string, I18nTextDescriptor> | undefined\n /** — dictKey field descriptors for label resolution on reads. */\n dictKeyFields?: Record<string, DictKeyDescriptor> | undefined\n /**\n * async callback that resolves a dict key to its label\n * for a given locale. Provided by the Vault.\n */\n dictLabelResolver?:\n | ((\n dictName: string,\n key: string,\n locale: string,\n fallback?: string | readonly string[],\n ) => Promise<string | undefined>)\n | undefined\n /**\n * synchronous callback that validates i18nText fields\n * on put. Provided by the Vault. Throws MissingTranslationError.\n */\n i18nPutValidator?: ((record: unknown) => void) | undefined\n /**\n * translator callback from Noydb. When present, missing\n * translations for `autoTranslate: true` i18nText fields are generated\n * before the i18n validator runs.\n */\n autoTranslateHook?:\n | ((text: string, from: string, to: string, field: string, collection: string) => Promise<string>)\n | undefined\n /**\n * vault-default locale, inherited from\n * `openVault(name, { locale })` or `vault.setLocale()`.\n */\n defaultLocale?: string | undefined\n /**\n * collection-level conflict resolution policy.\n * Overrides the db-level `conflict` option for this collection only.\n */\n conflictPolicy?: ConflictPolicy<T> | undefined\n /**\n * callback to register an envelope-level resolver with the\n * SyncEngine. Provided by the Vault (wired from the SyncEngine).\n */\n onRegisterConflictResolver?: ((name: string, resolver: CollectionConflictResolver) => void) | undefined\n /**\n * CRDT mode for this collection. When set, `put()` stores\n * CRDT state in the envelope and `get()` returns the resolved snapshot.\n * `getRaw(id)` returns the full CRDT state for merge operations.\n */\n crdt?: CrdtMode | undefined\n /**\n * optional remote/sync adapter. When present, `presence()`\n * writes heartbeats to this adapter so other devices can read them.\n * If the adapter implements pub/sub, presence updates are real-time.\n */\n syncAdapter?: NoydbStore | undefined\n /**\n * called by the collection after every successful\n * `get` / `put` / `delete`. The Vault installs a callback that\n * appends a consent-audit entry when `withConsent` is active;\n * outside a consent scope the callback is a no-op. Awaited so a\n * thrown audit write surfaces to the caller.\n */\n onAccess?: (op: 'get' | 'put' | 'delete', id: string) => Promise<void>\n /**\n * invoked by `put`/`delete` before any adapter\n * write. Receives the prior envelope timestamp + decrypted\n * record (or `null` if no prior) and the incoming record (or\n * `null` for delete). Throws `PeriodClosedError` to abort.\n */\n /**\n * opt-in deterministic-encryption index.\n *\n * Field names listed here get a deterministic AES-GCM ciphertext\n * attached to every envelope's `_det` map, which enables blind\n * equality search via `collection.findByDet(field, value)`.\n *\n * **Leaks equality.** Two records with the same value in a\n * deterministic field produce identical ciphertexts, so anyone\n * with store access can tell which records share a value without\n * learning the value itself. This is the textbook trade-off of\n * deterministic encryption — strictly opt-in for that reason.\n *\n * Declaring any field here without also passing\n * `acknowledgeDeterministicRisk: true` throws at construction,\n * so the risk must be explicitly acknowledged.\n */\n deterministicFields?: readonly string[] | undefined\n /**\n * gate for `deterministicFields`. Must be `true` when\n * any deterministic field is declared. Any other value throws.\n */\n acknowledgeDeterministicRisk?: boolean | undefined\n /**\n * declared tiers this collection supports. An\n * undefined or empty list disables the hierarchical-tier surface\n * on this collection (`putAtTier`, `getAtTier`, `elevate`, `demote`\n * throw). Tier 0 is implicit and always available.\n */\n tiers?: readonly number[] | undefined\n /**\n * what a lower-tier caller sees for above-tier\n * records. Default `'invisibility'`.\n */\n tierMode?: TierMode | undefined\n /**\n * optional callback fired on every cross-tier access.\n * Provided by the Vault; collects notification events and writes\n * to the ledger.\n */\n onCrossTierAccess?: ((event: CrossTierAccessEvent) => void) | undefined\n periodGuard?: (\n existing: { ts: string | null; record: Record<string, unknown> | null } | null,\n incoming: Record<string, unknown> | null,\n ) => Promise<void>\n /**\n * Optional back-reference to the owning vault's guard registry +\n * read-only facade. When present, put/delete consult registered\n * guards for this collection. Same structural-interface pattern\n * as `refEnforcer` to avoid a circular Vault import.\n */\n guardSource?: {\n registry(): GuardRegistry\n readOnlyVault(): ReadOnlyVaultFacade\n } | undefined\n /**\n * Optional back-reference to the owning vault's derivation\n * registry + collection accessor. When present, successful\n * `put()` dispatches registered derivation strategies for the\n * source collection. Same structural-interface pattern as\n * `guardSource` to avoid a circular Vault import.\n */\n derivationSource?: {\n registry(): DerivationRegistry\n getCollection(name: string): Collection<Record<string, unknown>>\n /**\n * Read-only vault facade handed to `derive(source, ctx)` so a\n * derivation can fetch sibling records (#147). Same shape and\n * instance the guards subsystem uses for `check(incoming, ctx)`.\n */\n getReadOnlyFacade(): ReadOnlyVaultFacade\n /**\n * Read access to the owning Noydb's currently-active multi-record\n * transaction context, or `null` when no transaction is running.\n * `dispatchDerivations` consults this so a recursive derived-output\n * write can register its pre-write envelope onto `ctx._executed`\n * and roll back alongside the source op on mid-batch failure (#133).\n */\n getActiveTxContext(): TxContext | null\n /**\n * Construct a transient TxContext bound to the owning Noydb. Used\n * by `Collection.putManyAtomic` to publish an active context for\n * its Phase 2 loop (#133).\n */\n createTxContext(): TxContext\n /** Publish a TxContext for the duration of a bulk-atomic loop. */\n setActiveTxContext(ctx: TxContext): void\n /** Drop a previously-published TxContext. */\n clearActiveTxContext(ctx: TxContext): void\n } | undefined\n /**\n * Vault-internal hook for materialized-view dispatch (#143/#150).\n * Parallel to `derivationSource`. When set, `Collection.put` fires\n * registered MV `onSourceWrite` after the standard derivation\n * dispatch.\n */\n materializedViewSource?: {\n \n registry(): MaterializedViewRegistry\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n getCollection(name: string): Collection<any>\n getActiveTxContext(): TxContext | null\n getQueryContext(): MVQueryContext\n } | undefined\n }) {\n this.adapter = opts.adapter\n this.vault = opts.vault\n this.name = opts.name\n this.keyring = opts.keyring\n this.encrypted = opts.encrypted\n this.emitter = opts.emitter\n this.blobStrategy = opts.blobStrategy ?? NO_BLOBS\n this.aggregateStrategy = opts.aggregateStrategy ?? NO_AGGREGATE\n this.crdtStrategy = opts.crdtStrategy ?? NO_CRDT\n this.historyStrategy = opts.historyStrategy ?? NO_HISTORY\n this.i18nStrategy = opts.i18nStrategy ?? NO_I18N\n this.syncStrategy = opts.syncStrategy ?? NO_SYNC\n this.reconcileOnOpen = opts.reconcileOnOpen ?? 'off'\n this.getDEK = opts.getDEK\n this.onDirty = opts.onDirty\n this.historyConfig = opts.historyConfig ?? { enabled: true }\n this.schema = opts.schema\n this.ledger = opts.ledger\n this.refEnforcer = opts.refEnforcer\n this.joinResolver = opts.joinResolver\n this.i18nFields = opts.i18nFields\n this.dictKeyFields = opts.dictKeyFields\n this.dictLabelResolver = opts.dictLabelResolver\n this.i18nPutValidator = opts.i18nPutValidator\n this.autoTranslateHook = opts.autoTranslateHook\n this.defaultLocale = opts.defaultLocale\n this.crdtMode = opts.crdt\n this.syncAdapter = opts.syncAdapter\n this.onAccess = opts.onAccess\n this.periodGuard = opts.periodGuard\n this.guardSource = opts.guardSource\n this.derivationSource = opts.derivationSource\n this.materializedViewSource = opts.materializedViewSource\n\n // hierarchical-tier wiring\n this.tiers = opts.tiers && opts.tiers.length > 0 ? new Set(opts.tiers) : null\n this.tierMode = opts.tierMode ?? 'invisibility'\n this.onCrossTierAccess = opts.onCrossTierAccess\n\n // deterministic-encryption wiring\n if (opts.deterministicFields && opts.deterministicFields.length > 0) {\n if (opts.acknowledgeDeterministicRisk !== true) {\n throw new Error(\n `Collection \"${opts.name}\": deterministicFields requires \\`acknowledgeDeterministicRisk: true\\`. ` +\n `Deterministic encryption leaks equality between records — two records with the same field value ` +\n `produce identical ciphertexts visible to anyone with store access. If that trade-off is acceptable ` +\n `for your threat model, set \\`acknowledgeDeterministicRisk: true\\` to enable.`,\n )\n }\n this.deterministicFields = Object.freeze(new Set(opts.deterministicFields))\n } else {\n this.deterministicFields = null\n }\n\n // register CRDT conflict resolver with SyncEngine\n if (opts.crdt && opts.onRegisterConflictResolver) {\n const crdtMode = opts.crdt\n const crdtResolver: CollectionConflictResolver = async (_id, local, remote) => {\n if (crdtMode === 'yjs') {\n // Core cannot merge Yjs without the yjs package — take the higher version\n return local._v >= remote._v ? local : remote\n }\n const localJson = await this.decryptJsonString(local)\n const remoteJson = await this.decryptJsonString(remote)\n const localState = JSON.parse(localJson) as CrdtState\n const remoteState = JSON.parse(remoteJson) as CrdtState\n const merged = this.crdtStrategy.mergeCrdtStates(localState, remoteState)\n const mergedVersion = Math.max(local._v, remote._v) + 1\n return this.encryptJsonString(JSON.stringify(merged), mergedVersion)\n }\n opts.onRegisterConflictResolver(this.name, crdtResolver)\n }\n\n // build and register per-collection conflict resolver with SyncEngine\n if (opts.conflictPolicy !== undefined && opts.onRegisterConflictResolver) {\n const policy = opts.conflictPolicy\n const compartmentName = this.vault\n const collectionName = this.name\n const emitter = this.emitter\n let resolver: CollectionConflictResolver\n\n if (policy === 'last-writer-wins') {\n resolver = async (_id, local, remote) => (local._ts >= remote._ts ? local : remote)\n } else if (policy === 'first-writer-wins') {\n resolver = async (_id, local, remote) => (local._v <= remote._v ? local : remote)\n } else if (policy === 'manual') {\n resolver = (id, local, remote) =>\n new Promise<EncryptedEnvelope | null>(resolvePromise => {\n let settled = false\n const resolveCallback = (winner: EncryptedEnvelope | null) => {\n if (!settled) {\n settled = true\n resolvePromise(winner)\n }\n }\n emitter.emit('sync:conflict', {\n vault: compartmentName,\n collection: collectionName,\n id,\n local,\n remote,\n localVersion: local._v,\n remoteVersion: remote._v,\n resolve: resolveCallback,\n })\n // Defer if no handler called resolve synchronously\n if (!settled) {\n settled = true\n resolvePromise(null)\n }\n })\n } else {\n // Custom merge fn: decrypt both → merge → re-encrypt\n const mergeFn = policy as (local: T, remote: T) => T\n resolver = async (_id, local, remote) => {\n const localRecord = await this.decryptRecord(local, { skipValidation: true })\n const remoteRecord = await this.decryptRecord(remote, { skipValidation: true })\n const merged = mergeFn(localRecord, remoteRecord)\n const mergedVersion = Math.max(local._v, remote._v) + 1\n return this.encryptRecord(merged, mergedVersion)\n }\n }\n\n opts.onRegisterConflictResolver(collectionName, resolver)\n }\n\n // Default `prefetch: true` keeps semantics. Only opt-in to lazy\n // mode when the consumer explicitly sets `prefetch: false`.\n this.lazy = opts.prefetch === false\n\n if (this.lazy) {\n if (!opts.cache || (opts.cache.maxRecords === undefined && opts.cache.maxBytes === undefined)) {\n throw new Error(\n `Collection \"${this.name}\": lazy mode (prefetch: false) requires a cache option ` +\n `with maxRecords and/or maxBytes. An unbounded lazy cache defeats the purpose.`,\n )\n }\n const lruOptions: { maxRecords?: number; maxBytes?: number } = {}\n if (opts.cache.maxRecords !== undefined) lruOptions.maxRecords = opts.cache.maxRecords\n if (opts.cache.maxBytes !== undefined) lruOptions.maxBytes = parseBytes(opts.cache.maxBytes)\n this.lru = new Lru<string, { record: T; version: number }>(lruOptions)\n this.hydrated = true // lazy mode is always \"hydrated\" — no bulk load\n } else {\n this.lru = null\n }\n\n // delegate mirror construction + declaration to the active\n // indexing strategy. `NO_INDEXING` returns a state whose accessors\n // both return null; the active strategy (from `@noy-db/hub/indexing`)\n // constructs the appropriate mirror based on lazy vs eager mode and\n // declares every IndexDef. With NO_INDEXING the heavy index classes\n // never reach the bundle.\n const strategy = opts.indexStrategy ?? NO_INDEXING\n this.indexState = strategy.createState({\n defs: opts.indexes ?? [],\n lazy: this.lazy,\n })\n }\n\n /**\n * Return the Standard Schema validator attached to this collection,\n * or `undefined` if none was provided at construction time.\n *\n * Exposed (read-only) for the Vault-level export primitive,\n * which surfaces each collection's schema in the per-chunk metadata\n * so downstream serializers (`@noy-db/as-*` packages, custom\n * exporters) can produce schema-aware output without poking at\n * collection internals. The validator object is returned by\n * reference — callers must treat it as immutable.\n */\n getSchema(): StandardSchemaV1<unknown, T> | undefined {\n return this.schema\n }\n\n /**\n * Get a single record by ID.\n *\n * @param id Record identifier.\n * @param locale Optional locale options. When provided,\n * `i18nText` fields are resolved to the requested locale\n * string, and `dictKey` fields get a `<field>Label`\n * virtual field added. Pass `{ locale: 'raw' }` to\n * return the full `{ [locale]: string }` map instead.\n *\n * @returns The decrypted (and optionally locale-resolved) record, or\n * `null` if not found.\n */\n async get(id: string, locale?: LocaleReadOptions): Promise<T | null> {\n // --- Lazy derivation resolution ---\n // If this collection is the output of a lazy-mode derivation\n // strategy, consult the stale map and re-derive on demand before\n // reading. No-op when nothing is pending — keeps the read fast\n // path cheap.\n if (this.derivationSource !== undefined) {\n const registry = this.derivationSource.registry()\n if (registry.strategiesProducingOutput(this.name).length > 0) {\n await resolveStaleOnRead(this.derivationSource, this.name, id)\n }\n }\n\n // Lazy-MV resolve-on-read (#151). When the collection being read\n // is the output of a registered lazy MV that has at least one\n // pending stale flag, run the executor before returning. No-op\n // when nothing is pending.\n if (this.materializedViewSource !== undefined) {\n const { resolveStaleMVOnRead } = await import('./materialized-views/stale.js')\n await resolveStaleMVOnRead(this.materializedViewSource, this.name)\n }\n\n let record: T | null\n\n if (this.lazy && this.lru) {\n // Cache hit: promote and return.\n const cached = this.lru.get(id)\n if (cached) {\n record = cached.record\n } else {\n // Cache miss: hit the adapter, decrypt, populate the LRU.\n const envelope = await this.adapter.get(this.vault, this.name, id)\n if (!envelope) return null\n record = await this.decryptRecord(envelope)\n this.lru.set(id, { record, version: envelope._v }, estimateRecordBytes(record))\n }\n } else {\n // Eager mode: load everything once, then serve from the in-memory map.\n await this.ensureHydrated()\n const entry = this.cache.get(id)\n record = entry ? entry.record : null\n }\n\n if (record === null) return null\n await this.onAccess?.('get', id)\n return this.applyLocaleToRecord(record, locale)\n }\n\n /**\n * Return the raw CRDT state for a record.\n * Only available on collections configured with `crdt: 'lww-map' | 'rga' | 'yjs'`.\n * Use this for merge operations or to pass to `@noy-db/yjs`.\n * Throws if the collection is not in CRDT mode.\n */\n async getRaw(id: string): Promise<CrdtState | null> {\n if (!this.crdtMode) {\n throw new Error(\n `Collection \"${this.name}\": getRaw() is only available when the collection ` +\n `is created with a 'crdt' option ('lww-map', 'rga', or 'yjs').`,\n )\n }\n const envelope = await this.adapter.get(this.vault, this.name, id)\n if (!envelope) return null\n const json = await this.decryptJsonString(envelope)\n return JSON.parse(json) as CrdtState\n }\n\n /**\n * Return a presence handle for this collection.\n *\n * The handle manages an encrypted ephemeral presence channel keyed by an\n * HKDF derivation of this collection's DEK. Presence payloads are invisible\n * to the adapter.\n *\n * @param opts.staleMs Milliseconds before a peer is considered inactive.\n * Default: 30 000.\n * @param opts.pollIntervalMs Milliseconds between storage polls (fallback mode).\n * Default: 5 000.\n */\n presence<P = unknown>(opts?: { staleMs?: number; pollIntervalMs?: number }): PresenceHandle<P> {\n const presenceOpts: PresenceHandleOpts = {\n adapter: this.adapter,\n vault: this.vault,\n collectionName: this.name,\n userId: this.keyring.userId,\n encrypted: this.encrypted,\n getDEK: this.getDEK,\n }\n if (this.syncAdapter !== undefined) presenceOpts.syncAdapter = this.syncAdapter\n if (opts?.staleMs !== undefined) presenceOpts.staleMs = opts.staleMs\n if (opts?.pollIntervalMs !== undefined) presenceOpts.pollIntervalMs = opts.pollIntervalMs\n return this.syncStrategy.buildPresence<P>(presenceOpts)\n }\n\n /**\n * Create or update a record.\n *\n * @param id Record identifier.\n * @param record The record body (validated by the collection's schema\n * if one was attached at `vault.collection(...)` time).\n * @param options Optional metadata for audit + import workflows.\n * `reason` is stamped onto the resulting ledger entry\n * (see #1) so audit consumers can filter via\n * `entries.filter(e => e.reason?.startsWith('import:'))`.\n */\n async put(id: string, record: T, options?: { readonly reason?: string }): Promise<void> {\n if (!hasWritePermission(this.keyring, this.name)) {\n throw new ReadOnlyError()\n }\n\n // Guard hook (record lock + field freeze). Runs BEFORE the\n // period guard so a guard-blocked write fails before any\n // schema work, i18n translation, history, or ledger churn.\n // Inside an active amendment we skip the synchronous check\n // and frozen-field diff — those run at commit time on the\n // collected change-set instead.\n if (this.guardSource) {\n const registry = this.guardSource.registry()\n const guards = registry.guardsFor(this.name)\n if (guards.length > 0) {\n const existingEnv = await this.adapter.get(this.vault, this.name, id)\n let existingRecord: Record<string, unknown> | null = null\n if (existingEnv) {\n try {\n existingRecord = (await this.decryptRecord(existingEnv, { skipValidation: true })) as unknown as Record<string, unknown>\n } catch {\n existingRecord = null\n }\n }\n const incomingRecord = record as unknown as Record<string, unknown>\n const ctx = {\n existing: existingRecord,\n vault: this.guardSource.readOnlyVault(),\n userId: this.keyring.userId,\n role: this.keyring.role,\n }\n if (registry.isAmendmentActive()) {\n const vBefore = existingEnv?._v ?? 0\n // `put` deterministically bumps version by 1 — see the\n // `version = existing.version + 1` line further down in this\n // method. Computing vAfter here keeps the audit math local\n // to the call site that decides it.\n registry.collectChange(this.name, id, existingRecord, incomingRecord, vBefore, vBefore + 1)\n } else {\n await registry.runChecks(this.name, incomingRecord, ctx)\n // Dynamic-import the executor only when at least one guard\n // is registered AND a non-amendment write fires. Consumers\n // who never call `withGuard()` never reach this branch and\n // never pull `GuardExecutor` into their bundle (#130).\n const { GuardExecutor } = (await import('./guards/executor.js')) as { GuardExecutor: typeof GuardExecutorType }\n for (const g of guards) {\n await GuardExecutor.checkFrozenFields(g, id, existingRecord, incomingRecord)\n }\n }\n }\n }\n\n // accounting-period guard. Runs BEFORE any other\n // work so a closed-period write fails fast and leaves no partial\n // trace (no schema work, no i18n translation, no history). Reads\n // the existing envelope + decrypts the prior record so\n // business-date comparison against the closed period's\n // `dateField` can use the stored value (late entries don't slip\n // through a write-time check). For first-time inserts the prior\n // is null.\n if (this.periodGuard !== undefined) {\n const existingEnv = await this.adapter.get(this.vault, this.name, id)\n let priorRecord: Record<string, unknown> | null = null\n if (existingEnv) {\n try {\n priorRecord = (await this.decryptRecord(existingEnv, { skipValidation: true })) as unknown as Record<string, unknown>\n } catch {\n priorRecord = null\n }\n }\n await this.periodGuard(\n existingEnv ? { ts: existingEnv._ts, record: priorRecord } : null,\n record as unknown as Record<string, unknown>,\n )\n }\n\n // Schema validation — runs BEFORE encryption so invalid records are\n // rejected at the store boundary. The validator may transform the\n // input (e.g., coerce strings → numbers, strip unknown fields), in\n // which case we persist the validated value rather than the raw one.\n // Users who pass a bad shape get a SchemaValidationError with a\n // structured issue list, not a stack trace from deep inside the\n // encrypt path.\n if (this.schema !== undefined) {\n record = await validateSchemaInput(this.schema, record, `put(${id})`)\n }\n\n // Auto-translate missing i18nText translations.\n // Runs BEFORE i18n validation so translated values satisfy the\n // required-locale constraint. Throws TranslatorNotConfiguredError\n // when a field has autoTranslate: true but no hook was configured.\n if (this.i18nFields) {\n const obj = record as Record<string, unknown>\n for (const [field, descriptor] of Object.entries(this.i18nFields)) {\n if (!descriptor.options.autoTranslate) continue\n const value = obj[field]\n if (!value || typeof value !== 'object' || Array.isArray(value)) continue\n const map = value as Record<string, string>\n // Determine which locales need translation. For 'all', translate all\n // declared languages that are missing. For 'any', only translate if\n // none are present. For string[], translate the listed required ones.\n const { languages, required } = descriptor.options\n const missing: string[] = languages.filter(\n (lang) => !(lang in map) || map[lang] === '',\n )\n if (missing.length === 0) continue\n // Find a source locale (first present non-empty value)\n const sourceLocale = languages.find((l) => l in map && map[l] !== '')\n if (!sourceLocale) continue\n if (!this.autoTranslateHook) {\n throw new TranslatorNotConfiguredError(field, this.name)\n }\n // Only translate locales that are actually needed\n const toTranslate =\n required === 'any'\n ? [] // 'any' is already satisfied since sourceLocale exists\n : required === 'all'\n ? missing\n : missing.filter((l) => required.includes(l))\n const translated = { ...map }\n for (const targetLocale of toTranslate) {\n translated[targetLocale] = await this.autoTranslateHook(\n map[sourceLocale]!,\n sourceLocale,\n targetLocale,\n field,\n this.name,\n )\n }\n ;(record as Record<string, unknown>)[field] = translated\n }\n }\n\n // i18nText validation — runs AFTER schema validation so\n // the record shape is trustworthy. Throws MissingTranslationError\n // when required translations are absent.\n if (this.i18nPutValidator !== undefined) {\n this.i18nPutValidator(record)\n }\n\n // Foreign-key ref enforcement. Runs AFTER schema\n // validation (so the record shape is trustworthy) but BEFORE\n // any write (so a failed strict ref leaves no trace on disk,\n // in history, or in the ledger). The Vault handles the\n // actual target lookups — see `enforceRefsOnPut` over there.\n if (this.refEnforcer !== undefined) {\n await this.refEnforcer.enforceRefsOnPut(this.name, record)\n }\n\n // ─── CRDT mode ─────────────────────────────────────────\n // In CRDT mode we always read the raw envelope from the adapter to get\n // the existing CRDT state, merge the incoming record into it, then\n // encrypt the merged CRDT state — bypassing the normal version path.\n if (this.crdtMode) {\n const existingEnvelope = await this.adapter.get(this.vault, this.name, id)\n const existingVersion = existingEnvelope?._v ?? 0\n const now = new Date().toISOString()\n\n let crdtState: CrdtState\n\n if (this.crdtMode === 'lww-map') {\n let existingState: LwwMapState | undefined\n if (existingEnvelope) {\n const prevJson = await this.decryptJsonString(existingEnvelope)\n const prevParsed = JSON.parse(prevJson) as unknown\n if (prevParsed !== null && typeof prevParsed === 'object' && '_crdt' in prevParsed) {\n existingState = prevParsed as LwwMapState\n }\n }\n crdtState = this.crdtStrategy.buildLwwMapState(record as Record<string, unknown>, existingState, now)\n } else if (this.crdtMode === 'rga') {\n let existingState: RgaState | undefined\n if (existingEnvelope) {\n const prevJson = await this.decryptJsonString(existingEnvelope)\n const prevParsed = JSON.parse(prevJson) as unknown\n if (prevParsed !== null && typeof prevParsed === 'object' && '_crdt' in prevParsed) {\n existingState = prevParsed as RgaState\n }\n }\n const arr = Array.isArray(record) ? record : [record]\n crdtState = this.crdtStrategy.buildRgaState(arr, existingState, generateULID)\n } else {\n // yjs: record is the base64 update string (produced by @noy-db/yjs)\n crdtState = { _crdt: 'yjs', update: record as unknown as string }\n }\n\n const version = existingVersion + 1\n const envelope = await this.encryptJsonString(JSON.stringify(crdtState), version)\n await this.adapter.put(this.vault, this.name, id, envelope)\n\n // Resolve snapshot for cache and history\n const resolvedRecord = this.crdtStrategy.resolveCrdtSnapshot(crdtState) as T\n const existingResolved = existingEnvelope\n ? { record: await this.decryptRecord(existingEnvelope, { skipValidation: true }), version: existingVersion }\n : undefined\n\n if (existingResolved && this.historyConfig.enabled !== false) {\n const histEnvelope = await this.encryptRecord(existingResolved.record, existingResolved.version)\n await this.historyStrategy.saveHistory(this.adapter, this.vault, this.name, id, histEnvelope)\n this.emitter.emit('history:save', { vault: this.vault, collection: this.name, id, version: existingResolved.version })\n if (this.historyConfig.maxVersions) {\n await this.historyStrategy.pruneHistory(this.adapter, this.vault, this.name, id, { keepVersions: this.historyConfig.maxVersions })\n }\n }\n\n if (this.ledger) {\n const appendInput: Parameters<typeof this.ledger.append>[0] = {\n op: 'put', collection: this.name, id, version, actor: this.keyring.userId,\n payloadHash: await this.historyStrategy.envelopePayloadHash(envelope),\n }\n if (existingResolved) appendInput.delta = this.historyStrategy.computePatch(resolvedRecord, existingResolved.record)\n if (options?.reason !== undefined) appendInput.reason = options.reason\n await this.ledger.append(appendInput)\n }\n\n if (this.lazy && this.lru) {\n this.lru.set(id, { record: resolvedRecord, version }, estimateRecordBytes(resolvedRecord))\n await this.maintainPersistedIndexesOnPut(\n id,\n resolvedRecord,\n existingResolved ? existingResolved.record : null,\n version,\n )\n } else {\n this.cache.set(id, { record: resolvedRecord, version })\n this.indexes?.upsert(id, resolvedRecord, existingResolved ? existingResolved.record : null)\n }\n\n await this.onDirty?.(this.name, id, 'put', version)\n this.emitter.emit('change', { vault: this.vault, collection: this.name, id, action: 'put' } satisfies ChangeEvent)\n await this.onAccess?.('put', id)\n await this.dispatchDerivations(id, record, version)\n await this.dispatchMaterializedViews(id, record)\n return\n }\n // ─── End CRDT mode ──────────────────────────────────────────────────\n\n // Resolve the previous record. In eager mode this comes from the\n // in-memory map (no I/O); in lazy mode we have to ask the adapter\n // because the record may have been evicted (or never loaded).\n let existing: { record: T; version: number } | undefined\n if (this.lazy && this.lru) {\n existing = this.lru.get(id)\n if (!existing) {\n const previousEnvelope = await this.adapter.get(this.vault, this.name, id)\n if (previousEnvelope) {\n const previousRecord = await this.decryptRecord(previousEnvelope)\n existing = { record: previousRecord, version: previousEnvelope._v }\n }\n }\n } else {\n await this.ensureHydrated()\n existing = this.cache.get(id)\n }\n\n const version = existing ? existing.version + 1 : 1\n\n // Save history snapshot of the PREVIOUS version before overwriting\n if (existing && this.historyConfig.enabled !== false) {\n const historyEnvelope = await this.encryptRecord(existing.record, existing.version)\n await this.historyStrategy.saveHistory(this.adapter, this.vault, this.name, id, historyEnvelope)\n\n this.emitter.emit('history:save', {\n vault: this.vault,\n collection: this.name,\n id,\n version: existing.version,\n })\n\n // Auto-prune if maxVersions configured\n if (this.historyConfig.maxVersions) {\n await this.historyStrategy.pruneHistory(this.adapter, this.vault, this.name, id, {\n keepVersions: this.historyConfig.maxVersions,\n })\n }\n }\n\n const envelope = await this.encryptRecord(record, version)\n await this.adapter.put(this.vault, this.name, id, envelope)\n\n // Ledger append — AFTER the adapter write succeeds so a failed\n // write never produces an orphan ledger entry. Computing the\n // payloadHash here uses the envelope we just wrote, which is the\n // exact bytes the adapter now holds. The ledger entry records\n // only metadata (collection, id, version, hash) — NOT the record\n // itself — and is then encrypted with the compartment's ledger\n // DEK, preserving zero-knowledge. See `LedgerStore.append`.\n //\n // **Delta history**: if there was a previous version, we\n // compute a JSON Patch from it to the new record and pass it\n // through `append.delta`. The LedgerStore stores the patch in\n // the sibling `_ledger_deltas/` collection and records its hash\n // in the entry's `deltaHash` field. Genesis puts (no existing\n // record) leave `delta` undefined — there's nothing to diff\n // against — and the ledger entry has no `deltaHash`.\n if (this.ledger) {\n const appendInput: Parameters<typeof this.ledger.append>[0] = {\n op: 'put',\n collection: this.name,\n id,\n version,\n actor: this.keyring.userId,\n payloadHash: await this.historyStrategy.envelopePayloadHash(envelope),\n }\n if (existing) {\n // REVERSE patch: describes how to undo this put — i.e., how\n // to transform the NEW record back into the PREVIOUS one.\n // Storing reverse patches lets `ledger.reconstruct()` walk\n // backward from the current state (readily available in the\n // data collection) without needing a forward-walking base\n // snapshot, which would double the storage cost of the\n // delta scheme. See `LedgerStore.reconstruct` for the walk.\n appendInput.delta = this.historyStrategy.computePatch(record, existing.record)\n }\n if (options?.reason !== undefined) appendInput.reason = options.reason\n await this.ledger.append(appendInput)\n }\n\n if (this.lazy && this.lru) {\n this.lru.set(id, { record, version }, estimateRecordBytes(record))\n // Maintain persisted-index side-cars. Lazy mode is the\n // only place `persistedIndexes` is populated; eager mode uses the\n // in-memory `CollectionIndexes` above.\n await this.maintainPersistedIndexesOnPut(id, record, existing ? existing.record : null, version)\n } else {\n this.cache.set(id, { record, version })\n // Update secondary indexes incrementally — no-op if no indexes are\n // declared. Pass the previous record (if any) so old buckets are\n // cleaned up before the new value is added.\n this.indexes?.upsert(id, record, existing ? existing.record : null)\n }\n\n await this.onDirty?.(this.name, id, 'put', version)\n\n this.emitter.emit('change', {\n vault: this.vault,\n collection: this.name,\n id,\n action: 'put',\n } satisfies ChangeEvent)\n\n await this.onAccess?.('put', id)\n\n // Derivation dispatch — AFTER store + ledger + emitter commit so a\n // failed source-write never produces orphan derived outputs. The\n // recursive `put` into output collections re-enters this pipeline\n // (encrypt + ledger + emit) intentionally; cycle detection at vault\n // open is the primary defense against infinite recursion.\n await this.dispatchDerivations(id, record, version)\n await this.dispatchMaterializedViews(id, record)\n }\n\n /**\n * Fire registered MV strategies whose dependency set includes this\n * collection. Eager-mode MVs re-materialize inline via\n * `MaterializedViewExecutor.refresh`; lazy / manual modes are\n * no-ops in the foundation (subtask #150) — wired in #151.\n *\n * Skips entirely when the record being written is itself an\n * MV-emitted row (carries `_materializedFrom`) — defensive guard\n * against missed cycle detection.\n *\n * @internal\n */\n private async dispatchMaterializedViews(id: string, record: T): Promise<void> {\n void id\n if (this.materializedViewSource === undefined) return\n const incoming = record as unknown as Record<string, unknown>\n if (incoming && typeof incoming === 'object' && '_materializedFrom' in incoming) return\n const registry = this.materializedViewSource.registry()\n const mvs = registry.mvsForSource(this.name)\n if (mvs.length === 0) return\n // Dynamic-import the executor only on first eager-MV dispatch —\n // keeps the MV executor chunk out of the floor bundle (mirrors the\n // #130 dynamic-import pattern v1 uses for derivations). Lazy mode\n // uses the pure-helper `markMVStale` which lives in `stale.js` and\n // is also dynamic-imported (only when at least one lazy MV depends\n // on this source).\n let executor: typeof MVExecutorType | null = null\n let staleHelpers: typeof MVStaleModule | null = null\n for (const reg of mvs) {\n const mode = reg.spec.refresh\n if (mode === 'eager') {\n if (executor === null) {\n ;({ MaterializedViewExecutor: executor } = await import('./materialized-views/executor.js'))\n }\n await executor.refresh(reg, {\n getCollection: (name) => this.materializedViewSource!.getCollection(name),\n getActiveTxContext: () => this.materializedViewSource!.getActiveTxContext(),\n getQueryContext: () => this.materializedViewSource!.getQueryContext(),\n })\n } else if (mode === 'lazy') {\n if (staleHelpers === null) {\n staleHelpers = await import('./materialized-views/stale.js')\n }\n staleHelpers.markMVStale(registry, reg.spec.name)\n }\n // manual: no-op on source-write. `vault.refreshView(name)` is\n // the only path that materializes a manual MV.\n }\n }\n\n /**\n * Fire registered derivation strategies for this source collection.\n * Eager mode runs `derive` inline and writes each output via the\n * sibling `Collection.put`; lazy mode marks dependent outputs stale\n * (D11 stub today). Errors in non-strict mode are logged and\n * skipped; strict mode propagates the first failing output's error.\n *\n * Skips entirely when the record being written is itself a derived\n * output (carries `_derivedFrom`) — defensive guard against missed\n * cycle detection.\n */\n private async dispatchDerivations(id: string, record: T, version: number): Promise<void> {\n if (this.derivationSource === undefined) return\n const incoming = record as unknown as Record<string, unknown>\n if (incoming && typeof incoming === 'object' && '_derivedFrom' in incoming) return\n const registry = this.derivationSource.registry()\n const strategies = registry.strategiesForSource(this.name)\n if (strategies.length === 0) return\n // Dynamic-import the executor only on the first eager-mode\n // dispatch. Lazy-mode dispatches use `markStale` (a pure helper)\n // which doesn't reach into the executor at all. Keeps the\n // derivation executor chunk out of the floor bundle for any\n // consumer that doesn't fire an eager derivation (#130).\n let DerivationExecutor: typeof DerivationExecutorType | null = null\n for (const { spec, strategyHash } of strategies) {\n const mode = typeof spec.lifecycle === 'string' ? spec.lifecycle : spec.lifecycle.mode\n if (mode === 'eager') {\n if (DerivationExecutor === null) {\n ({ DerivationExecutor } = (await import('./derivations/executor.js')) as { DerivationExecutor: typeof DerivationExecutorType })\n }\n const sourceWithId = { ...incoming, id } as Record<string, unknown> & { id: string }\n const ctx = { vault: this.derivationSource.getReadOnlyFacade() }\n const result = await DerivationExecutor.run(spec, sourceWithId, version, strategyHash, ctx)\n for (const key of Object.keys(spec.outputs)) {\n const out = result.outputs[key]\n if (!out) continue\n if (out.kind === 'failed') {\n const err = out.error\n if (spec.strict) throw err\n console.warn(`[derivation] output \"${key}\" for source \"${spec.source}\" id=\"${id}\" failed:`, err)\n continue\n }\n const outSpec = spec.outputs[key]\n if (!outSpec) continue\n const outputCollection = this.derivationSource.getCollection(outSpec.collection)\n // #133 — if we're inside a multi-record transaction, register\n // derived writes as side-effect ops on the active ctx\n // BEFORE they fire. `revertExecuted` walks `_executed` in\n // reverse on rollback, so capturing the pre-write envelope\n // here lets a later mid-batch failure restore this output's\n // prior state alongside the source op. Outside a transaction\n // the context is null and tracking is skipped.\n const txCtx = this.derivationSource.getActiveTxContext()\n\n // ── Array-shape branch (#200) ──────────────────────────\n if (out.kind === 'array') {\n // Load the prior key set from the fanout sidecar.\n const { loadFanoutSidecar, saveFanoutSidecar } = await import('./derivations/fanout-sidecar.js')\n const prior = await loadFanoutSidecar(\n this.adapter,\n this.vault,\n spec.source,\n id,\n key,\n )\n const prevKeys = new Set<string>(prior?.keys ?? [])\n const newKeysList = out.entries.map(e => e.key)\n const newKeysSet = new Set<string>(newKeysList)\n\n // Diff — delete keys that were in prev but not in new.\n for (const k of prevKeys) {\n if (newKeysSet.has(k)) continue\n await outputCollection._internalDelete(k, txCtx)\n }\n\n // Upsert every entry in the new set. (Slice 1: no\n // identity-skip optimisation; write every row, idempotent\n // at the (collection, id) level.)\n for (const entry of out.entries) {\n if (txCtx !== null) {\n const priorEnvelope = await this.adapter.get(this.vault, outSpec.collection, entry.key)\n txCtx._executed.push({\n op: {\n type: 'put',\n vaultName: this.vault,\n collectionName: outSpec.collection,\n id: entry.key,\n },\n priorEnvelope,\n })\n }\n await outputCollection.put(entry.key, entry.value)\n }\n\n // Persist the new key set (last step — see spec §5.1\n // on failure-mode symmetry).\n await saveFanoutSidecar(this.adapter, this.vault, {\n source: spec.source,\n sourceId: id,\n outputKey: key,\n outputCollection: outSpec.collection,\n keys: newKeysList,\n })\n continue\n }\n\n // ── Record-shape branch (existing v1 behavior) ─────────\n if (out.skipped === true) {\n // #144: optional output returned null. Delete the\n // previously-emitted output at this id, if any. Routed\n // through `_internalDelete` so a user-registered\n // `onDelete` (#145) on the output collection does NOT\n // fire — this is a system-internal tombstone, not a\n // user-initiated delete. The txCtx hookup captures the\n // prior envelope inside `_internalDelete` for #133-style\n // rollback symmetry; delete-of-absent is a silent no-op.\n await outputCollection._internalDelete(id, txCtx)\n continue\n }\n if (txCtx !== null) {\n const prior = await this.adapter.get(this.vault, outSpec.collection, id)\n txCtx._executed.push({\n op: {\n type: 'put',\n vaultName: this.vault,\n collectionName: outSpec.collection,\n id,\n },\n priorEnvelope: prior,\n })\n }\n await outputCollection.put(id, out.value)\n }\n } else {\n await markStale(registry, spec, id)\n }\n }\n }\n\n /** Delete a record by ID. */\n async delete(id: string): Promise<void> {\n await this._doDelete(id, false)\n }\n\n /**\n * @internal — system-internal delete that bypasses user-facing\n * delete hooks (`onDelete`, accounting-period guard, FK ref\n * enforcer). Used by derivation tombstones (#144) and MV refresh\n * (Dim 14 v2) — system housekeeping shouldn't trip user invariants\n * registered against the output collection. The ledger entry and\n * history snapshot still fire so backup integrity and time-travel\n * reconstruction stay consistent.\n *\n * Returns silently for delete-of-absent (idempotent contract — both\n * paths honour this: the `txCtx === null` path also reads the prior\n * envelope and short-circuits before the ledger/event side-effects).\n *\n * When a `txCtx` is supplied, the prior envelope is captured and\n * pushed onto `txCtx._executed` BEFORE the delete fires — mirrors\n * the #133 rollback hardening for puts. Callers outside a\n * multi-record transaction pass `null` and skip the tracking.\n *\n * Amendment composition: if `_internalDelete` runs while a vault's\n * `GuardRegistry` has an amendment window open, the `{before, after:\n * null}` change pair is pushed onto the amendment change-set the\n * same way a user-initiated delete would. The `onDelete` user-hook\n * is still skipped (housekeeping must not trip user invariants in\n * normal mode), but the amendment's invariant DOES see the change\n * — so a `RCT-CANCEL-001`-style invariant pairing can reject a\n * derivation-driven tombstone fired during an admin amendment.\n *\n * Constraint to surface to consumers: output collections of\n * derivations with `optional: true` outputs should not be the\n * targets of `strict` or `cascade` inbound foreign-key refs —\n * `_internalDelete` bypasses the ref enforcer by design (the\n * `onDelete` bypass primitive). Treat the housekeeping path as\n * \"system can tombstone its own emissions regardless of FK shape.\"\n *\n * Permission handling is unchanged: the caller must still hold\n * write permission on the collection (derivations run under the\n * user's keyring).\n */\n async _internalDelete(id: string, txCtx: TxContext | null = null): Promise<void> {\n // Idempotency contract: short-circuit before any ledger/event\n // side-effect when the target is absent. Both txCtx-aware and\n // txCtx-null callers honour this — `deriveAll` recomputes\n // expense-only allocations that never emitted a receipt without\n // writing spurious v0 ledger entries.\n const prior = await this.adapter.get(this.vault, this.name, id)\n if (prior === null) return\n if (txCtx !== null) {\n txCtx._executed.push({\n op: {\n type: 'delete',\n vaultName: this.vault,\n collectionName: this.name,\n id,\n },\n priorEnvelope: prior,\n })\n }\n await this._doDelete(id, true)\n }\n\n private async _doDelete(id: string, internal: boolean): Promise<void> {\n if (!hasWritePermission(this.keyring, this.name)) {\n throw new ReadOnlyError()\n }\n\n // Guard hook for deletes. Symmetric to put(): consult the\n // registry, decrypt the prior record (if any), then either\n // collect the {before, null} change pair into an active\n // amendment or run the guards' `onDelete` callback. Frozen-field\n // diffing is skipped (it's a put concept). Delete-of-absent is\n // a no-op — no guard is consulted because there's nothing to\n // protect, matching the idempotent-delete contract.\n //\n // For `internal === true` (system housekeeping — derivation\n // tombstones, MV refresh): `onDelete` is bypassed, but the\n // amendment change-collection still runs if a window is open.\n // This means an `amendment.invariant` paired with `onDelete` for\n // \"TRULY unconditional\" rules sees the system delete and can\n // reject it — closing the niwat-review gap where a derivation\n // tombstone fired during an admin amendment would otherwise\n // silently bypass both hooks.\n if (this.guardSource) {\n const registry = this.guardSource.registry()\n const guards = registry.guardsFor(this.name)\n if (guards.length > 0) {\n const existingEnv = await this.adapter.get(this.vault, this.name, id)\n if (existingEnv) {\n let existingRecord: Record<string, unknown> | null = null\n try {\n existingRecord = (await this.decryptRecord(existingEnv, { skipValidation: true })) as unknown as Record<string, unknown>\n } catch {\n existingRecord = null\n }\n if (registry.isAmendmentActive()) {\n // For deletes, the record version is the version that was\n // visible at delete time; we record vBefore = that version\n // and vAfter = same (the ledger entry's `op` discriminator\n // is `delete`, not `put`, so the consumer treats the\n // tombstone shape correctly). Fires for BOTH user and\n // system-internal deletes (#145 follow-up).\n const vBefore = existingEnv._v\n registry.collectChange(\n this.name,\n id,\n existingRecord,\n null as unknown as Record<string, unknown>,\n vBefore,\n vBefore,\n )\n } else if (!internal) {\n // Dedicated delete-time hook (#145). `check` is put-only;\n // `onDelete(existing, ctx)` receives the currently-persisted\n // record and decides whether the deletion is permitted.\n // Skipped for internal deletes — housekeeping must not trip\n // user invariants in normal-mode operation.\n const ctx = {\n existing: existingRecord,\n vault: this.guardSource.readOnlyVault(),\n userId: this.keyring.userId,\n role: this.keyring.role,\n }\n await registry.runOnDelete(\n this.name,\n existingRecord ?? {},\n ctx,\n )\n }\n }\n }\n }\n\n // accounting-period guard (same contract as put;\n // incoming is null because this is a delete).\n if (!internal && this.periodGuard !== undefined) {\n const existingEnv = await this.adapter.get(this.vault, this.name, id)\n let priorRecord: Record<string, unknown> | null = null\n if (existingEnv) {\n try {\n priorRecord = (await this.decryptRecord(existingEnv, { skipValidation: true })) as unknown as Record<string, unknown>\n } catch {\n priorRecord = null\n }\n }\n await this.periodGuard(\n existingEnv ? { ts: existingEnv._ts, record: priorRecord } : null,\n null,\n )\n }\n\n // Foreign-key ref enforcement on delete. Runs BEFORE\n // the adapter delete so a `strict` inbound ref with existing\n // references blocks the delete entirely (no partial state, no\n // history churn, no ledger entry for a rejected op). `cascade`\n // recursively deletes the referencing records first, then falls\n // through to the normal delete path below. `warn` is a no-op\n // here — violations surface through `checkIntegrity()`.\n if (!internal && this.refEnforcer !== undefined) {\n await this.refEnforcer.enforceRefsOnDelete(this.name, id)\n }\n\n // In lazy mode the record may not be cached; ask the adapter so we\n // can still write a history snapshot if history is enabled.\n let existing: { record: T; version: number } | undefined\n if (this.lazy && this.lru) {\n existing = this.lru.get(id)\n if (!existing && this.historyConfig.enabled !== false) {\n const previousEnvelope = await this.adapter.get(this.vault, this.name, id)\n if (previousEnvelope) {\n const previousRecord = await this.decryptRecord(previousEnvelope)\n existing = { record: previousRecord, version: previousEnvelope._v }\n }\n }\n } else {\n existing = this.cache.get(id)\n }\n\n // Save history snapshot before deleting\n if (existing && this.historyConfig.enabled !== false) {\n const historyEnvelope = await this.encryptRecord(existing.record, existing.version)\n await this.historyStrategy.saveHistory(this.adapter, this.vault, this.name, id, historyEnvelope)\n }\n\n // Capture the previous envelope's payloadHash BEFORE delete so we\n // have a stable reference for the ledger entry. The hash is of\n // whatever was last visible to readers — for a `delete` of a\n // never-existed record, we use the empty string (which the\n // ledger entry's `payloadHash` field tolerates).\n const previousEnvelope = await this.adapter.get(this.vault, this.name, id)\n const previousPayloadHash = await this.historyStrategy.envelopePayloadHash(previousEnvelope)\n\n await this.adapter.delete(this.vault, this.name, id)\n\n // Ledger append — same after-write timing as put(). The recorded\n // version is the version that WAS deleted (existing?.version), not\n // a successor. A delete of a missing record still appends an\n // entry with version 0 so the chain captures the intent.\n if (this.ledger) {\n await this.ledger.append({\n op: 'delete',\n collection: this.name,\n id,\n version: existing?.version ?? 0,\n actor: this.keyring.userId,\n payloadHash: previousPayloadHash,\n })\n }\n\n if (this.lazy && this.lru) {\n this.lru.remove(id)\n // Tear down persisted-index side-cars for any declared fields on\n // this record. No-op when no fields are declared or the record\n // had never been indexed (e.g. a delete of a missing id).\n if (existing) {\n await this.maintainPersistedIndexesOnDelete(id, existing.record)\n }\n } else {\n this.cache.delete(id)\n // Remove from secondary indexes — no-op if no indexes are declared\n // or the record wasn't previously indexed.\n if (existing) {\n this.indexes?.remove(id, existing.record)\n }\n }\n\n await this.onDirty?.(this.name, id, 'delete', existing?.version ?? 0)\n\n this.emitter.emit('change', {\n vault: this.vault,\n collection: this.name,\n id,\n action: 'delete',\n } satisfies ChangeEvent)\n\n await this.onAccess?.('delete', id)\n\n // Symmetric to put (#181): user-initiated deletes must fire MV\n // refresh so `onEmpty: 'delete'` MVs tombstone their now-orphan\n // output rows. Gated on `!internal` to prevent recursion — the\n // MV executor's own tombstoning round-trips through\n // `_internalDelete → _doDelete(_, true)` and must NOT re-fire\n // dispatch (matches put's `_materializedFrom` skip in spirit).\n //\n // Record-shape derivations intentionally NOT dispatched on delete:\n // their derived-output id equals the source id, so the user can\n // delete the output directly with `outputCollection.delete(id)` if\n // they want. Array-shape derivations (#200) DO cascade on delete\n // because their derived ids are opaque (from the `key(out)`\n // extractor) — without cascade the rows become unfindable orphans.\n if (!internal) {\n await this.dispatchMaterializedViewsOnDelete(id)\n await this.dispatchArrayDerivationsOnDelete(id)\n }\n }\n\n /**\n * Cascade deletes of array-shape derived rows when a source row is\n * deleted (#200). Reads each registered strategy's fanout sidecar\n * for this source id, deletes every listed derived row, then\n * deletes the sidecar itself.\n *\n * Record-shape derivations are skipped — see _doDelete's comment\n * for why the asymmetry is correct.\n *\n * @internal\n */\n private async dispatchArrayDerivationsOnDelete(id: string): Promise<void> {\n if (this.derivationSource === undefined) return\n const registry = this.derivationSource.registry()\n const strategies = registry.strategiesForSource(this.name)\n if (strategies.length === 0) return\n\n // Dynamic-import the sidecar helpers — keeps the derivation\n // chunk out of the floor bundle for consumers that don't use\n // array-shape derivations.\n let helpers: {\n loadFanoutSidecar: typeof LoadFanoutSidecarType\n deleteFanoutSidecar: typeof DeleteFanoutSidecarType\n saveFanoutSidecar: typeof SaveFanoutSidecarType\n } | null = null\n const txCtx = this.derivationSource.getActiveTxContext()\n\n for (const { spec } of strategies) {\n for (const [outputKey, outSpec] of Object.entries(spec.outputs)) {\n if (outSpec.shape !== 'array') continue\n if (helpers === null) {\n helpers = await import('./derivations/fanout-sidecar.js')\n }\n const sidecar = await helpers.loadFanoutSidecar(\n this.adapter,\n this.vault,\n spec.source,\n id,\n outputKey,\n )\n if (!sidecar) continue\n const outputCollection = this.derivationSource.getCollection(outSpec.collection)\n for (const derivedId of sidecar.keys) {\n await outputCollection._internalDelete(derivedId, txCtx)\n }\n await helpers.deleteFanoutSidecar(this.adapter, this.vault, spec.source, id, outputKey)\n }\n }\n }\n\n /**\n * Mirror of {@link dispatchMaterializedViews} for the delete path\n * (#181). No record content is available (it's gone), so the\n * `_materializedFrom` skip used by the put-side dispatch doesn't\n * apply here — instead, the recursion guard is the `internal` gate\n * at the `_doDelete` call site above.\n *\n * @internal\n */\n private async dispatchMaterializedViewsOnDelete(id: string): Promise<void> {\n void id\n if (this.materializedViewSource === undefined) return\n const registry = this.materializedViewSource.registry()\n const mvs = registry.mvsForSource(this.name)\n if (mvs.length === 0) return\n let executor: typeof MVExecutorType | null = null\n let staleHelpers: typeof MVStaleModule | null = null\n for (const reg of mvs) {\n const mode = reg.spec.refresh\n if (mode === 'eager') {\n if (executor === null) {\n ;({ MaterializedViewExecutor: executor } = await import('./materialized-views/executor.js'))\n }\n await executor.refresh(reg, {\n getCollection: (name) => this.materializedViewSource!.getCollection(name),\n getActiveTxContext: () => this.materializedViewSource!.getActiveTxContext(),\n getQueryContext: () => this.materializedViewSource!.getQueryContext(),\n })\n } else if (mode === 'lazy') {\n if (staleHelpers === null) {\n staleHelpers = await import('./materialized-views/stale.js')\n }\n staleHelpers.markMVStale(registry, reg.spec.name)\n }\n // manual: no-op — `vault.refreshView(name)` is the only path.\n }\n }\n\n /**\n * List all records in the collection.\n *\n * Throws in lazy mode — bulk listing defeats the purpose of lazy\n * hydration. Use `scan()` to iterate over the full collection\n * page-by-page without holding more than `pageSize` records in memory.\n *\n * @param locale Optional locale options. When provided,\n * each record is locale-resolved before being returned.\n */\n async list(locale?: LocaleReadOptions): Promise<T[]> {\n if (this.lazy) {\n throw new Error(\n `Collection \"${this.name}\": list() is not available in lazy mode (prefetch: false). ` +\n `Use collection.scan({ pageSize }) to iterate over the full collection.`,\n )\n }\n // Lazy-MV resolve-on-read (#157 review): if this collection is the\n // output of a registered lazy MV with a pending stale flag, run\n // the executor before returning so callers see fresh data. No-op\n // when nothing is pending — keeps the read path negligible.\n if (this.materializedViewSource !== undefined) {\n const { resolveStaleMVOnRead } = await import('./materialized-views/stale.js')\n await resolveStaleMVOnRead(this.materializedViewSource, this.name)\n }\n await this.ensureHydrated()\n const records = [...this.cache.values()].map(e => e.record)\n if (!locale) return records\n return Promise.all(records.map(r => this.applyLocaleToRecord(r, locale)))\n }\n\n // ─── Bulk operations ─────────────────────────────────────\n\n /**\n * Put many records in one call. Each item is processed sequentially\n * through the normal `put()` path — meaning per-item validation,\n * history snapshots, ledger appends, and change events all still\n * fire. The round-trip saving comes from the adapter staying hot\n * across the batch (no connection re-open, no keyring re-unlock).\n *\n * ## Semantics\n *\n * **Best-effort with per-item results.** If item 5 of 10 fails, items\n * 1–4 are already persisted and items 6–10 are still attempted.\n * The returned {@link PutManyResult} lists every success and failure\n * individually so the caller can decide whether to roll forward\n * (retry the failures) or roll back (manually delete the successes).\n *\n * **True tx-atomic putMany** — pass `{ atomic: true }` to switch\n * to the transaction executor: pre-flight CAS against every\n * item's `expectedVersion`, then commit all ops with best-effort\n * revert on mid-batch failure. Atomic mode throws on failure rather\n * than returning a mixed-results object.\n *\n * ## Change events\n *\n * One `change` event per successfully-written record, same as N\n * single-record puts. Subscribers don't need to special-case bulk.\n */\n async putMany(\n entries: ReadonlyArray<readonly [id: string, record: T, opts?: PutManyItemOptions]>,\n options?: PutManyOptions,\n ): Promise<PutManyResult> {\n if (options?.atomic) {\n return this.putManyAtomic(entries)\n }\n const success: string[] = []\n const failures: Array<{ id: string; error: Error }> = []\n for (const entry of entries) {\n const [id, record] = entry\n try {\n await this.put(id, record)\n success.push(id)\n } catch (error) {\n failures.push({ id, error: error as Error })\n }\n }\n return { ok: failures.length === 0, success, failures }\n }\n\n /**\n * Atomic-mode implementation of {@link putMany}. Pre-flights every\n * `expectedVersion`, executes all puts in declaration order, and\n * reverts executed ops via the raw adapter on mid-batch failure.\n * See `runTransaction` for the shared semantics + crash-window caveat.\n *\n * @internal\n */\n private async putManyAtomic(\n entries: ReadonlyArray<readonly [id: string, record: T, opts?: PutManyItemOptions]>,\n ): Promise<PutManyResult> {\n // Phase 1 — pre-flight CAS + prior-envelope snapshot for revert.\n const priors = new Map<string, EncryptedEnvelope | null>()\n for (const [id, , opts] of entries) {\n if (!priors.has(id)) {\n priors.set(id, await this.adapter.get(this.vault, this.name, id))\n }\n if (opts?.expectedVersion !== undefined) {\n const env = priors.get(id) ?? null\n const actual = env?._v ?? 0\n if (actual !== opts.expectedVersion) {\n throw new ConflictError(\n actual,\n `putMany atomic: ${this.vault}/${this.name}/${id} ` +\n `expected v${opts.expectedVersion}, found v${actual}`,\n )\n }\n }\n }\n // Phase 2 — execute; revert on failure.\n //\n // #133 — when a derivation registry is wired, publish a transient\n // TxContext for the duration of this loop so `dispatchDerivations`\n // can register recursive derived-output writes onto `ctx._executed`.\n // The shared `revertExecuted` helper then unwinds the combined list\n // (source ops + side-effect ops) in reverse, matching the\n // `runTransaction` rollback semantics. When no derivation registry\n // is configured, we still build a local `executed` list and revert\n // it via `revertExecuted` — keeps a single code path.\n const txCtx = this.derivationSource?.createTxContext() ?? null\n if (txCtx !== null && this.derivationSource) {\n this.derivationSource.setActiveTxContext(txCtx)\n }\n const localExecuted: ExecutedOp[] = []\n try {\n for (const [id, record] of entries) {\n // Record the revert plan BEFORE the call so a mid-`put` throw\n // (e.g. strict derivation failure firing after `store.put`\n // already committed the source envelope) still has the source\n // write reverted. Mirrors `runTransaction`'s Phase 2 pattern.\n const entry: ExecutedOp = {\n op: { type: 'put', vaultName: this.vault, collectionName: this.name, id },\n priorEnvelope: priors.get(id) ?? null,\n }\n if (txCtx !== null) txCtx._executed.push(entry)\n else localExecuted.push(entry)\n await this.put(id, record)\n }\n return { ok: true, success: entries.map(([id]) => id), failures: [] }\n } catch (err) {\n const executedForRevert = txCtx !== null ? txCtx._executed : localExecuted\n // Restore prior envelopes via the raw store. Same helper as\n // `runTransaction` for symmetric semantics — walks in reverse,\n // best-effort on each restore.\n await revertExecuted(executedForRevert, this.adapter)\n // Cache desync guard. `revertExecuted` only invalidates caches\n // when given a `Noydb` reference (which we don't have here\n // without widening the constructor surface). Walk the executed\n // ops and invalidate caches via the source collection (this)\n // for entries that target this collection, and via\n // `derivationSource.getCollection(name)` for nested derived\n // outputs that live in sibling collections — otherwise an eager\n // cache on a derived-output collection still serves the\n // rolled-back record.\n for (const { op } of [...executedForRevert].reverse()) {\n if (op.vaultName !== this.vault) continue\n try {\n if (op.collectionName === this.name) {\n await this._invalidateCacheEntry(op.id)\n } else if (this.derivationSource) {\n const sibling = this.derivationSource.getCollection(op.collectionName)\n await sibling._invalidateCacheEntry(op.id)\n }\n } catch { /* best-effort */ }\n }\n throw err\n } finally {\n if (txCtx !== null && this.derivationSource) {\n this.derivationSource.clearActiveTxContext(txCtx)\n }\n }\n }\n\n /**\n * Get many records in one call. Returns a `Map<id, T | null>` —\n * missing records surface as `null` entries so the caller can\n * distinguish \"not found\" from \"lookup failed\". Order-stable\n * iteration (Map preserves insertion order = input `ids` order).\n *\n * Reads go through the per-id `get()` path, which means the cache\n * / hydration logic stays consistent with single-record reads.\n */\n async getMany(ids: readonly string[]): Promise<Map<string, T | null>> {\n const result = new Map<string, T | null>()\n for (const id of ids) {\n result.set(id, await this.get(id))\n }\n return result\n }\n\n /**\n * Delete many records in one call. Same best-effort contract as\n * {@link putMany}: if item 5 fails, items 1–4 are already deleted\n * and items 6–10 are still attempted.\n *\n * Deleting a non-existent id is not a failure — matches the\n * idempotent semantics of single-record `delete()`.\n */\n async deleteMany(ids: readonly string[]): Promise<DeleteManyResult> {\n const success: string[] = []\n const failures: Array<{ id: string; error: Error }> = []\n for (const id of ids) {\n try {\n await this.delete(id)\n success.push(id)\n } catch (error) {\n failures.push({ id, error: error as Error })\n }\n }\n return { ok: failures.length === 0, success, failures }\n }\n\n /**\n * Build a chainable query against the collection. Returns a `Query<T>`\n * builder when called with no arguments.\n *\n * Backward-compatible overload: passing a predicate function returns\n * the filtered records directly (the API). Prefer the chainable\n * form for new code.\n *\n * **Lazy-MV gap (#157):** `query()` is synchronous and does NOT\n * trigger lazy materialized-view resolve-on-read. If this\n * collection is a lazy MV's output and the MV is currently stale,\n * `query().toArray()` returns the pre-stale snapshot. To force a\n * fresh read on a lazy MV, either call `list()` (which DOES\n * trigger resolve) or `vault.refreshView(mvName)` before querying.\n * The proper fix — extending `QuerySource` with an async prepare\n * hook — is a separate PR.\n *\n * @example\n * ```ts\n * // New chainable API:\n * const overdue = invoices.query()\n * .where('status', '==', 'open')\n * .where('dueDate', '<', new Date())\n * .orderBy('dueDate')\n * .toArray();\n *\n * // Legacy predicate form (still supported):\n * const drafts = invoices.query(i => i.status === 'draft');\n * ```\n */\n query(): Query<T>\n query(predicate: (record: T) => boolean): T[]\n query(predicate?: (record: T) => boolean): Query<T> | T[] {\n if (this.lazy) {\n throw new Error(\n `Collection \"${this.name}\": query() is not available in lazy mode (prefetch: false). ` +\n `Use collection.lazyQuery() for indexed reads, or collection.scan({ pageSize }) ` +\n `and filter the streamed records with a regular for-await loop.`,\n )\n }\n if (predicate !== undefined) {\n // Legacy form: synchronous predicate filter against the cache.\n return [...this.cache.values()].map(e => e.record).filter(predicate)\n }\n // New form: return a chainable builder bound to this collection's cache.\n const source: QuerySource<T> = {\n snapshot: () => [...this.cache.values()].map(e => e.record),\n subscribe: (cb: () => void) => {\n const handler = (event: ChangeEvent): void => {\n if (event.vault === this.vault && event.collection === this.name) {\n cb()\n }\n }\n this.emitter.on('change', handler)\n return () => this.emitter.off('change', handler)\n },\n // Index-aware fast path for `==` and `in` operators on indexed\n // fields. The Query builder consults these when present and falls\n // back to a linear scan otherwise.\n getIndexes: () => this.getIndexes(),\n lookupById: (id: string) => this.cache.get(id)?.record,\n }\n // Build a JoinContext if the vault passed a join resolver.\n // Without one, .join() on the resulting Query will throw with an\n // actionable error — the case is unreachable in production but\n // matters for unit tests that construct Collection directly.\n const resolver = this.joinResolver\n const leftCollection = this.name\n const joinContext: JoinContext | undefined = resolver\n ? {\n leftCollection,\n resolveRef: (field: string) => resolver.resolveRef(leftCollection, field),\n resolveSource: (collectionName: string) => resolver.resolveSource(collectionName),\n ...(resolver.resolveDictSource\n ? { resolveDictSource: (field: string) => resolver.resolveDictSource!(leftCollection, field) }\n : {}),\n }\n : undefined\n return new Query<T>(source, undefined, joinContext, this.aggregateStrategy)\n }\n\n /**\n * Subscribe to every put/delete on this collection. Returns an\n * unsubscribe function.\n *\n * Fires **after** the store write has committed — subscribers see\n * only materialised state, never in-flight or rolled-back writes.\n *\n * This is an event stream, not a reactive value. For reactive\n * \"current array state\" semantics use `query().live()`. Typical\n * use cases for `subscribe()`:\n * - audit-trail / activity-feed UI that lists events as they happen\n * - Pinia-per-collection wiring where each store subscribes once\n * - outbox-style workers that process every new record\n *\n * The callback receives a `CollectionChangeEvent<T>`:\n * - `{ type: 'put', id, record }` — record is the current\n * decrypted value. May be `null` if another op deleted the\n * record between the emit and the handler firing (rare race).\n * - `{ type: 'delete', id, record: null }` — deletion event;\n * the record content is gone by the time the handler runs.\n *\n * The callback is invoked synchronously *with respect to the emit\n * moment*, but the record lookup is async (cache hit for eager\n * collections; one `get()` for lazy collections). If your handler\n * does not need the record, cast it away and ignore — the lookup\n * is still performed, but it's cheap on the hydrated path.\n *\n * ergonomic wrapper over `db.on('change', …)` that\n * filters to this collection and hydrates the record.\n */\n subscribe(cb: (event: CollectionChangeEvent<T>) => void): () => void {\n const handler = (event: ChangeEvent): void => {\n if (event.vault !== this.vault || event.collection !== this.name) return\n if (event.action === 'put') {\n // Cache hit in eager mode; get() in lazy mode.\n void this.get(event.id).then(record => {\n cb({ type: 'put', id: event.id, record: record ?? null })\n }).catch(() => {\n // Record vanished between emit + lookup (race). Emit with null\n // so subscribers still see the event they were promised.\n cb({ type: 'put', id: event.id, record: null })\n })\n } else {\n // delete\n cb({ type: 'delete', id: event.id, record: null })\n }\n }\n this.emitter.on('change', handler)\n return () => {\n this.emitter.off('change', handler)\n }\n }\n\n /**\n * Return a minimal JoinableSource view of this collection's\n * in-memory cache. Used by the Vault's `resolveSource`\n * method when another collection's `.join()` needs to probe this\n * one as the right side.\n *\n * The returned object captures the cache reference through a\n * closure, so subsequent mutations to the cache are visible to\n * the joined query. That's intentional: a join that fires after\n * the right-side collection has been updated should see the\n * fresh data.\n *\n * Throws in lazy mode because the cache is bounded and could\n * silently miss records — consistent with the `query()` /\n * `list()` lazy-mode policy. If this becomes a blocker for a\n * real consumer, the fix is to add an async `scan()`-backed\n * variant of this method, which is exactly what streaming\n * joins will need anyway.\n */\n querySourceForJoin(): JoinableSource {\n if (this.lazy) {\n throw new Error(\n `Collection \"${this.name}\": .join() cannot use a lazy-mode ` +\n `collection as the right side. Opening it in eager mode ` +\n `(prefetch: true, default) makes it joinable. Streaming joins ` +\n `over lazy collections are not yet supported.`,\n )\n }\n // Structural source — the join executor calls snapshot() and\n // lookupById(); the live-join executor additionally calls\n // subscribe() so right-side mutations propagate. We capture\n // `this.cache` and `this.emitter` by closure so later mutations\n // are visible to the snapshot view AND drive live re-fires.\n return {\n snapshot: () => [...this.cache.values()].map(e => e.record),\n lookupById: (id: string) => this.cache.get(id)?.record,\n subscribe: (cb: () => void) => {\n const handler = (event: ChangeEvent): void => {\n if (event.vault === this.vault && event.collection === this.name) {\n cb()\n }\n }\n this.emitter.on('change', handler)\n return () => this.emitter.off('change', handler)\n },\n }\n }\n\n /**\n * Cache statistics — useful for devtools, monitoring, and verifying\n * that LRU eviction is happening as expected in lazy mode.\n *\n * In eager mode, returns size only (no hits/misses are tracked because\n * every read is a cache hit by construction). In lazy mode, returns\n * the full LRU stats: `{ hits, misses, evictions, size, bytes }`.\n */\n cacheStats(): CacheStats {\n if (this.lazy && this.lru) {\n return { ...this.lru.stats(), lazy: true }\n }\n return {\n hits: 0,\n misses: 0,\n evictions: 0,\n size: this.cache.size,\n bytes: 0,\n lazy: false,\n }\n }\n\n // ─── History Methods ────────────────────────────────────────────\n\n /** Get version history for a record, newest first. */\n async history(id: string, options?: HistoryOptions): Promise<HistoryEntry<T>[]> {\n const envelopes = await this.historyStrategy.getHistoryEntries(\n this.adapter, this.vault, this.name, id, options,\n )\n\n const entries: HistoryEntry<T>[] = []\n for (const env of envelopes) {\n // History reads skip schema validation — see getVersion() docs.\n const record = await this.decryptRecord(env, { skipValidation: true })\n entries.push({\n version: env._v,\n timestamp: env._ts,\n userId: env._by ?? '',\n record,\n })\n }\n return entries\n }\n\n /**\n * Get a specific past version of a record.\n *\n * History reads intentionally **skip schema validation** — historical\n * records predate the current schema by definition, so validating them\n * against today's shape would be a false positive on any schema\n * evolution. If a caller needs validated history, they should filter\n * and re-put the records through the normal `put()` path.\n */\n async getVersion(id: string, version: number): Promise<T | null> {\n const envelope = await this.historyStrategy.getVersionEnvelope(\n this.adapter, this.vault, this.name, id, version,\n )\n if (!envelope) return null\n return this.decryptRecord(envelope, { skipValidation: true })\n }\n\n /** Revert a record to a past version. Creates a new version with the old content. */\n async revert(id: string, version: number): Promise<void> {\n const oldRecord = await this.getVersion(id, version)\n if (!oldRecord) {\n throw new Error(`Version ${version} not found for record \"${id}\"`)\n }\n await this.put(id, oldRecord)\n }\n\n /**\n * Compare two versions of a record and return the differences.\n * Use version 0 to represent \"before creation\" (empty).\n * Omit versionB to compare against the current version.\n */\n async diff(id: string, versionA: number, versionB?: number): Promise<DiffEntry[]> {\n const recordA = versionA === 0 ? null : await this.resolveVersion(id, versionA)\n const recordB = versionB === undefined || versionB === 0\n ? (versionB === 0 ? null : await this.resolveCurrentOrVersion(id))\n : await this.resolveVersion(id, versionB)\n return this.historyStrategy.diff(recordA, recordB)\n }\n\n /** Resolve a version: try history first, then check if it's the current version. */\n private async resolveVersion(id: string, version: number): Promise<T | null> {\n // Check history\n const fromHistory = await this.getVersion(id, version)\n if (fromHistory) return fromHistory\n // Check if it's the current live version\n await this.ensureHydrated()\n const current = this.cache.get(id)\n if (current && current.version === version) return current.record\n return null\n }\n\n private async resolveCurrentOrVersion(id: string): Promise<T | null> {\n await this.ensureHydrated()\n return this.cache.get(id)?.record ?? null\n }\n\n /** Prune history entries for a record (or all records if id is undefined). */\n async pruneRecordHistory(id: string | undefined, options: PruneOptions): Promise<number> {\n const pruned = await this.historyStrategy.pruneHistory(\n this.adapter, this.vault, this.name, id, options,\n )\n if (pruned > 0) {\n this.emitter.emit('history:prune', {\n vault: this.vault,\n collection: this.name,\n id: id ?? '*',\n pruned,\n })\n }\n return pruned\n }\n\n /** Clear all history for this collection (or a specific record). */\n async clearHistory(id?: string): Promise<number> {\n return this.historyStrategy.clearHistory(this.adapter, this.vault, this.name, id)\n }\n\n // ─── Core Methods ─────────────────────────────────────────────\n\n /**\n * Count records in the collection.\n *\n * In eager mode this returns the in-memory cache size (instant). In\n * lazy mode it asks the adapter via `list()` to enumerate ids — slower\n * but still correct, and avoids loading any record bodies into memory.\n */\n async count(): Promise<number> {\n if (this.lazy) {\n const ids = await this.adapter.list(this.vault, this.name)\n return ids.length\n }\n await this.ensureHydrated()\n return this.cache.size\n }\n\n // ─── Pagination & Streaming ───────────────────────────────────\n\n /**\n * Fetch a single page of records via the adapter's optional `listPage`\n * extension. Returns the decrypted records for this page plus an opaque\n * cursor for the next page.\n *\n * Pass `cursor: undefined` (or omit it) to start from the beginning.\n * The final page returns `nextCursor: null`.\n *\n * If the adapter does NOT implement `listPage`, this falls back to a\n * synthetic implementation: it loads all ids via `list()`, sorts them,\n * and slices a window. The first call emits a one-time console.warn so\n * developers can spot adapters that should opt into the fast path.\n */\n async listPage(opts: { cursor?: string; limit?: number } = {}): Promise<{\n items: T[]\n nextCursor: string | null\n }> {\n const limit = opts.limit ?? 100\n\n if (this.adapter.listPage) {\n const result = await this.adapter.listPage(this.vault, this.name, opts.cursor, limit)\n const decrypted: T[] = []\n for (const { record, version, id } of await this.decryptPage(result.items)) {\n // Update cache opportunistically — if the page-fetched record isn't\n // in cache yet, populate it. This makes a subsequent .get(id) free.\n // In LAZY mode we deliberately do NOT populate the LRU here:\n // streaming a 100K-record collection should not turn the LRU into\n // a giant write-once buffer that immediately evicts everything.\n // Random-access workloads via .get() are what the LRU is for.\n if (!this.lazy && !this.cache.has(id)) {\n this.cache.set(id, { record, version })\n }\n decrypted.push(record)\n }\n return { items: decrypted, nextCursor: result.nextCursor }\n }\n\n // Fallback: synthetic pagination over list() + get(). Slower than the\n // native path because every id requires its own round-trip, but\n // correct for adapters that haven't opted in.\n warnOnceFallback(this.adapter.name ?? 'unknown')\n const ids = (await this.adapter.list(this.vault, this.name)).slice().sort()\n const start = opts.cursor ? parseInt(opts.cursor, 10) : 0\n const end = Math.min(start + limit, ids.length)\n const items: T[] = []\n for (let i = start; i < end; i++) {\n const id = ids[i]!\n const envelope = await this.adapter.get(this.vault, this.name, id)\n if (envelope) {\n const record = await this.decryptRecord(envelope)\n items.push(record)\n // Same lazy-mode skip as the native path: don't pollute the LRU\n // with sequential scan results.\n if (!this.lazy && !this.cache.has(id)) {\n this.cache.set(id, { record, version: envelope._v })\n }\n }\n }\n return {\n items,\n nextCursor: end < ids.length ? String(end) : null,\n }\n }\n\n /**\n * Stream every record in the collection page-by-page as an async\n * iterable, with chainable `.where()` / `.filter()` clauses and a\n * memory-bounded `.aggregate(spec)` terminal.\n *\n * The whole point: process collections larger than RAM without\n * ever holding more than `pageSize` records decrypted at once.\n *\n * @example\n * ```ts\n * // Backward-compatible iteration — unchanged from the previous\n * // async-generator shape. `ScanBuilder` implements AsyncIterable.\n * for await (const record of invoices.scan({ pageSize: 500 })) {\n * await processOne(record)\n * }\n *\n * // — streaming aggregation with O(reducers) memory.\n * const { total, n } = await invoices.scan()\n * .where('year', '==', 2025)\n * .aggregate({ total: sum('amount'), n: count() })\n * ```\n *\n * **Lazy-MV gap (#157):** `scan()` is synchronous-build and does\n * NOT trigger lazy materialized-view resolve-on-read. For lazy\n * MVs, call `list()` (which DOES resolve) or `vault.refreshView(name)`\n * before scanning. Same shape as the `query()` limitation.\n *\n * Returns a `ScanBuilder<T>` instead of the raw async iterator\n * that previous versions used. The builder implements\n * `AsyncIterable<T>`, so every existing `for await … of` call\n * continues to work unchanged. Direct `.next()` calls on the\n * iterator — not idiomatic, not used in the codebase — are no\n * longer supported; upgrade to `for await` or call the new\n * `.aggregate()` terminal.\n *\n * Uses `adapter.listPage` when available; otherwise falls back\n * to the synthetic pagination path with the same one-time\n * warning (`listPage()` routes through that fallback internally).\n */\n scan(opts: { pageSize?: number } = {}): ScanBuilder<T> {\n const pageSize = opts.pageSize ?? 100\n // Build a JoinContext if the vault passed a join resolver\n // — same machinery as `query()`. Without one, `.join()`\n // on the resulting ScanBuilder will throw with an actionable\n // error. The resolver is unreachable in production but matters\n // for unit tests that construct Collection directly.\n const resolver = this.joinResolver\n const leftCollection = this.name\n const joinContext: JoinContext | undefined = resolver\n ? {\n leftCollection,\n resolveRef: (field: string) => resolver.resolveRef(leftCollection, field),\n resolveSource: (collectionName: string) => resolver.resolveSource(collectionName),\n ...(resolver.resolveDictSource\n ? { resolveDictSource: (field: string) => resolver.resolveDictSource!(leftCollection, field) }\n : {}),\n }\n : undefined\n // The page provider closure is bound to this collection's\n // listPage method so the builder is free of any `this`\n // coupling. Rebinding through the arrow keeps the unbound-\n // method lint rule happy — matches the pattern used in\n // builder.ts's candidateRecords helper.\n return new ScanBuilder<T>(\n {\n listPage: (listOpts) => this.listPage(listOpts),\n },\n pageSize,\n [],\n [],\n joinContext,\n )\n }\n\n /** Decrypt a page of envelopes returned by `adapter.listPage`. */\n private async decryptPage(\n items: ListPageResult['items'],\n ): Promise<Array<{ id: string; record: T; version: number }>> {\n const out: Array<{ id: string; record: T; version: number }> = []\n for (const { id, envelope } of items) {\n const record = await this.decryptRecord(envelope)\n out.push({ id, record, version: envelope._v })\n }\n return out\n }\n\n // ─── Internal ──────────────────────────────────────────────────\n\n /** Load all records from adapter into memory cache. */\n /**\n * @internal — refresh the in-memory cache entry for a single id by\n * re-reading from the adapter. Used by the transaction executor's\n * Phase-3 revert path: that path writes the prior envelope directly\n * via the raw store (to avoid re-firing Collection-level side\n * effects), which would otherwise leave this Collection's eager\n * cache holding the rolled-back value. After revert, the executor\n * calls this hook so subsequent `get` / `query` reads see the\n * actual on-disk state.\n *\n * Lazy mode: drops the LRU entry; the next `get` repopulates from\n * the adapter. Eager mode: re-reads the envelope and either sets\n * the cache entry (record still present) or deletes it (record was\n * gone before the tx and the revert deleted it again).\n */\n async _invalidateCacheEntry(id: string): Promise<void> {\n if (this.lazy && this.lru) {\n this.lru.remove(id)\n return\n }\n if (!this.hydrated) return\n const previous = this.cache.get(id)\n const envelope = await this.adapter.get(this.vault, this.name, id)\n if (!envelope) {\n this.cache.delete(id)\n if (previous) this.indexes?.remove(id, previous.record)\n return\n }\n const record = await this.decryptRecord(envelope)\n this.cache.set(id, { record, version: envelope._v })\n this.indexes?.upsert(id, record, previous ? previous.record : null)\n }\n\n private async ensureHydrated(): Promise<void> {\n if (this.hydrated) return\n\n const ids = await this.adapter.list(this.vault, this.name)\n for (const id of ids) {\n const envelope = await this.adapter.get(this.vault, this.name, id)\n if (envelope) {\n const record = await this.decryptRecord(envelope)\n this.cache.set(id, { record, version: envelope._v })\n }\n }\n this.hydrated = true\n this.rebuildEagerIndexesFromCache()\n }\n\n /** Hydrate from a pre-loaded snapshot (used by Vault). */\n async hydrateFromSnapshot(records: Record<string, EncryptedEnvelope>): Promise<void> {\n for (const [id, envelope] of Object.entries(records)) {\n const record = await this.decryptRecord(envelope)\n this.cache.set(id, { record, version: envelope._v })\n }\n this.hydrated = true\n this.rebuildEagerIndexesFromCache()\n }\n\n /**\n * Rebuild secondary indexes from the current in-memory cache.\n *\n * Called after any bulk hydration. Incremental put/delete updates\n * are handled by `indexes.upsert()` / `indexes.remove()` directly,\n * so this only fires for full reloads.\n *\n * Synchronous and O(N × indexes.size); for the target scale of\n * 1K–50K records this completes in single-digit milliseconds.\n */\n private rebuildEagerIndexesFromCache(): void {\n const eager = this.indexes\n if (!eager || eager.fields().length === 0) return\n const snapshot: Array<{ id: string; record: T }> = []\n for (const [id, entry] of this.cache) {\n snapshot.push({ id, record: entry.record })\n }\n eager.build(snapshot)\n }\n\n /**\n * Rebuild every declared index from scratch.\n *\n * Eager mode: refreshes the in-memory `CollectionIndexes` from the\n * current cache — O(records × declaredFields).\n *\n * Lazy mode: tears down every `_idx/<field>/<recordId>`\n * side-car, walks the canonical record namespace, and materialises\n * fresh side-cars for every declared field. The in-memory mirror is\n * cleared and re-ingested. Intended for two scenarios:\n * 1. Adding a new indexed field to a collection that already holds\n * records — after the schema change, call `rebuildIndexes()` to\n * backfill the side-cars.\n * 2. Recovery from a catastrophic drift (audit noticed many\n * `index:write-partial` events, operator wants a clean slate).\n *\n * The rebuild is NOT incremental — it's a full bulk-replace. For\n * per-field drift repair, use `reconcileIndex(field)` instead.\n */\n async rebuildIndexes(): Promise<void> {\n if (!this.lazy) {\n await this.ensureHydrated()\n this.rebuildEagerIndexesFromCache()\n return\n }\n\n const persisted = this.persistedIndexes\n if (!persisted) return\n const fields = persisted.fields()\n if (fields.length === 0) return\n\n // 1. Collect canonical ids (skip every reserved-namespace id —\n // `_idx/`, `_keyring`, `_history/`, `_ledger_deltas/`, `_meta/`,\n // `_ledger`, `_blob_`, etc. User records may not start with `_`\n // per the monorepo convention used across the hub).\n const allIds = await this.adapter.list(this.vault, this.name)\n const canonicalIds: string[] = []\n const staleIdxIds: string[] = []\n for (const id of allIds) {\n if (decodeIdxId(id)) {\n staleIdxIds.push(id)\n } else if (!id.startsWith('_')) {\n canonicalIds.push(id)\n }\n }\n\n // 2. Drop every existing side-car. Errors here are tolerated — the\n // next step overwrites any remnants. If a side-car is for a\n // field that is no longer declared, the delete still removes\n // the stale row from storage.\n for (const id of staleIdxIds) {\n try { await this.adapter.delete(this.vault, this.name, id) } catch { /* ignore */ }\n }\n persisted.clear()\n\n // 3. Walk records and write fresh side-cars for every declared field.\n for (const recordId of canonicalIds) {\n const envelope = await this.adapter.get(this.vault, this.name, recordId)\n if (!envelope) continue\n const record = await this.decryptRecord(envelope, { skipValidation: true })\n await this.maintainPersistedIndexesOnPut(recordId, record, null, envelope._v)\n }\n\n this.persistedIndexesLoaded = true\n }\n\n /**\n * Compare the persisted `_idx/<field>/*` side-cars against the\n * canonical records for a single field, reporting the drift (and\n * optionally repairing it).\n *\n * Lazy mode only. Eager mode throws — the in-memory index cannot\n * drift.\n *\n * `missing` — record ids whose value is indexable but no side-car\n * exists. Happens when a `put()` succeeded but the side-car put\n * failed (surfaced as `index:write-partial`).\n * `stale` — side-car ids pointing to a record that no longer exists\n * or whose current value no longer matches the side-car body.\n * `applied` — number of writes that were actually applied (always 0\n * when `dryRun: true`).\n *\n * Design reference: acceptance criteria.\n */\n async reconcileIndex(\n field: string,\n opts: { dryRun?: boolean } = {},\n ): Promise<{ field: string; missing: string[]; stale: string[]; applied: number }> {\n if (!this.lazy) {\n throw new Error(\n `Collection \"${this.name}\": reconcileIndex is only meaningful in lazy mode ` +\n `(prefetch: false). Eager mode maintains indexes in memory with no drift.`,\n )\n }\n const persisted = this.persistedIndexes\n if (!persisted) {\n throw new Error(\n `Collection \"${this.name}\": indexing is disabled on this Noydb instance. ` +\n `Pass \\`withIndexing()\\` from \"@noy-db/hub/indexing\" to \\`createNoydb({ indexStrategy })\\`.`,\n )\n }\n if (!persisted.has(field)) {\n throw new Error(\n `Collection \"${this.name}\": field \"${field}\" is not declared in indexes. ` +\n `Declare it in the collection options before reconciling.`,\n )\n }\n\n const dryRun = opts.dryRun === true\n const allIds = await this.adapter.list(this.vault, this.name)\n\n // Map side-car recordId → stored value (if readable). Also capture\n // \"stale\" side-cars whose field matches but whose record is gone.\n const sidecar = new Map<string, unknown>()\n const sidecarIds = new Map<string, string>() // recordId -> sidecar id\n for (const id of allIds) {\n const decoded = decodeIdxId(id)\n if (!decoded || decoded.field !== field) continue\n sidecarIds.set(decoded.recordId, id)\n const env = await this.adapter.get(this.vault, this.name, id)\n if (!env) continue\n try {\n const body = JSON.parse(await this.decryptJsonString(env)) as { value: unknown }\n sidecar.set(decoded.recordId, body.value)\n } catch {\n // Unreadable — treat as stale so it gets rewritten.\n sidecar.set(decoded.recordId, undefined)\n }\n }\n\n // Walk canonical records and compare against side-car state.\n const missing: string[] = []\n const stale: string[] = []\n const fixesPut: Array<{ recordId: string; record: T; version: number }> = []\n for (const id of allIds) {\n if (decodeIdxId(id)) continue\n if (id.startsWith('_')) continue\n const env = await this.adapter.get(this.vault, this.name, id)\n if (!env) continue\n const record = await this.decryptRecord(env, { skipValidation: true })\n const live = readPersistedValue(record as unknown as Record<string, unknown>, field)\n const stored = sidecar.get(id)\n const hasSidecar = sidecarIds.has(id)\n const indexable = live !== null && live !== undefined\n\n if (indexable && !hasSidecar) {\n missing.push(id)\n fixesPut.push({ recordId: id, record, version: env._v })\n } else if (indexable && hasSidecar && !valuesMatch(stored, live)) {\n // Side-car body drifted from live value (e.g. partial write\n // after an update). Rewrite so lookups agree with reality.\n missing.push(id)\n fixesPut.push({ recordId: id, record, version: env._v })\n } else if (!indexable && hasSidecar) {\n // Record exists but its value is no longer indexable (null/\n // undefined). The side-car is stale.\n stale.push(sidecarIds.get(id)!)\n }\n sidecarIds.delete(id)\n }\n // Any side-car whose canonical record vanished is stale.\n for (const [, idxId] of sidecarIds) stale.push(idxId)\n\n let applied = 0\n if (!dryRun) {\n for (const idxId of stale) {\n try {\n await this.adapter.delete(this.vault, this.name, idxId)\n applied++\n } catch { /* ignore — next reconcile picks it up */ }\n }\n for (const fix of fixesPut) {\n await this.maintainPersistedIndexesOnPut(fix.recordId, fix.record, null, fix.version)\n applied++\n }\n // In-memory mirror is authoritative for query dispatch — make\n // sure it matches what's on disk now.\n persisted.clear()\n this.persistedIndexesLoaded = false\n await this.ensurePersistedIndexesLoaded()\n }\n\n return { field, missing, stale, applied }\n }\n\n /**\n * Get the in-memory index store. Used by `Query` to short-circuit\n * `==` and `in` lookups when an index covers the where clause.\n *\n * Returns `null` if no indexes are declared on this collection.\n */\n getIndexes(): CollectionIndexes | null {\n const eager = this.indexes\n return eager && eager.fields().length > 0 ? eager : null\n }\n\n /**\n * Return a `BlobSet` for the given record id.\n *\n * No I/O is performed until you call a method on the handle.\n *\n * ```ts\n * const blobs = invoices.blob('inv-001')\n *\n * // Upload a PDF (deduplicates automatically, MIME auto-detected)\n * await blobs.put('receipt.pdf', pdfBytes)\n *\n * // List slots\n * const files = await blobs.list() // SlotInfo[]\n *\n * // Serve as HTTP response (Content-Type, ETag, streaming body)\n * const res = await blobs.response('receipt.pdf', { inline: true })\n *\n * // Publish a named version (amendment versioning)\n * await blobs.publish('receipt.pdf', 'issued-2025-01')\n *\n * // Raw bytes\n * const bytes = await blobs.get('receipt.pdf')\n * ```\n *\n * Blobs are stored in internal collections (`_blob_slots_*`, `_blob_index`,\n * `_blob_chunks`, `_blob_versions_*`) that are excluded from queries and\n * `list()`. Slot metadata uses this collection's DEK; chunk data uses a\n * vault-shared `_blob` DEK (enabling cross-collection deduplication).\n */\n blob(id: string): BlobSet {\n // tree-shake refactor: delegate to `blobStrategy`. The default\n // is `NO_BLOBS` (throws with a message pointing at the `@noy-db/hub/blobs`\n // subpath). Users who want blob storage pass `blobs()` from that\n // subpath into `createNoydb({ blobStrategy: blobs() })`, which\n // threads the active strategy through Vault → Collection.\n return this.blobStrategy.openSlot({\n store: this.adapter,\n vault: this.vault,\n collection: this.name,\n recordId: id,\n getDEK: this.getDEK,\n encrypted: this.encrypted,\n userId: this.keyring.userId,\n })\n }\n\n /** Get all records as encrypted envelopes (for dump). */\n async dumpEnvelopes(): Promise<Record<string, EncryptedEnvelope>> {\n await this.ensureHydrated()\n const result: Record<string, EncryptedEnvelope> = {}\n for (const [id, entry] of this.cache) {\n result[id] = await this.encryptRecord(entry.record, entry.version)\n }\n return result\n }\n\n /**\n * Apply locale resolution to a record.\n *\n * Called from `get()` and `list()` when locale options are present.\n * Uses the effective locale: per-call `locale` takes precedence over\n * `this.defaultLocale`.\n *\n * - i18nText fields: replaced with the resolved string (or the full\n * map when `locale === 'raw'`).\n * - dictKey fields: `<field>Label` virtual fields added.\n *\n * Returns the record unchanged when no locale is active and no i18n/dict\n * fields are registered.\n */\n private async applyLocaleToRecord(\n record: T,\n localeOpts?: LocaleReadOptions,\n ): Promise<T> {\n const hasI18n = this.i18nFields && Object.keys(this.i18nFields).length > 0\n const hasDict = this.dictKeyFields && Object.keys(this.dictKeyFields).length > 0\n if (!hasI18n && !hasDict) return record\n\n const locale = localeOpts?.locale ?? this.defaultLocale\n if (!locale) return record\n\n let result = record as unknown as Record<string, unknown>\n\n // 1. i18nText resolution\n if (hasI18n && this.i18nFields) {\n result = this.i18nStrategy.applyI18nLocale(result, this.i18nFields, locale, localeOpts?.fallback)\n }\n\n // 2. dictKey label resolution\n if (hasDict && this.dictKeyFields && this.dictLabelResolver && locale !== 'raw') {\n const withLabels = { ...result }\n for (const [field, desc] of Object.entries(this.dictKeyFields)) {\n const key = result[field]\n if (typeof key !== 'string') continue\n const label = await this.dictLabelResolver(\n desc.name,\n key,\n locale,\n localeOpts?.fallback,\n )\n if (label !== undefined) {\n withLabels[`${field}Label`] = label\n }\n }\n result = withLabels\n }\n\n return result as T\n }\n\n /**\n * Low-level: encrypt a pre-serialised JSON string into an EncryptedEnvelope.\n * Used by both the normal record path and the CRDT path (which serialises\n * a CrdtState rather than a T).\n */\n /**\n * Write / update / delete the `_idx/<field>/<recordId>` side-cars for\n * every declared persistence-index field on this collection after a\n * successful main-record `put()`.\n *\n * Timing: called AFTER `adapter.put()` of the main record succeeds, so\n * a failed main write never leaves a stale index entry. Side-car write\n * failures do NOT fail the overall `put()` — the main record is already\n * durably committed. Per-field failures surface as\n * `IndexWriteFailureError` on the emitter's `index:write-partial`\n * channel and the operator runs a reconcile pass later.\n *\n * Null/undefined field values are not indexed — matches the\n * `PersistedCollectionIndex.stringifyKey` contract. If the prior value\n * was non-null and the new value is null, the side-car is deleted.\n */\n private async maintainPersistedIndexesOnPut(\n id: string,\n newRecord: T,\n previousRecord: T | null,\n version: number,\n ): Promise<void> {\n const persisted = this.persistedIndexes\n if (!persisted) return\n const defs = persisted.definitions()\n if (defs.length === 0) return\n\n const newRec = newRecord as unknown as Record<string, unknown>\n const prevRec = previousRecord as unknown as Record<string, unknown> | null\n\n for (const def of defs) {\n const newValue = extractIndexValue(newRec, def)\n const previousValue = prevRec ? extractIndexValue(prevRec, def) : null\n\n // Update the in-memory mirror first — it's the authoritative source\n // for query dispatch. If the adapter write below fails, the mirror\n // still reflects intended state; the reconciler compares mirror\n // against side-cars on next run.\n persisted.upsert(id, def.key, newValue, previousValue)\n\n const idxId = encodeIdxId(def.key, id)\n try {\n if (newValue === null || newValue === undefined) {\n // Clear any pre-existing side-car for this (field, record).\n if (previousValue !== null && previousValue !== undefined) {\n await this.adapter.delete(this.vault, this.name, idxId)\n }\n } else {\n const body = JSON.stringify({\n field: def.key,\n value: serializeIndexValue(newValue),\n recordId: id,\n writtenAt: new Date().toISOString(),\n })\n const envelope = await this.encryptJsonString(body, version)\n await this.adapter.put(this.vault, this.name, idxId, envelope)\n }\n } catch (cause) {\n this.emitter.emit('index:write-partial', {\n vault: this.vault,\n collection: this.name,\n id,\n action: 'put',\n error: new IndexWriteFailureError({ recordId: id, field: def.key, op: 'put', cause }),\n })\n }\n }\n }\n\n /**\n * Tear down `_idx/<field>/<recordId>` side-cars for a deleted record.\n * Mirror state updates regardless of adapter outcome; adapter failures\n * surface on `index:write-partial` the same way put does.\n */\n private async maintainPersistedIndexesOnDelete(id: string, previousRecord: T): Promise<void> {\n const persisted = this.persistedIndexes\n if (!persisted) return\n const defs = persisted.definitions()\n if (defs.length === 0) return\n\n const prevRec = previousRecord as unknown as Record<string, unknown>\n for (const def of defs) {\n const previousValue = extractIndexValue(prevRec, def)\n if (previousValue !== null && previousValue !== undefined) {\n persisted.remove(id, def.key, previousValue)\n }\n\n const idxId = encodeIdxId(def.key, id)\n try {\n await this.adapter.delete(this.vault, this.name, idxId)\n } catch (cause) {\n this.emitter.emit('index:write-partial', {\n vault: this.vault,\n collection: this.name,\n id,\n action: 'delete',\n error: new IndexWriteFailureError({ recordId: id, field: def.key, op: 'delete', cause }),\n })\n }\n }\n }\n\n /**\n * Bulk-load the persisted-index mirror from `_idx/<field>/*` side-cars\n * on first lazy-mode query. Idempotent — subsequent calls short-circuit\n * on the `persistedIndexesLoaded` flag.\n *\n * Listing the whole id namespace is acceptable here because the caller\n * has already decided to pay a first-query cost (this is the indexed\n * equivalent of lazy-mode hydration, not a per-query scan).\n */\n private async ensurePersistedIndexesLoaded(): Promise<void> {\n if (this.persistedIndexesLoaded) return\n const persisted = this.persistedIndexes\n if (!persisted || persisted.fields().length === 0) {\n this.persistedIndexesLoaded = true\n return\n }\n\n const ids = await this.adapter.list(this.vault, this.name)\n const byField = new Map<string, Array<{ recordId: string; value: unknown }>>()\n for (const id of ids) {\n const decoded = decodeIdxId(id)\n if (!decoded) continue\n if (!persisted.has(decoded.field)) continue\n const envelope = await this.adapter.get(this.vault, this.name, id)\n if (!envelope) continue\n try {\n const json = await this.decryptJsonString(envelope)\n const body = JSON.parse(json) as { value: unknown; recordId: string }\n if (typeof body.recordId !== 'string') continue\n const rows = byField.get(decoded.field) ?? []\n rows.push({ recordId: body.recordId, value: body.value })\n byField.set(decoded.field, rows)\n } catch {\n // Skip unreadable side-cars — the reconciler picks them up later.\n }\n }\n for (const [field, rows] of byField) {\n persisted.ingest(field, rows)\n }\n this.persistedIndexesLoaded = true\n\n // auto-reconcile on first query. The mirror is now\n // populated from whatever side-cars existed; reconcileIndex will\n // diff that against the canonical records and repair (or just\n // report) drift per-field. Skip on the inner reload triggered by\n // reconcileIndex itself — see `autoReconciling` guard.\n if (this.reconcileOnOpen !== 'off' && !this.autoReconciling) {\n await this.autoReconcile()\n }\n }\n\n /**\n * Walk every declared persisted-index field, run `reconcileIndex`\n * per the configured policy, and emit `index:reconciled` for each.\n * Called internally by `ensurePersistedIndexesLoaded()` — exposed as\n * a private helper for readability, not as a public API (the public\n * entry points are `reconcileIndex` and `rebuildIndexes`).\n */\n private async autoReconcile(): Promise<void> {\n const persisted = this.persistedIndexes\n if (!persisted) return\n this.autoReconciling = true\n try {\n const dryRun = this.reconcileOnOpen === 'dry-run'\n for (const def of persisted.definitions()) {\n try {\n const report = await this.reconcileIndex(def.key, { dryRun })\n this.emitter.emit('index:reconciled', {\n vault: this.vault,\n collection: this.name,\n field: def.key,\n missing: report.missing,\n stale: report.stale,\n applied: report.applied,\n skipped: false,\n })\n } catch {\n // Tolerate a single field's failure — a broken reconcile\n // shouldn't prevent the rest of the collection from\n // working. The `index:write-partial` channel captures\n // per-field failures during put/delete; this is its\n // sibling for the reconcile path.\n }\n }\n } finally {\n this.autoReconciling = false\n }\n }\n\n /**\n * Construct a `LazyQuery<T>` bound to this collection. Used by the\n * lazy-mode branch of `query()` and kept private because callers should\n * always go through `query()` to pick up the eager/lazy dispatch.\n */\n /**\n * Build a chainable indexed-read query against a lazy-mode collection.\n *\n * Companion to `query()`, which is eager-mode only and materialises a\n * snapshot. `lazyQuery()` dispatches every read through the persisted\n * index side-cars — no bulk decrypt, no snapshot. Every field touched by\n * `.where(...)` or `.orderBy(...)` MUST be declared in `indexes`;\n * otherwise `.toArray()` throws `IndexRequiredError`.\n *\n * The returned builder is always Promise-returning on its terminals\n * (`toArray`, `first`, `count`) because candidate records are decrypted\n * from the adapter on demand.\n *\n * @example\n * ```ts\n * const disbursements = vault.collection<Disbursement>('disbursements', {\n * prefetch: false,\n * cache: { maxRecords: 1000 },\n * indexes: ['clientId', 'period'],\n * })\n * const rows = await disbursements.lazyQuery()\n * .where('clientId', '==', 'c-42')\n * .orderBy('period', 'desc')\n * .limit(50)\n * .toArray()\n * ```\n *\n * Throws at call time when the collection is in eager mode — use\n * `query()` there. Throws if no index is declared, because a lazy\n * query with no index would need to enumerate the whole collection.\n */\n lazyQuery(): LazyQuery<T> {\n if (!this.lazy) {\n throw new Error(\n `Collection \"${this.name}\": lazyQuery() is only available in lazy mode ` +\n `(prefetch: false). Use collection.query() for eager-mode chainable reads.`,\n )\n }\n const persisted = this.persistedIndexes\n if (!persisted) {\n throw new Error(\n `Collection \"${this.name}\": lazyQuery() requires indexing to be enabled. ` +\n `Pass \\`withIndexing()\\` from \"@noy-db/hub/indexing\" to ` +\n `\\`createNoydb({ indexStrategy: withIndexing() })\\`.`,\n )\n }\n if (persisted.fields().length === 0) {\n throw new Error(\n `Collection \"${this.name}\": lazyQuery() requires at least one field declared ` +\n `in \\`indexes\\`. Declare the fields you'll filter or sort by, or use ` +\n `collection.scan({ pageSize }) for non-indexed iteration.`,\n )\n }\n const source: LazyQuerySource<T> = {\n collectionName: this.name,\n persistedIndexes: persisted,\n ensurePersistedIndexesLoaded: () => this.ensurePersistedIndexesLoaded(),\n getRecord: (id: string) => this.get(id),\n }\n return new LazyQuery<T>(source)\n }\n\n private async encryptJsonString(json: string, version: number): Promise<EncryptedEnvelope> {\n const by = this.keyring.userId\n\n if (!this.encrypted) {\n return {\n _noydb: NOYDB_FORMAT_VERSION,\n _v: version,\n _ts: new Date().toISOString(),\n _iv: '',\n _data: json,\n _by: by,\n }\n }\n\n const dek = await this.getDEK(this.name)\n const { iv, data } = await encrypt(json, dek)\n\n return {\n _noydb: NOYDB_FORMAT_VERSION,\n _v: version,\n _ts: new Date().toISOString(),\n _iv: iv,\n _data: data,\n _by: by,\n }\n }\n\n private async encryptRecord(record: T, version: number): Promise<EncryptedEnvelope> {\n const base = await this.encryptJsonString(JSON.stringify(record), version)\n if (!this.deterministicFields || !this.encrypted) return base\n\n // compute deterministic-ciphertext slots for every\n // declared field. Non-primitive values are JSON-stringified so\n // objects/arrays still dedupe on structural equality.\n const dek = await this.getDEK(this.name)\n const rec = record as unknown as Record<string, unknown>\n const det: Record<string, string> = {}\n for (const field of this.deterministicFields) {\n const value = rec[field]\n if (value === undefined || value === null) continue\n const plaintext = typeof value === 'string' ? value : JSON.stringify(value)\n const { iv, data } = await encryptDeterministic(plaintext, dek, `${this.name}/${field}`)\n det[field] = `${iv}:${data}`\n }\n if (Object.keys(det).length === 0) return base\n return { ...base, _det: det }\n }\n\n /**\n * find the first record whose deterministic field matches\n * the given plaintext. Returns `null` when no match exists.\n *\n * Reads every envelope via the adapter and compares the stored\n * `_det[field]` to a freshly computed deterministic ciphertext — no\n * record bodies are decrypted during the search, which is the whole\n * point of a deterministic index.\n *\n * Throws when the field is not declared in `deterministicFields`, so a\n * typo fails loudly at the call site rather than silently returning\n * null forever.\n */\n async findByDet(field: string, value: unknown): Promise<T | null> {\n if (!this.deterministicFields || !this.deterministicFields.has(field)) {\n throw new Error(\n `Collection \"${this.name}\": field \"${field}\" is not declared in deterministicFields`,\n )\n }\n if (!this.encrypted) {\n throw new Error(\n `Collection \"${this.name}\": findByDet is only meaningful on encrypted collections`,\n )\n }\n const dek = await this.getDEK(this.name)\n const plaintext = typeof value === 'string' ? value : JSON.stringify(value)\n const { iv, data } = await encryptDeterministic(plaintext, dek, `${this.name}/${field}`)\n const target = `${iv}:${data}`\n\n const ids = await this.adapter.list(this.vault, this.name)\n for (const id of ids) {\n const env = await this.adapter.get(this.vault, this.name, id)\n if (!env || !env._det) continue\n if (env._det[field] === target) {\n return this.decryptRecord(env)\n }\n }\n return null\n }\n\n /**\n * return every record whose deterministic field matches.\n * Same semantics as {@link findByDet} but without the short-circuit.\n */\n async queryByDet(field: string, value: unknown): Promise<T[]> {\n if (!this.deterministicFields || !this.deterministicFields.has(field)) {\n throw new Error(\n `Collection \"${this.name}\": field \"${field}\" is not declared in deterministicFields`,\n )\n }\n if (!this.encrypted) {\n throw new Error(\n `Collection \"${this.name}\": queryByDet is only meaningful on encrypted collections`,\n )\n }\n const dek = await this.getDEK(this.name)\n const plaintext = typeof value === 'string' ? value : JSON.stringify(value)\n const { iv, data } = await encryptDeterministic(plaintext, dek, `${this.name}/${field}`)\n const target = `${iv}:${data}`\n\n const ids = await this.adapter.list(this.vault, this.name)\n const matches: T[] = []\n for (const id of ids) {\n const env = await this.adapter.get(this.vault, this.name, id)\n if (!env || !env._det) continue\n if (env._det[field] === target) {\n matches.push(await this.decryptRecord(env))\n }\n }\n return matches\n }\n\n // ─── Hierarchical Access ──────────────────────────\n\n private assertTiersEnabled(): void {\n if (!this.tiers) {\n throw new Error(\n `Collection \"${this.name}\": hierarchical tiers are not enabled. ` +\n `Pass { tiers: [0, 1, 2, …] } to vault.collection() to opt in.`,\n )\n }\n }\n\n private assertDeclaredTier(tier: number): void {\n if (tier < 0 || !Number.isInteger(tier)) {\n throw new Error(`Collection \"${this.name}\": tier must be a non-negative integer, got ${tier}`)\n }\n if (tier === 0) return\n if (!this.tiers || !this.tiers.has(tier)) {\n throw new Error(\n `Collection \"${this.name}\": tier ${tier} is not declared in { tiers: [...] }`,\n )\n }\n }\n\n /**\n * tier-aware put. Encrypts the record with the\n * collection's tier-N DEK and stamps `_tier: N` on the envelope. The\n * caller's keyring must hold the tier-N DEK (directly, by\n * delegation, or by virtue of being the grantor); otherwise throws\n * `TierNotGrantedError`.\n *\n * accepts an optional `elevation` context. When\n * present, the emitted cross-tier event is stamped with\n * `authorization: 'elevation'`, the elevation's reason, and the\n * caller's pre-elevation tier. `vault.elevate(...).collection().put`\n * threads this through; direct `putAtTier` calls leave it undefined\n * and fall back to the inherent-write event shape.\n */\n async putAtTier(\n id: string,\n record: T,\n tier: number,\n opts?: { elevation?: { reason: string; fromTier: number } },\n ): Promise<void> {\n this.assertTiersEnabled()\n this.assertDeclaredTier(tier)\n assertTierAccess(this.keyring, this.name, tier)\n\n const key = dekKey(this.name, tier)\n const dek = await this.getDEK(key)\n\n const existing = await this.adapter.get(this.vault, this.name, id)\n const version = existing ? existing._v + 1 : 1\n const json = JSON.stringify(record)\n const { iv, data } = await encrypt(json, dek)\n const envelope: EncryptedEnvelope = {\n _noydb: NOYDB_FORMAT_VERSION,\n _v: version,\n _ts: new Date().toISOString(),\n _iv: iv,\n _data: data,\n _by: this.keyring.userId,\n ...(tier > 0 && { _tier: tier }),\n }\n\n await this.adapter.put(this.vault, this.name, id, envelope)\n\n if (tier > 0) {\n this.emitCrossTierEvent({\n actor: this.keyring.userId,\n collection: this.name,\n id,\n tier,\n authorization: opts?.elevation ? 'elevation' : 'inherent',\n op: 'put',\n ts: envelope._ts,\n ...(opts?.elevation && {\n reason: opts.elevation.reason,\n elevatedFrom: opts.elevation.fromTier,\n }),\n })\n }\n }\n\n /**\n * tier-aware get. When the stored record is at a\n * tier the caller cannot decrypt:\n * - `'invisibility'` mode (default) → returns `null`.\n * - `'ghost'` mode → returns a `GhostRecord` placeholder with the\n * tier and the record id (the record exists but contents are\n * withheld).\n *\n * Fully-cleared reads return the plaintext record and fire a\n * cross-tier event when `_tier > 0`.\n */\n async getAtTier(id: string): Promise<T | GhostRecord | null> {\n this.assertTiersEnabled()\n const envelope = await this.adapter.get(this.vault, this.name, id)\n if (!envelope) return null\n const tier = envelope._tier ?? 0\n if (tier === 0) {\n return this.decryptRecord(envelope)\n }\n\n const key = dekKey(this.name, tier)\n if (!this.keyring.deks.has(key)) {\n if (this.tierMode === 'ghost') {\n return { _ghost: true, _tier: tier } as GhostRecord\n }\n return null\n }\n\n const dek = await this.getDEK(key)\n const plaintext = await decrypt(envelope._iv, envelope._data, dek)\n const record = JSON.parse(plaintext) as T\n\n this.emitCrossTierEvent({\n actor: this.keyring.userId,\n collection: this.name,\n id,\n tier,\n authorization: this.isElevatorOrOwner() ? 'inherent' : 'delegation',\n op: 'get',\n ts: new Date().toISOString(),\n })\n\n return record\n }\n\n /**\n * list ids grouped by the caller's readability.\n * Returns only ids whose tier the caller can read. Above-tier ids\n * are omitted in `'invisibility'` mode and included (with tier\n * metadata) in `'ghost'` mode.\n */\n async listAtTier(): Promise<Array<{ id: string; tier: number; readable: boolean }>> {\n this.assertTiersEnabled()\n const ids = await this.adapter.list(this.vault, this.name)\n const out: Array<{ id: string; tier: number; readable: boolean }> = []\n for (const id of ids) {\n const env = await this.adapter.get(this.vault, this.name, id)\n if (!env) continue\n const tier = env._tier ?? 0\n const readable = tier === 0 || this.keyring.deks.has(dekKey(this.name, tier))\n if (!readable && this.tierMode === 'invisibility') continue\n out.push({ id, tier, readable })\n }\n return out\n }\n\n /**\n * elevate a record to a higher tier. Re-encrypts with\n * the target tier's DEK. The caller must hold DEKs for both the\n * current tier (to decrypt) and the target tier (to re-encrypt).\n * Stamps `_elevatedBy` with the caller id so `demote()` can check\n * the reverse operation.\n */\n async elevate(id: string, toTier: number): Promise<void> {\n this.assertTiersEnabled()\n this.assertDeclaredTier(toTier)\n assertTierAccess(this.keyring, this.name, toTier)\n\n const envelope = await this.adapter.get(this.vault, this.name, id)\n if (!envelope) throw new Error(`Record \"${id}\" not found in collection \"${this.name}\"`)\n const fromTier = envelope._tier ?? 0\n if (toTier === fromTier) return\n if (toTier < fromTier) {\n throw new Error(`Use demote() to lower the tier of \"${id}\" from ${fromTier} to ${toTier}`)\n }\n // Caller must have access at the existing tier to decrypt.\n if (fromTier > 0) assertTierAccess(this.keyring, this.name, fromTier)\n\n const fromKey = dekKey(this.name, fromTier)\n const toKey = dekKey(this.name, toTier)\n const fromDek = await this.getDEK(fromKey)\n const toDek = await this.getDEK(toKey)\n\n const plaintext = await decrypt(envelope._iv, envelope._data, fromDek)\n const { iv, data } = await encrypt(plaintext, toDek)\n const now = new Date().toISOString()\n const next: EncryptedEnvelope = {\n _noydb: NOYDB_FORMAT_VERSION,\n _v: envelope._v + 1,\n _ts: now,\n _iv: iv,\n _data: data,\n _by: this.keyring.userId,\n _tier: toTier,\n _elevatedBy: this.keyring.userId,\n }\n await this.adapter.put(this.vault, this.name, id, next)\n\n this.emitCrossTierEvent({\n actor: this.keyring.userId,\n collection: this.name,\n id,\n tier: toTier,\n authorization: 'elevation',\n op: 'elevate',\n ts: now,\n })\n }\n\n /**\n * demote a record to a lower tier. Allowed only for\n * the user who performed the last elevation or an owner.\n */\n async demote(id: string, toTier: number): Promise<void> {\n this.assertTiersEnabled()\n if (toTier < 0) throw new Error(`Cannot demote to negative tier ${toTier}`)\n\n const envelope = await this.adapter.get(this.vault, this.name, id)\n if (!envelope) throw new Error(`Record \"${id}\" not found in collection \"${this.name}\"`)\n const fromTier = envelope._tier ?? 0\n if (toTier === fromTier) return\n if (toTier > fromTier) {\n throw new Error(`Use elevate() to raise the tier of \"${id}\" from ${fromTier} to ${toTier}`)\n }\n const isOwner = this.keyring.role === 'owner'\n const isOriginalElevator = envelope._elevatedBy === this.keyring.userId\n if (!isOwner && !isOriginalElevator) {\n throw new TierDemoteDeniedError(id, fromTier)\n }\n // Caller must still hold the DEK of the current tier to decrypt.\n assertTierAccess(this.keyring, this.name, fromTier)\n if (toTier > 0) this.assertDeclaredTier(toTier)\n\n const fromDek = await this.getDEK(dekKey(this.name, fromTier))\n const toDek = await this.getDEK(dekKey(this.name, toTier))\n\n const plaintext = await decrypt(envelope._iv, envelope._data, fromDek)\n const { iv, data } = await encrypt(plaintext, toDek)\n const now = new Date().toISOString()\n const next: EncryptedEnvelope = {\n _noydb: NOYDB_FORMAT_VERSION,\n _v: envelope._v + 1,\n _ts: now,\n _iv: iv,\n _data: data,\n _by: this.keyring.userId,\n ...(toTier > 0 && { _tier: toTier }),\n }\n await this.adapter.put(this.vault, this.name, id, next)\n\n this.emitCrossTierEvent({\n actor: this.keyring.userId,\n collection: this.name,\n id,\n tier: fromTier,\n authorization: 'elevation',\n op: 'demote',\n ts: now,\n })\n }\n\n private isElevatorOrOwner(): boolean {\n return this.keyring.role === 'owner' || this.keyring.role === 'admin'\n }\n\n private emitCrossTierEvent(event: CrossTierAccessEvent): void {\n try {\n this.onCrossTierAccess?.(event)\n } catch {\n // notification sink failures must never block a tier operation\n }\n }\n\n /** Low-level: decrypt an envelope and return the raw JSON string. */\n private async decryptJsonString(envelope: EncryptedEnvelope): Promise<string> {\n if (!this.encrypted) return envelope._data\n const dek = await this.getDEK(this.name)\n return decrypt(envelope._iv, envelope._data, dek)\n }\n\n /**\n * Decrypt an envelope into a record of type `T`.\n *\n * When a schema is attached, the decrypted value is validated before\n * being returned. A divergence between the stored bytes and the\n * current schema throws `SchemaValidationError` with\n * `direction: 'output'` — silently returning drifted data would\n * propagate garbage into the UI and break the whole point of having\n * a schema.\n *\n * `skipValidation` exists for history reads: when calling\n * `getVersion()` the caller is explicitly asking for an old snapshot\n * that may predate a schema change, so validating it would be a\n * false positive. Every non-history read leaves this flag `false`.\n */\n private async decryptRecord(\n envelope: EncryptedEnvelope,\n opts: { skipValidation?: boolean } = {},\n ): Promise<T> {\n const json = await this.decryptJsonString(envelope)\n let parsed: unknown = JSON.parse(json)\n\n // CRDT resolution: if this collection is in CRDT mode, the\n // stored JSON is a CrdtState, not T directly. Resolve to the snapshot.\n if (this.crdtMode && parsed !== null && typeof parsed === 'object' && '_crdt' in parsed) {\n parsed = this.crdtStrategy.resolveCrdtSnapshot(parsed as CrdtState)\n }\n\n let record = parsed as T\n\n if (this.schema !== undefined && !opts.skipValidation) {\n // Context string deliberately avoids leaking the record id — the\n // envelope only carries the version, not the id (the id lives in\n // the adapter-side key). `<collection>@v<n>` is enough for the\n // developer to find the offending record.\n record = await validateSchemaOutput(\n this.schema,\n record,\n `${this.name}@v${envelope._v}`,\n )\n }\n\n return record\n }\n}\n\n/**\n * Read a field value from a plain record for persisted-index maintenance.\n * Supports dotted paths so declarations like `indexes: ['billing.clientId']`\n * work the same way `readPath` handles them for the eager-mode builder.\n */\nfunction readPersistedValue(record: Record<string, unknown>, field: string): unknown {\n if (!field.includes('.')) return record[field]\n const segments = field.split('.')\n let cursor: unknown = record\n for (const segment of segments) {\n if (cursor === null || cursor === undefined) return undefined\n cursor = (cursor as Record<string, unknown>)[segment]\n }\n return cursor\n}\n\n/**\n * Canonicalize a typed value for storage inside the side-car body so it\n * round-trips through `JSON.parse` without losing fidelity. Dates are\n * serialised as ISO strings; everything else passes through.\n *\n * The in-memory mirror compares on the stringified bucket key, so the\n * exact storage form is not query-critical — this just protects the\n * reconciler, which compares the stored body against the\n * live record value and would otherwise mismatch on Date objects.\n */\nfunction serializeIndexValue(value: unknown): unknown {\n if (value instanceof Date) return value.toISOString()\n return value\n}\n\n/**\n * Extract the indexable value for a declaration — a scalar for\n * single-field, or a tuple array for composite. Returns `null` when\n * the value is not indexable (single-field null/undefined, composite\n * with any null/undefined component — the whole composite is skipped\n * if any part is missing).\n */\nfunction extractIndexValue(\n record: Record<string, unknown>,\n def: PersistedIndexDef,\n): unknown {\n if (def.kind === 'single') {\n const v = readPersistedValue(record, def.field)\n return v === undefined || v === null ? null : v\n }\n const tuple: unknown[] = []\n for (const f of def.fields) {\n const v = readPersistedValue(record, f)\n if (v === undefined || v === null) return null\n tuple.push(v)\n }\n return tuple\n}\n\n/**\n * Compare the decrypted side-car body's `value` against the live record\n * field value, in the same canonical form used for storage. Handles the\n * Date-is-ISO-string round trip so reconcile doesn't flag a false drift.\n */\nfunction valuesMatch(stored: unknown, live: unknown): boolean {\n const serialized = serializeIndexValue(live)\n if (stored === serialized) return true\n if (stored === undefined || serialized === undefined) return stored === serialized\n // JSON-stringify both sides for structural equality on arrays/objects.\n try {\n return JSON.stringify(stored) === JSON.stringify(serialized)\n } catch {\n return false\n }\n}\n","import { OverlayIdMismatchError } from '../errors.js'\nimport type { Collection } from '../collection.js'\nimport type { OverlayedViewStrategy } from './types.js'\n\n/**\n * Virtual-collection proxy returned by `vault.collection(overlayName)`\n * when `overlayName` is a registered `withOverlayedView` (#154).\n *\n * Implements the core `Collection<T>`-shaped read/write surface with\n * merge-on-read semantics:\n * - `get(id)`: overlay row wins iff `overlay[shadowField] === shadowValue`\n * - `list()` / `.query()`: union of ids, per-id merge applied\n * - `put(record)` / `put(id, record)`: routes to overlay; id derived\n * via the base MV's `rowKey` (validated on the two-arg form)\n * - `delete(id)`: removes the overlay row only; base stays\n *\n * Reactive APIs (`live`, `subscribe`, `query().live()`) are out of\n * scope for #154 and surface as \"not yet implemented\" — wired in a\n * future sub-issue.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport class OverlayedCollection<T extends Record<string, unknown> = any> {\n constructor(\n private readonly spec: OverlayedViewStrategy,\n private readonly baseCollection: Collection<T>,\n private readonly overlayCollection: Collection<T>,\n private readonly baseRowKey: ((row: Record<string, unknown>) => string) | undefined,\n ) {}\n\n /**\n * Convenience accessors for advanced callers that need to bypass the\n * virtual layer (bulk imports, direct overlay queries). Mirrors the\n * spec's \"direct writes to the underlying overlay collection skip\n * the validation\" escape hatch.\n */\n readonly overlay = {\n rowKey: (row: Record<string, unknown>): string => {\n if (!this.baseRowKey) {\n throw new Error(\n `Overlay \"${this.spec.name}\": base \"${this.spec.base}\" is not an MV — ` +\n `cannot auto-derive id from the row. Use \\`put(id, record)\\` instead.`,\n )\n }\n return this.baseRowKey(row)\n },\n }\n\n /** Get the merged row by id. */\n async get(id: string): Promise<T | null> {\n const overlayRow = await this.overlayCollection.get(id)\n if (overlayRow !== null && this.shadowPredicateApplies(overlayRow)) {\n return overlayRow\n }\n const baseRow = await this.baseCollection.get(id)\n if (baseRow !== null) return baseRow\n // No base row — but if an overlay row exists with the shadow\n // predicate true, we returned it above. If overlay exists but\n // predicate is false, return null (overlay exists but doesn't\n // qualify, and there's no base to fall back to) — per spec\n // operations table row \"overlay exists, predicate false, no base\".\n return null\n }\n\n /** List union of base + overlay ids, applying the merge per row. */\n async list(): Promise<T[]> {\n const baseRows = await this.baseCollection.list()\n const overlayRows = await this.overlayCollection.list()\n // Build id → merged row, base-first then overlay applies shadow rule.\n const merged = new Map<string, T>()\n const idOf = (row: T): string => {\n // Best-effort: use baseRowKey if available, else assume the row\n // has a `.id` field (common pattern). The spec requires every\n // base MV to declare `rowKey`, so the first branch is the\n // canonical path.\n if (this.baseRowKey) return this.baseRowKey(row as Record<string, unknown>)\n const idField = (row as Record<string, unknown>).id\n return typeof idField === 'string' ? idField : ''\n }\n for (const row of baseRows) {\n const id = idOf(row)\n if (id) merged.set(id, row)\n }\n for (const row of overlayRows) {\n const id = idOf(row)\n if (!id) continue\n if (this.shadowPredicateApplies(row)) {\n merged.set(id, row) // overlay shadow wins\n } else if (!merged.has(id)) {\n // Overlay-only + predicate false + no base → don't surface\n // (matches spec operations table)\n continue\n }\n // else: overlay exists but predicate is false and base is\n // present → keep the base row already in `merged`\n }\n return [...merged.values()]\n }\n\n /**\n * Write to the overlay. Two forms:\n * - `put(record)`: id is derived via the base MV's `rowKey(record)`.\n * Throws if the base isn't an MV.\n * - `put(id, record)`: validates `id === rowKey(record)`; throws\n * `OverlayIdMismatchError` on mismatch.\n */\n async put(idOrRecord: string | T, maybeRecord?: T): Promise<void> {\n let id: string\n let record: T\n if (maybeRecord === undefined) {\n // Single-arg form: put(record). Derive id via base rowKey.\n record = idOrRecord as T\n if (!this.baseRowKey) {\n throw new Error(\n `Overlay \"${this.spec.name}\".put(record): base \"${this.spec.base}\" is not an MV. ` +\n `Use put(id, record) explicitly.`,\n )\n }\n id = this.baseRowKey(record as Record<string, unknown>)\n } else {\n // Two-arg form: put(id, record). Validate against rowKey.\n id = idOrRecord as string\n record = maybeRecord\n if (this.baseRowKey) {\n const expected = this.baseRowKey(record as Record<string, unknown>)\n if (id !== expected) {\n throw new OverlayIdMismatchError(id, expected)\n }\n }\n }\n await this.overlayCollection.put(id, record)\n }\n\n /**\n * Remove the overlay row only. Idempotent (no-op on absent).\n * The base row is untouched — if a base row exists for `id`,\n * subsequent reads return it.\n */\n async delete(id: string): Promise<void> {\n await this.overlayCollection.delete(id)\n }\n\n /** True when `overlay[shadowField] === shadowValue`. */\n private shadowPredicateApplies(row: T): boolean {\n return (row as Record<string, unknown>)[this.spec.shadowField] === this.spec.shadowValue\n }\n\n // ─── Throw-stubs for the unimplemented Collection<T> surface ───────\n //\n // `Vault.collection(name)` widens the return type to `Collection<T>`\n // for the overlay intercept, but `OverlayedCollection` doesn't\n // implement the full surface. These stubs catch the common\n // reactive / chainable APIs with a clear \"not yet implemented\"\n // error pointing at the relevant issue — so consumers don't hit a\n // cryptic `undefined is not a function` runtime crash.\n //\n // Closes niwat-review of PR #160.\n\n /** @throws — chainable Query<T> over a virtual collection is deferred. */\n query(): never {\n throw new Error(\n `OverlayedCollection \"${this.spec.name}\".query() is not yet implemented for overlay views (#154). ` +\n `Use \\`list()\\` + filter for now, or read from the underlying \\`${this.spec.base}\\` / \\`${this.spec.overlay}\\` collections directly. ` +\n `Reactive APIs land in a future MV sub-issue.`,\n )\n }\n\n /** @throws — change-stream subscription over a virtual collection is deferred. */\n subscribe(): never {\n throw new Error(\n `OverlayedCollection \"${this.spec.name}\".subscribe() is not yet implemented for overlay views (#154). ` +\n `Subscribe to the underlying \\`${this.spec.base}\\` / \\`${this.spec.overlay}\\` collections individually for now. ` +\n `Merged change-stream lands in a future MV sub-issue.`,\n )\n }\n\n /** @throws — live query over a virtual collection is deferred. */\n live(): never {\n throw new Error(\n `OverlayedCollection \"${this.spec.name}\".live() is not yet implemented for overlay views (#154). ` +\n `Reactive APIs land in a future MV sub-issue.`,\n )\n }\n\n /** @throws — async iteration over a virtual collection is deferred. */\n scan(): never {\n throw new Error(\n `OverlayedCollection \"${this.spec.name}\".scan() is not yet implemented for overlay views (#154). ` +\n `Use \\`list()\\` for now (no row-count ceiling at niwat scale), or scan the underlying collections directly.`,\n )\n }\n\n /** @throws — lazy-mode query is not applicable to virtual collections. */\n lazyQuery(): never {\n throw new Error(\n `OverlayedCollection \"${this.spec.name}\".lazyQuery() is not supported. ` +\n `Virtual collections always materialize through base + overlay reads — lazy-mode indexed lookups don't apply.`,\n )\n }\n\n /** @throws — bulk-atomic put is deferred to a future MV sub-issue. */\n putManyAtomic(): never {\n throw new Error(\n `OverlayedCollection \"${this.spec.name}\".putManyAtomic() is not yet implemented for overlay views (#154). ` +\n `Use sequential \\`.put(record)\\` calls for now, or write to \\`${this.spec.overlay}\\` directly.`,\n )\n }\n\n /** @throws — bulk delete is deferred to a future MV sub-issue. */\n deleteMany(): never {\n throw new Error(\n `OverlayedCollection \"${this.spec.name}\".deleteMany() is not yet implemented for overlay views (#154). ` +\n `Use sequential \\`.delete(id)\\` calls for now, or operate on \\`${this.spec.overlay}\\` directly.`,\n )\n }\n\n /** @throws — `.first()` over a virtual collection is deferred. */\n first(): never {\n throw new Error(\n `OverlayedCollection \"${this.spec.name}\".first() is not yet implemented for overlay views (#154). ` +\n `Use \\`(await list())[0]\\` for now.`,\n )\n }\n\n /** @throws — `.count()` over a virtual collection is deferred. */\n count(): never {\n throw new Error(\n `OverlayedCollection \"${this.spec.name}\".count() is not yet implemented for overlay views (#154). ` +\n `Use \\`(await list()).length\\` for now.`,\n )\n }\n}\n","/**\n * Strategy seam for the optional VaultFrame snapshot primitive.\n * Core imports `ShadowStrategy` as TYPE-ONLY and `NO_SHADOW` as a\n * 4-line stub. `VaultFrame` is only constructed inside `withShadow()`\n * — consumers who never import `@noy-db/hub/shadow` ship none of\n * the ~129 LOC.\n *\n * @internal\n */\n\nimport type { VaultFrame } from './vault-frame.js'\n\n/**\n * @internal\n */\nexport interface ShadowStrategy {\n /**\n * Build a `VaultFrame` bound to the given vault. The factory type\n * is kept loose (`unknown`) to avoid a core → shadow type\n * dependency — the consumer always calls this through\n * `vault.frame()`, which returns `VaultFrame` at its surface.\n */\n buildFrame(vault: unknown): VaultFrame\n}\n\nconst NOT_ENABLED = new Error(\n 'VaultFrame requires the shadow strategy. Import `{ withShadow }` ' +\n 'from \"@noy-db/hub/shadow\" and pass it to ' +\n '`createNoydb({ shadowStrategy: withShadow() })`.',\n)\n\n/**\n * No-shadow stub.\n *\n * @internal\n */\nexport const NO_SHADOW: ShadowStrategy = {\n buildFrame() { throw NOT_ENABLED },\n}\n","/**\n * Strategy seam for the optional consent-audit subsystem. Core\n * imports `ConsentStrategy` as a TYPE-ONLY symbol and `NO_CONSENT`\n * as a tiny runtime stub.\n *\n * `writeConsentEntry` / `loadConsentEntries` are only reachable from\n * `withConsent()` in `./active.ts`, exported through\n * `@noy-db/hub/consent`. Applications without a consent scope ship\n * none of the ~194 LOC.\n *\n * @internal\n */\n\nimport type { NoydbStore } from '../types.js'\nimport type {\n ConsentAuditEntry,\n ConsentAuditFilter,\n} from './consent.js'\n\n/**\n * @internal\n */\nexport interface ConsentStrategy {\n /**\n * Record one consent audit entry. No-op under NO_CONSENT.\n */\n write(\n adapter: NoydbStore,\n vault: string,\n encrypted: boolean,\n entry: Omit<ConsentAuditEntry, 'id' | 'timestamp'>,\n getDEK: (collectionName: string) => Promise<CryptoKey>,\n ): Promise<void>\n\n /**\n * Read filtered consent entries. Returns `[]` under NO_CONSENT.\n */\n read(\n adapter: NoydbStore,\n vault: string,\n encrypted: boolean,\n getDEK: (collectionName: string) => Promise<CryptoKey>,\n filter?: ConsentAuditFilter,\n ): Promise<ConsentAuditEntry[]>\n}\n\n/**\n * No-consent stub. `write` is a no-op (returns a resolved promise);\n * `read` returns `[]`. Consumers get a consistent API surface without\n * pulling the consent module into the bundle.\n *\n * @internal\n */\nexport const NO_CONSENT: ConsentStrategy = {\n async write() {},\n async read() { return [] },\n}\n","/**\n * Strategy seam for the optional accounting-periods subsystem. Core\n * imports `PeriodsStrategy` type-only + `NO_PERIODS` stub; the real\n * `loadPeriods` / `chainAnchor` / `assertTsWritable` /\n * `validatePeriodName` / `appendPeriodLedgerEntry` functions are\n * only reachable via `withPeriods()` in `./active.ts`.\n *\n * Applications that never call `vault.closePeriod()` /\n * `vault.openPeriod()` ship none of the ~363 LOC.\n *\n * @internal\n */\n\nimport type { EncryptedEnvelope, NoydbStore } from '../types.js'\nimport type { LedgerStore } from '../history/ledger/store.js'\nimport type { PeriodRecord } from './periods.js'\n\n/**\n * @internal\n */\nexport interface PeriodsStrategy {\n loadPeriods(\n adapter: NoydbStore,\n vault: string,\n decrypt: (envelope: EncryptedEnvelope) => Promise<PeriodRecord>,\n ): Promise<PeriodRecord[]>\n chainAnchor(records: readonly PeriodRecord[]): Promise<{\n priorPeriodName?: string\n priorPeriodHash: string\n }>\n assertTsWritable(\n existing: { ts: string | null; record: Record<string, unknown> | null } | null,\n incoming: Record<string, unknown> | null,\n periods: readonly PeriodRecord[],\n ): void\n validatePeriodName(name: string, existing: readonly PeriodRecord[]): void\n appendPeriodLedgerEntry(\n ledger: LedgerStore | null,\n actor: string,\n envelope: EncryptedEnvelope,\n periodName: string,\n ): Promise<void>\n}\n\n/**\n * No-periods stub. `loadPeriods` returns `[]`; the write-guards do\n * nothing (vaults without closed periods never reject writes);\n * `validatePeriodName` / `appendPeriodLedgerEntry` throw because\n * those paths are only reached when the user explicitly called\n * `closePeriod()` / `openPeriod()` — if they did that without the\n * strategy, they need to wire it.\n *\n * @internal\n */\nconst NOT_ENABLED = new Error(\n 'Accounting periods require the periods strategy. Import ' +\n '`{ withPeriods }` from \"@noy-db/hub/periods\" and pass it to ' +\n '`createNoydb({ periodsStrategy: withPeriods() })`.',\n)\n\nexport const NO_PERIODS: PeriodsStrategy = {\n async loadPeriods() { return [] },\n async chainAnchor() { return { priorPeriodHash: '' } },\n assertTsWritable() {},\n validatePeriodName() { throw NOT_ENABLED },\n async appendPeriodLedgerEntry() { throw NOT_ENABLED },\n}\n","/**\n * Foreign-key references — the soft-FK mechanism.\n *\n * A collection declares its references as metadata at construction\n * time:\n *\n * ```ts\n * import { ref } from '@noy-db/hub'\n *\n * const invoices = company.collection<Invoice>('invoices', {\n * refs: {\n * clientId: ref('clients'), // default: strict\n * categoryId: ref('categories', 'warn'),\n * parentId: ref('invoices', 'cascade'), // self-reference OK\n * },\n * })\n * ```\n *\n * Three modes:\n *\n * - **strict** — the default. `put()` rejects records whose\n * reference target doesn't exist, and `delete()` of the target\n * rejects if any strict-referencing records still exist.\n * Matches SQL's default FK semantics.\n *\n * - **warn** — both operations succeed unconditionally. Broken\n * references surface only through\n * `vault.checkIntegrity()`, which walks every collection\n * and reports orphans. Use when you want soft validation for\n * imports from messy sources.\n *\n * - **cascade** — `put()` is same as warn. `delete()` of the\n * target deletes every referencing record. Cycles are detected\n * and broken via an in-progress set, so mutual cascades\n * terminate instead of recursing forever.\n *\n * Cross-vault refs are explicitly rejected: if the target\n * name contains a `/`, `ref()` throws `RefScopeError`. Cross-\n * vault refs need an auth story (multi-keyring reads) that\n * doesn't ship — tracked for.\n */\n\nimport { NoydbError } from './errors.js'\n\n/** The three enforcement modes. Default for new refs is `'strict'`. */\nexport type RefMode = 'strict' | 'warn' | 'cascade'\n\n/**\n * Descriptor returned by `ref()`. Collections accept a\n * `Record<string, RefDescriptor>` in their options. The key is the\n * field name on the record (top-level only — dotted paths are out of\n * scope), the value describes which target collection the\n * field references and under what mode.\n *\n * The descriptor carries only plain data so it can be serialized,\n * passed around, and introspected without any class machinery.\n */\nexport interface RefDescriptor {\n readonly target: string\n readonly mode: RefMode\n}\n\n/**\n * Thrown when a strict reference is violated — either `put()` with a\n * missing target id, or `delete()` of a target that still has\n * strict-referencing records.\n *\n * Carries structured detail so UI code (and a potential future\n * devtools panel) can render \"client X cannot be deleted because\n * invoices 1, 2, and 3 reference it\" instead of a bare error string.\n */\nexport class RefIntegrityError extends NoydbError {\n readonly collection: string\n readonly id: string\n readonly field: string\n readonly refTo: string\n readonly refId: string | null\n\n constructor(opts: {\n collection: string\n id: string\n field: string\n refTo: string\n refId: string | null\n message: string\n }) {\n super('REF_INTEGRITY', opts.message)\n this.name = 'RefIntegrityError'\n this.collection = opts.collection\n this.id = opts.id\n this.field = opts.field\n this.refTo = opts.refTo\n this.refId = opts.refId\n }\n}\n\n/**\n * Thrown when `ref()` is called with a target name that looks like\n * a cross-vault reference (contains a `/`). Separate error\n * class because the fix is different: RefIntegrityError means \"data\n * is wrong\"; RefScopeError means \"the ref declaration is wrong\".\n */\nexport class RefScopeError extends NoydbError {\n constructor(target: string) {\n super(\n 'REF_SCOPE',\n `Cross-vault references are not supported in — got target \"${target}\". ` +\n `Use a simple collection name (e.g. \"clients\"), not a path. ` +\n `Cross-vault refs are tracked for a future release.`,\n )\n this.name = 'RefScopeError'\n }\n}\n\n/**\n * Helper constructor. Thin wrapper around the object literal so user\n * code reads like `ref('clients')` instead of `{ target: 'clients',\n * mode: 'strict' }` — this is the only ergonomics reason it exists.\n *\n * Validates the target name eagerly so a misconfigured ref declaration\n * fails at collection construction time, not at the first put.\n */\nexport function ref(target: string, mode: RefMode = 'strict'): RefDescriptor {\n if (target.includes('/')) {\n throw new RefScopeError(target)\n }\n if (!target || target.startsWith('_')) {\n throw new Error(\n `ref(): target collection name must be non-empty and cannot start with '_' (reserved for internal collections). Got \"${target}\".`,\n )\n }\n return { target, mode }\n}\n\n/**\n * Per-vault registry of reference declarations.\n *\n * The registry is populated by `Collection` constructors (which pass\n * their `refs` option through the Vault) and consulted by the\n * Vault on every `put` / `delete` and by `checkIntegrity`. A\n * single instance lives on the Vault for its lifetime; there's\n * no global state.\n *\n * The data structure is two parallel maps:\n *\n * - `outbound`: `collection → { field → RefDescriptor }` — what\n * refs does `collection` declare? Used on put to check\n * strict-target-exists and on checkIntegrity to walk each\n * collection's outbound refs.\n *\n * - `inbound`: `target → Array<{ collection, field, mode }>` —\n * which collections reference `target`? Used on delete to find\n * the records that might be affected by cascade / strict.\n *\n * The two views are kept in sync by `register()` and never mutated\n * otherwise — refs can't be unregistered at runtime in.\n */\nexport class RefRegistry {\n private readonly outbound = new Map<string, Record<string, RefDescriptor>>()\n private readonly inbound = new Map<\n string,\n Array<{ collection: string; field: string; mode: RefMode }>\n >()\n\n /**\n * Register the refs declared by a single collection. Idempotent in\n * the happy path — calling twice with the same data is a no-op.\n * Calling twice with DIFFERENT data throws, because silent\n * overrides would be confusing (\"I changed the ref and it doesn't\n * update\" vs \"I declared the same collection twice with different\n * refs and the second call won\").\n */\n register(collection: string, refs: Record<string, RefDescriptor>): void {\n const existing = this.outbound.get(collection)\n if (existing) {\n // Compare shallowly — if any field disagrees, reject.\n const existingKeys = Object.keys(existing).sort()\n const newKeys = Object.keys(refs).sort()\n if (existingKeys.join(',') !== newKeys.join(',')) {\n throw new Error(\n `RefRegistry: conflicting ref declarations for collection \"${collection}\"`,\n )\n }\n for (const k of existingKeys) {\n const a = existing[k]\n const b = refs[k]\n if (!a || !b || a.target !== b.target || a.mode !== b.mode) {\n throw new Error(\n `RefRegistry: conflicting ref declarations for collection \"${collection}\" field \"${k}\"`,\n )\n }\n }\n return\n }\n this.outbound.set(collection, { ...refs })\n for (const [field, desc] of Object.entries(refs)) {\n const list = this.inbound.get(desc.target) ?? []\n list.push({ collection, field, mode: desc.mode })\n this.inbound.set(desc.target, list)\n }\n }\n\n /** Get the outbound refs declared by a collection (or `{}` if none). */\n getOutbound(collection: string): Record<string, RefDescriptor> {\n return this.outbound.get(collection) ?? {}\n }\n\n /** Get the inbound refs that target a given collection (or `[]`). */\n getInbound(\n target: string,\n ): ReadonlyArray<{ collection: string; field: string; mode: RefMode }> {\n return this.inbound.get(target) ?? []\n }\n\n /**\n * Iterate every (collection → refs) pair that has at least one\n * declared reference. Used by `checkIntegrity` to walk the full\n * universe of outbound refs without needing to track collection\n * names elsewhere.\n */\n entries(): Array<[string, Record<string, RefDescriptor>]> {\n return [...this.outbound.entries()]\n }\n\n /** Clear the registry. Test-only escape hatch; never called from production code. */\n clear(): void {\n this.outbound.clear()\n this.inbound.clear()\n }\n}\n\n/**\n * Shape of a single violation reported by `vault.checkIntegrity()`.\n *\n * `refId` is the value we saw in the referencing field — it's the\n * ID we expected to find in `refTo`, but didn't. Left as `unknown`\n * because records are loosely typed at the integrity-check layer.\n */\nexport interface RefViolation {\n readonly collection: string\n readonly id: string\n readonly field: string\n readonly refTo: string\n readonly refId: unknown\n readonly mode: RefMode\n}\n","/**\n * _dict_* reserved collections + dictKey schema descriptor —\n *\n * Stores bounded enum-like field dictionaries as reserved encrypted\n * collections (`_dict_<name>/`) within a vault. Each dictionary\n * entry maps a stable key (e.g. `'paid'`) to a locale → label record\n * (e.g. `{ en: 'Paid', th: 'ชำระแล้ว' }`).\n *\n * Design decisions\n * ────────────────\n *\n * **Why reserved collections, not a separate store?**\n * Same answer as `_sync_credentials`: the compartment's existing\n * encryption stack is exactly right. Dictionaries are encrypted under the\n * same vault DEK, inherit ACL, ledger, and backup/restore for free.\n *\n * **One collection per dictionary, not one collection with namespaces.**\n * Each `_dict_<name>/` collection holds entries `{ id: key, labels: {...} }`.\n * This composes with `ref()` naturally (a dictKey IS a ref to the dict\n * collection), and means the query DSL works over dictionary entries\n * without any special-casing.\n *\n * **dictKey() is a descriptor, not a Zod type.**\n * The descriptor pattern matches `ref()`: declare NOYDB-specific metadata\n * in the collection options alongside `refs`. TypeScript inference comes\n * from the descriptor's generic parameter, not from Zod internals.\n *\n * API:\n * `dictKey(name, keys?)` — returns a DictKeyDescriptor\n * `vault.dictionary(name)` — returns a DictionaryHandle\n * `DictionaryHandle.put/putAll/get/delete/rename/list` — CRUD\n */\n\nimport type { NoydbStore, EncryptedEnvelope } from '../types.js'\nimport type { NoydbEventEmitter } from '../events.js'\nimport { NOYDB_FORMAT_VERSION } from '../types.js'\nimport type { UnlockedKeyring } from '../team/keyring.js'\nimport { encrypt, decrypt } from '../crypto.js'\nimport { ensureCollectionDEK } from '../team/keyring.js'\nimport type { LedgerStore } from '../history/ledger/store.js'\nimport { envelopePayloadHash } from '../history/ledger/hash.js'\nimport {\n PermissionDeniedError,\n DictKeyMissingError,\n} from '../errors.js'\n\n/** Reserved collection name prefix. Never collides with user collections. */\nexport const DICT_COLLECTION_PREFIX = '_dict_'\n\n/** Return the adapter collection name for a named dictionary. */\nexport function dictCollectionName(dictionaryName: string): string {\n return `${DICT_COLLECTION_PREFIX}${dictionaryName}`\n}\n\n/** Return true when a collection name is a reserved dictionary collection. */\nexport function isDictCollectionName(name: string): boolean {\n return name.startsWith(DICT_COLLECTION_PREFIX)\n}\n\n// ─── DictKey descriptor ────────────────────────────────────────────────\n\n/**\n * Descriptor returned by `dictKey()`. Attach to the collection's\n * `dictKeyFields` option to declare which fields are dictionary-backed:\n *\n * ```ts\n * const invoices = company.collection<Invoice>('invoices', {\n * dictKeyFields: {\n * status: dictKey('status', ['draft', 'open', 'paid'] as const),\n * },\n * })\n * ```\n *\n * The generic parameter `Keys` narrows the TypeScript type of the field\n * to a literal union; the runtime value of `keys` is used by `put()`\n * validation to reject unknown keys when a key set is declared.\n */\nexport interface DictKeyDescriptor<Keys extends string = string> {\n readonly _noydbDictKey: true\n /** Which dictionary this field references. */\n readonly name: string\n /** Declared valid keys. When set, `put()` rejects keys not in this set. */\n readonly keys: readonly Keys[] | undefined\n}\n\n/**\n * Create a `DictKeyDescriptor` for a dictionary-backed enum field.\n *\n * @param name The dictionary name (corresponds to `_dict_<name>` collection).\n * @param keys Optional `as const` array of valid key literals — narrows the\n * TypeScript type to a literal union and enables put-time\n * validation.\n *\n * @example\n * ```ts\n * const invoices = company.collection<Invoice>('invoices', {\n * dictKeyFields: {\n * status: dictKey('status', ['draft', 'open', 'paid'] as const),\n * },\n * })\n * ```\n */\nexport function dictKey<Keys extends string>(\n name: string,\n keys?: readonly Keys[],\n): DictKeyDescriptor<Keys> {\n return { _noydbDictKey: true, name, keys }\n}\n\n/** Runtime predicate for detecting a DictKeyDescriptor. */\nexport function isDictKeyDescriptor(x: unknown): x is DictKeyDescriptor {\n return (\n typeof x === 'object' &&\n x !== null &&\n (x as { _noydbDictKey?: unknown })._noydbDictKey === true\n )\n}\n\n// ─── Dictionary entry shape ────────────────────────────────────────────\n\n/**\n * One entry in a `_dict_*` collection. The record `id` (adapter-side\n * key) IS the stable dictionary key (e.g. `'paid'`). The `labels`\n * record maps locale codes to display strings.\n */\nexport interface DictEntry {\n /** Stable key — same as the record id in the adapter. */\n readonly key: string\n /** Locale → label map, e.g. `{ en: 'Paid', th: 'ชำระแล้ว' }`. */\n readonly labels: Record<string, string>\n}\n\n// ─── Per-dictionary options ────────────────────────────────────────────\n\n/**\n * Options for `vault.dictionary(name, options?)`.\n *\n * `writableBy` controls the minimum role for write operations (put,\n * putAll, delete, rename). Defaults to `'admin'` to match the standard\n * \"dictionary contents are owned by admins\" convention; set to\n * `'operator'` for user-editable dictionaries like custom tags.\n */\nexport interface DictionaryOptions {\n /** Minimum role allowed to write dictionary entries. Default: `'admin'`. */\n readonly writableBy?: 'owner' | 'admin' | 'operator'\n}\n\n// ─── DictionaryHandle ──────────────────────────────────────────────────\n\n/**\n * Handle to a named dictionary within a vault.\n *\n * Obtained via `vault.dictionary(name)`. Provides strongly-typed\n * CRUD for dictionary entries, plus the `rename()` operation that is the\n * only sanctioned mass-mutation path for dictKey fields.\n *\n * All writes are encrypted under the compartment's DEK for the\n * `_dict_<name>` collection. Adapters never see plaintext.\n */\nexport class DictionaryHandle<Keys extends string = string> {\n private readonly collName: string\n\n /**\n * Synchronous write-through cache for dict-join support.\n * Populated on every `put()`, `delete()`, and `rename()`. The snapshot\n * is built from this cache by `snapshotEntries()` — the query executor\n * calls this synchronously inside `.toArray()`.\n *\n * `null` means \"not yet initialized\" — callers should use `list()`\n * to warm the cache before using dict joins on pre-existing data.\n */\n private readonly _syncCache = new Map<string, DictEntry>()\n\n /**\n * Return all cached entries as `{ key, labels, ...labels }` records —\n * usable synchronously by the join executor's `snapshot()` call.\n * Returns an empty array when the cache has never been populated.\n */\n snapshotEntries(): readonly Record<string, unknown>[] {\n return Array.from(this._syncCache.values()).map((e) => ({\n key: e.key,\n labels: e.labels,\n ...e.labels,\n }))\n }\n\n constructor(\n private readonly adapter: NoydbStore,\n private readonly compartmentName: string,\n private readonly dictionaryName: string,\n private readonly keyring: UnlockedKeyring,\n private readonly getDEK: (collectionName: string) => Promise<CryptoKey>,\n private readonly encrypted: boolean,\n private readonly ledger: LedgerStore | undefined,\n private readonly options: DictionaryOptions,\n /**\n * Callback provided by the Vault to find and rewrite records\n * in any registered collection that has a dictKeyField pointing at\n * this dictionary, used by `rename()`.\n */\n private readonly findAndUpdateReferences:\n | ((\n dictionaryName: string,\n oldKey: string,\n newKey: string,\n ) => Promise<void>)\n | undefined,\n private readonly emitter: NoydbEventEmitter,\n ) {\n this.collName = dictCollectionName(dictionaryName)\n }\n\n // ─── Access checks ────────────────────────────────────────────────\n\n private requireWriteAccess(): void {\n const minRole = this.options.writableBy ?? 'admin'\n const roleRank: Record<string, number> = {\n client: 1,\n viewer: 2,\n operator: 3,\n admin: 4,\n owner: 5,\n }\n const callerRank = roleRank[this.keyring.role] ?? 0\n const requiredRank = roleRank[minRole] ?? 4\n if (callerRank < requiredRank) {\n throw new PermissionDeniedError(\n `Dictionary \"${this.dictionaryName}\" writes require \"${minRole}\" role or above. ` +\n `Current role: \"${this.keyring.role}\".`,\n )\n }\n }\n\n // ─── Internal helpers ─────────────────────────────────────────────\n\n private async getDekForDict(): Promise<CryptoKey> {\n const resolve = await ensureCollectionDEK(\n this.adapter,\n this.compartmentName,\n this.keyring,\n )\n return resolve(this.collName)\n }\n\n private async encryptEntry(entry: DictEntry, version: number): Promise<EncryptedEnvelope> {\n if (!this.encrypted) {\n return {\n _noydb: NOYDB_FORMAT_VERSION,\n _v: version,\n _ts: new Date().toISOString(),\n _iv: '',\n _data: JSON.stringify(entry),\n _by: this.keyring.userId,\n }\n }\n const dek = await this.getDekForDict()\n const { iv, data } = await encrypt(JSON.stringify(entry), dek)\n return {\n _noydb: NOYDB_FORMAT_VERSION,\n _v: version,\n _ts: new Date().toISOString(),\n _iv: iv,\n _data: data,\n _by: this.keyring.userId,\n }\n }\n\n private async decryptEntry(envelope: EncryptedEnvelope): Promise<DictEntry> {\n if (!this.encrypted) {\n return JSON.parse(envelope._data) as DictEntry\n }\n const dek = await this.getDekForDict()\n const json = await decrypt(envelope._iv, envelope._data, dek)\n return JSON.parse(json) as DictEntry\n }\n\n // ─── Public API ───────────────────────────────────────────────────\n\n /**\n * Add or overwrite a single dictionary entry.\n *\n * @param key The stable key to store (e.g. `'paid'`).\n * @param labels Locale → label map (e.g. `{ en: 'Paid', th: 'ชำระแล้ว' }`).\n */\n async put(key: Keys, labels: Record<string, string>): Promise<void> {\n this.requireWriteAccess()\n\n const entry: DictEntry = { key, labels }\n const existing = await this.adapter.get(\n this.compartmentName,\n this.collName,\n key,\n )\n const version = existing ? existing._v + 1 : 1\n const envelope = await this.encryptEntry(entry, version)\n\n await this.adapter.put(\n this.compartmentName,\n this.collName,\n key,\n envelope,\n existing ? existing._v : undefined,\n )\n\n // Maintain synchronous cache for dict-join snapshot\n this._syncCache.set(key, entry)\n\n this.emitter.emit('change', {\n vault: this.compartmentName,\n collection: this.collName,\n id: key,\n action: 'put',\n })\n\n if (this.ledger) {\n await this.ledger.append({\n op: 'put',\n collection: this.collName,\n id: key,\n version,\n actor: this.keyring.userId,\n // — must be the real envelope hash so\n // vault.verifyBackupIntegrity()'s data-cross-check matches.\n payloadHash: await envelopePayloadHash(envelope),\n })\n }\n }\n\n /**\n * Batch-add or overwrite multiple dictionary entries in one call.\n *\n * @param entries `{ key: { locale: label } }` map.\n */\n async putAll(entries: Record<Keys, Record<string, string>>): Promise<void> {\n this.requireWriteAccess()\n for (const [key, labels] of Object.entries(entries) as [Keys, Record<string, string>][]) {\n await this.put(key, labels)\n }\n }\n\n /**\n * Load the label map for a single key.\n *\n * @returns The label map, or `null` if the key doesn't exist.\n */\n async get(key: Keys): Promise<Record<string, string> | null> {\n const envelope = await this.adapter.get(\n this.compartmentName,\n this.collName,\n key,\n )\n if (!envelope) return null\n const entry = await this.decryptEntry(envelope)\n return entry.labels\n }\n\n /**\n * Delete a dictionary key.\n *\n * Default mode is `'strict'` — throws `DictKeyInUseError` if any\n * registered collection has a record referencing this key. Pass\n * `{ mode: 'warn' }` to skip the check (dev-mode cleanup only).\n */\n async delete(key: Keys, opts: { mode?: 'strict' | 'warn' } = {}): Promise<void> {\n this.requireWriteAccess()\n\n const existing = await this.adapter.get(\n this.compartmentName,\n this.collName,\n key,\n )\n if (!existing) {\n throw new DictKeyMissingError(this.dictionaryName, key)\n }\n\n const mode = opts.mode ?? 'strict'\n if (mode === 'strict' && this.findAndUpdateReferences) {\n // Check for references by attempting a rename to a sentinel that\n // doesn't exist — we reuse the reference-finding machinery but\n // abort before applying changes. Simpler: the vault\n // exposes a separate checkReferences() callback. For now we rely\n // on the caller to confirm no references exist, or use warn mode.\n // A dedicated findReferences API is tracked as a follow-up.\n }\n\n await this.adapter.delete(this.compartmentName, this.collName, key)\n\n // Maintain synchronous cache for dict-join snapshot\n this._syncCache.delete(key)\n\n this.emitter.emit('change', {\n vault: this.compartmentName,\n collection: this.collName,\n id: key,\n action: 'delete',\n })\n\n if (this.ledger) {\n await this.ledger.append({\n op: 'delete',\n collection: this.collName,\n id: key,\n version: existing._v,\n actor: this.keyring.userId,\n // — for delete the prior envelope is what was just\n // removed; we hash it so the chain captures intent. The\n // verifyBackupIntegrity data-cross-check skips delete\n // entries entirely (the live record is gone), but the\n // chain still benefits from a stable non-empty hash.\n payloadHash: await envelopePayloadHash(existing),\n })\n }\n }\n\n /**\n * Rename a dictionary key — the only sanctioned mass-mutation path.\n *\n * Atomically:\n * 1. Adds the new key with the same labels as the old key.\n * 2. Updates every registered record that stores the old key to\n * store the new key instead.\n * 3. Deletes the old key.\n * 4. Appends a single ledger entry recording the rename.\n *\n * Respects ACL: throws `PermissionDeniedError` before any mutation\n * if the caller can't write. The cascade is best-effort atomic\n * within this call — no two-phase commit across adapter calls.\n *\n * Cascade-on-delete is NOT supported. Use `rename()` when you need\n * to change a key that records reference.\n */\n async rename(oldKey: Keys, newKey: string): Promise<void> {\n this.requireWriteAccess()\n\n // 1. Load old entry\n const existing = await this.adapter.get(\n this.compartmentName,\n this.collName,\n oldKey,\n )\n if (!existing) {\n throw new DictKeyMissingError(this.dictionaryName, oldKey)\n }\n const oldEntry = await this.decryptEntry(existing)\n\n // 2. Write new key\n const newEntry: DictEntry = { key: newKey, labels: oldEntry.labels }\n const newEnvelope = await this.encryptEntry(newEntry, 1)\n await this.adapter.put(\n this.compartmentName,\n this.collName,\n newKey,\n newEnvelope,\n )\n\n // 3. Update all referencing records in registered collections\n if (this.findAndUpdateReferences) {\n await this.findAndUpdateReferences(this.dictionaryName, oldKey, newKey)\n }\n\n // 4. Delete old key\n await this.adapter.delete(this.compartmentName, this.collName, oldKey)\n\n // Maintain synchronous cache for dict-join snapshot\n this._syncCache.delete(oldKey)\n this._syncCache.set(newKey, newEntry)\n\n this.emitter.emit('change', {\n vault: this.compartmentName,\n collection: this.collName,\n id: oldKey,\n action: 'delete',\n })\n this.emitter.emit('change', {\n vault: this.compartmentName,\n collection: this.collName,\n id: newKey,\n action: 'put',\n })\n\n // 5. Ledger — record the rename as delete(oldKey) + put(newKey)\n // so verifyBackupIntegrity()'s data-cross-check matches reality\n // (the oldKey envelope is gone; the newKey envelope is what was\n // just written). Two entries instead of one — the chain still\n // captures the rename intent via the matching ts + actor.\n if (this.ledger) {\n await this.ledger.append({\n op: 'delete',\n collection: this.collName,\n id: oldKey,\n version: existing._v,\n actor: this.keyring.userId,\n payloadHash: await envelopePayloadHash(existing),\n })\n await this.ledger.append({\n op: 'put',\n collection: this.collName,\n id: newKey,\n version: 1,\n actor: this.keyring.userId,\n payloadHash: await envelopePayloadHash(newEnvelope),\n })\n }\n }\n\n /**\n * List all entries in this dictionary.\n *\n * @returns Array of `{ key, labels }` objects.\n */\n async list(): Promise<DictEntry[]> {\n const keys = await this.adapter.list(this.compartmentName, this.collName)\n const entries: DictEntry[] = []\n for (const key of keys) {\n const envelope = await this.adapter.get(\n this.compartmentName,\n this.collName,\n key,\n )\n if (!envelope) continue\n const entry = await this.decryptEntry(envelope)\n entries.push(entry)\n // Warm the synchronous cache\n this._syncCache.set(key, entry)\n }\n return entries\n }\n\n /**\n * Resolve a key to its label for the given locale.\n *\n * Used by the collection's locale-aware read path to populate\n * `<field>Label` virtual fields. Returns `undefined` when the\n * key doesn't exist or has no label for the requested locale\n * (after exhausting the fallback chain).\n */\n async resolveLabel(\n key: string,\n locale: string,\n fallback?: string | readonly string[],\n ): Promise<string | undefined> {\n const labels = await this.get(key as Keys)\n if (!labels) return undefined\n\n // Try primary locale\n if (labels[locale] !== undefined) return labels[locale]\n\n // Try fallback chain\n const chain = Array.isArray(fallback) ? (fallback as readonly string[]) : fallback ? [fallback as string] : []\n for (const fb of chain) {\n if (fb === 'any') {\n // Return any available label\n const any = Object.values(labels)[0]\n if (any !== undefined) return any\n } else if (labels[fb] !== undefined) {\n return labels[fb]\n }\n }\n\n return undefined\n }\n}\n","/**\n * Accounting-period closure + opening.\n *\n * A closed period seals every record whose envelope `_ts` is at or\n * before the period's `endDate`: further writes (`put` / `delete`)\n * against such records throw {@link PeriodClosedError}. The period\n * itself is stored as a record in the reserved `_periods` collection\n * and written through the normal ledger-instrumented path, so every\n * closure appends a tamper-evident entry to the vault's hash chain.\n *\n * ## Closure model\n *\n * ```\n * vault.closePeriod({ name: 'FY2026-Q1', endDate: '2026-03-31' })\n * └─► PeriodRecord written to _periods/<name>\n * ├─ priorPeriodName / priorPeriodHash — chain to last close\n * ├─ closedAt / closedBy — provenance\n * └─ normal ledger append fires (LedgerStore.append)\n * ```\n *\n * Enforcement (`assertTsWritable`) is vault-local: the Vault caches\n * the list of closed periods on first read and consults that cache in\n * the `Collection.put` / `.delete` path via the `periodGuard` hook.\n *\n * ## Opening model\n *\n * ```\n * vault.openPeriod({\n * name: 'FY2026-Q2',\n * startDate: '2026-04-01',\n * fromPeriod: 'FY2026-Q1',\n * carryForward: async (priorView) => Record<string, Record<string, unknown>>,\n * })\n * ```\n *\n * `carryForward` receives a read-only `VaultInstant` anchored at the\n * prior period's `endDate` (built via `vault.at(endDate)`) so the\n * callback can compute closing aggregates from the sealed state. The\n * returned `{ [collectionName]: { [id]: record } }` map is written\n * before the new `PeriodRecord` lands — opening balances materialise\n * as normal records with fresh timestamps that fall outside every\n * closed period.\n *\n * ## Not covered\n *\n * - Partial re-opening of a closed period. If an auditor needs to\n * make a correction inside a sealed period, the sanctioned path is\n * a compensating entry in the NEW period, not an unlock of the\n * old one.\n * - Automatic period rollover. `closePeriod` / `openPeriod` are\n * deliberately explicit operator calls so the caller decides when\n * the boundary lands.\n *\n * @module\n */\n\nimport type { NoydbStore, EncryptedEnvelope } from '../types.js'\nimport type { LedgerStore } from '../history/ledger/index.js'\nimport { sha256Hex, canonicalJson } from '../history/ledger/index.js'\nimport { PeriodClosedError, ValidationError } from '../errors.js'\n\n/** The reserved collection name holding closed-period metadata. */\nexport const PERIODS_COLLECTION = '_periods'\n\n/**\n * Stored record for one closed or opened accounting period. One entry\n * per period, keyed by `name` in the reserved `_periods` collection.\n *\n * The hash chain between periods is computed at read time by\n * `loadPeriods()` — each record carries the name + hash of its\n * predecessor so a tamper with any period's record breaks the chain\n * into the next one, the same way the ledger's `prevHash` works.\n */\nexport interface PeriodRecord {\n /** Human-readable name (e.g., `'FY2026-Q1'`). Unique per vault. */\n readonly name: string\n /**\n * Role discriminator. A period is `'closed'` from the moment its\n * `closedAt` is recorded; `'opened'` marks a period whose opening\n * entries have been carried forward via {@link openPeriod}. Many\n * workflows will produce one opened period per closed period (the\n * opened one is the SUCCESSOR — its `startDate` equals the prior\n * `endDate + 1 day`).\n */\n readonly kind: 'closed' | 'opened'\n /** ISO date — inclusive upper bound for records belonging to this period. */\n readonly endDate: string\n /** ISO date — lower bound (present on opened periods only). */\n readonly startDate?: string\n /**\n * Record field carrying the business date (e.g. `'date'` on an\n * invoice, `'paidAt'` on a payment). The guard compares\n * `record[dateField]` against `endDate` — NOT the envelope `_ts`.\n * Accounting entries booked late (business date `2026-01-15`,\n * write-time `2026-04-22`) still get sealed when Q1 closes at\n * `2026-03-31` because the comparison uses the business date.\n *\n * Optional for backwards compat. When absent, the guard falls back\n * to envelope `_ts` — that's a write-time seal, appropriate for\n * content that doesn't carry a logical business date (e.g. system\n * settings) but almost never right for accounting ledgers.\n */\n readonly dateField?: string\n /** ISO timestamp recorded at `closePeriod()` / `openPeriod()` call time. */\n readonly closedAt: string\n /** userId of the keyring that invoked the close/open. */\n readonly closedBy: string\n /** Name of the prior period this one chains to, if any. */\n readonly priorPeriodName?: string\n /** sha256(canonicalJson(priorPeriod)) — empty for the first period. */\n readonly priorPeriodHash: string\n /**\n * Opened periods only — the names of the collections whose\n * carry-forward aggregates were written by {@link openPeriod}.\n * Recorded for auditability so a future `verifyPeriodChain()` can\n * cross-check the opening balances against the closing snapshot.\n */\n readonly openingCollections?: readonly string[]\n}\n\n/** Options for `vault.closePeriod()`. */\nexport interface ClosePeriodOptions {\n /** Human-readable name. Must not collide with an existing period. */\n readonly name: string\n /**\n * Inclusive upper cutoff. A record is sealed when its\n * `record[dateField]` (or, if absent, the envelope `_ts`) is at or\n * before this ISO timestamp.\n */\n readonly endDate: string\n /**\n * Record field carrying the business date used for period\n * membership. Recommended for accounting workflows — e.g. an\n * invoice booked late (write-time after close) is still sealed\n * when its `invoice.date` falls inside the closed period.\n *\n * Omit to use envelope `_ts` (write-time seal). This fallback\n * rarely matches real-world accounting semantics; prefer passing\n * an explicit `dateField`.\n */\n readonly dateField?: string\n}\n\n/** Options for `vault.openPeriod()`. */\nexport interface OpenPeriodOptions<TCollections = Record<string, Record<string, unknown>>> {\n /** Human-readable name for the new period. Must be unique. */\n readonly name: string\n /** ISO lower bound of the new period (usually prior `endDate + 1 day`). */\n readonly startDate: string\n /**\n * Name of the prior CLOSED period this one chains from. The prior\n * period's record is verified to exist and to be `kind: 'closed'`;\n * its `endDate` is made available to the `carryForward` callback.\n */\n readonly fromPeriod: string\n /**\n * Receives a read-only facade over the vault's CURRENT state,\n * plus the prior period's `endDate`. Accounting semantics: after\n * a period closes, records with `record[dateField] <= endDate`\n * are frozen — current state equals closing state, so a caller\n * can compute closing balances by querying the live collection\n * with a `where('date', '<=', priorEndDate)` filter.\n *\n * Returns opening-balance records keyed by collection name.\n * Example:\n *\n * ```ts\n * carryForward: async (ctx) => {\n * const closing = await ctx.collection<Journal>('journal')\n * .query().where('date', '<=', ctx.priorEndDate).toArray()\n * const opening: Record<string, Journal> = {}\n * for (const entry of closing) {\n * opening[`OB-${entry.id}`] = { ...entry, date: '2026-04-01' }\n * }\n * return { journal: opening }\n * }\n * ```\n */\n readonly carryForward: (\n ctx: CarryForwardContext,\n ) => Promise<TCollections> | TCollections\n}\n\n/**\n * Context passed to `OpenPeriodOptions.carryForward`. Exposes a\n * read-only subset of the live vault (`collection(name).get/list`)\n * plus the prior period's `endDate` so business-date filters can\n * be built by the caller.\n *\n * Writes go via the return value, not via the facade — the\n * `collection()` here is deliberately restricted to reads.\n */\nexport interface CarryForwardContext {\n /** The prior period's `endDate` — the boundary of the closing snapshot. */\n readonly priorEndDate: string\n /** Read-only collection facade over current vault state. */\n collection<T = unknown>(name: string): ReadOnlyCollection<T>\n}\n\n/** Minimum read surface exposed to `carryForward`. */\nexport interface ReadOnlyCollection<T> {\n get(id: string): Promise<T | null>\n list(): Promise<T[]>\n}\n\n/**\n * Load every period record currently stored on the adapter.\n * Decrypting is the caller's responsibility (we return plain records\n * so the vault can use its own `_periods` DEK).\n *\n * @internal — called by Vault methods that need the closed-period\n * cache. Not part of the public API surface.\n */\nexport async function loadPeriods(\n adapter: NoydbStore,\n vault: string,\n decrypt: (envelope: EncryptedEnvelope) => Promise<PeriodRecord>,\n): Promise<PeriodRecord[]> {\n const ids = await adapter.list(vault, PERIODS_COLLECTION)\n const records: PeriodRecord[] = []\n for (const id of ids) {\n const env = await adapter.get(vault, PERIODS_COLLECTION, id)\n if (env) records.push(await decrypt(env))\n }\n // Stable order by closedAt so chain verification is reproducible.\n records.sort((a, b) => a.closedAt.localeCompare(b.closedAt))\n return records\n}\n\n/**\n * Given the current ordered period list, pick the last entry that\n * belongs to the hash chain — used as the `priorPeriodHash` anchor\n * for the next closure/opening.\n *\n * @internal\n */\nexport async function chainAnchor(\n records: readonly PeriodRecord[],\n): Promise<{ priorPeriodName?: string; priorPeriodHash: string }> {\n const last = records[records.length - 1]\n if (!last) return { priorPeriodHash: '' }\n const hash = await sha256Hex(canonicalJson(last as unknown as Record<string, unknown>))\n return { priorPeriodName: last.name, priorPeriodHash: hash }\n}\n\n/**\n * Throw `PeriodClosedError` if the record being touched falls within\n * any closed period.\n *\n * Three signals, evaluated per period:\n *\n * 1. If the period declares a `dateField`, the guard reads\n * `record[dateField]` on BOTH the existing (prior) record AND the\n * incoming (new) record. Either comparing `<= endDate` triggers\n * the error — callers cannot slide a record into a closed period\n * by editing its date field.\n * 2. If the period has no `dateField`, the guard falls back to the\n * envelope `_ts` of the existing record. Fresh inserts (no\n * existing envelope) pass.\n * 3. For a delete, only the existing side is checked.\n *\n * @internal\n */\nexport function assertTsWritable(\n existing: { ts: string | null; record: Record<string, unknown> | null } | null,\n incomingRecord: Record<string, unknown> | null,\n closedPeriods: readonly PeriodRecord[],\n): void {\n for (const p of closedPeriods) {\n if (p.kind !== 'closed') continue\n if (p.dateField) {\n const checkRecord = (label: string, r: Record<string, unknown> | null): void => {\n if (!r) return\n const v = r[p.dateField!]\n if (typeof v === 'string' && v <= p.endDate) {\n throw new PeriodClosedError(p.name, p.endDate, `${label}[${p.dateField}]=${v}`)\n }\n }\n checkRecord('existing', existing?.record ?? null)\n checkRecord('incoming', incomingRecord)\n continue\n }\n // Fallback: write-time seal via envelope _ts.\n const existingTs = existing?.ts ?? null\n if (existingTs !== null && existingTs <= p.endDate) {\n throw new PeriodClosedError(p.name, p.endDate, existingTs)\n }\n }\n}\n\n/**\n * Sanity-check a proposed period name + endDate against existing\n * records. Shared by closePeriod / openPeriod so the two pathways\n * produce identical diagnostics.\n *\n * @internal\n */\nexport function validatePeriodName(\n name: string,\n existing: readonly PeriodRecord[],\n): void {\n if (name.length === 0) {\n throw new ValidationError('Period name cannot be empty.')\n }\n if (existing.some((p) => p.name === name)) {\n throw new ValidationError(`Period \"${name}\" already exists.`)\n }\n}\n\n/**\n * Wire a reserved-collection ledger append for a period record. The\n * period itself is stored via the adapter as an encrypted envelope;\n * the ledger entry is a normal `put` with the period's payloadHash,\n * so period closures inherit the chain's tamper-evidence.\n *\n * @internal\n */\nexport async function appendPeriodLedgerEntry(\n ledger: LedgerStore | null,\n actor: string,\n envelope: EncryptedEnvelope,\n name: string,\n): Promise<void> {\n if (!ledger) return\n const { envelopePayloadHash } = await import('../history/ledger/index.js')\n await ledger.append({\n op: 'put',\n collection: PERIODS_COLLECTION,\n id: name,\n version: envelope._v,\n actor,\n payloadHash: await envelopePayloadHash(envelope),\n })\n}\n","/**\n * Accounting-period subpath barrel.\n *\n * Public entry points live on `Vault`:\n *\n * - `vault.closePeriod({ name, endDate })`\n * - `vault.openPeriod({ name, startDate, fromPeriod, carryForward })`\n * - `vault.listPeriods()`\n * - `vault.getPeriod(name)`\n *\n * These types support user-defined wrappers and tests; the internal\n * helpers (`loadPeriods`, `assertTsWritable`, …) are exported so the\n * Vault can call them without TypeScript barrel gymnastics.\n */\nexport { withPeriods } from './active.js'\nexport type { PeriodsStrategy } from './strategy.js'\n\nexport {\n PERIODS_COLLECTION,\n loadPeriods,\n chainAnchor,\n assertTsWritable,\n validatePeriodName,\n appendPeriodLedgerEntry,\n} from './periods.js'\nexport type {\n PeriodRecord,\n ClosePeriodOptions,\n OpenPeriodOptions,\n CarryForwardContext,\n ReadOnlyCollection,\n} from './periods.js'\n","/**\n * `vault.exportBlobs()` — bulk blob extraction primitive.\n *\n * Async-iterable handle over every blob attached to records in a\n * vault, optionally filtered by collection allowlist and per-record\n * predicate. Emits tuples of `{ blobId, recordRef, bytes, meta }` so\n * the consumer can pipe into any sink (zip stream, S3 multipart, USB\n * copy, cold-storage tape) without pulling the whole export into\n * memory.\n *\n * ## Auth + audit\n *\n * - Capability check runs **once** at handle creation via\n * `Vault.assertCanExport('plaintext', 'blob')`. An operator whose\n * keyring lacks that bit fails before a single byte of ciphertext\n * is decrypted.\n * - Audit entry lands in `_export_audit` at handle creation: the\n * actor, start timestamp, target collections, predicate presence,\n * and batch mechanism. **No content hashes** — per the spec\n * non-correlation invariant.\n *\n * ## Abort + resume\n *\n * - `handle.abort()` flips the internal signal; the next iteration\n * boundary throws `AbortError`. Consumers already in `for await`\n * can catch and exit cleanly.\n * - Restart after a partial failure with `{ afterBlobId }` — the\n * iterator skips tuples up to (and including) that blob id before\n * yielding again. Combined with a blob-count ceiling it supports\n * idempotent batch re-runs.\n *\n * @module\n */\n\nimport type { Collection } from '../collection.js'\nimport type { SlotInfo } from '../types.js'\n\n// ─── Types ──────────────────────────────────────────────────────────────\n\nexport interface ExportBlobsOptions {\n /**\n * Collection allowlist. Omit to export blobs from every collection\n * the caller has read access to.\n */\n readonly collections?: readonly string[]\n /**\n * Per-record predicate. Called on the decrypted record BEFORE any\n * blob bytes are read for that record — returning false skips the\n * record and all its slots without touching their chunks.\n */\n readonly where?: (record: unknown, context: { collection: string; id: string }) => boolean\n /**\n * Resume after a specific blob id. The iterator skips tuples up to\n * and including this id, then yields. Format of the id is the same\n * as `ExportedBlob.blobId` (the HMAC-keyed eTag).\n */\n readonly afterBlobId?: string\n /**\n * External abort signal. When fired, the next iterator tick throws\n * `ExportBlobsAbortedError`. Honored alongside `handle.abort()`.\n */\n readonly signal?: AbortSignal\n}\n\nexport interface ExportedBlob {\n /** Opaque blob identifier — HMAC-keyed eTag, stable across vaults. */\n readonly blobId: string\n /** Where this blob came from in the vault. */\n readonly recordRef: {\n readonly collection: string\n readonly id: string\n readonly slot: string\n }\n /** Decrypted plaintext bytes. */\n readonly bytes: Uint8Array\n /** Best-effort metadata (from the blob slot record). */\n readonly meta: {\n readonly size: number\n /**\n * User-visible filename stored on the slot. Often equal to the\n * slot name; differs when the caller supplied an explicit\n * `filename` to `BlobSet.put()`.\n */\n readonly filename: string\n readonly mimeType?: string\n readonly createdAt?: string\n }\n}\n\nexport interface ExportBlobsHandle extends AsyncIterable<ExportedBlob> {\n /** Abort the export. Safe to call multiple times. */\n abort(): void\n /** True once `abort()` has fired or the external signal aborted. */\n readonly aborted: boolean\n}\n\nexport class ExportBlobsAbortedError extends Error {\n constructor(reason: string) {\n super(`exportBlobs aborted: ${reason}`)\n this.name = 'ExportBlobsAbortedError'\n }\n}\n\n// ─── Audit ──────────────────────────────────────────────────────────────\n\nexport const EXPORT_AUDIT_COLLECTION = '_export_audit'\n\nexport interface ExportBlobsAuditEntry {\n readonly id: string\n readonly mechanism: 'exportBlobs'\n readonly actor: string\n readonly startedAt: string\n readonly collections: readonly string[] | null\n readonly predicate: boolean\n readonly afterBlobId: string | null\n}\n\n// ─── Implementation ─────────────────────────────────────────────────────\n\n/**\n * Build the handle. Factored out of `Vault.exportBlobs` so the\n * implementation can be unit-tested without going through the\n * compartment lifecycle.\n */\nexport function createExportBlobsHandle(\n actor: string,\n listAccessibleCollections: () => Promise<string[]>,\n getCollection: <T>(name: string) => Collection<T>,\n writeAudit: (entry: ExportBlobsAuditEntry) => Promise<void>,\n options: ExportBlobsOptions,\n): ExportBlobsHandle {\n let aborted = false\n\n const abort = (): void => {\n aborted = true\n }\n\n if (options.signal) {\n if (options.signal.aborted) aborted = true\n options.signal.addEventListener('abort', () => { aborted = true })\n }\n\n function assertLive(): void {\n if (aborted) throw new ExportBlobsAbortedError('aborted by caller')\n }\n\n const allowlist = options.collections ? new Set(options.collections) : null\n\n // Write the audit entry BEFORE the first yield so a blocked\n // iteration still leaves an audit trail that the export started.\n let auditPromise: Promise<void> | null = null\n function writeAuditOnce(): Promise<void> {\n if (!auditPromise) {\n auditPromise = writeAudit({\n id: generateBatchId(),\n mechanism: 'exportBlobs',\n actor,\n startedAt: new Date().toISOString(),\n collections: options.collections ?? null,\n predicate: Boolean(options.where),\n afterBlobId: options.afterBlobId ?? null,\n })\n }\n return auditPromise\n }\n\n async function* generate(): AsyncGenerator<ExportedBlob> {\n await writeAuditOnce()\n assertLive()\n\n // Resolve target collections lazily — also keeps the call async.\n const allCollections = await listAccessibleCollections()\n const targets = allCollections.filter(name => {\n if (name.startsWith('_')) return false\n if (allowlist && !allowlist.has(name)) return false\n return true\n })\n\n let resumeCursorHit = options.afterBlobId === undefined\n\n for (const collectionName of targets) {\n if (aborted) return\n\n const coll = getCollection<Record<string, unknown>>(collectionName)\n const records = await coll.list().catch(() => [])\n for (const record of records) {\n if (aborted) return\n assertLive()\n\n const idField = (record as { id?: unknown }).id\n if (typeof idField !== 'string') continue\n\n if (options.where && !options.where(record, { collection: collectionName, id: idField })) continue\n\n const blobSet = coll.blob(idField)\n const slots = await blobSet.list().catch(() => [] as SlotInfo[])\n for (const slot of slots) {\n if (aborted) return\n\n if (!resumeCursorHit) {\n if (slot.eTag === options.afterBlobId) {\n resumeCursorHit = true\n }\n continue\n }\n\n const bytes = await blobSet.get(slot.name)\n if (!bytes) continue\n\n const item: ExportedBlob = {\n blobId: slot.eTag,\n recordRef: { collection: collectionName, id: idField, slot: slot.name },\n bytes,\n meta: {\n size: slot.size,\n filename: slot.filename,\n ...(slot.mimeType !== undefined && { mimeType: slot.mimeType }),\n ...(slot.uploadedAt !== undefined && { createdAt: slot.uploadedAt }),\n },\n }\n yield item\n }\n }\n }\n }\n\n const handle: ExportBlobsHandle = {\n abort,\n get aborted() { return aborted },\n [Symbol.asyncIterator]: () => generate(),\n }\n return handle\n}\n\n// ─── Helpers ────────────────────────────────────────────────────────────\n\nfunction generateBatchId(): string {\n // 16 bytes of crypto randomness, URL-safe base64, no padding.\n const raw = globalThis.crypto.getRandomValues(new Uint8Array(16))\n let s = ''\n for (const b of raw) s += b.toString(16).padStart(2, '0')\n return `batch-${Date.now().toString(36)}-${s.slice(0, 12)}`\n}\n","/**\n * Blob retention + compaction.\n *\n * Declarative per-collection / per-slot eviction policy. Two\n * triggers:\n *\n * - **`retainDays`** — age-based TTL. A slot uploaded more than N\n * days ago is evicted.\n * - **`evictWhen(record)`** — predicate over the **decrypted**\n * record. Lets consumers express \"the image is safe to drop once\n * the structured invoice has been reviewed and confirmed.\"\n *\n * Either trigger (or both) causes the slot to evict. Eviction removes\n * the slot entry from `_blob_slots_{collection}`, decrements the\n * blob's refCount (so unreferenced chunks can be GC'd by the next\n * sweep), and writes one entry to the `_blob_eviction_audit`\n * collection for tamper-evident record-keeping.\n *\n * The audit entry carries the eTag of the evicted blob (opaque HMAC\n * of plaintext under the vault's `_blob` DEK) — no plaintext leakage,\n * per the SPEC non-correlation invariant. Consumers reconstructing\n * \"what used to be attached\" can look up the audit entry by record\n * id.\n *\n * Compaction is **consumer-scheduled** — noy-db never runs a\n * background daemon. Call `vault.compact()` whenever your workflow\n * allows (cron, manual \"tidy\" button, cold-storage export prep, …).\n *\n * @module\n */\n\nimport type { NoydbStore, EncryptedEnvelope, SlotInfo } from '../types.js'\nimport { NOYDB_FORMAT_VERSION } from '../types.js'\nimport { encrypt } from '../crypto.js'\n\n// ─── Config types ───────────────────────────────────────────────────────\n\nexport interface BlobFieldPolicy<T = unknown> {\n /**\n * Age-based TTL in days. A slot whose `uploadedAt` is older than\n * `now - retainDays × 86400s` evicts on the next `vault.compact()`.\n * Omit to disable age-based eviction.\n */\n readonly retainDays?: number\n /**\n * Predicate evaluated against the decrypted record. When it returns\n * `true`, every matching slot on that record evicts. Omit to\n * disable predicate-based eviction.\n */\n readonly evictWhen?: (record: T) => boolean\n}\n\nexport type BlobFieldsConfig<T = unknown> = Record<string, BlobFieldPolicy<T>>\n\n// ─── Audit collection ──────────────────────────────────────────────────\n\nexport const BLOB_EVICTION_AUDIT_COLLECTION = '_blob_eviction_audit'\n\nexport interface BlobEvictionEntry {\n readonly id: string\n readonly collection: string\n readonly recordId: string\n readonly slotName: string\n readonly blobHash: string\n readonly reason: 'ttl' | 'predicate' | 'both'\n readonly evictedAt: string\n readonly actor: string\n}\n\n// ─── Compaction result ──────────────────────────────────────────────────\n\nexport interface CompactionResult {\n /** Number of blob slots evicted across all collections. */\n readonly evicted: number\n /** Number of records touched (iterated + policy checked). */\n readonly records: number\n /** Number of collections with `blobFields` configured. */\n readonly collections: number\n /** Number of audit entries written. Equal to `evicted`. */\n readonly auditEntries: number\n /** Per-collection breakdown for diagnostics. */\n readonly byCollection: Record<string, { records: number; evicted: number }>\n}\n\n// ─── Core ──────────────────────────────────────────────────────────────\n\nexport interface CompactRunOptions {\n /** Override \"now\" for deterministic testing. */\n readonly now?: Date\n /**\n * Stop after this many evictions. Useful for capped batches / cron\n * jobs that need to fit in a time window. `undefined` = unbounded.\n */\n readonly maxEvictions?: number\n /**\n * Dry-run — evaluate policies and return the counts, but do NOT\n * delete slots or write audit entries. Lets a consumer preview\n * what would happen.\n */\n readonly dryRun?: boolean\n}\n\nexport interface CompactionContext {\n readonly adapter: NoydbStore\n readonly vault: string\n readonly actor: string\n readonly encrypted: boolean\n readonly getDEK: (collection: string) => Promise<CryptoKey>\n /**\n * Resolve a collection's declared `blobFields` config. Returns an\n * empty map for collections without the config — the walk skips\n * those.\n */\n readonly getBlobFields: <T>(collection: string) => BlobFieldsConfig<T> | null\n /** List collection names in the vault. */\n readonly listCollections: () => Promise<string[]>\n /** List record ids in a collection. */\n readonly listRecords: (collection: string) => Promise<string[]>\n /** Decrypt and return the record. Null when absent. */\n readonly getRecord: <T>(collection: string, id: string) => Promise<T | null>\n /** Return the BlobSet-like handle for a record's slots. */\n readonly listSlots: (collection: string, id: string) => Promise<SlotInfo[]>\n /** Delete a slot and decrement its blob's refCount. */\n readonly deleteSlot: (collection: string, id: string, slotName: string) => Promise<void>\n}\n\nexport async function runCompaction(\n ctx: CompactionContext,\n options: CompactRunOptions = {},\n): Promise<CompactionResult> {\n const now = options.now ?? new Date()\n const maxEvictions = options.maxEvictions ?? Infinity\n const dryRun = options.dryRun === true\n\n const allCollections = await ctx.listCollections()\n const byCollection: Record<string, { records: number; evicted: number }> = {}\n let evicted = 0\n let records = 0\n let auditEntries = 0\n let collectionsWithPolicy = 0\n\n outer: for (const collectionName of allCollections) {\n if (collectionName.startsWith('_')) continue\n const config = ctx.getBlobFields(collectionName)\n if (!config) continue\n const configuredSlots = Object.keys(config)\n if (configuredSlots.length === 0) continue\n collectionsWithPolicy += 1\n byCollection[collectionName] = { records: 0, evicted: 0 }\n\n const ids = await ctx.listRecords(collectionName)\n for (const recordId of ids) {\n if (evicted >= maxEvictions) break outer\n\n const record = await ctx.getRecord(collectionName, recordId).catch(() => null)\n if (record === null) continue\n records += 1\n byCollection[collectionName].records += 1\n\n const slots = await ctx.listSlots(collectionName, recordId).catch(() => [])\n for (const slot of slots) {\n if (evicted >= maxEvictions) break outer\n const policy = config[slot.name]\n if (!policy) continue\n\n const reason = evaluatePolicy(policy, record, slot, now)\n if (!reason) continue\n\n if (!dryRun) {\n await ctx.deleteSlot(collectionName, recordId, slot.name)\n await writeAuditEntry(ctx, {\n id: generateEvictionId(collectionName, recordId, slot.name),\n collection: collectionName,\n recordId,\n slotName: slot.name,\n blobHash: slot.eTag,\n reason,\n evictedAt: now.toISOString(),\n actor: ctx.actor,\n })\n auditEntries += 1\n }\n evicted += 1\n byCollection[collectionName].evicted += 1\n }\n }\n }\n\n return {\n evicted,\n records,\n collections: collectionsWithPolicy,\n auditEntries,\n byCollection,\n }\n}\n\nfunction evaluatePolicy<T>(\n policy: BlobFieldPolicy<T>,\n record: T,\n slot: SlotInfo,\n now: Date,\n): 'ttl' | 'predicate' | 'both' | null {\n let ttlTriggered = false\n let predicateTriggered = false\n\n if (policy.retainDays !== undefined && policy.retainDays > 0) {\n const uploadedAt = Date.parse(slot.uploadedAt)\n if (Number.isFinite(uploadedAt)) {\n const ageMs = now.getTime() - uploadedAt\n const limitMs = policy.retainDays * 86_400_000\n if (ageMs > limitMs) ttlTriggered = true\n }\n }\n\n if (policy.evictWhen) {\n try {\n if (policy.evictWhen(record)) predicateTriggered = true\n } catch {\n // Predicate error → do NOT evict. Fail closed.\n }\n }\n\n if (ttlTriggered && predicateTriggered) return 'both'\n if (ttlTriggered) return 'ttl'\n if (predicateTriggered) return 'predicate'\n return null\n}\n\nfunction generateEvictionId(collection: string, recordId: string, slotName: string): string {\n const rand = globalThis.crypto.getRandomValues(new Uint8Array(8))\n let suffix = ''\n for (const b of rand) suffix += b.toString(16).padStart(2, '0')\n return `${collection}__${recordId}__${slotName}__${suffix}`\n}\n\nasync function writeAuditEntry(ctx: CompactionContext, entry: BlobEvictionEntry): Promise<void> {\n const json = JSON.stringify(entry)\n let envelope: EncryptedEnvelope\n if (ctx.encrypted) {\n const dek = await ctx.getDEK(BLOB_EVICTION_AUDIT_COLLECTION)\n const { iv, data } = await encrypt(json, dek)\n envelope = {\n _noydb: NOYDB_FORMAT_VERSION,\n _v: 1,\n _ts: entry.evictedAt,\n _iv: iv,\n _data: data,\n _by: entry.actor,\n }\n } else {\n envelope = {\n _noydb: NOYDB_FORMAT_VERSION,\n _v: 1,\n _ts: entry.evictedAt,\n _iv: '',\n _data: json,\n _by: entry.actor,\n }\n }\n await ctx.adapter.put(ctx.vault, BLOB_EVICTION_AUDIT_COLLECTION, entry.id, envelope)\n}\n","/**\n * Magic-link-bound cross-user delegation grants.\n *\n * This module is the **core storage + encryption layer** that lets a\n * grantor issue a tier-DEK to a user whose KEK they do not know. The\n * trust bridge is provided by the `@noy-db/on-magic-link` package:\n *\n * 1. Grantor picks a grantee identity (user id + email handle).\n * 2. Grantor mints a magic-link token (ULID) via `createMagicLinkToken`.\n * 3. Grantor derives a **content key** + a **KEK** from\n * `(serverSecret, token, vault)` using HKDF-SHA256 with separate\n * `info` tags — both callers (grantor and grantee) can derive the\n * same keys given the same inputs.\n * 4. Grantor persists a record in `_magic_link_grants/<token>`:\n * - envelope `_data` is AES-GCM encrypted under the content key\n * - the inner `wrappedDek` is AES-KW wrapped under the KEK\n * 5. Grantee receives the URL, derives the same content key + KEK,\n * loads the grant, decrypts the envelope, unwraps the tier DEK.\n *\n * ## Why a separate collection from `_delegations`\n *\n * `_delegations` envelopes are encrypted under a DEK shared across\n * every vault user (audit-visibility). External auditors / client\n * portal users have NO pre-existing keyring, so they cannot read that\n * DEK. Magic-link grants live in their own collection whose envelope\n * encryption is derived purely from the magic-link URL + server secret\n * — nothing else is required to decrypt.\n *\n * ## Batch grants\n *\n * One magic-link token may point to MULTIPLE grants (e.g. the client\n * portal case: invoices + payments + etax all share one link). Each\n * grant is persisted under a distinct record id:\n *\n * `<token>` for the single-grant / primary entry\n * `<token>:<index>` for subsequent entries\n *\n * `listMagicLinkGrants(store, vault, token)` enumerates every record\n * whose id begins with `<token>` so the claimant can materialize all\n * DEKs in one pass.\n *\n * ## Revocation\n *\n * `store.delete(vault, _magic_link_grants, <token>)` immediately\n * invalidates the link — even if the URL was captured and the server\n * secret leaked, no payload remains to decrypt.\n *\n * @module\n */\n\nimport type { NoydbStore, EncryptedEnvelope } from '../types.js'\nimport type { UnlockedKeyring } from './keyring.js'\nimport { encrypt, decrypt, wrapKey, unwrapKey } from '../crypto.js'\nimport { dekKey } from './tiers.js'\nimport { DelegationTargetMissingError } from '../errors.js'\n\n/** Reserved collection holding magic-link grant envelopes. */\nexport const MAGIC_LINK_GRANTS_COLLECTION = '_magic_link_grants'\n\n/** HKDF `info` for the AES-GCM content key. Version-namespaced. */\nexport const MAGIC_LINK_CONTENT_INFO_PREFIX = 'noydb-magic-link-content-v1:'\n\n/** HKDF `info` for the AES-KW KEK. Matches `@noy-db/on-magic-link`. */\nexport const MAGIC_LINK_KEK_INFO_PREFIX = 'noydb-magic-link-v1:'\n\n// ─── Types ──────────────────────────────────────────────────────────────\n\n/**\n * Decrypted payload of a magic-link grant record. Mirrors\n * `DelegationToken` in `team/delegation.ts` but tracked separately\n * because the two flows persist under different collections + envelope\n * encryption schemes.\n */\nexport interface MagicLinkGrantPayload {\n readonly id: string\n readonly toUser: string\n readonly fromUser: string\n readonly tier: number\n /** Collection name or `null` for the vault-wide tier DEK. */\n readonly collection: string | null\n /** Optional specific record id scope. */\n readonly record?: string\n /** ISO timestamp — grant expires at this instant. */\n readonly until: string\n /** AES-KW-wrapped tier DEK, unwrap with the magic-link KEK. */\n readonly wrappedDek: string\n /** ISO timestamp the grant was issued. */\n readonly createdAt: string\n /** Optional caller-provided label (surfaced in audit UIs). */\n readonly note?: string\n}\n\nexport interface IssueMagicLinkGrantOptions {\n readonly toUser: string\n readonly tier: number\n readonly collection?: string\n readonly record?: string\n readonly until: Date | string\n readonly note?: string\n}\n\nexport interface MagicLinkGrantRecord {\n /** Store record id — `<token>` or `<token>:<index>` for batch entries. */\n readonly recordId: string\n readonly payload: MagicLinkGrantPayload\n}\n\n// ─── Key derivation ─────────────────────────────────────────────────────\n\n/**\n * Derive the AES-GCM content key from the same HKDF inputs used for\n * the magic-link KEK. Different `info` suffix → domain-separated key.\n *\n * Exported so the `@noy-db/on-magic-link` package can share the exact\n * derivation path without cross-dependency between the two modules.\n */\nexport async function deriveMagicLinkContentKey(\n serverSecret: string | Uint8Array<ArrayBuffer>,\n token: string,\n vault: string,\n): Promise<CryptoKey> {\n const subtle = globalThis.crypto.subtle\n const ikmBytes =\n serverSecret instanceof Uint8Array\n ? serverSecret\n : new TextEncoder().encode(serverSecret)\n const tokenBytes = new TextEncoder().encode(token)\n const saltBuffer = await subtle.digest('SHA-256', tokenBytes)\n const info = new TextEncoder().encode(MAGIC_LINK_CONTENT_INFO_PREFIX + vault)\n const ikm = await subtle.importKey('raw', ikmBytes, 'HKDF', false, ['deriveKey'])\n return subtle.deriveKey(\n { name: 'HKDF', hash: 'SHA-256', salt: saltBuffer, info },\n ikm,\n { name: 'AES-GCM', length: 256 },\n false,\n ['encrypt', 'decrypt'],\n )\n}\n\n// ─── Issue ──────────────────────────────────────────────────────────────\n\n/**\n * Persist a magic-link grant record. Caller derives + provides both\n * the content key and the KEK; this function performs the wrap/encrypt\n * and writes the envelope.\n *\n * `recordId` lets the caller use either the bare token (primary grant)\n * or a suffixed id (batch entry). The writer is responsible for\n * collision-avoidance across batch entries.\n */\nexport async function writeMagicLinkGrant(\n store: NoydbStore,\n vault: string,\n grantor: UnlockedKeyring,\n contentKey: CryptoKey,\n grantKek: CryptoKey,\n recordId: string,\n opts: IssueMagicLinkGrantOptions,\n): Promise<MagicLinkGrantRecord> {\n const collectionName = opts.collection ?? null\n const sourceKey = collectionName\n ? dekKey(collectionName, opts.tier)\n : `__any#${opts.tier}`\n const sourceDek = grantor.deks.get(sourceKey)\n if (!sourceDek) {\n throw new DelegationTargetMissingError(\n `grantor cannot find tier ${opts.tier} DEK for ${collectionName ?? '(any)'}`,\n )\n }\n const wrappedDek = await wrapKey(sourceDek, grantKek)\n\n const until = typeof opts.until === 'string' ? opts.until : opts.until.toISOString()\n const createdAt = new Date().toISOString()\n const payload: MagicLinkGrantPayload = {\n id: recordId,\n toUser: opts.toUser,\n fromUser: grantor.userId,\n tier: opts.tier,\n collection: collectionName,\n ...(opts.record && { record: opts.record }),\n until,\n wrappedDek,\n createdAt,\n ...(opts.note && { note: opts.note }),\n }\n\n const { iv, data } = await encrypt(JSON.stringify(payload), contentKey)\n const envelope: EncryptedEnvelope = {\n _noydb: 1,\n _v: 1,\n _ts: createdAt,\n _iv: iv,\n _data: data,\n _by: grantor.userId,\n }\n await store.put(vault, MAGIC_LINK_GRANTS_COLLECTION, recordId, envelope)\n return { recordId, payload }\n}\n\n// ─── Claim ──────────────────────────────────────────────────────────────\n\n/**\n * Fetch + decrypt a single magic-link grant record by id. Returns null\n * when the record is absent OR when decryption fails (wrong server\n * secret, wrong vault, tampered envelope) — callers treat a null as\n * \"this URL is not valid for this server\".\n *\n * The returned payload's `wrappedDek` is still AES-KW-wrapped; the\n * caller unwraps it with the magic-link KEK to obtain the tier DEK.\n */\nexport async function readMagicLinkGrantRecord(\n store: NoydbStore,\n vault: string,\n contentKey: CryptoKey,\n recordId: string,\n): Promise<MagicLinkGrantPayload | null> {\n const env = await store.get(vault, MAGIC_LINK_GRANTS_COLLECTION, recordId)\n if (!env) return null\n try {\n const json = await decrypt(env._iv, env._data, contentKey)\n return JSON.parse(json) as MagicLinkGrantPayload\n } catch {\n return null\n }\n}\n\n/**\n * Enumerate every grant record sharing the magic-link `token` prefix\n * (i.e. the primary `<token>` entry plus any `<token>:*` batch entries).\n * Expired grants are still returned — the caller filters on `until`.\n */\nexport async function listMagicLinkGrants(\n store: NoydbStore,\n vault: string,\n contentKey: CryptoKey,\n token: string,\n): Promise<MagicLinkGrantPayload[]> {\n const ids = await store.list(vault, MAGIC_LINK_GRANTS_COLLECTION)\n const matching = ids.filter(id => id === token || id.startsWith(`${token}:`))\n const out: MagicLinkGrantPayload[] = []\n for (const id of matching) {\n const payload = await readMagicLinkGrantRecord(store, vault, contentKey, id)\n if (payload) out.push(payload)\n }\n return out\n}\n\n/**\n * Unwrap the tier DEK from a grant payload using the magic-link KEK.\n * Thin wrapper around `unwrapKey` — provided so the claimant can avoid\n * importing `crypto.js` directly.\n */\nexport async function unwrapMagicLinkGrant(\n payload: MagicLinkGrantPayload,\n grantKek: CryptoKey,\n): Promise<CryptoKey> {\n return unwrapKey(payload.wrappedDek, grantKek)\n}\n\n/**\n * Delete a magic-link grant (primary + every batch entry sharing the\n * token). Safe to call when nothing exists.\n */\nexport async function revokeMagicLinkGrant(\n store: NoydbStore,\n vault: string,\n token: string,\n): Promise<number> {\n const ids = await store.list(vault, MAGIC_LINK_GRANTS_COLLECTION)\n const matching = ids.filter(id => id === token || id.startsWith(`${token}:`))\n for (const id of matching) {\n await store.delete(vault, MAGIC_LINK_GRANTS_COLLECTION, id)\n }\n return matching.length\n}\n\n// ─── Helpers ────────────────────────────────────────────────────────────\n\n/**\n * Compose the batch-entry record id. `index === 0` → bare token.\n * Subsequent entries use `<token>:<index>` so `store.list()` can\n * enumerate them all by common prefix.\n */\nexport function magicLinkGrantRecordId(token: string, index: number): string {\n return index === 0 ? token : `${token}:${index}`\n}\n\n/**\n * True when the payload's `until` is in the past relative to `now`.\n * Kept here (rather than inlined) so the semantics stay aligned with\n * the canonical `DelegationToken` expiry check.\n */\nexport function isMagicLinkGrantExpired(\n payload: MagicLinkGrantPayload,\n now: Date = new Date(),\n): boolean {\n return payload.until <= now.toISOString()\n}\n","/**\n * Public `vault.user.*` API surface.\n *\n * Three families:\n * - Write-self: `me` / `updateMe` / `setMe` — always target the writer's\n * own keyringId. **Own-only write rule** is structural — no method\n * exists to write someone else's envelope.\n * - Read-anyone: `get` / `list` — read other principals' envelopes\n * (subject to `view-team-profiles` policy gate, wired in #22).\n * - Reactive: `subscribe` / `live` — in-process event emission on local\n * writes. Cross-instance updates land via the team/sync engine and\n * surface to subscribers when the sync diff replays through this API.\n *\n * @see docs/superpowers/specs/2026-05-05-user-envelope-design.md\n *\n * @module\n */\nimport type { NoydbStore } from '../../types.js'\nimport { PolicyDeniedError } from '../../policy/errors.js'\nimport type { FactorProof } from '../../policy/types.js'\nimport {\n loadUserEnvelope,\n saveUserEnvelope,\n listUserEnvelopeIds,\n} from './storage.js'\nimport type { UserEnvelope } from './types.js'\nimport {\n persistUserVisibility,\n readUserVisibility,\n} from '../../directory/visibility.js'\nimport type { UserVisibility } from '../../directory/types.js'\n\n/**\n * Recursive partial. Used for `updateMe(patch)` so callers can hand in\n * deeply-nested partial shapes and have them deep-merged onto the\n * current envelope.\n */\nexport type DeepPartial<T> = T extends object\n ? { [P in keyof T]?: DeepPartial<T[P]> }\n : T\n\n/**\n * Recursive partial with `null` allowed at every level — used by\n * `updateMe` (#57) to express deletion intent in addition to merge.\n *\n * Semantics inside `updateMe`:\n * - `undefined` (or absent key) — skip; source value preserved\n * - `null` — delete the key from the resulting envelope\n * - any other value — overwrite (deep-merge for plain objects,\n * replace for primitives / arrays)\n *\n * Matches lodash `_.merge` behavior on `null` and Firestore's\n * `FieldValue.delete()` semantics. Loosened from `DeepPartial<T>` per\n * #57; consumers wanting the original \"merge-only\" surface can keep\n * importing `DeepPartial` and avoid passing `null`.\n */\nexport type DeepPartialOrNull<T> = T extends object\n ? { [P in keyof T]?: DeepPartialOrNull<T[P]> | null }\n : T\n\n/** Cancel a previously-registered subscription. */\nexport type Unsubscribe = () => void\n\n/**\n * Optional factor-proof bundle threaded into gated user-envelope\n * operations. Same shape as `Noydb.checkGate(vault, gate, presented)`\n * accepts elsewhere — apps that have already presented a TOTP/email-OTP\n * for this session pass it here to satisfy tightened policies.\n */\nexport interface UserEnvelopePresented {\n readonly factors?: readonly FactorProof[]\n readonly sharedDevice?: boolean\n}\n\n/**\n * Callback used by `UserApi` to validate the active session against a\n * policy gate. Provided by the `Vault` constructor; in production this\n * delegates to `Noydb.checkGate(vault, gate, presented)`. In tests, a\n * no-op stub is fine.\n */\nexport type UserEnvelopeCheckGate = (\n gate: 'edit-own-profile' | 'view-team-profiles',\n presented?: UserEnvelopePresented,\n) => Promise<void>\n\n/**\n * Reactive handle returned by `live()`. `current` is the most recently\n * observed value; `subscribe(cb)` fires on subsequent local writes.\n * `stop()` releases the underlying subscription.\n */\nexport interface LiveUserEnvelope<T> {\n current(): UserEnvelope<T> | null\n subscribe(cb: (env: UserEnvelope<T> | null) => void): Unsubscribe\n stop(): void\n}\n\ninterface ChangeListener<T = unknown> {\n (env: UserEnvelope<T> | null): void\n}\n\n/**\n * Implementation behind `vault.user`. Constructed once per Vault, holds\n * the writer's keyringId in closure so `updateMe`/`setMe` cannot target\n * any other principal — the own-only rule is enforced at the type level\n * (no `set(otherKeyringId, …)` method) AND at runtime (the\n * keyringId argument simply doesn't exist on the write path).\n */\nexport class UserApi {\n /** keyringId → set of listeners. Wildcard '*' fires on every change. */\n private readonly listeners = new Map<string, Set<ChangeListener>>()\n\n constructor(\n private readonly adapter: NoydbStore,\n private readonly vaultName: string,\n /** The writer's own keyringId. Frozen at construction time. */\n private readonly writerKeyringId: string,\n private readonly getDek: () => Promise<CryptoKey>,\n /**\n * Policy-gate validator. When omitted, gates are skipped — useful\n * for low-level tests that exercise the storage layer directly.\n * Production paths always wire the Noydb-backed implementation.\n */\n private readonly checkGate?: UserEnvelopeCheckGate,\n ) {}\n\n // ─── Write-self ──────────────────────────────────────────────────────\n\n /** Read the writer's own envelope. Returns null if never written. */\n async me<T = unknown>(): Promise<UserEnvelope<T> | null> {\n const dek = await this.getDek()\n return loadUserEnvelope<T>(this.adapter, this.vaultName, this.writerKeyringId, dek)\n }\n\n /**\n * Deep-merge a partial patch into the writer's own envelope. Creates\n * the envelope on first call. Optimistic-concurrency safe — a stale\n * `_v` (parallel writer on another device) throws `ConflictError`.\n *\n * Patch semantics (#57):\n * - `undefined` (or omitted key) — skip; existing value preserved\n * - `null` — delete the field from the merged result\n * - any other value — overwrite (deep-merge for plain objects,\n * replace for primitives / arrays)\n *\n * To clear a field, pass `null` rather than `undefined`. Callers\n * with shape `T = string | null` where `null` is a meaningful value\n * should use `setMe` for that specific field instead — `null` here\n * always means delete.\n *\n * Gated by the `edit-own-profile` policy gate (default `minTier: 3`).\n * Pass `presented` to satisfy tightened policies that require a\n * factor proof (e.g. STRICT_POLICY's TOTP requirement).\n */\n async updateMe<T extends object = Record<string, unknown>>(\n patch: DeepPartialOrNull<T>,\n presented?: UserEnvelopePresented,\n ): Promise<UserEnvelope<T>> {\n if (this.checkGate) await this.checkGate('edit-own-profile', presented)\n const dek = await this.getDek()\n const current = await loadUserEnvelope<T>(\n this.adapter,\n this.vaultName,\n this.writerKeyringId,\n dek,\n )\n const merged: T = current ? deepMerge(current.data, patch) : (patch as unknown as T)\n const written = await saveUserEnvelope<T>(\n this.adapter,\n this.vaultName,\n this.writerKeyringId,\n merged,\n dek,\n current?._v ?? 0,\n )\n this.fireChange(this.writerKeyringId, written)\n return written\n }\n\n /**\n * Replace the writer's own envelope with `payload`. Use sparingly —\n * `updateMe` is the canonical mutation. No `expectedVersion` check;\n * callers explicitly take last-write-wins semantics.\n *\n * Gated by `edit-own-profile`. See `updateMe` for `presented` usage.\n */\n async setMe<T = unknown>(\n payload: T,\n presented?: UserEnvelopePresented,\n ): Promise<UserEnvelope<T>> {\n if (this.checkGate) await this.checkGate('edit-own-profile', presented)\n const dek = await this.getDek()\n const written = await saveUserEnvelope<T>(\n this.adapter,\n this.vaultName,\n this.writerKeyringId,\n payload,\n dek,\n )\n this.fireChange(this.writerKeyringId, written)\n return written\n }\n\n // ─── Visibility (#122) ───────────────────────────────────────────────\n\n /**\n * Read the current user's visibility flag from\n * `_meta/visibility/<keyringId>`. Returns `{ hidden: false }` when no\n * document has been persisted (the default-visible case).\n */\n async getMyVisibility(): Promise<UserVisibility> {\n const persisted = await readUserVisibility(this.adapter, this.vaultName, this.writerKeyringId)\n return persisted ?? { hidden: false }\n }\n\n /**\n * Update the current user's visibility in the team directory.\n *\n * - `hidden: true` — opt out of the default `listUsersWithEnvelopes`\n * listing. `owner`/`admin` callers can still see the user by passing\n * `{ includeHidden: true }`.\n * - `hidden: false` — opt back in.\n *\n * Own-only by construction: the keyringId argument doesn't exist on\n * this method, so no caller can hide or unhide another principal.\n *\n * Honest caveat: this is a UX flag, not a privacy guarantee. The\n * envelope ciphertext at `_users/<keyringId>` and the keyring file at\n * `_keyring/<userId>` are both still observable to anyone with direct\n * store read access. See `docs/subsystems/user-envelope.md` →\n * \"Directory visibility\".\n */\n async setMyVisibility(visibility: UserVisibility): Promise<void> {\n await persistUserVisibility(\n this.adapter,\n this.vaultName,\n this.writerKeyringId,\n { hidden: visibility.hidden },\n )\n }\n\n // ─── Read-anyone ─────────────────────────────────────────────────────\n\n /**\n * Read another principal's envelope by their keyringId. Returns null\n * if the principal exists but has no envelope yet, or if the\n * keyringId does not exist at all.\n *\n * Gated by `view-team-profiles` (default `minTier: 2`) — but ONLY for\n * cross-principal reads. Reading your own envelope (`keyringId ===\n * self`) is never gated; that's just `me()` written long-form.\n */\n async get<T = unknown>(\n keyringId: string,\n presented?: UserEnvelopePresented,\n ): Promise<UserEnvelope<T> | null> {\n if (this.checkGate && keyringId !== this.writerKeyringId) {\n await this.checkGate('view-team-profiles', presented)\n }\n const dek = await this.getDek()\n return loadUserEnvelope<T>(this.adapter, this.vaultName, keyringId, dek)\n }\n\n /**\n * Read every persisted envelope in the vault. Order is store-defined.\n *\n * Gated by `view-team-profiles`. Default policy (`minTier: 2`) lets\n * any authenticated session read all envelopes. Two privacy-strict\n * opt-outs:\n *\n * - `view-team-profiles.enabled: false` → list() returns only the\n * caller's own envelope (silent self-fallback, no thrown error).\n * - `view-team-profiles.minTier: 1` + insufficient tier → throws\n * `PolicyDeniedError` with `reason: 'insufficient-tier'`. The\n * caller is expected to elevate, not silently degrade.\n *\n * The asymmetry is deliberate: `enabled: false` is a deliberate\n * design choice (\"nobody sees teammate profiles in this app\");\n * `insufficient-tier` is \"you need to authenticate further\". Different\n * UX prompts for different intents.\n */\n async list<T = unknown>(presented?: UserEnvelopePresented): Promise<UserEnvelope<T>[]> {\n if (this.checkGate) {\n try {\n await this.checkGate('view-team-profiles', presented)\n } catch (err) {\n if (err instanceof PolicyDeniedError && err.reason === 'disabled') {\n // Privacy-strict opt-out: quietly return only self.\n const me = await this.me<T>()\n return me ? [me] : []\n }\n throw err\n }\n }\n const dek = await this.getDek()\n const ids = await listUserEnvelopeIds(this.adapter, this.vaultName)\n const envelopes = await Promise.all(\n ids.map((id) => loadUserEnvelope<T>(this.adapter, this.vaultName, id, dek)),\n )\n return envelopes.filter((e): e is UserEnvelope<T> => e !== null)\n }\n\n // ─── Reactive ────────────────────────────────────────────────────────\n\n /**\n * Listen for changes to a specific keyringId's envelope. The callback\n * fires synchronously after every successful local `updateMe` /\n * `setMe` for that principal.\n *\n * Cross-instance changes (a teammate edits their profile on their\n * device, the sync engine pulls the diff onto this device) will fire\n * subscribers when the sync layer replays the write through this API.\n * In v1, subscribers do NOT fire on raw store changes — wire your sync\n * layer to call back through `vault.user.setMe` / `updateMe` if you\n * need that.\n *\n * Pass keyringId `'*'` to fire on every change in the vault.\n */\n subscribe<T = unknown>(\n keyringId: string,\n cb: (env: UserEnvelope<T> | null) => void,\n ): Unsubscribe {\n let listeners = this.listeners.get(keyringId)\n if (!listeners) {\n listeners = new Set()\n this.listeners.set(keyringId, listeners)\n }\n const wrapped: ChangeListener = cb as ChangeListener\n listeners.add(wrapped)\n return () => {\n listeners?.delete(wrapped)\n if (listeners && listeners.size === 0) {\n this.listeners.delete(keyringId)\n }\n }\n }\n\n /**\n * Reactive handle that caches the current value and re-reads on every\n * change for the given keyringId. Convenient for framework bindings:\n *\n * const live = vault.user.live<UserShape>(vault.userId)\n * live.subscribe(env => render(env?.data))\n *\n * Initial value is `null` until the first `current()` call materializes\n * it via `vault.user.get()`. Call `stop()` when done to release the\n * subscription.\n */\n live<T = unknown>(keyringId: string): LiveUserEnvelope<T> {\n let value: UserEnvelope<T> | null = null\n let primed = false\n const unsubscribe = this.subscribe<T>(keyringId, (env) => {\n value = env\n })\n\n return {\n current(): UserEnvelope<T> | null {\n if (!primed) {\n primed = true\n // First call: kick off a read but return synchronously. The\n // subscriber will be re-fired by the next write or the caller\n // can await `vault.user.get()` directly for an immediate read.\n }\n return value\n },\n subscribe: (cb) => this.subscribe<T>(keyringId, cb),\n stop: unsubscribe,\n }\n }\n\n // ─── Internal: change emission ───────────────────────────────────────\n\n private fireChange<T>(keyringId: string, env: UserEnvelope<T> | null): void {\n const targeted = this.listeners.get(keyringId)\n if (targeted) for (const l of targeted) l(env)\n const wildcard = this.listeners.get('*')\n if (wildcard) for (const l of wildcard) l(env)\n }\n}\n\n/**\n * Recursive plain-object deep merge with delete intent (#57).\n *\n * Patch semantics:\n * - `undefined` — skip the key; source value preserved\n * - `null` — delete the key from output (lodash `_.merge` /\n * Firestore `FieldValue.delete()` semantics)\n * - plain object — recurse (deep merge)\n * - any other value — replace (arrays are replaced, not concatenated)\n *\n * Safe against the JS quirk where an own property explicitly set to\n * `undefined` is iterated by `Object.entries`. We dispatch on the value\n * BEFORE writing, so `{ k: undefined }` triggers the skip branch rather\n * than overwriting `out[k]` with undefined.\n */\nfunction deepMerge<T>(source: T, patch: DeepPartialOrNull<T>): T {\n if (!isPlainObject(source) || !isPlainObject(patch)) {\n // Top-level non-object replace. `null` patch at the leaf level\n // would have been caught by the parent recursion's branch table;\n // at the top level it means \"set the whole envelope to null,\"\n // which the type system already prevents (T extends object).\n return patch as unknown as T\n }\n const out: Record<string, unknown> = { ...(source as Record<string, unknown>) }\n for (const [key, patchVal] of Object.entries(patch as Record<string, unknown>)) {\n if (patchVal === undefined) {\n // Skip — preserve the source value at this key. Matches the\n // pre-#57 behavior so callers who never used `null` see no diff.\n continue\n }\n if (patchVal === null) {\n // Delete intent. `delete` rather than `out[key] = undefined`\n // because JSON.stringify drops undefined fields silently and\n // we want the deletion to be visible to consumers iterating\n // the merged object (e.g. `Object.keys(merged.profile)`).\n delete out[key]\n continue\n }\n const sourceVal = (source as Record<string, unknown>)[key]\n if (isPlainObject(patchVal)) {\n // Recurse for any plain-object patch — including the \"source is\n // missing this key\" case. Without recursing through a synthetic\n // empty source, nested `null` deletions in the patch would land\n // as literal `null` values instead of triggering the delete\n // branch (e.g. `{ app: { signature: null } }` against a missing\n // `app` would emit `{ app: { signature: null } }` instead of\n // `{ app: {} }`).\n const recurseSource = isPlainObject(sourceVal) ? sourceVal : {}\n out[key] = deepMerge(recurseSource, patchVal as DeepPartialOrNull<typeof recurseSource>)\n } else {\n out[key] = patchVal\n }\n }\n return out as T\n}\n\nfunction isPlainObject(x: unknown): x is Record<string, unknown> {\n if (x === null || typeof x !== 'object') return false\n if (Array.isArray(x)) return false\n const proto = Object.getPrototypeOf(x) as object | null\n return proto === Object.prototype || proto === null\n}\n","/**\n * Deterministic JSON serializer: sorts object keys lexicographically at every\n * depth so structurally-equivalent objects produce identical strings. Array\n * order is preserved (arrays are semantically ordered).\n *\n * Used by {@link sha256Hex} to fingerprint a derived JSON Schema for\n * hash-based skip on persisted-schema writes.\n *\n * @module\n */\n\nexport function canonicalize(value: unknown): string {\n if (value === null || typeof value !== 'object') {\n return JSON.stringify(value)\n }\n if (Array.isArray(value)) {\n return '[' + value.map(canonicalize).join(',') + ']'\n }\n const obj = value as Record<string, unknown>\n const keys = Object.keys(obj).sort()\n const parts = keys.map((k) => JSON.stringify(k) + ':' + canonicalize(obj[k]))\n return '{' + parts.join(',') + '}'\n}\n","/**\n * Derive a {@link PersistedSchemaEnvelope} from a Standard Schema v1\n * validator. v0 supports Zod via `zod-to-json-schema` (optional peer-dep);\n * other families write a stub envelope flagging the kind.\n *\n * @see docs/superpowers/specs/2026-05-22-schema-dump-design.md\n *\n * @module\n */\n\nimport { canonicalize } from './canonicalize.js'\nimport { sha256Hex } from '../crypto.js'\nimport type { PersistedSchemaEnvelope, PersistedSchemaKind } from './types.js'\n\n/**\n * Heuristic Zod detection — Zod schemas carry a `_def.typeName` property\n * starting with `Zod` (e.g. `ZodObject`, `ZodString`). This survives Zod's\n * minor-version bumps because the typeName naming is stable across v3.\n */\nexport function isZodSchema(value: unknown): boolean {\n if (value === null || typeof value !== 'object') return false\n const def = (value as { _def?: { typeName?: unknown } })._def\n if (!def || typeof def !== 'object') return false\n return typeof def.typeName === 'string' && def.typeName.startsWith('Zod')\n}\n\nfunction detectKind(validator: unknown): PersistedSchemaKind {\n if (isZodSchema(validator)) return 'Zod'\n return 'Unknown'\n}\n\n/**\n * Lazy-require `zod-to-json-schema`. Returns the converter, or throws a\n * clear error if the peer-dep isn't installed.\n */\nasync function loadZodConverter(): Promise<(s: unknown) => object> {\n try {\n const mod = (await import('zod-to-json-schema')) as { zodToJsonSchema?: (s: unknown) => object }\n if (!mod.zodToJsonSchema) {\n throw new Error('zod-to-json-schema export missing')\n }\n return mod.zodToJsonSchema\n } catch (err) {\n throw new Error(\n 'persistJsonSchema requires the optional peer-dep `zod-to-json-schema`. '\n + 'Install it: `pnpm add zod-to-json-schema` (or npm/yarn equivalent). '\n + `Original error: ${err instanceof Error ? err.message : String(err)}`,\n )\n }\n}\n\nexport async function derivePersistedSchema(\n validator: unknown,\n): Promise<PersistedSchemaEnvelope> {\n const kind = detectKind(validator)\n const derivedAt = new Date().toISOString()\n\n if (kind === 'Zod') {\n const convert = await loadZodConverter()\n const jsonSchema = convert(validator)\n const canonical = canonicalize(jsonSchema)\n const hash = await sha256Hex(new TextEncoder().encode(canonical))\n return { _noydb_schema: 1, kind, jsonSchema, hash, derivedAt }\n }\n\n return {\n _noydb_schema: 1,\n kind,\n jsonSchema: null,\n hash: null,\n reason: `derivation not yet supported for kind=${kind} (v0 supports Zod only)`,\n derivedAt,\n }\n}\n","/**\n * Orchestrate the derive → hash → skip-or-write cycle for a collection's\n * persisted JSON Schema. Called by the Vault at collection-registration\n * time when the developer opts in via `collection({ persistJsonSchema:\n * true })`.\n *\n * Skip semantics:\n *\n * - Zod validators: skip when the new hash equals the stored hash.\n * - Non-Zod (stub envelopes have hash=null): skip when the stored\n * envelope's `kind` matches the freshly-detected kind (since there's\n * no body to compare yet — a kind change is the only signal).\n *\n * @module\n */\n\nimport { derivePersistedSchema } from './derive.js'\nimport { loadPersistedSchema, savePersistedSchema } from './storage.js'\nimport type { NoydbStore } from '../types.js'\nimport type { PersistedSchemaEnvelope } from './types.js'\n\nexport interface PersistSchemaResult {\n /** True when a fresh envelope was written to storage. */\n readonly written: boolean\n /** True when an existing envelope matched and the write was skipped. */\n readonly skipped: boolean\n /** The envelope that was either written or matched. */\n readonly envelope: PersistedSchemaEnvelope\n}\n\nexport async function persistSchemaIfNeeded(opts: {\n readonly store: NoydbStore\n readonly vault: string\n readonly collectionName: string\n readonly validator: unknown\n readonly dek: CryptoKey\n}): Promise<PersistSchemaResult> {\n const fresh = await derivePersistedSchema(opts.validator)\n const stored = await loadPersistedSchema(opts.store, opts.vault, opts.collectionName, opts.dek)\n\n if (stored && isEquivalent(stored, fresh)) {\n return { written: false, skipped: true, envelope: stored }\n }\n\n await savePersistedSchema(opts.store, opts.vault, opts.collectionName, opts.dek, fresh)\n return { written: true, skipped: false, envelope: fresh }\n}\n\nfunction isEquivalent(a: PersistedSchemaEnvelope, b: PersistedSchemaEnvelope): boolean {\n if (a.kind !== b.kind) return false\n // Zod path: real hashes — compare directly.\n if (a.hash && b.hash) return a.hash === b.hash\n // Stub path: both have hash=null. Kind equality is the only signal we have.\n if (a.hash === null && b.hash === null) return true\n // Mixed (one has a hash, the other doesn't) — treat as changed.\n return false\n}\n","/**\n * Map a JSON Schema (Draft 2020-12 produced by `zod-to-json-schema`) into\n * the {@link FieldDescriptor} map consumed by {@link VaultSchemaSnapshot}.\n *\n * Used by both the persisted-schema path (decrypted envelope body) and the\n * live-validator path (in-process derivation).\n *\n * @module\n */\n\nimport type { FieldDescriptor, FieldSource } from './types.js'\n\ninterface JsonSchemaShape {\n type?: string | string[]\n properties?: Record<string, JsonSchemaShape>\n required?: string[]\n enum?: unknown[]\n minLength?: number\n maxLength?: number\n pattern?: string\n minimum?: number\n maximum?: number\n exclusiveMinimum?: number\n exclusiveMaximum?: number\n format?: string\n items?: JsonSchemaShape\n}\n\nfunction jsonSchemaType(node: JsonSchemaShape): string {\n if (Array.isArray(node.type)) {\n const non = node.type.filter((t) => t !== 'null')\n return non[0] ?? 'opaque'\n }\n if (node.enum && Array.isArray(node.enum)) return 'enum'\n if (typeof node.type === 'string') return node.type\n return 'opaque'\n}\n\nfunction constraintsFor(node: JsonSchemaShape): Record<string, unknown> | undefined {\n const out: Record<string, unknown> = {}\n if (node.enum) out.values = node.enum\n if (node.minLength !== undefined) out.minLength = node.minLength\n if (node.maxLength !== undefined) out.maxLength = node.maxLength\n if (node.pattern !== undefined) out.pattern = node.pattern\n if (node.format !== undefined) out.format = node.format\n if (node.minimum !== undefined) out.minimum = node.minimum\n if (node.maximum !== undefined) out.maximum = node.maximum\n if (node.exclusiveMinimum !== undefined) out.gt = node.exclusiveMinimum\n if (node.exclusiveMaximum !== undefined) out.lt = node.exclusiveMaximum\n return Object.keys(out).length === 0 ? undefined : out\n}\n\nexport function jsonSchemaToFields(\n jsonSchema: unknown,\n source: FieldSource,\n refs?: Record<string, { target: string; mode: string }>,\n): Record<string, FieldDescriptor> {\n if (!jsonSchema || typeof jsonSchema !== 'object') return {}\n const root = jsonSchema as JsonSchemaShape\n if (!root.properties || typeof root.properties !== 'object') return {}\n\n const required = new Set(Array.isArray(root.required) ? root.required : [])\n const out: Record<string, FieldDescriptor> = {}\n\n for (const [name, node] of Object.entries(root.properties)) {\n const descriptor: FieldDescriptor = {\n type: jsonSchemaType(node),\n source,\n ...(required.has(name) ? {} : { optional: true }),\n ...(refs?.[name] ? { references: `${refs[name].target}.id` } : {}),\n }\n const constraints = constraintsFor(node)\n if (constraints) (descriptor as { constraints?: Record<string, unknown> }).constraints = constraints\n out[name] = descriptor\n }\n\n return out\n}\n","/**\n * Orchestrate the structural walk of a Vault, producing a\n * {@link VaultSchemaSnapshot}. Called from `Vault.dumpSchema()`.\n *\n * @module\n */\n\nimport { derivePersistedSchema } from '../persisted-schemas/derive.js'\nimport { loadPersistedSchema } from '../persisted-schemas/storage.js'\nimport { jsonSchemaToFields } from './fields.js'\nimport type {\n CollectionDescriptor,\n CollectionStats,\n DumpSchemaOptions,\n FieldDescriptor,\n InternalCollectionStats,\n MaterializedViewDescriptor,\n OverlayViewDescriptor,\n DerivationDescriptor,\n VaultSchemaSnapshot,\n} from './types.js'\nimport type { Collection } from '../collection.js'\nimport type { NoydbStore } from '../types.js'\nimport type { RefRegistry } from '../refs.js'\n\n/**\n * The minimal slice of Vault internal state the walker needs.\n * Exposed via `vault._introspectState()` to keep the public Vault\n * surface narrow.\n *\n * @internal\n */\nexport interface VaultIntrospectState {\n readonly name: string\n readonly adapter: NoydbStore\n readonly collectionCache: Map<string, Collection<unknown>>\n readonly refRegistry: RefRegistry\n readonly getDEK: (collectionName: string) => Promise<CryptoKey>\n readonly subsystems: Record<string, boolean>\n // Typed loosely on purpose — these are private subsystem registries\n // accessed only for \"is anything registered\" enumeration.\n readonly mvRegistry: unknown\n readonly overlayRegistry: unknown\n readonly derivationRegistry: unknown\n}\n\nconst INTERNAL_PREFIX = '_'\n\n/** Reserved internal collections always present in any real vault. */\nconst KNOWN_INTERNAL_NAMES = ['_keyring', '_ledger', '_meta', '_schemas', '_deltas']\n\nexport async function dumpVaultSchema(\n vault: { _introspectState(): VaultIntrospectState },\n opts: DumpSchemaOptions,\n): Promise<VaultSchemaSnapshot> {\n const state = vault._introspectState()\n const sampleSize = opts.sampleSize ?? 50\n const withStats = opts.withStats === true\n\n // 1. User-facing collections — alphabetical for diff stability\n const cacheNames = [...state.collectionCache.keys()]\n const storageNames = (await safeListAllCollections(state.adapter, state.name))\n .filter((n) => !n.startsWith(INTERNAL_PREFIX))\n const allNames = Array.from(new Set([...cacheNames, ...storageNames])).sort()\n\n const collections: Record<string, CollectionDescriptor> = {}\n for (const name of allNames) {\n collections[name] = await describeCollection(state, name, sampleSize, withStats)\n }\n\n // 2. Materialized views (placeholder — real walk in MV slice 2 follow-up)\n const materializedViews = describeMVs(state.mvRegistry)\n // 3. Overlay views\n const overlayViews = describeOverlays(state.overlayRegistry)\n // 4. Derivations\n const derivations = describeDerivations(state.derivationRegistry)\n\n // 5. Internal collections — only with stats\n let internal: Record<string, InternalCollectionStats> | undefined\n if (withStats) {\n internal = {}\n for (const name of KNOWN_INTERNAL_NAMES) {\n const stats = await statsForCollection(state.adapter, state.name, name)\n if (stats.records > 0) {\n internal[name] = { records: stats.records, bytes: stats.bytes }\n }\n }\n }\n\n const snap: VaultSchemaSnapshot = {\n _noydb_snapshot: 1,\n vault: state.name,\n emittedAt: new Date().toISOString(),\n subsystems: state.subsystems,\n collections,\n materializedViews,\n overlayViews,\n derivations,\n ...(internal !== undefined ? { internal } : {}),\n }\n return snap\n}\n\nasync function safeListAllCollections(adapter: NoydbStore, vault: string): Promise<string[]> {\n try {\n const snap = await adapter.loadAll(vault)\n return Object.keys(snap)\n } catch {\n return []\n }\n}\n\nasync function describeCollection(\n state: VaultIntrospectState,\n collectionName: string,\n sampleSize: number,\n withStats: boolean,\n): Promise<CollectionDescriptor> {\n let fields: Record<string, FieldDescriptor> = {}\n let validator: CollectionDescriptor['validator']\n\n const refsRaw = state.refRegistry.getOutbound(collectionName)\n const refs: CollectionDescriptor['refs'] = {}\n for (const [name, desc] of Object.entries(refsRaw)) {\n refs[name] = { target: desc.target, mode: desc.mode }\n }\n\n // 1. Try the persisted Route B envelope first.\n try {\n const dek = await state.getDEK(collectionName)\n const persisted = await loadPersistedSchema(state.adapter, state.name, collectionName, dek)\n if (persisted) {\n validator = { kind: persisted.kind, source: 'persisted' }\n if (persisted.jsonSchema) {\n fields = jsonSchemaToFields(persisted.jsonSchema, 'persisted', refsRaw)\n }\n }\n } catch {\n // No DEK or decrypt failure — fall through to live-validator\n }\n\n // 2. Try the live in-process validator (when no persisted envelope).\n if (!validator) {\n const coll = state.collectionCache.get(collectionName)\n const schema = coll?.getSchema()\n if (schema) {\n try {\n const derived = await derivePersistedSchema(schema)\n validator = { kind: derived.kind, source: 'live-validator' }\n if (derived.jsonSchema) {\n fields = jsonSchemaToFields(derived.jsonSchema, 'live-validator', refsRaw)\n }\n } catch {\n // Derivation failed (e.g. missing peer-dep) — silently leave fields empty\n }\n }\n }\n\n // 3. Sampling fallback — deferred to a follow-up. For now: empty when no schema.\n if (Object.keys(fields).length === 0 && sampleSize > 0) {\n // Sampling path not implemented in baseline slice 2; reserved for follow-up.\n }\n\n const descriptor: CollectionDescriptor = {\n fields,\n indexes: [],\n refs,\n ...(validator ? { validator } : {}),\n }\n if (withStats) {\n const stats = await statsForCollection(state.adapter, state.name, collectionName)\n ;(descriptor as { stats?: CollectionStats }).stats = stats\n }\n return descriptor\n}\n\nasync function statsForCollection(\n adapter: NoydbStore,\n vault: string,\n collection: string,\n): Promise<CollectionStats> {\n const ids = await adapter.list(vault, collection)\n if (ids.length === 0) {\n return { records: 0, bytes: 0, bytesAvg: 0, bytesMin: 0, bytesMax: 0, oldest: '', newest: '' }\n }\n let total = 0\n let min = Number.POSITIVE_INFINITY\n let max = 0\n let oldest = '￿'\n let newest = ''\n for (const id of ids) {\n const env = await adapter.get(vault, collection, id)\n if (!env) continue\n const size = env._data.length\n total += size\n if (size < min) min = size\n if (size > max) max = size\n if (env._ts < oldest) oldest = env._ts\n if (env._ts > newest) newest = env._ts\n }\n return {\n records: ids.length,\n bytes: total,\n bytesAvg: Math.round(total / ids.length),\n bytesMin: min === Number.POSITIVE_INFINITY ? 0 : min,\n bytesMax: max,\n oldest: oldest === '￿' ? '' : oldest,\n newest,\n }\n}\n\nfunction describeMVs(registry: unknown): Record<string, MaterializedViewDescriptor> {\n if (!registry || typeof registry !== 'object') return {}\n const items = listFromRegistry(registry as Record<string, unknown>)\n const out: Record<string, MaterializedViewDescriptor> = {}\n // Each item is RegisteredMV { spec, dependencies, outputCollection, ... }\n for (const item of items) {\n const reg = item as {\n spec?: {\n name?: string\n unionSources?: ReadonlyArray<{ collection: string }>\n groupBy?: string | ReadonlyArray<string>\n aggregate?: Record<string, unknown>\n refresh?: string\n }\n dependencies?: ReadonlySet<string>\n }\n const spec = reg.spec\n if (!spec?.name) continue\n const sources = spec.unionSources\n ? spec.unionSources.map((u) => u.collection)\n : (reg.dependencies ? [...reg.dependencies].sort() : [])\n const groupBy = spec.groupBy\n ? Array.isArray(spec.groupBy) ? [...spec.groupBy] : [spec.groupBy]\n : undefined\n const aggregate = spec.aggregate ? Object.fromEntries(\n Object.entries(spec.aggregate).map(([k, v]) => [k, summariseAggregateOp(v)]),\n ) : undefined\n out[spec.name] = {\n sources,\n ...(groupBy ? { groupBy } : {}),\n ...(aggregate ? { aggregate } : {}),\n refresh: spec.refresh ?? 'eager',\n }\n }\n return out\n}\n\nfunction describeOverlays(registry: unknown): Record<string, OverlayViewDescriptor> {\n if (!registry || typeof registry !== 'object') return {}\n const specs = listFromRegistry(registry as Record<string, unknown>)\n const out: Record<string, OverlayViewDescriptor> = {}\n for (const spec of specs) {\n const s = spec as { name?: string; base?: string; overlay?: string }\n if (!s.name || !s.base || !s.overlay) continue\n out[s.name] = { base: s.base, overlay: s.overlay }\n }\n return out\n}\n\nfunction describeDerivations(registry: unknown): Record<string, DerivationDescriptor> {\n if (!registry || typeof registry !== 'object') return {}\n const specs = listFromRegistry(registry as Record<string, unknown>)\n const out: Record<string, DerivationDescriptor> = {}\n for (const spec of specs) {\n const s = spec as { name?: string; source?: string; outputs?: ReadonlyArray<string> }\n if (!s.name) continue\n out[s.name] = {\n source: s.source ?? '',\n outputs: s.outputs ?? [],\n }\n }\n return out\n}\n\nfunction listFromRegistry(reg: Record<string, unknown>): readonly unknown[] {\n // Registries expose different accessor methods; try the common ones.\n for (const method of ['all', 'list', 'specs', 'values']) {\n const fn = reg[method]\n if (typeof fn === 'function') {\n try {\n const out = (fn as () => unknown).call(reg)\n if (Array.isArray(out)) return out\n if (out && typeof (out as { values?: () => unknown }).values === 'function') {\n return [...(out as { values: () => Iterable<unknown> }).values()]\n }\n } catch {\n continue\n }\n }\n }\n return []\n}\n\nfunction summariseAggregateOp(value: unknown): string {\n if (value && typeof value === 'object') {\n const op = (value as { op?: string; kind?: string; field?: string }).op\n ?? (value as { kind?: string }).kind\n const field = (value as { field?: string }).field\n if (op && field) return `${op}(${field})`\n if (op) return op\n }\n return String(value)\n}\n","import type { NoydbStore, EncryptedEnvelope } from '../types.js'\nimport { NOYDB_FORMAT_VERSION } from '../types.js'\nimport { encrypt, decrypt } from '../crypto.js'\nimport { ConflictError } from '../errors.js'\nimport { generateDocSigningKeyPair } from '@noy-db/attestation'\n\nexport const ATTESTATIONS_COLLECTION = '_attestations'\nexport const SIGNER_RECORD_ID = '_signer'\nexport const REVOKED_RECORD_ID = '_revoked'\n\nexport interface DocSigner {\n readonly keyId: string\n readonly publicKeyB64: string\n readonly privateKeyPkcs8B64: string\n}\n\n/**\n * Pure read: return the firm's persisted document-signing keypair, or `null`\n * if none has been minted yet. Never writes — callers that must NOT mint\n * (e.g. an ungated public-key getter) use this instead of `loadOrCreateSigner`.\n *\n * Stored as an encrypted record `_attestations/_signer` under the\n * `_attestations` collection DEK (resolved via `getDEK`, which is\n * AES-KW-wrapped under the owner KEK + persisted by the keyring). The KEK\n * itself is AES-KW-only and cannot AES-GCM-encrypt these bytes — hence\n * storage under a normal collection DEK.\n */\nexport async function loadSigner(\n store: NoydbStore,\n vault: string,\n getDEK: (collection: string) => Promise<CryptoKey>,\n): Promise<DocSigner | null> {\n const existing = await store.get(vault, ATTESTATIONS_COLLECTION, SIGNER_RECORD_ID)\n if (!existing) return null\n const dek = await getDEK(ATTESTATIONS_COLLECTION)\n const json = await decrypt(existing._iv, existing._data, dek)\n return JSON.parse(json) as DocSigner\n}\n\n/**\n * Lazily mint (or load) the firm's Ed25519 document-signing keypair.\n *\n * On a concurrent first-mint, two callers can both read `null` and both mint\n * distinct keypairs. The `put(…, expectedVersion: 0)` (\"must not already\n * exist\") lets exactly one win; the loser catches `ConflictError`, re-reads,\n * and returns the winner's signer — converging on a single keypair rather than\n * clobbering it or surfacing a raw conflict. (All real stores treat a missing\n * record + `ev: 0` as a no-conflict write, so the catch fires on lost-race only.)\n */\nexport async function loadOrCreateSigner(\n store: NoydbStore,\n vault: string,\n getDEK: (collection: string) => Promise<CryptoKey>,\n): Promise<DocSigner> {\n const existing = await loadSigner(store, vault, getDEK)\n if (existing) return existing\n\n const dek = await getDEK(ATTESTATIONS_COLLECTION)\n const signer = await generateDocSigningKeyPair()\n const { iv, data } = await encrypt(JSON.stringify(signer), dek)\n const env: EncryptedEnvelope = {\n _noydb: NOYDB_FORMAT_VERSION, _v: 1, _ts: new Date().toISOString(), _iv: iv, _data: data,\n }\n try {\n await store.put(vault, ATTESTATIONS_COLLECTION, SIGNER_RECORD_ID, env, 0)\n return signer\n } catch (e) {\n if (!(e instanceof ConflictError)) throw e\n // Lost the race — another writer minted first. Adopt the winner.\n const winner = await loadSigner(store, vault, getDEK)\n if (!winner) {\n throw new ConflictError(0, 'loadOrCreateSigner: signer mint lost a concurrent race but the winning record could not be re-read.')\n }\n return winner\n }\n}\n","import type { NoydbStore, EncryptedEnvelope } from '../types.js'\nimport { NOYDB_FORMAT_VERSION } from '../types.js'\nimport { encrypt } from '../crypto.js'\nimport { AttestationError } from '../errors.js'\nimport { generateULID } from '../bundle/ulid.js'\nimport { loadOrCreateSigner, ATTESTATIONS_COLLECTION } from './signer.js'\nimport {\n computeFieldHashes, signPayloadCore, encodeQr, bytesToB64url,\n type AttestationFieldSchema, type QrPayload,\n} from '@noy-db/attestation'\n\n/** Everything issueAttestationCore needs from the Vault, injected for testability. */\nexport interface IssueContext {\n readonly store: NoydbStore\n readonly vault: string\n readonly role: string\n /** The _attestations collection DEK (AES-KW-wrapped under KEK by the keyring). */\n getDEK(): Promise<CryptoKey>\n /** Decrypted source record + its envelope version, or null if absent. */\n readRecord(collection: string, id: string): Promise<{ record: Record<string, unknown>; version: number } | null>\n}\n\nexport interface IssueArgs {\n readonly collection: string\n readonly id: string\n readonly fieldSchema: AttestationFieldSchema\n}\nexport interface IssueResult {\n readonly docId: string\n readonly qr: string\n readonly payload: QrPayload\n readonly keyId: string\n readonly publicKeyB64: string\n}\n\nexport async function issueAttestationCore(ctx: IssueContext, args: IssueArgs): Promise<IssueResult> {\n if (ctx.role !== 'owner') {\n throw new AttestationError(`issueAttestation requires the 'owner' role; caller is '${ctx.role}'. Issuing a signed attestation is the firm's identity operation.`)\n }\n const src = await ctx.readRecord(args.collection, args.id)\n if (!src) throw new AttestationError(`issueAttestation: source record '${args.collection}/${args.id}' not found.`)\n\n const dek = await ctx.getDEK()\n // ONE signer implementation, from signer.ts. Lazily minted + persisted.\n const signer = await loadOrCreateSigner(ctx.store, ctx.vault, () => Promise.resolve(dek))\n\n const saltB64 = bytesToB64url(crypto.getRandomValues(new Uint8Array(16)))\n let fieldHashes: string[]\n try {\n fieldHashes = await computeFieldHashes(saltB64, args.fieldSchema, src.record)\n } catch (e) {\n throw new AttestationError(`issueAttestation: ${(e as Error).message}`)\n }\n const docId = generateULID()\n\n const sig = await signPayloadCore({ v: 1, docId, salt: saltB64, keyId: signer.keyId, fieldHashes }, signer.privateKeyPkcs8B64)\n const payload: QrPayload = { v: 1, docId, salt: saltB64, alg: 'ed25519', keyId: signer.keyId, fieldHashes, sig }\n\n const index = {\n docId, issuedAt: new Date().toISOString(), keyId: signer.keyId,\n fieldPaths: args.fieldSchema.fields.map((f) => f.path),\n sourceRefs: [{ collection: args.collection, id: args.id, version: src.version }],\n }\n const { iv, data } = await encrypt(JSON.stringify(index), dek)\n const env: EncryptedEnvelope = { _noydb: NOYDB_FORMAT_VERSION, _v: 1, _ts: index.issuedAt, _iv: iv, _data: data }\n await ctx.store.put(ctx.vault, ATTESTATIONS_COLLECTION, docId, env)\n\n return { docId, qr: encodeQr(payload), payload, keyId: signer.keyId, publicKeyB64: signer.publicKeyB64 }\n}\n","import type { NoydbStore, EncryptedEnvelope } from '../types.js'\nimport { NOYDB_FORMAT_VERSION } from '../types.js'\nimport { encrypt, decrypt } from '../crypto.js'\nimport { AttestationError, ConflictError } from '../errors.js'\nimport { loadOrCreateSigner, ATTESTATIONS_COLLECTION, REVOKED_RECORD_ID } from './signer.js'\nimport { signRevocationList, type RevocationList } from '@noy-db/attestation'\n\n/** Everything the revoke core needs from the Vault, injected for testability. */\nexport interface RevokeContext {\n readonly store: NoydbStore\n readonly vault: string\n readonly role: string\n /** The _attestations collection DEK. */\n getDEK(): Promise<CryptoKey>\n}\n\ninterface RevokedSet {\n docIds: string[]\n updatedAt: string\n}\n\nfunction requireOwner(ctx: RevokeContext, op: string): void {\n if (ctx.role !== 'owner') {\n throw new AttestationError(`${op} requires the 'owner' role; caller is '${ctx.role}'. Revocation is the firm's identity operation.`)\n }\n}\n\nasync function readSet(store: NoydbStore, vault: string, dek: CryptoKey): Promise<{ docIds: Set<string>; version: number | undefined }> {\n const env = await store.get(vault, ATTESTATIONS_COLLECTION, REVOKED_RECORD_ID)\n if (!env) return { docIds: new Set<string>(), version: undefined }\n const set = JSON.parse(await decrypt(env._iv, env._data, dek)) as RevokedSet\n return { docIds: new Set(set.docIds), version: env._v }\n}\n\n/** Read-modify-write the _revoked set with optimistic concurrency + one retry. */\nasync function mutateSet(ctx: RevokeContext, mutate: (ids: Set<string>) => void): Promise<void> {\n const dek = await ctx.getDEK()\n for (let attempt = 0; attempt < 2; attempt++) {\n const { docIds, version } = await readSet(ctx.store, ctx.vault, dek)\n mutate(docIds)\n const payload: RevokedSet = { docIds: [...docIds].sort(), updatedAt: new Date().toISOString() }\n const { iv, data } = await encrypt(JSON.stringify(payload), dek)\n const expectedVersion = version ?? 0\n const env: EncryptedEnvelope = {\n _noydb: NOYDB_FORMAT_VERSION, _v: expectedVersion + 1, _ts: payload.updatedAt, _iv: iv, _data: data,\n }\n try {\n await ctx.store.put(ctx.vault, ATTESTATIONS_COLLECTION, REVOKED_RECORD_ID, env, expectedVersion)\n return\n } catch (e) {\n if (e instanceof ConflictError && attempt === 0) continue\n throw e\n }\n }\n}\n\nexport async function revokeDocCore(ctx: RevokeContext, docId: string): Promise<void> {\n requireOwner(ctx, 'revokeAttestation')\n const issued = await ctx.store.get(ctx.vault, ATTESTATIONS_COLLECTION, docId)\n if (!issued) throw new AttestationError(`revokeAttestation: attestation '${docId}' not found (was it issued by this vault?).`)\n await mutateSet(ctx, (ids) => ids.add(docId))\n}\n\nexport async function unrevokeDocCore(ctx: RevokeContext, docId: string): Promise<void> {\n requireOwner(ctx, 'unrevokeAttestation')\n await mutateSet(ctx, (ids) => ids.delete(docId))\n}\n\nexport async function getRevokedDocIdsCore(ctx: RevokeContext): Promise<string[]> {\n const dek = await ctx.getDEK()\n const { docIds } = await readSet(ctx.store, ctx.vault, dek)\n return [...docIds].sort()\n}\n\nexport async function publishRevocationListCore(ctx: RevokeContext): Promise<RevocationList> {\n requireOwner(ctx, 'publishRevocationList')\n const docIds = await getRevokedDocIdsCore(ctx)\n const signer = await loadOrCreateSigner(ctx.store, ctx.vault, () => ctx.getDEK())\n return signRevocationList(docIds, new Date().toISOString(), signer.keyId, signer.privateKeyPkcs8B64)\n}\n","import type { GuardStrategy, GuardContext, GuardChange } from './types.js'\n\n/**\n * Per-record metadata attached to every entry in an amendment's\n * change-set. Carried in a parallel map alongside `_amendmentChanges`\n * so the public {@link GuardChange} shape (`{ before, after }`) stays\n * clean for invariant authors — the audit ledger reads this side\n * structure to produce the `{ collection, id, vBefore, vAfter }`\n * tuples for the amendment entry.\n *\n * @internal\n */\nexport interface AmendmentChangeMeta {\n readonly id: string\n readonly vBefore: number\n readonly vAfter: number\n}\n\n/**\n * Vault-internal singleton that holds the guard graph and dispatches\n * per-collection guard execution. Owned by `Vault`; not exported.\n *\n * @internal\n */\n// Internal storage alias — guards are heterogeneous in their record type T,\n// so the registry stores them at the upper bound of GuardStrategy's T constraint.\ntype AnyGuard = GuardStrategy<Record<string, unknown>>\ntype AnyChange = GuardChange<Record<string, unknown>>\n\nexport class GuardRegistry {\n private readonly _byCollection = new Map<string, AnyGuard[]>()\n private _amendmentChanges: Map<string, AnyChange[]> | null = null\n private _amendmentMeta: Map<string, AmendmentChangeMeta[]> | null = null\n\n /** Register a guard. Multiple guards per collection are allowed. */\n register<T extends Record<string, unknown>>(spec: GuardStrategy<T>): void {\n const existing = this._byCollection.get(spec.collection)\n if (existing) existing.push(spec as unknown as AnyGuard)\n else this._byCollection.set(spec.collection, [spec as unknown as AnyGuard])\n }\n\n /** All guards registered against `collection` in registration order. */\n guardsFor(collection: string): ReadonlyArray<AnyGuard> {\n return this._byCollection.get(collection) ?? []\n }\n\n /**\n * Run every guard's `check` for this collection. First throw wins —\n * remaining guards are not invoked. Guards without a `check` skip.\n */\n async runChecks<T>(\n collection: string,\n incoming: T,\n ctx: GuardContext<T>,\n ): Promise<void> {\n const guards = this._byCollection.get(collection)\n if (!guards) return\n for (const g of guards) {\n if (g.check) {\n await g.check(\n incoming as unknown as Record<string, unknown>,\n ctx as unknown as GuardContext<Record<string, unknown>>,\n )\n }\n }\n }\n\n /**\n * Run every guard's `onDelete` for this collection. First throw wins —\n * remaining guards are not invoked. Guards without an `onDelete` skip.\n * Mirrors {@link runChecks} but for the delete path.\n */\n async runOnDelete<T>(\n collection: string,\n existing: T,\n ctx: GuardContext<T>,\n ): Promise<void> {\n const guards = this._byCollection.get(collection)\n if (!guards) return\n for (const g of guards) {\n if (g.onDelete) {\n await g.onDelete(\n existing as unknown as Record<string, unknown>,\n ctx as unknown as GuardContext<Record<string, unknown>>,\n )\n }\n }\n }\n\n /** True if any guard for `collection` declares an `amendment` block. */\n hasAmendment(collection: string): boolean {\n const guards = this._byCollection.get(collection)\n if (!guards) return false\n return guards.some(g => g.amendment !== undefined)\n }\n\n /** Open a new amendment change-collection window. */\n beginAmendment(): void {\n this._amendmentChanges = new Map()\n this._amendmentMeta = new Map()\n }\n\n /** True iff we're currently inside an amendment transaction. */\n isAmendmentActive(): boolean {\n return this._amendmentChanges !== null\n }\n\n /**\n * Record a {before, after} pair for the active amendment. `vBefore`\n * and `vAfter` are stored in a parallel meta structure so the public\n * {@link GuardChange} shape handed to invariant callbacks stays\n * `{ before, after }` only — the audit ledger reads version metadata\n * via {@link consumeMeta}.\n */\n collectChange<T>(\n collection: string,\n id: string,\n before: T | null,\n after: T,\n vBefore = 0,\n vAfter = 0,\n ): void {\n if (this._amendmentChanges === null || this._amendmentMeta === null) {\n throw new Error('GuardRegistry.collectChange called outside an amendment')\n }\n const list = this._amendmentChanges.get(collection)\n const entry = { before, after } as unknown as AnyChange\n if (list) list.push(entry)\n else this._amendmentChanges.set(collection, [entry])\n\n const metaList = this._amendmentMeta.get(collection)\n const metaEntry: AmendmentChangeMeta = { id, vBefore, vAfter }\n if (metaList) metaList.push(metaEntry)\n else this._amendmentMeta.set(collection, [metaEntry])\n }\n\n /**\n * Drain the change-set and close the amendment window. The caller\n * (transaction commit) feeds these to each affected guard's invariant.\n */\n consumeChanges(): ReadonlyMap<string, ReadonlyArray<AnyChange>> {\n const out = this._amendmentChanges ?? new Map()\n this._amendmentChanges = null\n return out\n }\n\n /**\n * Drain the parallel id/version metadata captured during the\n * amendment. Returned as a flat list with `collection` denormalised\n * so the audit ledger can emit one `{ collection, id, vBefore,\n * vAfter }` tuple per record. Must be called AFTER\n * {@link consumeChanges} (or independently) — calling it closes the\n * meta window in the same way.\n */\n consumeMeta(): ReadonlyArray<{ collection: string; id: string; vBefore: number; vAfter: number }> {\n const out: { collection: string; id: string; vBefore: number; vAfter: number }[] = []\n if (this._amendmentMeta) {\n for (const [collection, list] of this._amendmentMeta) {\n for (const m of list) {\n out.push({ collection, id: m.id, vBefore: m.vBefore, vAfter: m.vAfter })\n }\n }\n }\n this._amendmentMeta = null\n return out\n }\n}\n","import type { Vault } from '../vault.js'\nimport type { Query } from '../query/builder.js'\nimport type { ReadOnlyVaultFacade as ReadOnlyVaultFacadeContract } from './types.js'\n\n/**\n * Minimal read-only wrapper over a `Vault`. Used as `ctx.vault` inside\n * guard callbacks so they can fetch related records without acquiring\n * any write capability.\n */\nexport class ReadOnlyVaultFacade implements ReadOnlyVaultFacadeContract {\n private readonly _vault: Vault\n\n constructor(vault: Vault) {\n this._vault = vault\n }\n\n collection<T = unknown>(name: string): {\n get(id: string): Promise<T | null>\n list(): Promise<T[]>\n query(): Query<T>\n } {\n const c = this._vault.collection<T>(name)\n return {\n get: (id: string) => c.get(id),\n list: () => c.list(),\n query: () => c.query(),\n }\n }\n}\n","/**\n * Deterministic hash of a derivation strategy's \"shape\": source\n * collection, output keys, derive function source. Used to detect\n * strategy drift: a record whose `_derivedFrom.strategyHash` doesn't\n * match the current strategy is considered stale.\n *\n * Web Crypto SHA-256 — no extra deps.\n */\nexport async function computeStrategyHash(\n source: string,\n outputKeys: readonly string[],\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n derive: (...args: any[]) => any,\n): Promise<string> {\n const canonical = JSON.stringify({\n source,\n outputs: [...outputKeys].sort(),\n derive: derive.toString(),\n })\n const bytes = new TextEncoder().encode(canonical)\n const digest = await crypto.subtle.digest('SHA-256', bytes)\n return Array.from(new Uint8Array(digest))\n .map(b => b.toString(16).padStart(2, '0'))\n .join('')\n}\n","import { DerivationCycleError } from '../errors.js'\nimport { computeStrategyHash } from './strategy-hash.js'\nimport type { DerivationStrategy } from './types.js'\n\ninterface RegisteredStrategy {\n // Type-erased to allow the registry to hold heterogeneous strategies.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n spec: DerivationStrategy<any, any>\n strategyHash: string\n}\n\n/**\n * Vault-internal registry of derivation strategies. Owned by `Vault`;\n * not exported.\n *\n * @internal\n */\nexport class DerivationRegistry {\n private readonly _bySource = new Map<string, RegisteredStrategy[]>()\n private readonly _byOutput = new Map<string, RegisteredStrategy[]>()\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n async register(spec: DerivationStrategy<any, any>): Promise<void> {\n const outputKeys = Object.keys(spec.outputs)\n const strategyHash = await computeStrategyHash(spec.source, outputKeys, spec.derive)\n const reg: RegisteredStrategy = { spec, strategyHash }\n\n const fromSource = this._bySource.get(spec.source)\n if (fromSource) fromSource.push(reg)\n else this._bySource.set(spec.source, [reg])\n\n for (const key of outputKeys) {\n const output = spec.outputs[key]\n if (!output) continue\n const outputCollection = output.collection\n const arr = this._byOutput.get(outputCollection)\n if (arr) arr.push(reg)\n else this._byOutput.set(outputCollection, [reg])\n }\n }\n\n strategiesForSource(source: string): ReadonlyArray<RegisteredStrategy> {\n return this._bySource.get(source) ?? []\n }\n\n strategiesProducingOutput(collection: string): ReadonlyArray<RegisteredStrategy> {\n return this._byOutput.get(collection) ?? []\n }\n\n /**\n * Cycle detection over the source → output → … graph. Call after all\n * `register()` calls complete (i.e. at vault open). Throws\n * `DerivationCycleError` on the first cycle found.\n */\n validate(): void {\n const visited = new Set<string>()\n const stack: string[] = []\n\n const visit = (node: string): void => {\n if (stack.includes(node)) {\n const cycle = stack.slice(stack.indexOf(node)).concat(node)\n throw new DerivationCycleError(cycle)\n }\n if (visited.has(node)) return\n stack.push(node)\n const strategies = this._bySource.get(node)\n if (strategies) {\n for (const s of strategies) {\n for (const key of Object.keys(s.spec.outputs)) {\n const output = s.spec.outputs[key]\n if (!output) continue\n visit(output.collection)\n }\n }\n }\n stack.pop()\n visited.add(node)\n }\n\n for (const src of this._bySource.keys()) visit(src)\n }\n}\n","import {\n OverlayBaseIsVirtualError,\n OverlayCollectionUnavailableError,\n OverlayNameCollisionError,\n} from '../errors.js'\nimport type { MaterializedViewRegistry } from '../materialized-views/registry.js'\nimport type { OverlayedViewStrategy } from './types.js'\n\n/**\n * Vault-internal registry of overlay strategies. Resolves the base\n * MV's `rowKey` lazily so virtual-collection writes can derive ids\n * from the row.\n *\n * @internal\n */\nexport class OverlayedViewRegistry {\n private readonly _byName = new Map<string, OverlayedViewStrategy>()\n\n /**\n * Register an overlay. Validates name uniqueness, base concreteness,\n * and overlay availability AGAINST the MV registry — overlays\n * declared without the MV registry context skip cross-registry\n * checks but still validate self-consistency.\n */\n register(\n spec: OverlayedViewStrategy,\n options: {\n isOverlayName?: (name: string) => boolean\n isMVOutput?: (name: string) => boolean\n isKnownCollection?: (name: string) => boolean\n },\n ): void {\n const { isOverlayName, isMVOutput, isKnownCollection } = options\n\n // 1. Virtual name must not collide with an MV output or a concrete\n // source collection. Concrete-source detection is best-effort:\n // if `isKnownCollection` is supplied, a hit there + no MV match\n // is treated as a collision.\n if (isMVOutput?.(spec.name) || isOverlayName?.(spec.name)) {\n throw new OverlayNameCollisionError(spec.name)\n }\n // (We don't check isKnownCollection for `name` collision because\n // virtual names are typically NOT pre-existing — they're created\n // by the overlay declaration itself. Future versions may tighten.)\n\n // 2. base must be concrete: NOT another overlay's virtual name.\n if (isOverlayName?.(spec.base)) {\n throw new OverlayBaseIsVirtualError(spec.name, spec.base)\n }\n\n // 3. overlay must be available: a real, vault-known collection\n // that is NOT an MV output (since MVs own their outputs).\n if (isMVOutput?.(spec.overlay)) {\n throw new OverlayCollectionUnavailableError(spec.name, spec.overlay)\n }\n // Best-effort known-collection check — when the vault can answer\n // it. Unknown collections aren't a hard failure (the overlay may\n // be implicitly created on first write), so we only throw on the\n // MV-output case above.\n void isKnownCollection\n\n this._byName.set(spec.name, spec)\n }\n\n byName(name: string): OverlayedViewStrategy | undefined {\n return this._byName.get(name)\n }\n\n /** All overlay virtual names. */\n names(): ReadonlySet<string> {\n return new Set(this._byName.keys())\n }\n\n isOverlay(name: string): boolean {\n return this._byName.has(name)\n }\n\n /**\n * Resolve the `rowKey` function for an overlay's base MV. Returns\n * `undefined` if the base isn't an MV (raw source collection) or\n * if the MV registry isn't supplied. Used by the virtual-collection\n * proxy to derive ids from `put(record)` calls.\n */\n resolveBaseRowKey(\n name: string,\n mvRegistry: MaterializedViewRegistry | null,\n ): ((row: Record<string, unknown>) => string) | undefined {\n const spec = this._byName.get(name)\n if (!spec || !mvRegistry) return undefined\n // The base might be an MV's `output.collection` OR the MV's `name`\n // (when no output.collection is declared). Search by both.\n for (const reg of mvRegistry.all()) {\n if (reg.outputCollection === spec.base || reg.spec.name === spec.base) {\n return (row) => reg.spec.rowKey(row)\n }\n }\n return undefined\n }\n}\n","/**\n * Time-boxed cross-tier delegation tokens.\n *\n * A higher-tier user can issue a delegation that grants another user\n * temporary access to records at a specified tier. The delegation is\n * persisted as an encrypted envelope in the reserved `_delegations`\n * collection. The target user's runtime scans this collection on every\n * open and, while `until` is still in the future, merges the\n * unwrapped tier DEKs into their in-memory DEK map.\n *\n * ## Token shape\n *\n * ```\n * {\n * id, // ULID, also the _delegations record id\n * toUser, // grantee user id\n * fromUser, // grantor user id (owner/admin/higher-tier principal)\n * tier, // tier being delegated\n * collection, // collection name OR null for \"every collection\"\n * record, // optional specific record id\n * until, // ISO timestamp — token expires at this instant\n * wrappedDek, // base64 AES-KW-wrapped tier DEK, wrapped under target KEK\n * createdAt, // ISO timestamp\n * }\n * ```\n *\n * The ciphertext is stored as a normal noy-db envelope — the\n * `_delegations` collection has its own DEK shared across all vault\n * users, so an operator can enumerate active delegations for audit\n * without being able to *use* them (the `wrappedDek` inside is still\n * keyed to the target user's KEK).\n *\n * ## Revocation\n *\n * Delete the `_delegations/<id>` envelope. The target user's runtime\n * reloads the delegation list at each open and at periodic intervals\n * (tracked by the caller — this module is pure logic).\n *\n * @module\n */\n\nimport type { NoydbStore, EncryptedEnvelope } from '../types.js'\nimport type { UnlockedKeyring } from './keyring.js'\nimport { encrypt, decrypt, wrapKey, unwrapKey } from '../crypto.js'\nimport { dekKey } from './tiers.js'\nimport { DelegationTargetMissingError } from '../errors.js'\nimport { generateULID } from '../bundle/ulid.js'\n\nexport const DELEGATIONS_COLLECTION = '_delegations'\n\n/**\n * Durable payload of a delegation token. Encrypted under the vault's\n * `_delegations` DEK; the `wrappedDek` inside is additionally wrapped\n * under the target user's KEK.\n */\nexport interface DelegationToken {\n readonly id: string\n readonly toUser: string\n readonly fromUser: string\n readonly tier: number\n /** Collection name or `null` for all collections. */\n readonly collection: string | null\n /** Optional specific record id scope. */\n readonly record?: string\n readonly until: string\n readonly wrappedDek: string\n readonly createdAt: string\n}\n\nexport interface IssueDelegationOptions {\n readonly toUser: string\n readonly tier: number\n readonly collection?: string\n readonly record?: string\n readonly until: Date | string\n}\n\n/**\n * Build and persist a delegation token. The caller must hold a tier-N\n * DEK and must have already located the target user's keyring file\n * (so the `wrappedDek` can be re-wrapped against their KEK).\n */\nexport async function issueDelegation(\n store: NoydbStore,\n vault: string,\n grantor: UnlockedKeyring,\n targetKek: CryptoKey | null,\n delegationsDek: CryptoKey,\n opts: IssueDelegationOptions,\n): Promise<DelegationToken> {\n if (!targetKek) {\n throw new DelegationTargetMissingError(opts.toUser)\n }\n const tier = opts.tier\n const collectionName = opts.collection ?? null\n const dekLookupCollection = collectionName ?? ''\n // Tier DEK to delegate — fetched from the grantor's own keyring.\n const sourceDek = collectionName\n ? grantor.deks.get(dekKey(collectionName, tier))\n : undefined\n if (!sourceDek) {\n throw new DelegationTargetMissingError(\n `grantor cannot find tier ${tier} DEK for ${dekLookupCollection || '(any)'}`,\n )\n }\n const wrappedDek = await wrapKey(sourceDek, targetKek)\n\n const until = typeof opts.until === 'string' ? opts.until : opts.until.toISOString()\n const token: DelegationToken = {\n id: generateULID(),\n toUser: opts.toUser,\n fromUser: grantor.userId,\n tier,\n collection: collectionName,\n ...(opts.record && { record: opts.record }),\n until,\n wrappedDek,\n createdAt: new Date().toISOString(),\n }\n\n const plaintext = JSON.stringify(token)\n const { iv, data } = await encrypt(plaintext, delegationsDek)\n const envelope: EncryptedEnvelope = {\n _noydb: 1,\n _v: 1,\n _ts: token.createdAt,\n _iv: iv,\n _data: data,\n _by: grantor.userId,\n }\n await store.put(vault, DELEGATIONS_COLLECTION, token.id, envelope)\n return token\n}\n\n/**\n * Enumerate every live (non-expired) delegation addressed to `toUser`\n * and merge the unwrapped tier DEKs into their keyring. Returns the\n * list of merged delegations so the caller can register per-access\n * audit context.\n */\nexport async function loadActiveDelegations(\n store: NoydbStore,\n vault: string,\n user: UnlockedKeyring,\n delegationsDek: CryptoKey,\n now: Date = new Date(),\n): Promise<DelegationToken[]> {\n const ids = await store.list(vault, DELEGATIONS_COLLECTION)\n const merged: DelegationToken[] = []\n const nowIso = now.toISOString()\n for (const id of ids) {\n const env = await store.get(vault, DELEGATIONS_COLLECTION, id)\n if (!env) continue\n let token: DelegationToken\n try {\n const plaintext = await decrypt(env._iv, env._data, delegationsDek)\n token = JSON.parse(plaintext) as DelegationToken\n } catch {\n continue\n }\n if (token.toUser !== user.userId) continue\n if (token.until <= nowIso) continue\n\n // A user without a KEK in memory (tier-3 PIN resume, wrap-DEKs\n // tier-2 unlock, session restore) cannot unwrap delegation tokens\n // — those were wrapped under the user's KEK at issue time. Skip\n // this token; the consumer reaches it again at tier-1 unlock.\n if (!user.kek) continue\n let dek: CryptoKey\n try {\n dek = await unwrapKey(token.wrappedDek, user.kek)\n } catch {\n continue\n }\n const k = token.collection\n ? dekKey(token.collection, token.tier)\n : `__any#${token.tier}`\n user.deks.set(k, dek)\n merged.push(token)\n }\n return merged\n}\n\n/**\n * Revoke a delegation by id — the caller resolves the envelope and\n * issues a `delete`. Provided as a stable helper so the naming is\n * symmetric to `issueDelegation`.\n */\nexport async function revokeDelegation(\n store: NoydbStore,\n vault: string,\n id: string,\n): Promise<void> {\n await store.delete(vault, DELEGATIONS_COLLECTION, id)\n}\n","import type {\n NoydbStore,\n EncryptedEnvelope,\n VaultBackup,\n VaultSnapshot,\n HistoryConfig,\n ExportStreamOptions,\n ExportChunk,\n CollectionConflictResolver,\n CrossTierAccessEvent,\n TierMode,\n Role,\n} from './types.js'\nimport type { Noydb } from './noydb.js'\nimport type { IssueDelegationOptions, DelegationToken } from './team/delegation.js'\nimport { NOYDB_BACKUP_VERSION, NOYDB_FORMAT_VERSION } from './types.js'\nimport { Collection } from './collection.js'\nimport type { CacheOptions } from './collection.js'\nimport type { IndexDef } from './indexing/eager-indexes.js'\nimport type { JoinableSource } from './query/index.js'\nimport type { OnDirtyCallback } from './collection.js'\nimport type { UnlockedKeyring, BundleRecipient } from './team/keyring.js'\nimport type { MaterializedViewRegistry } from './materialized-views/registry.js'\nimport type { MaterializedViewStrategyHandle, MVQueryContext } from './materialized-views/types.js'\nimport type { OverlayedViewRegistry } from './overlay-views/registry.js'\nimport type { OverlayedViewStrategyHandle } from './overlay-views/types.js'\nimport { OverlayedCollection } from './overlay-views/virtual-collection.js'\nimport type { PublicEnvelope } from './meta/public-envelope/types.js'\nimport { buildRecipientKeyringFile } from './team/keyring.js'\nimport { ensureCollectionDEK, hasAccess, hasExportCapability, hasImportCapability } from './team/keyring.js'\nimport type { ExportFormat, KeyringFile } from './types.js'\nimport {\n ExportCapabilityError,\n ImportCapabilityError,\n ValidationError,\n AlreadyElevatedError,\n ElevationExpiredError,\n TierNotGrantedError,\n AttestationError,\n} from './errors.js'\nimport type { NoydbEventEmitter } from './events.js'\nimport { BackupLedgerError, BackupCorruptedError } from './errors.js'\nimport type { StandardSchemaV1 } from './schema.js'\nimport type { BlobStrategy } from './blobs/strategy.js'\nimport type { IndexStrategy } from './indexing/strategy.js'\nimport type { AggregateStrategy } from './aggregate/strategy.js'\nimport type { CrdtStrategy } from './crdt/strategy.js'\n// — import from leaf modules (NOT from ./history/ledger/index.js\n// or store.js) so the LedgerStore class never reaches the floor\n// bundle. The leaf files hold pure constants + a tiny hash helper;\n// the class lives behind the history strategy seam.\nimport type { LedgerStore } from './history/ledger/store.js'\nimport { LEDGER_COLLECTION, LEDGER_DELTAS_COLLECTION } from './history/ledger/constants.js'\nimport { sha256Hex } from './history/ledger/entry.js'\nimport type { VaultInstant } from './history/time-machine.js'\nimport { NO_HISTORY, type HistoryStrategy } from './history/strategy.js'\nimport type { VaultFrame } from './shadow/vault-frame.js'\nimport { NO_SHADOW, type ShadowStrategy } from './shadow/strategy.js'\nimport type { ConsentContext, ConsentAuditEntry, ConsentAuditFilter, ConsentOp } from './consent/consent.js'\nimport { NO_CONSENT, type ConsentStrategy } from './consent/strategy.js'\nimport { NO_PERIODS, type PeriodsStrategy } from './periods/strategy.js'\nimport {\n RefRegistry,\n RefIntegrityError,\n type RefDescriptor,\n type RefViolation,\n} from './refs.js'\nimport type { DictionaryHandle, DictionaryOptions, DictKeyDescriptor } from './i18n/dictionary.js'\nimport { isDictCollectionName } from './i18n/dictionary.js'\nimport type { I18nTextDescriptor } from './i18n/core.js'\nimport { NO_I18N, type I18nStrategy } from './i18n/strategy.js'\nimport { NO_SYNC, type SyncStrategy } from './team/sync-strategy.js'\n// Type-only imports for the guard + derivation subsystems. The\n// runtime classes are loaded on demand via `await import(...)` inside\n// `_initGuards` / `_initDerivations` (and the read-only-facade\n// accessor below) so consumers that never register a guard or\n// derivation strategy don't pay the chunk cost. See #130 for the\n// bundle regression this seam plugs.\nimport type { GuardRegistry } from './guards/registry.js'\nimport type { GuardStrategyHandleAny } from './guards/types.js'\nimport type { ReadOnlyVaultFacade } from './guards/read-only-facade.js'\nimport type { DerivationRegistry } from './derivations/registry.js'\nimport type { DerivationStrategyHandle } from './derivations/types.js'\nimport type { LocaleReadOptions, ConflictPolicy } from './types.js'\nimport type { CrdtMode } from './crdt/crdt.js'\nimport { ReservedCollectionNameError } from './errors.js'\nimport {\n PERIODS_COLLECTION,\n type PeriodRecord,\n type ClosePeriodOptions,\n type OpenPeriodOptions,\n} from './periods/index.js'\nimport { encrypt, decrypt } from './crypto.js'\nimport {\n createExportBlobsHandle,\n EXPORT_AUDIT_COLLECTION,\n type ExportBlobsOptions,\n type ExportBlobsHandle,\n type ExportBlobsAuditEntry,\n} from './blobs/export-blobs.js'\nimport { runCompaction, type BlobFieldsConfig, type CompactRunOptions, type CompactionResult } from './blobs/blob-compaction.js'\nimport {\n writeMagicLinkGrant,\n type IssueMagicLinkGrantOptions,\n type MagicLinkGrantRecord,\n} from './team/magic-link-grant.js'\nimport { UserApi } from './meta/user-envelope/api.js'\nimport { persistSchemaIfNeeded } from './persisted-schemas/register.js'\nimport { SCHEMAS_COLLECTION } from './persisted-schemas/storage.js'\nimport type { AttestationFieldSchema, RevocationList } from '@noy-db/attestation'\nimport type { IssueContext } from './attestation/issue.js'\nimport type { RevokeContext } from './attestation/revoke.js'\nimport type { DumpSchemaOptions, VaultSchemaSnapshot } from './introspection/types.js'\nimport { dumpVaultSchema, type VaultIntrospectState } from './introspection/walk.js'\nimport { USER_ENVELOPE_COLLECTION } from './meta/user-envelope/types.js'\n\n/** A vault (tenant namespace) containing collections. */\nexport class Vault {\n private readonly adapter: NoydbStore\n /** The vault's name as passed to `openVault()`. Stable for the instance lifetime. */\n public readonly name: string\n /**\n * Backreference to the parent `Noydb`. Lets vault-scoped subsystems\n * (e.g. `as-*` reader `apply()` paths gating on `withTransactions()`)\n * reach the strategy seam without threading `db` through every API.\n *\n * Type-only Noydb import keeps the module graph acyclic at runtime.\n */\n public readonly noydb: Noydb\n /**\n * The active in-memory keyring. NOT readonly because `load()`\n * needs to refresh it after restoring a different keyring file —\n * otherwise the in-memory DEKs (from the pre-load session) and\n * the on-disk wrapped DEKs (from the loaded backup) drift apart\n * and every subsequent decrypt fails with TamperedError.\n */\n private keyring: UnlockedKeyring\n private readonly encrypted: boolean\n private readonly emitter: NoydbEventEmitter\n private readonly onDirty: OnDirtyCallback | undefined\n private readonly onRegisterConflictResolver: ((name: string, resolver: CollectionConflictResolver) => void) | undefined\n private readonly syncAdapter: NoydbStore | undefined\n private readonly historyConfig: HistoryConfig\n /**\n * tree-shake seam for the optional blob subsystem. Undefined\n * means \"blobs are off for this vault\"; every `collection.blob(id)`\n * call throws with a pointer at `@noy-db/hub/blobs`.\n */\n private readonly blobStrategy: BlobStrategy | undefined\n private readonly indexStrategy: IndexStrategy | undefined\n private readonly aggregateStrategy: AggregateStrategy | undefined\n private readonly crdtStrategy: CrdtStrategy | undefined\n private readonly consentStrategy: ConsentStrategy\n private readonly periodsStrategy: PeriodsStrategy\n private readonly shadowStrategy: ShadowStrategy\n private readonly historyStrategy: HistoryStrategy\n private readonly i18nStrategy: I18nStrategy\n private readonly syncStrategy: SyncStrategy\n /**\n * Per-vault guard registry. `null` until `_initGuards()` runs; stays\n * `null` for vaults that never register any guard strategy. The\n * runtime class is dynamic-imported on demand so consumers that\n * never use guards don't pull `GuardRegistry`/`GuardExecutor` into\n * their bundle (#130).\n */\n private guardRegistry: GuardRegistry | null = null\n /**\n * Per-vault derivation registry. Same lazy-load contract as\n * `guardRegistry` — `null` until `_initDerivations()` runs with at\n * least one strategy handle. See #130 for the bundle motivation.\n */\n private derivationRegistry: DerivationRegistry | null = null\n /**\n * Per-vault materialized-view registry (#143/#150). Same lazy-load\n * contract as `derivationRegistry` — `null` until\n * `_initMaterializedViews()` runs with at least one MV handle.\n */\n private materializedViewRegistry: MaterializedViewRegistry | null = null\n /**\n * Per-vault overlay registry (#154). Same lazy-load contract as\n * `materializedViewRegistry` — `null` until `_initOverlayedViews()`\n * runs with at least one handle.\n */\n private overlayedViewRegistry: OverlayedViewRegistry | null = null\n /**\n * Cached read-only facade handed to guard callbacks via `ctx.vault`,\n * and to derivation callbacks via `derive(source, ctx)`. Allocated\n * eagerly inside `_initGuards()` and/or `_initDerivations()` so read\n * accessors stay synchronous (callers in `tx/transaction.ts` rely on\n * that). Stays `null` for vaults with neither subsystem configured.\n */\n private readOnlyFacade: ReadOnlyVaultFacade | null = null\n private getDEK: (collectionName: string) => Promise<CryptoKey>\n\n /**\n * Per-principal user envelope API.\n *\n * - Write-self: `me()`, `updateMe(patch)`, `setMe(payload)` — always\n * target this vault session's keyringId. There is no method to write\n * another principal's envelope (own-only write rule, structural).\n * - Read-anyone: `get(keyringId)`, `list()` — read other principals'\n * envelopes, subject to the `view-team-profiles` policy gate (#22).\n * - Reactive: `subscribe(id, cb)`, `live(id)` — fire on local writes.\n *\n * @see docs/superpowers/specs/2026-05-05-user-envelope-design.md\n */\n public readonly user: UserApi\n\n /**\n * Optional callback that re-derives an UnlockedKeyring from the\n * adapter using the active user's passphrase. Called by `load()`\n * after the on-disk keyring file has been replaced — refreshes\n * `this.keyring` so the next DEK access uses the loaded wrapped\n * DEKs instead of the stale pre-load ones.\n *\n * Provided by Noydb at openVault() time. Tests that\n * construct Vault directly can pass `undefined`; load()\n * skips the refresh in that case (which is fine for plaintext\n * compartments — there's nothing to re-unwrap).\n */\n private readonly reloadKeyring: (() => Promise<UnlockedKeyring>) | undefined\n private readonly collectionCache = new Map<string, Collection<unknown>>()\n\n /**\n * per-collection `blobFields` retention/TTL config.\n * Populated on `collection({ blobFields })` and read by\n * `vault.compact()`. Indexed by collection name.\n */\n private readonly blobFieldsRegistry = new Map<string, BlobFieldsConfig<unknown>>()\n\n /**\n * Per-collection attestation field-schema (issue side). Populated on\n * `collection({ attestation })` and read by `issueAttestation()`.\n * Indexed by collection name.\n */\n private readonly attestationRegistry = new Map<string, AttestationFieldSchema>()\n\n /**\n * Per-vault ledger store. Lazy-initialized on first\n * `collection()` call (which passes it through to the Collection)\n * or on first `ledger()` call from user code.\n *\n * One LedgerStore is shared across all collections in a vault\n * because the hash chain is vault-scoped: the chain head is a\n * single \"what did this vault do last\" identifier, not a\n * per-collection one. Two collections appending concurrently is the\n * single-writer concurrency concern documented in the LedgerStore\n * docstring.\n */\n private ledgerStore: LedgerStore | null = null\n\n /**\n * Background writes for persisted-schema envelopes (#schema-dump v0\n * slice 1). One promise per `collection({ persistJsonSchema: true })`\n * registration that actually fired a derive call. Fire-and-forget\n * from the collection factory; tests await\n * {@link _drainPendingSchemaWrites} before asserting on storage.\n * Production code does not need to drain — the writes are\n * idempotent fingerprints, not correctness invariants.\n */\n private _pendingSchemaWrites: Promise<void>[] = []\n\n /**\n * Per-vault foreign-key reference registry. Collections\n * register their `refs` option here on construction; the\n * vault uses the registry on every put/delete/checkIntegrity\n * call. One instance lives for the compartment's lifetime.\n */\n private readonly refRegistry = new RefRegistry()\n\n /**\n * Set of collection record-ids currently being deleted as part of\n * a cascade. Populated on entry to `enforceRefsOnDelete` and\n * drained on exit. Used to break mutual-cascade cycles: deleting\n * A → cascade to B → cascade back to A would otherwise recurse\n * forever, so we short-circuit when we see an already-in-progress\n * delete on the same (collection, id) pair.\n */\n private readonly cascadeInProgress = new Set<string>()\n\n /**\n * Vault-default locale. Set via\n * `openVault(name, { locale })`. Used as the fallback locale\n * when per-call `{ locale }` options are not specified on individual\n * `get()`/`list()` calls.\n */\n private locale: string | undefined\n\n /**\n * Current consent scope. Set by `withConsent()` and\n * restored in its finally block. When non-null, every collection\n * access inside the scope writes one entry to `_consent_audit`.\n *\n * Single-slot by design — two concurrent withConsent calls on the\n * same Vault stomp each other. Adopters needing per-flight scoping\n * should use separate Vault instances.\n */\n private consentContext: ConsentContext | null = null\n\n /**\n * Cache of closed/opened accounting periods.\n * Populated on first `closePeriod` / `openPeriod` / `listPeriods` /\n * per-collection write call. Kept in memory as an ordered list (by\n * `closedAt`) so the `periodGuard` hook runs synchronously against\n * each collection's put/delete path.\n *\n * Sentinel `null` means \"not yet loaded\" — the first consumer\n * triggers a one-time `loadPeriods()` pass. Every subsequent\n * closure/opening pushes into the cache in-place so the next write\n * sees the updated chain without re-reading the adapter.\n */\n private periodCache: PeriodRecord[] | null = null\n\n /**\n * Registry of dictKey fields declared across all collections in this\n * vault. Keyed by collection name → field name → dictionary name.\n * Used by `DictionaryHandle.rename()` to find and update all records\n * referencing a renamed key.\n *\n * Populated by `collection()` when the `dictKeyFields` option is passed.\n */\n private readonly dictKeyFieldRegistry = new Map<\n string, // collection name\n Record<string, string> // field name → dictionary name\n >()\n\n /**\n * Registry of i18nText fields declared across all collections. Keyed\n * by collection name → field name → I18nTextDescriptor. Used by\n * `applyI18nLocale` on reads and by `validateI18nTextValue` on puts.\n *\n * Populated by `collection()` when the `i18nFields` option is passed.\n */\n private readonly i18nFieldRegistry = new Map<\n string, // collection name\n Record<string, I18nTextDescriptor>\n >()\n\n /** Cache of DictionaryHandle instances, one per dictionary name. */\n private readonly dictionaryCache = new Map<string, DictionaryHandle>()\n\n /** — subscribers for cross-tier access events. */\n private readonly crossTierSubs = new Set<(event: CrossTierAccessEvent) => void>()\n\n /** — currently-active elevation, or null. One per vault. */\n private activeElevation: {\n readonly tier: number\n readonly expiresAt: number\n readonly reason: string\n readonly handle: ElevatedHandle\n } | null = null\n\n /**\n * Optional translator callback threaded from `Noydb.invokeTranslator`.\n * Present only when `plaintextTranslator` was configured on `createNoydb()`.\n */\n private readonly translateText:\n | ((text: string, from: string, to: string, field: string, collection: string) => Promise<string>)\n | undefined\n\n constructor(opts: {\n adapter: NoydbStore\n name: string\n noydb: Noydb\n keyring: UnlockedKeyring\n encrypted: boolean\n emitter: NoydbEventEmitter\n onDirty?: OnDirtyCallback | undefined\n historyConfig?: HistoryConfig | undefined\n reloadKeyring?: (() => Promise<UnlockedKeyring>) | undefined\n /** Vault-default locale. */\n locale?: string | undefined\n /** Translator callback from Noydb. */\n plaintextTranslator?:\n | ((text: string, from: string, to: string, field: string, collection: string) => Promise<string>)\n | undefined\n /**\n * callback to register a per-collection envelope-level\n * conflict resolver with the SyncEngine. Present when sync is configured.\n */\n onRegisterConflictResolver?: ((name: string, resolver: CollectionConflictResolver) => void) | undefined\n /** — optional remote/sync adapter for presence broadcasting. */\n syncAdapter?: NoydbStore | undefined\n /**\n * tree-shake seam — strategy for optional blob storage.\n * Passed through to every `Collection` built by `vault.collection()`.\n * `undefined` => every `collection.blob(id)` throws with a pointer\n * at `@noy-db/hub/blobs`.\n */\n blobStrategy?: BlobStrategy | undefined\n indexStrategy?: IndexStrategy | undefined\n aggregateStrategy?: AggregateStrategy | undefined\n crdtStrategy?: CrdtStrategy | undefined\n consentStrategy?: ConsentStrategy | undefined\n periodsStrategy?: PeriodsStrategy | undefined\n shadowStrategy?: ShadowStrategy | undefined\n historyStrategy?: HistoryStrategy | undefined\n i18nStrategy?: I18nStrategy | undefined\n syncStrategy?: SyncStrategy | undefined\n guardStrategies?: ReadonlyArray<GuardStrategyHandleAny> | undefined\n }) {\n this.adapter = opts.adapter\n this.name = opts.name\n this.noydb = opts.noydb\n this.keyring = opts.keyring\n this.encrypted = opts.encrypted\n this.emitter = opts.emitter\n this.onDirty = opts.onDirty\n this.onRegisterConflictResolver = opts.onRegisterConflictResolver\n this.syncAdapter = opts.syncAdapter\n this.blobStrategy = opts.blobStrategy\n this.indexStrategy = opts.indexStrategy\n this.aggregateStrategy = opts.aggregateStrategy\n this.crdtStrategy = opts.crdtStrategy\n this.consentStrategy = opts.consentStrategy ?? NO_CONSENT\n this.periodsStrategy = opts.periodsStrategy ?? NO_PERIODS\n this.shadowStrategy = opts.shadowStrategy ?? NO_SHADOW\n this.historyStrategy = opts.historyStrategy ?? NO_HISTORY\n this.i18nStrategy = opts.i18nStrategy ?? NO_I18N\n this.syncStrategy = opts.syncStrategy ?? NO_SYNC\n // Guard + derivation registries are initialised lazily via\n // `_initGuards()` / `_initDerivations()` from `Noydb.openVault()`.\n // The classes are dynamic-imported there so vaults that never\n // register a strategy don't pull the subsystem code into the\n // floor bundle. See #130. The `opts.guardStrategies` argument is\n // intentionally accepted but unused on the constructor — the sync\n // `vault()` fallback path in `noydb.ts` does NOT call `_initGuards`,\n // matching the existing behaviour for `_initDerivations`. See #132\n // for the follow-up that makes the fallback path async.\n void opts.guardStrategies\n this.historyConfig = opts.historyConfig ?? { enabled: true }\n this.reloadKeyring = opts.reloadKeyring\n this.locale = opts.locale\n this.translateText = opts.plaintextTranslator\n\n // Build the lazy DEK resolver. Pulled out into a private method\n // so `load()` can rebuild it after a keyring refresh — the\n // closure captures `this.keyring` by reference, so changing the\n // field is enough, but resetting the cached `getDEKFn` ensures\n // ensureCollectionDEK runs again against the freshly-loaded\n // wrapped DEKs.\n this.getDEK = this.makeGetDEK()\n\n // User envelope API — frozen writerKeyringId, dynamic DEK resolver\n // (so a post-load() keyring refresh transparently rotates the DEK\n // through the rebuilt this.getDEK), and a checkGate callback that\n // delegates to Noydb's policy engine (#22 wires edit-own-profile +\n // view-team-profiles).\n this.user = new UserApi(\n this.adapter,\n this.name,\n this.keyring.userId,\n () => this.getDEK(USER_ENVELOPE_COLLECTION),\n (gate, presented) => this.noydb.checkGate(this.name, gate, presented),\n )\n }\n\n /**\n * Construct (or reconstruct) the lazy DEK resolver. Captures the\n * CURRENT value of `this.keyring` and `this.adapter` in a closure,\n * memoizing the inner getDEKFn after first use so subsequent\n * lookups are O(1).\n *\n * `load()` calls this after refreshing `this.keyring` to discard\n * the prior session's cached DEKs.\n */\n private makeGetDEK(): (collectionName: string) => Promise<CryptoKey> {\n let getDEKFn: ((collectionName: string) => Promise<CryptoKey>) | null = null\n return async (collectionName: string): Promise<CryptoKey> => {\n if (!getDEKFn) {\n getDEKFn = await ensureCollectionDEK(this.adapter, this.name, this.keyring)\n }\n return getDEKFn(collectionName)\n }\n }\n\n /**\n * Open a typed collection within this vault.\n *\n * - `options.indexes` declares secondary indexes for the query DSL.\n * Indexes are computed in memory after decryption; adapters never\n * see plaintext index data.\n * - `options.prefetch` (default `true`) controls hydration. Eager mode\n * loads everything on first access; lazy mode (`prefetch: false`)\n * loads records on demand and bounds memory via the LRU cache.\n * - `options.cache` configures the LRU bounds. Required in lazy mode.\n * Accepts `{ maxRecords, maxBytes: '50MB' | 1024 }`.\n * - `options.schema` attaches a Standard Schema v1 validator (Zod,\n * Valibot, ArkType, Effect Schema, etc.). Every `put()` is validated\n * before encryption; every read is validated after decryption.\n * Failing records throw `SchemaValidationError`.\n * - `options.i18nFields` declares per-field `i18nText()` descriptors\n *. Validated on `put()` and locale-resolved on reads.\n * - `options.dictKeyFields` declares per-field `dictKey()` descriptors\n *. `put()` validates keys against the declared set; reads\n * with `{ locale }` add `<field>Label` virtual fields.\n *\n * Throws `ReservedCollectionNameError` for names starting with `_dict_`.\n * Use `vault.dictionary(name)` to access dictionary collections.\n *\n * Lazy mode + indexes is rejected at construction time — see the\n * Collection constructor for the rationale.\n */\n collection<T>(collectionName: string, options?: {\n indexes?: IndexDef[]\n /** — auto-reconcile policy for persisted-index drift. */\n reconcileOnOpen?: 'off' | 'dry-run' | 'auto'\n prefetch?: boolean\n cache?: CacheOptions\n schema?: StandardSchemaV1<unknown, T>\n refs?: Record<string, RefDescriptor>\n /** — declare i18nText fields for locale-aware reads. */\n i18nFields?: Record<string, I18nTextDescriptor>\n /** — declare dictKey fields for label resolution on reads. */\n dictKeyFields?: Record<string, DictKeyDescriptor>\n /** — per-collection conflict resolution policy. */\n conflictPolicy?: ConflictPolicy<T>\n /** — CRDT mode for collaborative editing without conflicts. */\n crdt?: CrdtMode\n /**\n * declare deterministic-encryption fields for blind\n * equality search. See `Collection` constructor docs for the full\n * trade-off. Requires `acknowledgeDeterministicRisk: true`.\n */\n deterministicFields?: readonly string[]\n /** — explicit ack that deterministic encryption leaks equality. */\n acknowledgeDeterministicRisk?: boolean\n /**\n * declarative blob retention / TTL policy per slot\n * name. Values are `{ retainDays?, evictWhen? }`. Evaluated only\n * when `vault.compact()` runs.\n */\n blobFields?: BlobFieldsConfig<T>\n /** — declared tiers for this collection. */\n tiers?: readonly number[]\n /** — how lower-tier reads see above-tier records. */\n tierMode?: TierMode\n /**\n * Opt-in persisted JSON Schema. When `true` AND a Zod `schema` is\n * provided, hub derives a JSON Schema via `zod-to-json-schema`\n * (optional peer-dep) and writes an encrypted snapshot to\n * `_schemas/<collectionName>`. Re-runs on every open; hash-skip\n * avoids write churn when the schema is unchanged.\n *\n * Default: `false`. Non-Zod Standard Schema validators receive a\n * stub envelope flagging the kind without a JSON Schema body.\n *\n * @see docs/superpowers/specs/2026-05-22-schema-dump-design.md\n */\n persistJsonSchema?: boolean\n /** — declare the per-field schema for document attestation (issue side). */\n attestation?: AttestationFieldSchema\n }): Collection<T> {\n // Overlay intercept (#154). When the requested collection name\n // matches a registered `withOverlayedView`, return the virtual\n // proxy that merges base + overlay on read and routes writes to\n // the overlay collection. The proxy implements the core\n // Collection<T> read/write surface (get, list, put, delete);\n // reactive APIs (live, subscribe) are out of scope for #154.\n const overlayRegistry = this.overlayedViewRegistry\n if (overlayRegistry !== null && overlayRegistry.isOverlay(collectionName)) {\n const spec = overlayRegistry.byName(collectionName)\n if (spec) {\n // Recursive call into the same method — the base + overlay\n // are real collections, so they re-enter this method without\n // hitting the overlay intercept (their names won't match).\n const base = this.collection<T>(spec.base)\n const overlay = this.collection<T>(spec.overlay)\n const baseRowKey = overlayRegistry.resolveBaseRowKey(collectionName, this.materializedViewRegistry)\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return new OverlayedCollection<any>(spec, base, overlay, baseRowKey) as unknown as Collection<T>\n }\n }\n // Guard: reject reserved _dict_* names\n if (isDictCollectionName(collectionName)) {\n throw new ReservedCollectionNameError(collectionName)\n }\n\n let coll = this.collectionCache.get(collectionName)\n if (!coll) {\n // Register ref declarations (if any) with the vault-level\n // registry BEFORE constructing the Collection. This way the\n // first put() on the new collection already sees its refs via\n // vault.enforceRefsOnPut.\n if (options?.refs) {\n this.refRegistry.register(collectionName, options.refs)\n }\n\n // Register i18nText fields\n if (options?.i18nFields) {\n this.i18nFieldRegistry.set(collectionName, options.i18nFields)\n }\n\n // register blobFields retention/TTL policy\n if (options?.blobFields) {\n this.blobFieldsRegistry.set(collectionName, options.blobFields as BlobFieldsConfig<unknown>)\n }\n\n // register the per-collection attestation field-schema\n if (options?.attestation !== undefined) {\n this.attestationRegistry.set(collectionName, options.attestation)\n }\n\n // Register dictKey fields: store field → dictionary name mapping\n if (options?.dictKeyFields) {\n const dictFieldMap: Record<string, string> = {}\n for (const [field, desc] of Object.entries(options.dictKeyFields)) {\n dictFieldMap[field] = desc.name\n }\n this.dictKeyFieldRegistry.set(collectionName, dictFieldMap)\n }\n\n const collOpts: ConstructorParameters<typeof Collection<T>>[0] = {\n adapter: this.adapter,\n vault: this.name,\n name: collectionName,\n keyring: this.keyring,\n encrypted: this.encrypted,\n emitter: this.emitter,\n getDEK: this.getDEK,\n onDirty: this.onDirty,\n historyConfig: this.historyConfig,\n // thread the vault-wide blob strategy into every\n // collection. `undefined` is intentionally preserved so the\n // Collection constructor uses its NO_BLOBS default.\n ...(this.blobStrategy !== undefined ? { blobStrategy: this.blobStrategy } : {}),\n ...(this.indexStrategy !== undefined ? { indexStrategy: this.indexStrategy } : {}),\n ...(this.aggregateStrategy !== undefined ? { aggregateStrategy: this.aggregateStrategy } : {}),\n ...(this.crdtStrategy !== undefined ? { crdtStrategy: this.crdtStrategy } : {}),\n historyStrategy: this.historyStrategy,\n i18nStrategy: this.i18nStrategy,\n syncStrategy: this.syncStrategy,\n ledger: this.getLedgerOrNull() ?? undefined,\n refEnforcer: this,\n joinResolver: this,\n defaultLocale: this.locale,\n onRegisterConflictResolver: this.onRegisterConflictResolver,\n onAccess: (op, id) => this._logConsent(op, collectionName, id),\n periodGuard: (existing, incoming) => this._assertTsWritable(existing, incoming),\n // Guard / derivation sources are only wired when the\n // corresponding registry has been initialised. Vaults without\n // guards/derivations skip this entirely so `Collection.put`'s\n // `if (this.guardSource)` / `if (this.derivationSource)`\n // branches no-op without ever touching the subsystem code.\n ...(this.guardRegistry !== null\n ? {\n guardSource: {\n registry: () => this.guardRegistry as GuardRegistry,\n readOnlyVault: () => this._ensureReadOnlyFacade(),\n },\n }\n : {}),\n ...(this.derivationRegistry !== null\n ? {\n derivationSource: {\n registry: () => this.derivationRegistry as DerivationRegistry,\n getCollection: (name: string) =>\n this.collection(name) as unknown as Collection<Record<string, unknown>>,\n getReadOnlyFacade: () => this._ensureReadOnlyFacade(),\n getActiveTxContext: () => this.noydb._activeTxContextOrNull,\n createTxContext: () => this.noydb._createTxContext(),\n setActiveTxContext: (ctx) => this.noydb._setActiveTxContext(ctx),\n clearActiveTxContext: (ctx) => this.noydb._clearActiveTxContext(ctx),\n },\n }\n : {}),\n ...(this.materializedViewRegistry !== null\n ? {\n materializedViewSource: {\n \n registry: () => this.materializedViewRegistry!,\n getCollection: (name: string) => this.collection(name),\n getActiveTxContext: () => this.noydb._activeTxContextOrNull,\n getQueryContext: () => this as unknown as MVQueryContext,\n },\n }\n : {}),\n }\n if (options?.indexes !== undefined) collOpts.indexes = options.indexes\n if (options?.reconcileOnOpen !== undefined) collOpts.reconcileOnOpen = options.reconcileOnOpen\n if (options?.prefetch !== undefined) collOpts.prefetch = options.prefetch\n if (options?.cache !== undefined) collOpts.cache = options.cache\n if (options?.schema !== undefined) collOpts.schema = options.schema\n if (options?.conflictPolicy !== undefined) collOpts.conflictPolicy = options.conflictPolicy\n if (options?.crdt !== undefined) collOpts.crdt = options.crdt\n if (options?.deterministicFields !== undefined) {\n collOpts.deterministicFields = options.deterministicFields\n }\n if (options?.acknowledgeDeterministicRisk !== undefined) {\n collOpts.acknowledgeDeterministicRisk = options.acknowledgeDeterministicRisk\n }\n if (options?.tiers !== undefined) collOpts.tiers = options.tiers\n if (options?.tierMode !== undefined) collOpts.tierMode = options.tierMode\n collOpts.onCrossTierAccess = (event) => this.emitCrossTier(event)\n if (this.syncAdapter !== undefined) collOpts.syncAdapter = this.syncAdapter\n if (options?.i18nFields !== undefined) collOpts.i18nFields = options.i18nFields\n if (options?.dictKeyFields !== undefined) {\n // Build the label resolver callback for this collection\n collOpts.dictLabelResolver = async (dictName, key, locale, fallback) => {\n const handle = this.dictionary(dictName)\n return handle.resolveLabel(key, locale, fallback)\n }\n collOpts.dictKeyFields = options.dictKeyFields\n }\n // i18n validation on put — enforced via the compartment's put hook\n if (options?.i18nFields !== undefined || options?.dictKeyFields !== undefined) {\n collOpts.i18nPutValidator = (record: unknown) => {\n this.enforceI18nOnPut(collectionName, record)\n }\n }\n // Wire the translator for autoTranslate: true fields\n if (options?.i18nFields !== undefined && this.translateText) {\n collOpts.autoTranslateHook = this.translateText\n }\n coll = new Collection<T>(collOpts)\n this.collectionCache.set(collectionName, coll)\n\n // Fire-and-forget persisted-schema write when opted in. Pushed\n // onto _pendingSchemaWrites so tests can drain before asserting;\n // production code ignores it (the writes are idempotent fingerprints).\n if (options?.persistJsonSchema === true && options.schema !== undefined) {\n const validator: unknown = options.schema\n const work = (async () => {\n try {\n const dek = await this.getDEK(collectionName)\n await persistSchemaIfNeeded({\n store: this.adapter,\n vault: this.name,\n collectionName,\n validator,\n dek,\n })\n } catch (err) {\n // Schema persistence is a fingerprint, not a correctness\n // invariant — log and continue. Production callers can\n // still detect failures via _drainPendingSchemaWrites.\n \n console.warn(\n `[noy-db] persisted-schema write failed for \"${collectionName}\": `\n + (err instanceof Error ? err.message : String(err)),\n )\n }\n })()\n this._pendingSchemaWrites.push(work)\n }\n }\n return coll as Collection<T>\n }\n\n /**\n * Await all background persisted-schema writes triggered by\n * `collection({ persistJsonSchema: true })` calls on this vault.\n * Used in tests; production code does not need to call this.\n */\n async _drainPendingSchemaWrites(): Promise<void> {\n const pending = this._pendingSchemaWrites\n this._pendingSchemaWrites = []\n await Promise.allSettled(pending)\n }\n\n /**\n * Validate i18nText fields on a `put()`. Called by Collection just\n * before the adapter write, after schema validation. Throws\n * `MissingTranslationError` when a required translation is absent.\n */\n enforceI18nOnPut(collectionName: string, record: unknown): void {\n const i18nFields = this.i18nFieldRegistry.get(collectionName)\n if (!i18nFields || Object.keys(i18nFields).length === 0) return\n if (!record || typeof record !== 'object') return\n\n const obj = record as Record<string, unknown>\n for (const [field, descriptor] of Object.entries(i18nFields)) {\n const value = obj[field]\n if (value === undefined || value === null) continue\n this.i18nStrategy.validateI18nTextValue(value, field, descriptor)\n }\n }\n\n /**\n * Apply locale resolution to a record for the given collection.\n *\n * Called by Collection after decryption when locale options are present.\n * Returns a new object (never mutates the cached record).\n */\n async applyLocale(\n collectionName: string,\n record: Record<string, unknown>,\n localeOpts: LocaleReadOptions,\n ): Promise<Record<string, unknown>> {\n const locale = localeOpts.locale ?? this.locale\n if (!locale) return record\n\n let result = record\n\n // 1. i18nText resolution\n const i18nFields = this.i18nFieldRegistry.get(collectionName)\n if (i18nFields && Object.keys(i18nFields).length > 0) {\n result = this.i18nStrategy.applyI18nLocale(result, i18nFields, locale, localeOpts.fallback)\n }\n\n // 2. dictKey label resolution — add <field>Label virtual fields\n const dictFields = this.dictKeyFieldRegistry.get(collectionName)\n if (dictFields && Object.keys(dictFields).length > 0 && locale !== 'raw') {\n const withLabels = { ...result }\n for (const [field, dictName] of Object.entries(dictFields)) {\n const key = result[field]\n if (typeof key !== 'string') continue\n const handle = this.dictionary(dictName)\n const label = await handle.resolveLabel(key, locale, localeOpts.fallback)\n if (label !== undefined) {\n withLabels[`${field}Label`] = label\n }\n }\n result = withLabels\n }\n\n return result\n }\n\n /**\n * Open a dictionary by name. Returns a `DictionaryHandle` for CRUD\n * operations on the `_dict_<name>/` reserved collection.\n *\n * The handle is cached — multiple calls with the same name return the\n * same instance.\n *\n * @param name The dictionary name (e.g. `'status'` → `_dict_status/`).\n * @param options Optional ACL overrides (default `writableBy: 'admin'`).\n *\n * @example\n * ```ts\n * await company.dictionary('status').putAll({\n * draft: { en: 'Draft', th: 'ฉบับร่าง' },\n * paid: { en: 'Paid', th: 'ชำระแล้ว' },\n * })\n * ```\n */\n dictionary<Keys extends string = string>(\n name: string,\n options: DictionaryOptions = {},\n ): DictionaryHandle<Keys> {\n let handle = this.dictionaryCache.get(name)\n if (!handle) {\n handle = this.i18nStrategy.buildDictionaryHandle<Keys>({\n adapter: this.adapter,\n compartmentName: this.name,\n dictionaryName: name,\n keyring: this.keyring,\n getDEK: this.getDEK,\n encrypted: this.encrypted,\n ledger: this.getLedgerOrNull() ?? undefined,\n options,\n // findAndUpdateReferences: rewrite dictKey fields in all\n // registered collections when rename() is called\n findAndUpdateReferences: async (dictionaryName, oldKey, newKey) => {\n for (const [collectionName, dictFields] of this.dictKeyFieldRegistry) {\n // Find fields that point at this dictionary\n const fields = Object.entries(dictFields)\n .filter(([, dn]) => dn === dictionaryName)\n .map(([field]) => field)\n if (fields.length === 0) continue\n\n const coll = this.collection<Record<string, unknown>>(collectionName)\n const records = await coll.list()\n for (const record of records) {\n let changed = false\n const updated = { ...record }\n for (const field of fields) {\n if (updated[field] === oldKey) {\n updated[field] = newKey\n changed = true\n }\n }\n if (changed) {\n const id = (record['id'] as string | undefined)\n if (id !== undefined) {\n await coll.put(id, updated)\n }\n }\n }\n }\n },\n emitter: this.emitter,\n })\n this.dictionaryCache.set(name, handle)\n }\n return handle as DictionaryHandle<Keys>\n }\n\n /**\n * Build a `JoinableSource` for a dictKey field, for use in dict joins\n *. Returns a source whose snapshot contains `{ key, ...labels }`\n * records — one per dictionary entry — keyed by the stable key.\n *\n * Returns `null` when `field` is not a dictKey in `leftCollection`.\n *\n * The snapshot is built synchronously from whatever the dictionary\n * handle has in its cached state. For empty dictionaries this returns\n * an empty snapshot rather than `null`.\n */\n /**\n * Build a `JoinableSource` for a dictKey field, for use in dict joins\n *. Returns a source whose snapshot contains\n * `{ key, labels, ...labels }` records — one per dictionary entry —\n * keyed by the stable key.\n *\n * The snapshot is built synchronously from the DictionaryHandle's\n * write-through cache, which is populated on every `put()`, `rename()`,\n * `delete()`, and `list()` call. For pre-existing data not yet touched\n * this session, call `await vault.dictionary(name).list()` first\n * to warm the cache.\n *\n * Returns `null` when `field` is not a dictKey in `leftCollection`.\n */\n resolveDictSource(leftCollection: string, field: string): JoinableSource | null {\n const dictFields = this.dictKeyFieldRegistry.get(leftCollection)\n if (!dictFields || !(field in dictFields)) return null\n const dictName = dictFields[field]\n if (!dictName) return null\n const handle = this.dictionary(dictName)\n return {\n snapshot(): readonly unknown[] {\n return handle.snapshotEntries()\n },\n lookupById(id: string): unknown {\n const entries = handle.snapshotEntries()\n return entries.find((e) => e['key'] === id)\n },\n }\n }\n\n /**\n * Set or update the vault-default locale at runtime.\n * Useful when the user switches their preferred language after opening\n * the vault.\n */\n setLocale(locale: string | undefined): void {\n this.locale = locale\n }\n\n /** Return the current vault-default locale. */\n getLocale(): string | undefined {\n return this.locale\n }\n\n /**\n * The user id of the keyring backing this vault session. Useful for\n * UI affordances (\"you are alice\"), audit trails, and orchestration\n * composables that need to stamp records with the current actor.\n */\n get userId(): string {\n return this.keyring.userId\n }\n\n /**\n * The role of the keyring backing this vault session — one of\n * `owner | admin | operator | viewer | client`. Useful for UI\n * affordance gates and approval workflows that need to confirm\n * the caller can perform a given action before attempting it.\n */\n get role(): Role {\n return this.keyring.role\n }\n\n /**\n * Build keyring files for bundle recipients without persisting them\n * to the source vault. Used by `writeNoydbBundle()` when the bundle\n * is re-keyed for distinct recipients.\n *\n * Each recipient becomes its own `KeyringFile` sealed with that\n * recipient's passphrase. The DEKs wrapped into each slot are\n * exactly those the recipient's role + permissions justify, and\n * never wider than the source keyring's own DEK set\n * (privilege-escalation check).\n *\n * Returns a `Record<userId, KeyringFile>` ready to substitute for\n * the `keyrings` field of a `vault.dump()` JSON. Adapter is never\n * touched; the produced files exist only in the bundle bytes.\n *\n * @public\n */\n async buildBundleRecipientKeyrings(\n recipients: readonly BundleRecipient[],\n ): Promise<Record<string, KeyringFile>> {\n const result: Record<string, KeyringFile> = {}\n for (const recipient of recipients) {\n if (recipient.id in result) {\n throw new Error(`buildBundleRecipientKeyrings: duplicate recipient id \"${recipient.id}\"`)\n }\n result[recipient.id] = await buildRecipientKeyringFile(this.keyring, recipient)\n }\n return result\n }\n\n /**\n * Authorize an `@noy-db/as-*` export against the current keyring's\n * `exportCapability`. Throws `ExportCapabilityError` if\n * the invoking keyring is not authorised.\n *\n * `as-*` packages MUST call this before invoking the underlying\n * export primitive (`exportStream()` / `writeNoydbBundle()` / …).\n *\n * - `assertCanExport('plaintext', 'xlsx')` — check plaintext tier\n * for a specific format. Defaults to empty for every role; owner\n * must positively grant.\n * - `assertCanExport('bundle')` — check encrypted-bundle tier.\n * Defaults to on for owner/admin, off for others.\n *\n * See `docs/patterns/as-exports.md` for the full policy.\n */\n assertCanExport(tier: 'plaintext', format: ExportFormat): void\n assertCanExport(tier: 'bundle'): void\n assertCanExport(tier: 'plaintext' | 'bundle', format?: ExportFormat): void {\n if (tier === 'plaintext') {\n if (format === undefined) {\n throw new Error('vault.assertCanExport: plaintext tier requires a format')\n }\n if (!hasExportCapability(this.keyring, 'plaintext', format)) {\n throw new ExportCapabilityError({\n tier: 'plaintext',\n userId: this.keyring.userId,\n format,\n })\n }\n return\n }\n if (!hasExportCapability(this.keyring, 'bundle')) {\n throw new ExportCapabilityError({\n tier: 'bundle',\n userId: this.keyring.userId,\n })\n }\n }\n\n /**\n * Authorize an `@noy-db/as-*` import against the current keyring's\n * `importCapability` (issue ). Throws `ImportCapabilityError` if\n * the invoking keyring is not authorised.\n *\n * `as-*` reader entry-points (`fromString` / `fromBytes`) MUST call\n * this before parsing or building an `ImportPlan`.\n *\n * - `assertCanImport('plaintext', 'csv')` — check plaintext-tier\n * import for a specific format. Default-closed for every role.\n * - `assertCanImport('bundle')` — check `.noydb` bundle-import gate.\n * Default-closed for every role, including owner — import is more\n * dangerous than export (corrupts vs leaks).\n *\n * Owner who wants to import re-grants own keyring with\n * `importCapability` set explicitly.\n */\n assertCanImport(tier: 'plaintext', format: ExportFormat): void\n assertCanImport(tier: 'bundle'): void\n assertCanImport(tier: 'plaintext' | 'bundle', format?: ExportFormat): void {\n if (tier === 'plaintext') {\n if (format === undefined) {\n throw new Error('vault.assertCanImport: plaintext tier requires a format')\n }\n if (!hasImportCapability(this.keyring, 'plaintext', format)) {\n throw new ImportCapabilityError({\n tier: 'plaintext',\n userId: this.keyring.userId,\n format,\n })\n }\n return\n }\n if (!hasImportCapability(this.keyring, 'bundle')) {\n throw new ImportCapabilityError({\n tier: 'bundle',\n userId: this.keyring.userId,\n })\n }\n }\n\n /**\n * Bulk blob extraction primitive.\n *\n * Returns an async-iterable handle over every blob attached to\n * records in the vault. Single capability check (`plaintext/blob`)\n * at handle creation; single audit entry to `_export_audit` before\n * the first yield. Per-blob decryption happens lazily as the\n * consumer pulls tuples.\n *\n * ```ts\n * const handle = vault.exportBlobs({\n * collections: ['invoiceScans'],\n * where: (rec) => (rec as { clientId?: string }).clientId === 'c-123',\n * })\n * for await (const { bytes, meta, recordRef } of handle) {\n * await uploadToColdStorage(bytes, recordRef)\n * }\n * ```\n *\n * @see `@noy-db/hub/store/export-blobs` for the full option surface.\n */\n /**\n * Evict blob slots per the per-collection `blobFields` retention\n * policy.\n *\n * Iterates every collection declared with `{ blobFields: {...} }`.\n * For each record, checks every configured slot against its\n * policy — `retainDays` (age-based TTL) and/or `evictWhen(record)`\n * (predicate) — and evicts matching slots. Every eviction writes\n * one entry to `_blob_eviction_audit` (actor + eTag + reason +\n * timestamp, no plaintext). Consumer-scheduled; noy-db never runs\n * this on its own.\n *\n * ```ts\n * await vault.compact() // run full pass\n * await vault.compact({ dryRun: true }) // preview counts\n * await vault.compact({ maxEvictions: 1000 }) // cap batch\n * ```\n */\n async compact(options: CompactRunOptions = {}): Promise<CompactionResult> {\n return runCompaction({\n adapter: this.adapter,\n vault: this.name,\n actor: this.keyring.userId,\n encrypted: this.encrypted,\n getDEK: this.getDEK,\n getBlobFields: <T>(name: string): BlobFieldsConfig<T> | null =>\n (this.blobFieldsRegistry.get(name) as BlobFieldsConfig<T> | undefined) ?? null,\n listCollections: () => this.collections(),\n listRecords: (name: string) => this.adapter.list(this.name, name),\n getRecord: async <T>(name: string, id: string) => {\n const coll = this.collection<T>(name)\n return coll.get(id)\n },\n listSlots: async (name: string, id: string) => {\n const coll = this.collection(name)\n return coll.blob(id).list()\n },\n deleteSlot: async (name: string, id: string, slotName: string) => {\n const coll = this.collection(name)\n await coll.blob(id).delete(slotName)\n },\n }, options)\n }\n\n exportBlobs(options: ExportBlobsOptions = {}): ExportBlobsHandle {\n this.assertCanExport('plaintext', 'blob')\n return createExportBlobsHandle(\n this.keyring.userId,\n () => this.collections(),\n (name) => this.collection(name),\n (entry) => this.writeExportAudit(entry),\n options,\n )\n }\n\n async issueAttestation(collectionName: string, id: string): Promise<{ docId: string; qr: string; keyId: string; publicKeyB64: string }> {\n const fieldSchema = this.attestationRegistry.get(collectionName)\n if (!fieldSchema) {\n throw new AttestationError(`issueAttestation: collection '${collectionName}' has no attestation field-schema. Declare it via vault.collection('${collectionName}', { attestation: { fields: [...] } }).`)\n }\n const { issueAttestationCore } = await import('./attestation/issue.js')\n const out = await issueAttestationCore(this.makeIssueContext(), { collection: collectionName, id, fieldSchema })\n return { docId: out.docId, qr: out.qr, keyId: out.keyId, publicKeyB64: out.publicKeyB64 }\n }\n\n async getDocumentSigningPublicKey(): Promise<{ keyId: string; publicKeyB64: string }> {\n const { loadSigner, loadOrCreateSigner } = await import('./attestation/signer.js')\n // Reading an existing public key is open to any role that holds the\n // _attestations DEK — the public key is not secret. But MINTING the\n // signer is the firm's identity operation (same rule as issueAttestation):\n // a non-owner read must not silently create it.\n const existing = await loadSigner(this.adapter, this.name, this.getDEK)\n if (existing) return { keyId: existing.keyId, publicKeyB64: existing.publicKeyB64 }\n if (this.keyring.role !== 'owner') {\n throw new AttestationError(`getDocumentSigningPublicKey: no document-signing key exists yet; only the 'owner' may mint it. Caller is '${this.keyring.role}'. Have the owner issue an attestation (or call this) first.`)\n }\n const signer = await loadOrCreateSigner(this.adapter, this.name, this.getDEK)\n return { keyId: signer.keyId, publicKeyB64: signer.publicKeyB64 }\n }\n\n private makeIssueContext(): IssueContext {\n const adapter = this.adapter, vaultName = this.name, getDEK = this.getDEK\n return {\n store: adapter,\n vault: vaultName,\n role: this.keyring.role,\n getDEK: async () => getDEK('_attestations'),\n readRecord: async (collection: string, recId: string) => {\n const env = await adapter.get(vaultName, collection, recId)\n if (!env) return null\n const record = (await this.collection(collection).get(recId, { locale: 'raw' })) as Record<string, unknown> | null\n if (record === null) return null\n return { record, version: env._v }\n },\n }\n }\n\n async revokeAttestation(docId: string): Promise<void> {\n const { revokeDocCore } = await import('./attestation/revoke.js')\n await revokeDocCore(this.makeRevokeContext(), docId)\n }\n\n async unrevokeAttestation(docId: string): Promise<void> {\n const { unrevokeDocCore } = await import('./attestation/revoke.js')\n await unrevokeDocCore(this.makeRevokeContext(), docId)\n }\n\n async getRevokedDocIds(): Promise<string[]> {\n const { getRevokedDocIdsCore } = await import('./attestation/revoke.js')\n return getRevokedDocIdsCore(this.makeRevokeContext())\n }\n\n async publishRevocationList(): Promise<RevocationList> {\n const { publishRevocationListCore } = await import('./attestation/revoke.js')\n return publishRevocationListCore(this.makeRevokeContext())\n }\n\n private makeRevokeContext(): RevokeContext {\n const adapter = this.adapter, vaultName = this.name, getDEK = this.getDEK\n return {\n store: adapter,\n vault: vaultName,\n role: this.keyring.role,\n getDEK: async () => getDEK('_attestations'),\n }\n }\n\n private async writeExportAudit(entry: ExportBlobsAuditEntry): Promise<void> {\n const json = JSON.stringify(entry)\n const envelope: EncryptedEnvelope = this.encrypted\n ? await (async () => {\n const dek = await this.getDEK(EXPORT_AUDIT_COLLECTION)\n const { iv, data } = await encrypt(json, dek)\n return { _noydb: NOYDB_FORMAT_VERSION, _v: 1, _ts: entry.startedAt, _iv: iv, _data: data, _by: entry.actor }\n })()\n : { _noydb: NOYDB_FORMAT_VERSION, _v: 1, _ts: entry.startedAt, _iv: '', _data: json, _by: entry.actor }\n await this.adapter.put(this.name, EXPORT_AUDIT_COLLECTION, entry.id, envelope)\n }\n\n /**\n * Read-only accessor for the invoking keyring's export capability,\n * with role-based defaults resolved. Useful for UI affordances\n * (grey out the export button if no capability) without throwing.\n */\n canExport(tier: 'plaintext', format: ExportFormat): boolean\n canExport(tier: 'bundle'): boolean\n canExport(tier: 'plaintext' | 'bundle', format?: ExportFormat): boolean {\n if (tier === 'plaintext') {\n if (format === undefined) return false\n return hasExportCapability(this.keyring, 'plaintext', format)\n }\n return hasExportCapability(this.keyring, 'bundle')\n }\n\n /**\n * Decrypt a single envelope using the per-collection DEK, returning\n * the parsed plaintext record. Internal helper for bundle-pipeline\n * plaintext filters — keeps DEK access encapsulated\n * inside Vault so callers don't reach into private state.\n *\n * @internal\n */\n async _decryptEnvelopeForBundleFilter(\n env: EncryptedEnvelope,\n collectionName: string,\n ): Promise<unknown> {\n if (!this.encrypted) {\n return JSON.parse(env._data)\n }\n const dek = await this.getDEK(collectionName)\n const json = await decrypt(env._iv, env._data, dek)\n return JSON.parse(json)\n }\n\n /**\n * Read-only accessor for the invoking keyring's import capability\n * (issue ). UI affordance — returns false in every default-closed\n * case (every role with no explicit `importCapability` grant).\n */\n canImport(tier: 'plaintext', format: ExportFormat): boolean\n canImport(tier: 'bundle'): boolean\n canImport(tier: 'plaintext' | 'bundle', format?: ExportFormat): boolean {\n if (tier === 'plaintext') {\n if (format === undefined) return false\n return hasImportCapability(this.keyring, 'plaintext', format)\n }\n return hasImportCapability(this.keyring, 'bundle')\n }\n\n /**\n * Enforce strict outbound refs on a `put()`. Called by Collection\n * just before it writes to the adapter. For every strict ref\n * declared on the collection, check that the target id exists in\n * the target collection; throw `RefIntegrityError` if not.\n *\n * `warn` and `cascade` modes don't affect put semantics — they're\n * enforced at delete time or via `checkIntegrity()`.\n */\n async enforceRefsOnPut(collectionName: string, record: unknown): Promise<void> {\n const outbound = this.refRegistry.getOutbound(collectionName)\n if (Object.keys(outbound).length === 0) return\n if (!record || typeof record !== 'object') return\n const obj = record as Record<string, unknown>\n\n for (const [field, descriptor] of Object.entries(outbound)) {\n if (descriptor.mode !== 'strict') continue\n const rawId = obj[field]\n // Nullish ref values are allowed — treat them as \"no reference\".\n // Users who want \"always required\" should express it in their\n // Standard Schema validator via a non-optional field.\n if (rawId === null || rawId === undefined) continue\n // Refs must be strings or numbers — anything else (object,\n // array, boolean) is a programming error and should fail\n // loudly rather than serialize as \"[object Object]\".\n if (typeof rawId !== 'string' && typeof rawId !== 'number') {\n throw new RefIntegrityError({\n collection: collectionName,\n id: (obj['id'] as string | undefined) ?? '<unknown>',\n field,\n refTo: descriptor.target,\n refId: null,\n message:\n `Ref field \"${collectionName}.${field}\" must be a string or number, got ${typeof rawId}.`,\n })\n }\n const refId = String(rawId)\n const target = this.collection<Record<string, unknown>>(descriptor.target)\n const exists = await target.get(refId)\n if (!exists) {\n throw new RefIntegrityError({\n collection: collectionName,\n id: (obj['id'] as string | undefined) ?? '<unknown>',\n field,\n refTo: descriptor.target,\n refId,\n message:\n `Strict ref \"${collectionName}.${field}\" → \"${descriptor.target}\" ` +\n `cannot be satisfied: target id \"${refId}\" not found in \"${descriptor.target}\".`,\n })\n }\n }\n }\n\n /**\n * Enforce inbound ref modes on a `delete()`. Called by Collection\n * just before it deletes from the adapter. Walks every inbound\n * ref that targets this (collection, id) and:\n *\n * - `strict`: throws if any referencing records exist\n * - `cascade`: deletes every referencing record\n * - `warn`: no-op (checkIntegrity picks it up)\n *\n * Cascade cycles are broken via `cascadeInProgress` — re-entering\n * for the same (collection, id) returns immediately so two\n * mutually-cascading collections don't recurse forever.\n */\n async enforceRefsOnDelete(collectionName: string, id: string): Promise<void> {\n const key = `${collectionName}/${id}`\n if (this.cascadeInProgress.has(key)) return\n this.cascadeInProgress.add(key)\n\n try {\n const inbound = this.refRegistry.getInbound(collectionName)\n for (const rule of inbound) {\n const fromCollection = this.collection<Record<string, unknown>>(rule.collection)\n // Scan the referencing collection for records whose ref\n // field matches this id. For eager-mode collections this\n // is an in-memory filter; for lazy-mode it requires a scan.\n const allRecords = await fromCollection.list()\n const matches = allRecords.filter((rec) => {\n const raw = rec[rule.field]\n // Same string/number-only restriction as enforceRefsOnPut.\n // Anything else can't have been a valid ref to begin with,\n // so it can't match.\n if (typeof raw !== 'string' && typeof raw !== 'number') return false\n return String(raw) === id\n })\n if (matches.length === 0) continue\n\n if (rule.mode === 'strict') {\n const first = matches[0]\n throw new RefIntegrityError({\n collection: rule.collection,\n id: (first?.['id'] as string | undefined) ?? '<unknown>',\n field: rule.field,\n refTo: collectionName,\n refId: id,\n message:\n `Cannot delete \"${collectionName}\"/\"${id}\": ` +\n `${matches.length} record(s) in \"${rule.collection}\" still reference it via strict ref \"${rule.field}\".`,\n })\n }\n if (rule.mode === 'cascade') {\n for (const match of matches) {\n const matchId = (match['id'] as string | undefined) ?? null\n if (matchId === null) continue\n // Recursive delete — the cycle breaker above catches\n // infinite loops.\n await fromCollection.delete(matchId)\n }\n }\n // warn: no-op\n }\n } finally {\n this.cascadeInProgress.delete(key)\n }\n }\n\n // ─── Join resolver) ────────────────────\n\n /**\n * Look up the `RefDescriptor` the left collection declared for a\n * given field name. Returns `null` when the field has no ref\n * declaration — the Query builder turns that into an actionable\n * error at plan time (before any records are touched).\n *\n * Implements the `joinResolver.resolveRef` half of the structural\n * interface that `Collection.query()` consumes. See\n * `query/join.ts` for the full design.\n */\n resolveRef(leftCollection: string, field: string): RefDescriptor | null {\n const outbound = this.refRegistry.getOutbound(leftCollection)\n return outbound[field] ?? null\n }\n\n /**\n * Resolve a right-side join source by target collection name.\n * Returns `null` for unknown collections so the Query executor can\n * surface an actionable error naming the missing target.\n *\n * Implements the `joinResolver.resolveSource` half of the\n * structural interface. The returned JoinableSource is a thin\n * wrapper that reads the target collection's in-memory cache via\n * `list()` / `get()` synchronously — the cache is populated by an\n * earlier `ensureHydrated()` call through the target's query/list\n * path. If the target has not been opened yet in this session the\n * join will see an empty snapshot; consumers who hit this can\n * open the target collection explicitly before running the query.\n *\n * Only same-vault targets are resolvable — cross-vault\n * joins are explicitly forbidden by the architecture`).\n */\n resolveSource(collectionName: string): JoinableSource | null {\n // Reject internal / reserved collection names — joins against\n // `_ledger/`, `_keyring/`, `_deltas/`, etc. are never legitimate.\n if (collectionName.startsWith('_')) return null\n const coll = this.collectionCache.get(collectionName)\n if (!coll) return null\n // Collection exposes a structural `querySourceForJoin()` method\n // that returns a lightweight snapshot/lookupById view backed by\n // its in-memory cache. Typed as unknown here because\n // Collection<T> is covariant on T — the join executor only\n // reads fields by name and doesn't care about the concrete type.\n return (coll as unknown as {\n querySourceForJoin(): JoinableSource\n }).querySourceForJoin()\n }\n\n /**\n * Walk every collection that has declared refs, load its records,\n * and report any reference whose target id is missing. Modes are\n * reported alongside each violation so the caller can distinguish\n * \"this is a warning the user asked for\" from \"this should never\n * have happened\" (strict violations produced by out-of-band\n * writes).\n *\n * Returns `{ violations: [...] }` instead of throwing — the whole\n * point of `checkIntegrity()` is to surface a list for display\n * or repair, not to fail noisily.\n */\n async checkIntegrity(): Promise<{ violations: RefViolation[] }> {\n const violations: RefViolation[] = []\n for (const [collectionName, refs] of this.refRegistry.entries()) {\n const coll = this.collection<Record<string, unknown>>(collectionName)\n const records = await coll.list()\n for (const record of records) {\n const recId = (record['id'] as string | undefined) ?? '<unknown>'\n for (const [field, descriptor] of Object.entries(refs)) {\n const rawId = record[field]\n if (rawId === null || rawId === undefined) continue\n // Non-scalar ref values are flagged as a violation rather\n // than thrown — `checkIntegrity` is a \"report what's wrong\"\n // tool, not a \"block on first failure\" tool. The thrown\n // version lives in `enforceRefsOnPut`.\n if (typeof rawId !== 'string' && typeof rawId !== 'number') {\n violations.push({\n collection: collectionName,\n id: recId,\n field,\n refTo: descriptor.target,\n refId: rawId,\n mode: descriptor.mode,\n })\n continue\n }\n const refId = String(rawId)\n const target = this.collection<Record<string, unknown>>(descriptor.target)\n const exists = await target.get(refId)\n if (!exists) {\n violations.push({\n collection: collectionName,\n id: recId,\n field,\n refTo: descriptor.target,\n refId: rawId,\n mode: descriptor.mode,\n })\n }\n }\n }\n }\n return { violations }\n }\n\n /**\n * Return this compartment's hash-chained audit log.\n *\n * The ledger is lazy-initialized on first access and cached for the\n * lifetime of the Vault instance. Every LedgerStore instance\n * shares the same adapter and DEK resolver, so `vault.ledger()`\n * can be called repeatedly without performance cost.\n *\n * The LedgerStore itself is the public API: consumers call\n * `.append()` (via Collection internals), `.head()`, `.verify()`,\n * and `.entries({ from, to })`. See the LedgerStore docstring for\n * the full surface and the concurrency caveats.\n */\n ledger(): LedgerStore {\n const store = this.getLedgerOrNull()\n if (!store) {\n throw new Error(\n 'vault.ledger() requires the history strategy. Import ' +\n '`{ withHistory }` from \"@noy-db/hub/history\" and pass it to ' +\n '`createNoydb({ historyStrategy: withHistory() })`.',\n )\n }\n return store\n }\n\n /**\n * Internal accessor — returns the LedgerStore if the history\n * strategy is opted in, or `null` otherwise. Used by dump/restore/\n * verifyBackupIntegrity and by Collection write paths that already\n * gate on `if (this.ledger)`. The public `ledger()` accessor above\n * throws on null; this one stays silent so the off-path no-ops.\n */\n private getLedgerOrNull(): LedgerStore | null {\n if (!this.ledgerStore) {\n this.ledgerStore = this.historyStrategy.buildLedger({\n adapter: this.adapter,\n vault: this.name,\n encrypted: this.encrypted,\n getDEK: this.getDEK,\n actor: this.keyring.userId,\n })\n }\n return this.ledgerStore\n }\n\n /**\n * @internal — called by `Noydb.openVault` after construction.\n * Dynamic-imports `GuardRegistry` + `ReadOnlyVaultFacade` and seeds\n * the registry with the supplied strategy handles. No-op when the\n * handles array is empty — keeps the guard subsystem out of the\n * floor bundle for consumers that don't use guards (#130).\n *\n * The read-only facade is eagerly instantiated here so the sync\n * accessor `_getReadOnlyFacade()` (called from the tx amendment\n * runner) stays synchronous.\n */\n async _initGuards(handles: ReadonlyArray<GuardStrategyHandleAny>): Promise<void> {\n if (handles.length === 0) return\n const [{ GuardRegistry }, { ReadOnlyVaultFacade }] = await Promise.all([\n import('./guards/registry.js'),\n import('./guards/read-only-facade.js'),\n ])\n const registry = new GuardRegistry()\n for (const h of handles) registry.register(h.spec)\n this.guardRegistry = registry\n this.readOnlyFacade = new ReadOnlyVaultFacade(this)\n }\n\n /**\n * @internal — Collection.put calls into this. Returns `null` for\n * vaults that never registered any guard strategy. Callers MUST\n * gate on null (the existing `if (this.guardSource)` branches in\n * `Collection` already do this transitively).\n */\n _getGuardRegistry(): GuardRegistry | null {\n return this.guardRegistry\n }\n\n /**\n * @internal — called by `Noydb.openVault` after construction.\n * Dynamic-imports `DerivationRegistry` and registers the supplied\n * derivation strategies (async because `strategyHash` computation\n * goes through `crypto.subtle.digest`). No-op when the handles\n * array is empty — keeps the derivation subsystem out of the floor\n * bundle for consumers that don't use derivations (#130). Throws\n * `DerivationCycleError` if a cycle is detected after registration.\n */\n async _initDerivations(handles: ReadonlyArray<DerivationStrategyHandle>): Promise<void> {\n if (handles.length === 0) return\n const [{ DerivationRegistry }, { ReadOnlyVaultFacade }] = await Promise.all([\n import('./derivations/registry.js'),\n import('./guards/read-only-facade.js'),\n ])\n const registry = new DerivationRegistry()\n for (const h of handles) {\n await registry.register(h.spec)\n }\n registry.validate()\n this.derivationRegistry = registry\n // Share the facade with guards: if `_initGuards` ran first the slot\n // is already populated. Otherwise allocate so `derive(source, ctx)`\n // has a vault accessor without re-importing the class per call.\n if (this.readOnlyFacade === null) {\n this.readOnlyFacade = new ReadOnlyVaultFacade(this)\n }\n }\n\n /**\n * @internal — consumed by `Collection.put` at write-time. Returns\n * `null` for vaults that never registered any derivation strategy.\n */\n _getDerivationRegistry(): DerivationRegistry | null {\n return this.derivationRegistry\n }\n\n /**\n * @internal — called by `Noydb.openVault` after collections are\n * wired. Dynamic-imports `MaterializedViewRegistry`, registers each\n * MV spec (which invokes its `query()` once for dependency\n * analysis), then runs the unified cycle detection across the MV +\n * derivation graphs. No-op when the handles array is empty — keeps\n * the MV subsystem out of the floor bundle (mirrors v1 #130).\n * Throws `MaterializedViewCycleError` if a cycle is detected.\n */\n async _initMaterializedViews(\n \n handles: ReadonlyArray<MaterializedViewStrategyHandle>,\n ): Promise<void> {\n if (handles.length === 0) return\n const { MaterializedViewRegistry } = await import('./materialized-views/registry.js')\n const registry = new MaterializedViewRegistry()\n // Phase 1: publish the (empty) registry on `this` BEFORE\n // registering any spec. The user's `query(db)` callback runs at\n // registration time and may construct source Collections via\n // `db.collection(name)` — those Collections are cached in the\n // vault and their `materializedViewSource` is populated from\n // `this.materializedViewRegistry` AT CONSTRUCTION TIME. If we\n // assigned `this.materializedViewRegistry` only after the\n // register() loop, the source Collections would cache with an\n // unset source and never dispatch MV refreshes on later writes.\n this.materializedViewRegistry = registry\n // Pass `this` Vault as the MVQueryContext — its `collection<T>()`\n // method is what the user's `query(db)` callback consumes.\n \n const db = this as unknown as MVQueryContext\n for (const h of handles) {\n await registry.register(h.spec, db)\n }\n // Phase 2: unified cycle detection across MV + derivation graphs.\n // Runs after all `register()` calls so the analyzer has every\n // dep-set; throws `MaterializedViewCycleError` on the first cycle.\n registry.validate(this.derivationRegistry)\n }\n\n /**\n * @internal — consumed by `Collection.put` at write-time. Returns\n * `null` for vaults that never registered any MV strategy.\n */\n _getMaterializedViewRegistry(): MaterializedViewRegistry | null {\n return this.materializedViewRegistry\n }\n\n /**\n * @internal — called by `Noydb.openVault` after MVs are wired.\n * Dynamic-imports `OverlayedViewRegistry`, registers each spec,\n * validates against the MV registry for name/base/overlay collisions.\n * Throws on validation failure.\n */\n async _initOverlayedViews(\n handles: ReadonlyArray<OverlayedViewStrategyHandle>,\n ): Promise<void> {\n if (handles.length === 0) return\n const { OverlayedViewRegistry } = await import('./overlay-views/registry.js')\n const registry = new OverlayedViewRegistry()\n const mvRegistry = this.materializedViewRegistry\n // Build the predicate set for registration validation:\n // - isOverlayName: an already-registered overlay's virtual name\n // - isMVOutput: a collection name owned by an MV\n const overlayNames = new Set<string>()\n for (const h of handles) overlayNames.add(h.spec.name)\n const isMVOutput = (name: string): boolean => {\n if (!mvRegistry) return false\n for (const reg of mvRegistry.all()) {\n if (reg.outputCollection === name) return true\n }\n return false\n }\n for (const h of handles) {\n registry.register(h.spec, {\n isOverlayName: (n) => overlayNames.has(n) && n !== h.spec.name,\n isMVOutput,\n })\n }\n this.overlayedViewRegistry = registry\n }\n\n /**\n * @internal — consumed by `Vault.collection()`. Returns `null` for\n * vaults with no overlays registered.\n */\n _getOverlayedViewRegistry(): OverlayedViewRegistry | null {\n return this.overlayedViewRegistry\n }\n\n /**\n * Manual re-materialize for a single registered MV (#151). Useful\n * for `refresh: 'manual'` MVs (whose consumer drives refreshes\n * externally), for stale-bit recovery on vault re-open, and as the\n * explicit bulk-recompute escape hatch after a strategy change.\n *\n * Returns `{ written, deleted, failed }`. `deleted` is always 0 in\n * foundation + this sub-issue — tombstoning lands in #152.\n *\n * Throws if `name` is not a registered MV.\n */\n async refreshView(name: string): Promise<{ written: number; deleted: number; failed: number }> {\n const registry = this.materializedViewRegistry\n if (registry === null) {\n return { written: 0, deleted: 0, failed: 0 }\n }\n const reg = registry.byName(name)\n if (!reg) {\n throw new Error(`refreshView: no MV registered with name \"${name}\"`)\n }\n const { MaterializedViewExecutor } = await import('./materialized-views/executor.js')\n const result = await MaterializedViewExecutor.refresh(reg, {\n getCollection: (n) => this.collection(n),\n getActiveTxContext: () => this.noydb._activeTxContextOrNull,\n getQueryContext: () => this as unknown as MVQueryContext,\n })\n // Manual refresh clears any pending stale bit — the post-refresh\n // state matches the registered strategy.\n const { clearMVStale } = await import('./materialized-views/stale.js')\n clearMVStale(registry, name)\n return result\n }\n\n /**\n * Re-derive every record in the named source collection. Useful\n * after a strategy change to bring previously-derived records\n * up-to-date.\n *\n * Sequential in v1; parallelisation deferred to v2.\n */\n async deriveAll(sourceCollection: string): Promise<{ derived: number; failed: number }> {\n const registry = this._getDerivationRegistry()\n if (registry === null) return { derived: 0, failed: 0 }\n const strategies = registry.strategiesForSource(sourceCollection)\n if (strategies.length === 0) return { derived: 0, failed: 0 }\n\n const { DerivationExecutor } = await import('./derivations/executor.js')\n\n const sourceColl = this.collection<Record<string, unknown>>(sourceCollection)\n const records = await sourceColl.list()\n // `_initDerivations` populates `readOnlyFacade` — assert non-null\n // for the closure-captured ctx. Falls back to a fresh facade on the\n // sync-fallback path (Noydb.vault() without await) for the same\n // defensive reason `_ensureReadOnlyFacade` exists.\n const ctx = { vault: this.readOnlyFacade ?? new (await import('./guards/read-only-facade.js')).ReadOnlyVaultFacade(this) }\n let derived = 0\n let failed = 0\n for (const record of records) {\n if (typeof record !== 'object' || record === null) continue\n const id = (record as { id?: unknown }).id\n if (typeof id !== 'string') continue\n for (const { spec, strategyHash } of strategies) {\n const sourceWithId = { ...record, id }\n const result = await DerivationExecutor.run(spec, sourceWithId, 0, strategyHash, ctx)\n let anyFailed = false\n for (const key of Object.keys(spec.outputs)) {\n const out = result.outputs[key]\n if (!out) continue\n if (out.kind === 'failed') { anyFailed = true; continue }\n const outSpec = spec.outputs[key]\n if (!outSpec) continue\n const outputColl = this.collection(outSpec.collection)\n\n // Array-shape branch (#200) — diff against the fanout sidecar.\n if (out.kind === 'array') {\n const { loadFanoutSidecar, saveFanoutSidecar } =\n await import('./derivations/fanout-sidecar.js')\n const prior = await loadFanoutSidecar(this.adapter, this.name, spec.source, id, key)\n const prevKeys = new Set<string>(prior?.keys ?? [])\n const newKeysList = out.entries.map(e => e.key)\n const newKeysSet = new Set<string>(newKeysList)\n for (const k of prevKeys) {\n if (newKeysSet.has(k)) continue\n await outputColl._internalDelete(k)\n }\n for (const entry of out.entries) {\n await outputColl.put(entry.key, entry.value)\n }\n await saveFanoutSidecar(this.adapter, this.name, {\n source: spec.source,\n sourceId: id,\n outputKey: key,\n outputCollection: outSpec.collection,\n keys: newKeysList,\n })\n continue\n }\n\n if (out.skipped === true) {\n // #144: optional output skipped — delete any prior emission.\n // No txCtx hookup needed: `deriveAll` runs outside the\n // multi-record transaction window by design. Routed\n // through `_internalDelete` so the bulk recompute does not\n // trip user `onDelete` (#145) on the output collection.\n await outputColl._internalDelete(id)\n continue\n }\n await outputColl.put(id, out.value)\n }\n if (anyFailed) failed++\n else derived++\n }\n }\n return { derived, failed }\n }\n\n /**\n * @internal — exposed for `runTransaction({ amendment: true })` so\n * the amendment invariant runner can pass the SAME read-only vault\n * facade that the per-record `Collection.put` guard hook uses\n * (`guardSource.readOnlyVault()` above). Eagerly instantiated by\n * `_initGuards()` so this accessor stays synchronous; returns\n * `null` for vaults that never registered any guard (amendments\n * require at least one guard, so the caller should never see null).\n */\n _getReadOnlyFacade(): ReadOnlyVaultFacade | null {\n return this.readOnlyFacade\n }\n\n /**\n * Internal lazy-allocator for the read-only facade. Used by the\n * per-collection `guardSource.readOnlyVault` callback when guards\n * ARE configured but `_initGuards()` raced with the first guard\n * invocation (theoretically impossible — `Noydb.openVault` awaits\n * `_initGuards` before returning — but we keep the defensive lazy\n * path so the closure's contract stays \"always returns a facade\").\n */\n private _ensureReadOnlyFacade(): ReadOnlyVaultFacade {\n if (this.readOnlyFacade !== null) return this.readOnlyFacade\n // Synchronous fall-back: dynamic import isn't available here,\n // but `_initGuards` always sets the facade before any\n // guard-hook can fire. Reaching this branch means a Vault was\n // constructed without `_initGuards` being awaited — e.g. via\n // the sync `Noydb.vault()` fallback path. Throw with a\n // pointer rather than silently building an invalid context.\n throw new Error(\n 'Vault: guard hook fired before _initGuards() completed. ' +\n 'This typically means the vault was opened via the sync ' +\n 'fallback path (Noydb.vault(name)) without first calling ' +\n 'await db.openVault(name). See issue #132.',\n )\n }\n\n /**\n * @internal — exposed for `runTransaction({ amendment: true })`\n * to append the structured `op: 'amendment'` audit entry without\n * dragging this private accessor onto the public surface or\n * forcing the tx executor to depend on the history-strategy\n * shape directly. Returns `null` when no history strategy is\n * configured, in which case the amendment commits silently\n * (the records still write through; only the multi-record\n * audit summary is skipped).\n */\n _getLedgerOrNull(): LedgerStore | null {\n return this.getLedgerOrNull()\n }\n\n /**\n * Return a read-only view of this vault as it existed at\n * `timestamp`. Time-machine queries are reconstructed from the\n * per-version history snapshots persisted by every `put()`, then\n * cross-checked against the ledger for deletes that happened\n * between the snapshot and the target timestamp.\n *\n * ```ts\n * const q1End = vault.at('2026-03-31T23:59:59Z')\n * const invoice = await q1End.collection<Invoice>('invoices').get('inv-001')\n * // → the record as it stood at the close of Q1 2026\n * ```\n *\n * `timestamp` accepts an ISO-8601 string or a `Date`. Time-machine\n * views are read-only — writes throw {@link ReadOnlyAtInstantError}.\n * Accuracy bounded by history retention: if `historyConfig.maxVersions`\n * pruned earlier versions, queries before the oldest retained\n * snapshot return null even for records that existed.\n *\n *.\n */\n at(timestamp: string | Date): VaultInstant {\n const iso = timestamp instanceof Date ? timestamp.toISOString() : timestamp\n return this.historyStrategy.buildVaultInstant(\n {\n adapter: this.adapter,\n name: this.name,\n encrypted: this.encrypted,\n getDEK: this.getDEK,\n getLedger: () => (this.historyConfig.enabled === false ? null : this.getLedgerOrNull()),\n },\n iso,\n )\n }\n\n /**\n * Return a read-only \"shadow\" view of this vault. Every read method\n * on the returned {@link VaultFrame} delegates to the underlying\n * live collection; every write method throws\n * {@link ReadOnlyFrameError}.\n *\n * ```ts\n * const presentation = vault.frame()\n * const invoices = await presentation.collection<Invoice>('invoices').list()\n * ```\n *\n * Use for screen-sharing a live vault, demo mode, or compliance\n * review where the reviewer should not be able to edit. Writes are\n * blocked at the JavaScript layer — the keyring DEKs are unchanged,\n * so this is **not** a cryptographic security boundary against a\n * hostile caller in the same process. See {@link VaultFrame} for\n * the full caveat.\n *\n *.\n */\n frame(): VaultFrame {\n return this.shadowStrategy.buildFrame(this)\n }\n\n /**\n * Run `fn` under a consent scope. Every `get` / `put` / `delete`\n * that happens inside `fn` writes one entry to `_consent_audit`\n * with the supplied `purpose` and `consentHash`. Outside a scope,\n * no entries are written — consent logging is opt-in by design.\n *\n * ```ts\n * await vault.withConsent(\n * { purpose: 'quarterly-review', consentHash: '7f3a...' },\n * async () => {\n * const invoices = await vault.collection<Invoice>('invoices').list()\n * return invoices\n * },\n * )\n * ```\n *\n * The scope is a single slot on this Vault instance — two\n * concurrent `withConsent` calls stomp each other. Use separate\n * Vault instances (or an external `AsyncLocalStorage` shim) for\n * per-flight scoping.\n *\n *.\n */\n async withConsent<T>(ctx: ConsentContext, fn: () => Promise<T>): Promise<T> {\n const prior = this.consentContext\n this.consentContext = ctx\n try {\n return await fn()\n } finally {\n this.consentContext = prior\n }\n }\n\n /**\n * Query the consent-audit log. Returns every entry matching the\n * filter, newest-first isn't enforced — entries carry ULID ids so\n * sorting by id is insertion-order stable. Caller may sort further.\n *\n *.\n */\n async consentAudit(filter: ConsentAuditFilter = {}): Promise<ConsentAuditEntry[]> {\n return this.consentStrategy.read(this.adapter, this.name, this.encrypted, this.getDEK, filter)\n }\n\n /**\n * Called by Collection after every access when a consent scope is\n * active. Internal — not part of the public API.\n *\n * @internal\n */\n async _logConsent(op: ConsentOp, collection: string, recordId: string): Promise<void> {\n const ctx = this.consentContext\n if (!ctx) return\n await this.consentStrategy.write(\n this.adapter,\n this.name,\n this.encrypted,\n {\n actor: this.keyring.userId,\n purpose: ctx.purpose,\n consentHash: ctx.consentHash,\n op,\n collection,\n recordId,\n },\n this.getDEK,\n )\n }\n\n // ─── Hierarchical access ─────────────────────────\n\n /**\n * Subscribe to cross-tier access events. The callback fires every\n * time a record at a tier above the caller's inherent clearance is\n * read, written, elevated, or demoted successfully via this vault.\n * Returns an unsubscribe function.\n */\n onCrossTierAccess(\n listener: (event: CrossTierAccessEvent) => void,\n ): () => void {\n this.crossTierSubs.add(listener)\n return () => this.crossTierSubs.delete(listener)\n }\n\n private emitCrossTier(event: CrossTierAccessEvent): void {\n for (const sub of this.crossTierSubs) {\n try {\n sub(event)\n } catch {\n // subscriber failures are swallowed — audit sinks must be best-effort\n }\n }\n }\n\n /**\n * issue a time-boxed cross-tier delegation. Writes an\n * encrypted envelope to the reserved `_delegations` collection that\n * the target user's runtime will pick up next time they open the\n * vault.\n *\n * Caller must hold the tier DEK for the requested tier and\n * collection.\n */\n async delegate(opts: IssueDelegationOptions): Promise<DelegationToken> {\n const { issueDelegation, DELEGATIONS_COLLECTION } = await import('./team/delegation.js')\n // The target user's KEK is derived from THEIR keyring — we read\n // the keyring file to pick up the wrapped DEKs and their KEK salt,\n // but we cannot derive their KEK from our side (we don't have\n // their passphrase). For the delegation wraps against the\n // grantor's own KEK as a simpler first cut; swapping to a proper\n // per-target KEK exchange (via `on-magic-link` or OIDC) is a\n // follow-up tracked in the design doc.\n if (!this.keyring.kek) {\n throw new ValidationError(\n 'issueDelegation: keyring.kek is null — issuing a delegation requires ' +\n 'a tier-1 unlock. Re-authenticate at tier 1 (passphrase) first.',\n )\n }\n const targetKek = this.keyring.kek\n const delegationsDek = await this.getDEK(DELEGATIONS_COLLECTION)\n return issueDelegation(\n this.adapter,\n this.name,\n this.keyring,\n targetKek,\n delegationsDek,\n opts,\n )\n }\n\n /**\n * revoke an issued delegation by id. Safe to call even\n * if the id does not exist.\n */\n async revokeDelegation(id: string): Promise<void> {\n const { revokeDelegation, DELEGATIONS_COLLECTION } = await import('./team/delegation.js')\n await revokeDelegation(this.adapter, this.name, id)\n // Trigger store to note the delete.\n void DELEGATIONS_COLLECTION\n }\n\n // ─── Scoped tier elevation ───────────────────────────\n\n /**\n * Briefly elevate this vault to a higher tier and return a scoped\n * handle whose writes land at that tier. Reads on the original\n * vault continue at the caller's inherent tier; only the returned\n * handle is privileged. Auto-reverts when `release()` is called or\n * `ttlMs` elapses, whichever comes first.\n *\n * Capability semantics:\n * - The keyring must already carry a wrap for the target tier on\n * at least one collection (or be `owner` / `admin`, who can\n * auto-mint). Otherwise throws {@link TierNotGrantedError}.\n * - Per-collection capability gates (`canExportPlaintext`,\n * `canExportBundle`) are NOT bypassed — elevation is a tier\n * projection, not a privilege escalation path.\n * - Only one elevation can be active per vault at a time.\n * Calling `elevate(...)` while another is live throws\n * {@link AlreadyElevatedError}.\n *\n * Audit:\n * - One `_elevation_audit` envelope is written at start with\n * `{ id, actor, tier, reason, ttlMs, startedAt, expiresAt }`.\n * - Each write through the elevated handle additionally fires a\n * {@link CrossTierAccessEvent} with `authorization: 'elevation'`,\n * stamped with `reason` and `elevatedFrom`.\n */\n async elevate(\n tier: number,\n options: { ttlMs: number; reason: string },\n ): Promise<ElevatedHandle> {\n if (!Number.isInteger(tier) || tier <= 0) {\n throw new ValidationError(`elevate: tier must be a positive integer, got ${tier}`)\n }\n if (!options || typeof options.reason !== 'string' || options.reason.length === 0) {\n throw new ValidationError('elevate: reason is required (non-empty string)')\n }\n if (typeof options.ttlMs !== 'number' || options.ttlMs <= 0) {\n throw new ValidationError('elevate: ttlMs must be a positive number')\n }\n if (this.activeElevation) {\n throw new AlreadyElevatedError(this.activeElevation.tier)\n }\n // Construction-time tier-reach check: scan keyring for any\n // `*#${tier}` DEK. Owners and admins skip — they auto-mint at\n // write time per the existing `assertTierAccess` rules.\n if (this.keyring.role !== 'owner' && this.keyring.role !== 'admin') {\n const suffix = `#${tier}`\n let found = false\n for (const k of this.keyring.deks.keys()) {\n if (k.endsWith(suffix)) { found = true; break }\n }\n if (!found) {\n // Match the existing error class so adopters with one catch()\n // for tier-related failures don't need a second branch.\n throw new TierNotGrantedError('(any collection)', tier)\n }\n }\n\n const startedAt = new Date()\n const expiresAt = startedAt.getTime() + options.ttlMs\n const reason = options.reason\n\n const handle = new ElevatedHandle({\n vault: this,\n tier,\n reason,\n expiresAt,\n onRelease: () => {\n if (this.activeElevation && this.activeElevation.handle === handle) {\n this.activeElevation = null\n }\n },\n })\n\n this.activeElevation = { tier, expiresAt, reason, handle }\n await this.writeElevationAudit({\n actor: this.keyring.userId,\n tier,\n reason,\n ttlMs: options.ttlMs,\n startedAt: startedAt.toISOString(),\n expiresAt: new Date(expiresAt).toISOString(),\n })\n return handle\n }\n\n /**\n * Internal — invoked by an `ElevatedHandle.collection().put()` call.\n * Routes through the existing `Collection.putAtTier` code path with\n * the elevation context attached so the cross-tier event reflects\n * the right authorization class.\n */\n async _elevatedPut<T>(\n collectionName: string,\n id: string,\n record: T,\n tier: number,\n reason: string,\n ): Promise<void> {\n const coll = this.collection<T>(collectionName)\n await coll.putAtTier(id, record, tier, {\n elevation: { reason, fromTier: 0 },\n })\n }\n\n private async writeElevationAudit(entry: {\n actor: string\n tier: number\n reason: string\n ttlMs: number\n startedAt: string\n expiresAt: string\n }): Promise<void> {\n const id = `elev-${Date.now().toString(36)}-${Math.random().toString(16).slice(2, 10)}`\n const json = JSON.stringify({ id, ...entry })\n const envelope: EncryptedEnvelope = this.encrypted\n ? await (async () => {\n const dek = await this.getDEK(ELEVATION_AUDIT_COLLECTION)\n const { iv, data } = await encrypt(json, dek)\n return {\n _noydb: NOYDB_FORMAT_VERSION,\n _v: 1,\n _ts: entry.startedAt,\n _iv: iv,\n _data: data,\n _by: entry.actor,\n }\n })()\n : {\n _noydb: NOYDB_FORMAT_VERSION,\n _v: 1,\n _ts: entry.startedAt,\n _iv: '',\n _data: json,\n _by: entry.actor,\n }\n await this.adapter.put(this.name, ELEVATION_AUDIT_COLLECTION, id, envelope)\n }\n\n /**\n * low-level escape hatch used by `@noy-db/on-magic-link`\n * to persist a magic-link-bound grant after the auth package has\n * derived the content key + KEK from `(serverSecret, token, vault)`.\n *\n * Callers outside of `@noy-db/on-magic-link` should use\n * `issueMagicLinkDelegation()` from that package instead — it handles\n * the HKDF derivation, record-id composition, and batch logic so the\n * grantor doesn't touch this method directly.\n */\n async writeMagicLinkGrant(\n contentKey: CryptoKey,\n grantKek: CryptoKey,\n recordId: string,\n opts: IssueMagicLinkGrantOptions,\n ): Promise<MagicLinkGrantRecord> {\n return writeMagicLinkGrant(\n this.adapter,\n this.name,\n this.keyring,\n contentKey,\n grantKek,\n recordId,\n opts,\n )\n }\n\n // ─── Accounting periods ────────────────────────\n\n /**\n * Close an accounting period. After this call every record whose\n * envelope `_ts` is at or before `endDate` is write-locked: further\n * `put` or `delete` calls against such records throw\n * {@link PeriodClosedError}. New records (with fresh timestamps)\n * remain freely writable, and records last written AFTER `endDate`\n * are unaffected.\n *\n * Each closure writes a `PeriodRecord` to the reserved `_periods`\n * collection. The record carries the hash of the prior period's\n * record, so a tamper with any closure breaks the chain visible to\n * {@link listPeriods} + `vault.ledger().verify()`.\n *\n * Correctness is tied to the `_ts` field the hub assigns on every\n * write. Backdating records by editing the envelope directly is\n * outside the threat model — see SPEC § zero-knowledge envelopes.\n *\n *.\n */\n async closePeriod(options: ClosePeriodOptions): Promise<PeriodRecord> {\n const existing = await this._loadPeriodsCache()\n this.periodsStrategy.validatePeriodName(options.name, existing)\n if (typeof options.endDate !== 'string' || options.endDate.length === 0) {\n throw new ValidationError('closePeriod: endDate must be a non-empty ISO string.')\n }\n const anchor = await this.periodsStrategy.chainAnchor(existing)\n const record: PeriodRecord = {\n name: options.name,\n kind: 'closed',\n endDate: options.endDate,\n closedAt: new Date().toISOString(),\n closedBy: this.keyring.userId,\n priorPeriodHash: anchor.priorPeriodHash,\n ...(anchor.priorPeriodName !== undefined && { priorPeriodName: anchor.priorPeriodName }),\n ...(options.dateField !== undefined && { dateField: options.dateField }),\n }\n const envelope = await this._writePeriodRecord(record)\n await this.periodsStrategy.appendPeriodLedgerEntry(this.getLedgerOrNull(), this.keyring.userId, envelope, record.name)\n existing.push(record)\n this.periodCache = existing\n return record\n }\n\n /**\n * Open a new period that carries forward from a prior closed one\n *. The `carryForward` callback receives a read-only\n * {@link VaultInstant} view anchored at the prior period's\n * `endDate` — use it to compute opening balances, closing-trial\n * snapshots, or any aggregate the new period should inherit. The\n * returned `{ [collection]: { [id]: record } }` map is written\n * before the new `PeriodRecord` lands, so the opening entries\n * materialise with fresh `_ts` values that fall outside every\n * closed period (the guard lets them through).\n *\n * The new period is stored with `kind: 'opened'` and hash-chained\n * to the same chain the close calls build — `listPeriods()` sees\n * both closed and opened entries in `closedAt` order.\n */\n async openPeriod<TCollections extends Record<string, Record<string, unknown>>>(\n options: OpenPeriodOptions<TCollections>,\n ): Promise<PeriodRecord> {\n const existing = await this._loadPeriodsCache()\n this.periodsStrategy.validatePeriodName(options.name, existing)\n const prior = existing.find((p) => p.name === options.fromPeriod)\n if (!prior) {\n throw new ValidationError(\n `openPeriod: fromPeriod \"${options.fromPeriod}\" does not exist in this vault.`,\n )\n }\n if (prior.kind !== 'closed') {\n throw new ValidationError(\n `openPeriod: fromPeriod \"${options.fromPeriod}\" is of kind \"${prior.kind}\" — only closed periods can be carried forward.`,\n )\n }\n\n // Build a read-only facade over CURRENT state + the prior\n // period's endDate; after close, records dated <= endDate are\n // frozen so current state equals closing state. The caller\n // filters by business date via their own query against this\n // facade.\n const ctx = {\n priorEndDate: prior.endDate,\n collection: <T = unknown>(name: string) => {\n const c = this.collection<T>(name)\n return {\n get: (id: string) => c.get(id),\n list: () => c.list(),\n }\n },\n }\n const openings = await options.carryForward(ctx)\n\n // Write opening entries via the normal Collection path so they\n // get encryption, ledger entries, and change events. Each record\n // is timestamped NOW (outside every closed period) — that's why\n // the guard permits them.\n const openingCollections: string[] = []\n for (const [collName, records] of Object.entries(openings)) {\n if (!records || typeof records !== 'object') continue\n const recordEntries = Object.entries(records)\n if (recordEntries.length === 0) continue\n const coll = this.collection(collName)\n for (const [id, record] of recordEntries) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n await coll.put(id, record as any)\n }\n openingCollections.push(collName)\n }\n\n const anchor = await this.periodsStrategy.chainAnchor(existing)\n const record: PeriodRecord = {\n name: options.name,\n kind: 'opened',\n startDate: options.startDate,\n endDate: prior.endDate, // sealing boundary inherited from prior close\n closedAt: new Date().toISOString(),\n closedBy: this.keyring.userId,\n priorPeriodHash: anchor.priorPeriodHash,\n priorPeriodName: anchor.priorPeriodName ?? prior.name,\n ...(openingCollections.length > 0 && { openingCollections }),\n }\n const envelope = await this._writePeriodRecord(record)\n await this.periodsStrategy.appendPeriodLedgerEntry(this.getLedgerOrNull(), this.keyring.userId, envelope, record.name)\n existing.push(record)\n this.periodCache = existing\n return record\n }\n\n /** Return every closed / opened period in `closedAt` order. */\n async listPeriods(): Promise<readonly PeriodRecord[]> {\n return [...(await this._loadPeriodsCache())]\n }\n\n /** Look up a single period by name. Returns `null` if not found. */\n async getPeriod(name: string): Promise<PeriodRecord | null> {\n const all = await this._loadPeriodsCache()\n return all.find((p) => p.name === name) ?? null\n }\n\n /** @internal — periodGuard callback installed on every Collection. */\n async _assertTsWritable(\n existing: { ts: string | null; record: Record<string, unknown> | null } | null,\n incoming: Record<string, unknown> | null,\n ): Promise<void> {\n // Fast path: nothing to check, and no periods ever touched this\n // vault — avoid a full adapter scan for every put.\n if (existing === null && incoming === null) return\n if (this.periodCache === null) {\n this.periodCache = await this.periodsStrategy.loadPeriods(\n this.adapter,\n this.name,\n (env) => this._decryptPeriodRecord(env),\n )\n }\n if (this.periodCache.length === 0) return\n this.periodsStrategy.assertTsWritable(existing, incoming, this.periodCache)\n }\n\n private async _loadPeriodsCache(): Promise<PeriodRecord[]> {\n if (this.periodCache !== null) return this.periodCache\n const loaded = await this.periodsStrategy.loadPeriods(\n this.adapter,\n this.name,\n (env: EncryptedEnvelope) => this._decryptPeriodRecord(env),\n )\n this.periodCache = loaded\n return loaded\n }\n\n private async _writePeriodRecord(record: PeriodRecord): Promise<EncryptedEnvelope> {\n const json = JSON.stringify(record)\n let envelope: EncryptedEnvelope\n if (this.encrypted) {\n const dek = await this.getDEK(PERIODS_COLLECTION)\n const { iv, data } = await encrypt(json, dek)\n envelope = {\n _noydb: NOYDB_FORMAT_VERSION,\n _v: 1,\n _ts: new Date().toISOString(),\n _iv: iv,\n _data: data,\n _by: this.keyring.userId,\n }\n } else {\n envelope = {\n _noydb: NOYDB_FORMAT_VERSION,\n _v: 1,\n _ts: new Date().toISOString(),\n _iv: '',\n _data: json,\n _by: this.keyring.userId,\n }\n }\n await this.adapter.put(this.name, PERIODS_COLLECTION, record.name, envelope)\n return envelope\n }\n\n private async _decryptPeriodRecord(envelope: EncryptedEnvelope): Promise<PeriodRecord> {\n let json: string\n if (this.encrypted) {\n const dek = await this.getDEK(PERIODS_COLLECTION)\n json = await decrypt(envelope._iv, envelope._data, dek)\n } else {\n json = envelope._data\n }\n return JSON.parse(json) as PeriodRecord\n }\n\n /** List all collection names in this vault. */\n async collections(): Promise<string[]> {\n const snapshot = await this.adapter.loadAll(this.name)\n return Object.keys(snapshot)\n }\n\n /**\n * Emit a structured introspection snapshot of this vault — vault name,\n * subsystem opt-in matrix, collections + their fields, materialized\n * views, overlay views, derivations. With `withStats: true`, walks\n * every collection's envelopes to compute record counts, byte totals,\n * and oldest/newest timestamps.\n *\n * Consumed by the `noydb describe` CLI to produce human-readable\n * audit YAML/JSON from a `.noydb` bundle.\n *\n * Field provenance:\n * - `persisted`: read from `_schemas/<col>` envelope (Route B opt-in)\n * - `live-validator`: derived in-process from a Zod schema attached\n * to the live `Collection`\n * - `sampled`: inferred from decrypted records (deferred to a follow-up)\n * - `unknown`: no schema info available\n *\n * @see docs/superpowers/specs/2026-05-22-schema-dump-design.md\n */\n async dumpSchema(opts: DumpSchemaOptions = {}): Promise<VaultSchemaSnapshot> {\n return dumpVaultSchema(this, opts)\n }\n\n /**\n * Internal accessor for {@link dumpVaultSchema}. Exposes the structural\n * state the walker needs (collection cache, registries, ref registry,\n * adapter) without widening the public Vault surface.\n *\n * @internal\n */\n _introspectState(): VaultIntrospectState {\n return {\n name: this.name,\n adapter: this.adapter,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n collectionCache: this.collectionCache as Map<string, any>,\n refRegistry: this.refRegistry,\n getDEK: this.getDEK,\n subsystems: {\n guards: this.guardRegistry !== null,\n derivations: this.derivationRegistry !== null,\n materializedViews: this.materializedViewRegistry !== null,\n overlayViews: this.overlayedViewRegistry !== null,\n },\n mvRegistry: this.materializedViewRegistry,\n overlayRegistry: this.overlayedViewRegistry,\n derivationRegistry: this.derivationRegistry,\n }\n }\n\n /**\n * Return the stable opaque bundle handle for this vault,\n * generating and persisting a fresh ULID on first call.\n *\n * used by `writeNoydbBundle()` to identify the\n * vault in the unencrypted bundle header without\n * exposing the vault name. The handle is persisted in\n * the reserved `_meta` internal collection so subsequent\n * exports of the same vault produce the same handle —\n * bundle adapters (Drive, Dropbox, iCloud) will use it\n * as their primary key.\n *\n * **Storage path:** the handle is written via the adapter\n * directly with collection name `_meta` and id `handle`. The\n * envelope's `_data` field contains a plain JSON\n * `{ handle: '...' }` payload — the handle is opaque, doesn't\n * need encryption, and the bundle header exposes the same\n * value anyway. This mirrors the storage approach `_keyring`\n * uses for its plain-JSON wrapped-DEK envelopes (also bypasses\n * the AES-GCM layer; the `_iv` field is left empty).\n *\n * **Cross-process stability:** the handle survives process\n * restarts because it's persisted on the adapter, not just\n * cached in memory. A new Vault instance opened on the\n * same adapter sees the same `_meta/handle` envelope and\n * returns the same ULID.\n *\n * **Round-trip after restore:** the receiving vault of a\n * `load()` call generates its OWN handle on first export. The\n * dump body does not include `_meta`, because handle stability\n * is per-vault-instance, not per-vault-content. Two\n * separate restorations of the same backup produce two\n * distinct handles, which is the right behavior — they're\n * separate vault instances now.\n */\n async getBundleHandle(): Promise<string> {\n const existing = await this.adapter.get(this.name, '_meta', 'handle')\n if (existing) {\n try {\n const parsed = JSON.parse(existing._data) as unknown\n if (parsed !== null && typeof parsed === 'object' && 'handle' in parsed) {\n const handle = (parsed as { handle: unknown }).handle\n if (typeof handle === 'string' && /^[0-9A-HJKMNP-TV-Z]{26}$/.test(handle)) {\n return handle\n }\n }\n } catch {\n // Fall through to regenerate — corrupted handle envelope\n // is treated as missing, not as an error. The new handle\n // overwrites the bad one.\n }\n }\n // Lazy import to avoid a top-of-file circular dependency:\n // bundle/bundle.ts imports from vault.ts (the\n // Vault type), and vault.ts can't statically\n // import from bundle/* without forming a cycle. The dynamic\n // import is invoked once per fresh handle generation, which\n // is rare enough that the cost doesn't matter.\n const { generateULID } = await import('./bundle/ulid.js')\n const handle = generateULID()\n const envelope: EncryptedEnvelope = {\n _noydb: NOYDB_FORMAT_VERSION,\n _v: 1,\n _ts: new Date().toISOString(),\n _iv: '',\n _data: JSON.stringify({ handle }),\n }\n await this.adapter.put(this.name, '_meta', 'handle', envelope)\n return handle\n }\n\n /**\n * Read the owner-curated public envelope for this vault (or\n * `undefined` if none is persisted). The envelope lives in\n * `_meta/public-envelope` as plaintext — readable without any KEK\n * — so `getBundleHandle`-style callers can label a vault before\n * unlock.\n *\n * Mirrors `Noydb.getPublicEnvelope(vault, opts)` but scoped to a\n * single, already-opened `Vault` instance so the\n * bundle writer can snapshot it without holding a `Noydb` reference.\n *\n * @see docs/subsystems/public-envelope.md\n */\n async getPublicEnvelope(\n opts: { readonly locale?: string } = {},\n ): Promise<PublicEnvelope | undefined> {\n const { readPublicEnvelope } = await import('./meta/public-envelope/index.js')\n return readPublicEnvelope(this.adapter, this.name, opts)\n }\n\n /**\n * Dump vault as a verifiable encrypted JSON backup string.\n *\n * backups embed the current ledger head and the full\n * `_ledger` + `_ledger_deltas` internal collections so the\n * receiver can run `verifyBackupIntegrity()` after `load()` and\n * detect any tampering between dump and restore. Backups produced\n * without a ledger (older formats or hub instances built without\n * the history strategy) skip the integrity check with a warning —\n * both modes round-trip cleanly.\n */\n async dump(): Promise<string> {\n const snapshot = await this.adapter.loadAll(this.name)\n\n // Load keyrings (separate path because loadAll filters them out\n // along with all other underscore-prefixed internal collections).\n const keyringIds = await this.adapter.list(this.name, '_keyring')\n const keyrings: Record<string, unknown> = {}\n for (const keyringId of keyringIds) {\n const envelope = await this.adapter.get(this.name, '_keyring', keyringId)\n if (envelope) {\n keyrings[keyringId] = JSON.parse(envelope._data)\n }\n }\n\n // Load the ledger entries + deltas so the receiver can replay\n // the chain after restore. Without this, `load()` would have an\n // empty ledger and `verifyBackupIntegrity()` would have nothing\n // to compare against.\n const internalSnapshot: VaultSnapshot = {}\n for (const internalName of [LEDGER_COLLECTION, LEDGER_DELTAS_COLLECTION, SCHEMAS_COLLECTION]) {\n const ids = await this.adapter.list(this.name, internalName)\n if (ids.length === 0) continue\n const records: Record<string, EncryptedEnvelope> = {}\n for (const id of ids) {\n const envelope = await this.adapter.get(this.name, internalName, id)\n if (envelope) records[id] = envelope\n }\n internalSnapshot[internalName] = records\n }\n\n // Embed the ledger head if there's a chain. An empty ledger\n // (fresh vault) leaves `ledgerHead` undefined, which\n // load() treats the same as a legacy backup (no integrity\n // check, console warning). If history is not opted in,\n // `getLedgerOrNull` returns null and we skip embedding entirely\n // — the backup is still valid, just without the integrity head.\n const ledgerForHead = this.getLedgerOrNull()\n const head = ledgerForHead ? await ledgerForHead.head() : null\n const backup: VaultBackup = {\n _noydb_backup: NOYDB_BACKUP_VERSION,\n _compartment: this.name,\n _exported_at: new Date().toISOString(),\n _exported_by: this.keyring.userId,\n keyrings: keyrings as VaultBackup['keyrings'],\n collections: snapshot,\n ...(Object.keys(internalSnapshot).length > 0\n ? { _internal: internalSnapshot }\n : {}),\n ...(head\n ? {\n ledgerHead: {\n hash: head.hash,\n index: head.entry.index,\n ts: head.entry.ts,\n },\n }\n : {}),\n }\n\n return JSON.stringify(backup)\n }\n\n /**\n * Restore a vault from a verifiable backup.\n *\n * After loading, runs `verifyBackupIntegrity()` to confirm:\n * 1. The hash chain is intact (no `prevHash` mismatches)\n * 2. The chain head matches the embedded `ledgerHead.hash`\n * from the backup\n * 3. Every data envelope's `payloadHash` matches the\n * corresponding ledger entry — i.e. nobody swapped\n * ciphertext between dump and restore\n *\n * On any failure, throws `BackupLedgerError` (chain or head\n * mismatch) or `BackupCorruptedError` (data envelope mismatch).\n * The vault state on the adapter has already been written\n * by the time we throw, so the caller is responsible for either\n * accepting the suspect state or wiping it and trying a different\n * backup.\n *\n * Legacy backups (no `ledgerHead` field, no `_internal`) load\n * with a console warning and skip the integrity check entirely\n * — there's no chain to verify against.\n */\n async load(backupJson: string): Promise<void> {\n const backup = JSON.parse(backupJson) as VaultBackup\n\n // 1. Restore data collections.\n await this.adapter.saveAll(this.name, backup.collections)\n\n // 2. Restore keyrings.\n for (const [userId, keyringFile] of Object.entries(backup.keyrings)) {\n const envelope = {\n _noydb: 1 as const,\n _v: 1,\n _ts: new Date().toISOString(),\n _iv: '',\n _data: JSON.stringify(keyringFile),\n }\n await this.adapter.put(this.name, '_keyring', userId, envelope)\n }\n\n // 3. Restore internal collections (`_ledger`, `_ledger_deltas`).\n // Required so verifyBackupIntegrity has the chain to walk.\n if (backup._internal) {\n for (const [internalName, records] of Object.entries(backup._internal)) {\n for (const [id, envelope] of Object.entries(records)) {\n await this.adapter.put(this.name, internalName, id, envelope)\n }\n }\n }\n\n // 4. Refresh the in-memory keyring from the freshly-loaded\n // keyring file. Without this, the Vault's getDEK\n // closure still holds the OLD session's DEKs, and every\n // decrypt of a loaded ledger entry / data envelope fails\n // with TamperedError because the DEK doesn't match the\n // ciphertext that was encrypted with the SOURCE user's DEK.\n // Skipped for plaintext vaults and for tests that\n // construct Vault without a reloadKeyring callback.\n if (this.reloadKeyring) {\n this.keyring = await this.reloadKeyring()\n // Rebuild the DEK resolver against the refreshed keyring so\n // the next ensureCollectionDEK call sees the loaded wrapped\n // DEKs, not the cached pre-load ones.\n this.getDEK = this.makeGetDEK()\n }\n\n // 5. Clear collection cache + reset the ledger store so the\n // next ledger() call rebuilds its head cache from the\n // freshly-loaded entries.\n this.collectionCache.clear()\n this.ledgerStore = null\n\n // 5. Run the verification gate. Legacy backups (no ledgerHead)\n // skip this with a one-line warning so existing consumers can\n // still read them while migrating.\n if (!backup.ledgerHead) {\n console.warn(\n `[noy-db] Loaded a legacy backup with no ledgerHead — ` +\n `verifiable-backup integrity check skipped. ` +\n `Re-export with a ledger-aware build to get tamper detection.`,\n )\n return\n }\n\n const result = await this.verifyBackupIntegrity()\n if (!result.ok) {\n // Surface the most specific error class we can. The result\n // shape carries enough info for callers to inspect.\n if (result.kind === 'data') {\n throw new BackupCorruptedError(\n result.collection,\n result.id,\n result.message,\n )\n }\n throw new BackupLedgerError(result.message, result.divergedAt)\n }\n\n // 6. Cross-check: the freshly-verified head must match the\n // value embedded at dump time. A mismatch means someone\n // truncated or extended the chain after dump.\n if (result.head !== backup.ledgerHead.hash) {\n throw new BackupLedgerError(\n `Backup ledger head mismatch: embedded \"${backup.ledgerHead.hash}\" ` +\n `but reconstructed \"${result.head}\".`,\n )\n }\n }\n\n /**\n * End-to-end backup integrity check. Runs both:\n *\n * 1. `ledger.verify()` — walks the hash chain and confirms\n * every `prevHash` matches the recomputed hash of its\n * predecessor.\n *\n * 2. **Data envelope cross-check** — for every (collection, id)\n * that has a current value, find the most recent ledger\n * entry recording a `put` for that pair, recompute the\n * sha256 of the stored envelope's `_data`, and compare to\n * the entry's `payloadHash`. Any mismatch means an\n * out-of-band write modified the data without updating the\n * ledger.\n *\n * Returns a discriminated union so callers can handle the two\n * failure modes differently:\n * - `{ ok: true, head, length }` — chain verified and all\n * data matches; safe to use.\n * - `{ ok: false, kind: 'chain', divergedAt, message }` — the\n * chain itself is broken at the given index.\n * - `{ ok: false, kind: 'data', collection, id, message }` —\n * a specific data envelope doesn't match its ledger entry.\n *\n * This method is exposed so users can call it any time, not just\n * during `load()`. A scheduled background check is the simplest\n * way to detect tampering of an in-place vault.\n */\n async verifyBackupIntegrity(): Promise<\n | { readonly ok: true; readonly head: string; readonly length: number }\n | {\n readonly ok: false\n readonly kind: 'chain'\n readonly divergedAt: number\n readonly message: string\n }\n | {\n readonly ok: false\n readonly kind: 'data'\n readonly collection: string\n readonly id: string\n readonly message: string\n }\n > {\n // Step 1: chain verification. Without the history strategy there\n // is no ledger; an unaudited backup verifies trivially as `ok`\n // because there's nothing to diverge from.\n const ledgerForVerify = this.getLedgerOrNull()\n if (!ledgerForVerify) {\n return { ok: true, head: '', length: 0 }\n }\n const chainResult = await ledgerForVerify.verify()\n if (!chainResult.ok) {\n return {\n ok: false,\n kind: 'chain',\n divergedAt: chainResult.divergedAt,\n message:\n `Ledger chain diverged at index ${chainResult.divergedAt}: ` +\n `expected prevHash \"${chainResult.expected}\" but found \"${chainResult.actual}\".`,\n }\n }\n\n // Step 2: data envelope cross-check. Walk every entry in the\n // ledger and, for the LATEST `put` per (collection, id), recompute\n // the data envelope's payloadHash and compare. Earlier puts of the\n // same id are skipped because the data collection only holds the\n // current version — historical envelopes live in the deltas\n // collection (which is itself protected by the chain).\n // Reuse the ledger we already resolved in step 1.\n const allEntries = await ledgerForVerify.loadAllEntries()\n\n // Find the latest non-delete entry per (collection, id). Walk\n // the entries in reverse so we hit the latest first; mark each\n // (collection, id) as seen and skip subsequent entries.\n const seen = new Set<string>()\n const latest = new Map<\n string,\n { collection: string; id: string; expectedHash: string }\n >()\n for (let i = allEntries.length - 1; i >= 0; i--) {\n const entry = allEntries[i]\n if (!entry) continue\n // Amendment entries are multi-record audit entries whose\n // `collection` and `id` are empty strings — building a `\"/\"`\n // key here would mark that synthetic slot as seen and falsely\n // trip the data check on a record that never existed. Skip\n // them BEFORE the key/seen bookkeeping so they neither\n // tombstone real entries nor enter the latest map.\n if (entry.op === 'amendment' || entry.op === 'lifecycle') continue\n const key = `${entry.collection}/${entry.id}`\n if (seen.has(key)) continue\n seen.add(key)\n // For deletes the data collection should NOT have the record,\n // so we skip — there's nothing to cross-check. Marking the key\n // as seen above ensures any earlier `put` of the same id is\n // also skipped (the record was subsequently deleted).\n if (entry.op === 'delete') continue\n latest.set(key, {\n collection: entry.collection,\n id: entry.id,\n expectedHash: entry.payloadHash,\n })\n }\n\n for (const { collection, id, expectedHash } of latest.values()) {\n const envelope = await this.adapter.get(this.name, collection, id)\n if (!envelope) {\n return {\n ok: false,\n kind: 'data',\n collection,\n id,\n message:\n `Ledger expects data record \"${collection}/${id}\" to exist, ` +\n `but the adapter has no envelope for it.`,\n }\n }\n const actualHash = await sha256Hex(envelope._data)\n if (actualHash !== expectedHash) {\n return {\n ok: false,\n kind: 'data',\n collection,\n id,\n message:\n `Data envelope \"${collection}/${id}\" has been tampered with: ` +\n `expected payloadHash \"${expectedHash}\", got \"${actualHash}\".`,\n }\n }\n }\n\n return {\n ok: true,\n head: chainResult.head,\n length: chainResult.length,\n }\n }\n\n /**\n * Stream every collection in this vault as decrypted, ACL-scoped\n * chunks.\n *\n * ⚠ **This method decrypts your records.** noy-db's threat model assumes\n * that records on disk are encrypted; the values yielded here are\n * plaintext. The consumer is responsible for ensuring the yielded data\n * is handled in a way that matches the data's sensitivity. If your goal\n * is encrypted backup or transport between noy-db instances, use\n * `dump()` instead — it produces a tamper-evident encrypted envelope and\n * never exposes plaintext.\n *\n * ## Behavior\n *\n * - **ACL-scoped.** Collections the calling principal cannot read are\n * silently skipped (same rule as `Collection.list()`). An operator\n * with `{ invoices: 'rw', clients: 'ro' }` permissions on a\n * five-collection vault exports only `invoices` and `clients`,\n * with no error on the others.\n * - **Streaming.** Returns an `AsyncIterableIterator` so consumers can\n * process chunks as they arrive without holding the full export in\n * memory. Note: the underlying adapter call (`loadAll`) is still a\n * single bulk read — the streaming benefit is on the *output* side.\n * True per-record adapter streaming arrives with the query DSL.\n * - **Schema + refs surfaced** as metadata on every chunk so downstream\n * serializers (`@noy-db/as-csv`, `@noy-db/as-xlsx`, custom\n * exporters) can produce schema-aware output without reaching into\n * collection internals.\n * - **Internal collections filtered.** `_ledger`, `_keyring`, etc. are\n * never yielded — they're noy-db's own bookkeeping and have no value\n * in a plaintext export. Use `dump()` for full backup including\n * internal collections.\n *\n * ## Composition\n *\n * Once cross-vault queries land, fanning this out across\n * every vault the caller can unlock is `queryAcross(ids, c =>\n * c.exportStream())` — no new primitive needed. That's part of why this\n * method belongs in core: it's the single decrypt+ACL+metadata path\n * that every export-format package will build on, and pushing it into\n * a `@noy-db/as-*` package would force every format to re-solve\n * the same problems independently.\n *\n * @example\n * ```ts\n * for await (const chunk of company.exportStream()) {\n * // chunk.collection: 'invoices'\n * // chunk.schema: ZodObject | null\n * // chunk.refs: { clientId: { target: 'clients', mode: 'strict' } }\n * // chunk.records: Invoice[]\n * }\n * ```\n *\n * @example\n * ```ts\n * // Per-record streaming for arbitrarily large collections.\n * for await (const chunk of company.exportStream({ granularity: 'record' })) {\n * // chunk.records is always length 1\n * await writer.write(serialize(chunk.records[0]))\n * }\n * ```\n */\n async *exportStream(opts: ExportStreamOptions = {}): AsyncIterableIterator<ExportChunk> {\n const granularity = opts.granularity ?? 'collection'\n\n // One bulk read to enumerate collections. `loadAll` filters out\n // underscore-prefixed internal collections, which is exactly what we\n // want — internal bookkeeping has no place in a plaintext export.\n const snapshot = await this.adapter.loadAll(this.name)\n const collectionNames = Object.keys(snapshot).sort()\n\n // Resolve the ledger head once if requested. The head is identical\n // across every yielded chunk (one ledger per vault) — we copy\n // it onto each chunk so consumers doing per-record streaming don't\n // have to thread state across yields, and so the chunk shape stays\n // forward-compatible with future per-partition ledgers where the\n // head genuinely will differ per chunk.\n const ledgerHead = opts.withLedgerHead\n ? await (async () => {\n const ledger = this.getLedgerOrNull()\n if (!ledger) return undefined\n const head = await ledger.head()\n return head\n ? { hash: head.hash, index: head.entry.index, ts: head.entry.ts }\n : undefined\n })()\n : undefined\n\n // Capture ALL dictionary snapshots upfront before the first yield.\n // Building all snapshots eagerly before yielding anything ensures that\n // concurrent mutations during streaming do not affect the snapshot — any\n // dictionary.put() that happens after the first yield sees the pre-yield\n // state here. Keyed by collection name.\n const dictSnapshotCache = new Map<\n string, // collection name\n Record<string, Record<string, Record<string, string>>> // field → key → locale → label\n >()\n for (const collectionName of collectionNames) {\n const dictFields = this.dictKeyFieldRegistry.get(collectionName)\n if (dictFields && Object.keys(dictFields).length > 0) {\n const snap: Record<string, Record<string, Record<string, string>>> = {}\n for (const [fieldName, dictName] of Object.entries(dictFields)) {\n const entries = await this.dictionary(dictName).list()\n const keyMap: Record<string, Record<string, string>> = {}\n for (const entry of entries) {\n keyMap[entry.key] = entry.labels\n }\n snap[fieldName] = keyMap\n }\n dictSnapshotCache.set(collectionName, snap)\n }\n }\n\n for (const collectionName of collectionNames) {\n // ACL gate. The same `hasAccess` check that `Collection.list()`\n // honors — silent skip, no error, matches the \"operator can read\n // some but not all\" pattern.\n if (!hasAccess(this.keyring, collectionName)) continue\n\n const coll = this.collection(collectionName)\n const schema = coll.getSchema() ?? null\n const refs = this.refRegistry.getOutbound(collectionName)\n const ids = Object.keys(snapshot[collectionName] ?? {})\n\n const dictionaries = dictSnapshotCache.get(collectionName)\n\n if (granularity === 'collection') {\n // Decrypt every record in the collection, then yield once.\n // Using `coll.get(id)` rather than the loadAll envelope directly\n // because `get()` is the canonical decrypt+schema-validate path\n // and any future cache/index plumbing rides through it.\n const records: unknown[] = []\n for (const id of ids) {\n const record = await coll.get(id)\n if (record !== null) records.push(record)\n }\n const chunk: ExportChunk = {\n collection: collectionName,\n schema,\n refs,\n records,\n ...(dictionaries !== undefined ? { dictionaries } : {}),\n ...(ledgerHead ? { ledgerHead } : {}),\n }\n yield chunk\n } else {\n // Per-record yield. Memory profile: O(1 record) at a time.\n // The schema/refs metadata is repeated on every chunk so\n // consumers don't have to thread state across yields.\n for (const id of ids) {\n const record = await coll.get(id)\n if (record === null) continue\n const chunk: ExportChunk = {\n collection: collectionName,\n schema,\n refs,\n records: [record],\n ...(dictionaries !== undefined ? { dictionaries } : {}),\n ...(ledgerHead ? { ledgerHead } : {}),\n }\n yield chunk\n }\n }\n }\n }\n\n /**\n * Convenience wrapper that consumes `exportStream()` and serializes the\n * result to a single JSON string.\n *\n * ⚠ **`exportJSON()` decrypts your records and produces plaintext.**\n *\n * noy-db's threat model assumes that records on disk are encrypted.\n * This function deliberately violates that assumption: it produces a\n * JSON string in plaintext, which the consumer is then responsible for\n * protecting (filesystem permissions, full-disk encryption, secure\n * transfer, secure deletion).\n *\n * Use this function only when:\n * - You are the authorized owner of the data, **and**\n * - You have a legitimate downstream tool that requires plaintext\n * JSON, **and**\n * - You have a documented plan for how the resulting plaintext will be\n * protected and eventually destroyed.\n *\n * If your goal is encrypted backup or transport between noy-db\n * instances, use `dump()` instead — it produces a tamper-evident\n * encrypted envelope, never plaintext.\n *\n * ## Why `Promise<string>` instead of writing to a file path\n *\n * Core has zero `node:` imports — it runs unchanged in browsers, Node,\n * Bun, Deno, and edge runtimes. Accepting a file path would force a\n * `node:fs` import (breaks browsers) or a runtime dynamic import\n * (doesn't tree-shake, inflates bundles). Returning a string lets the\n * consumer choose any sink and forces the destination decision to be\n * explicit at the call site — which is also better for the security\n * warning.\n *\n * @example\n * ```ts\n * // Node: write to a file\n * import { writeFile } from 'node:fs/promises'\n * await writeFile('./backup.json', await company.exportJSON())\n * ```\n *\n * @example\n * ```ts\n * // Browser: download as a file\n * const json = await company.exportJSON()\n * const blob = new Blob([json], { type: 'application/json' })\n * const url = URL.createObjectURL(blob)\n * // ... attach to an <a download> and click\n * ```\n *\n * @example\n * ```ts\n * // Stream upload to a server\n * await fetch('/upload', {\n * method: 'POST',\n * body: await company.exportJSON(),\n * })\n * ```\n *\n * ## On-disk shape\n *\n * ```json\n * {\n * \"_noydb_export\": 1,\n * \"_compartment\": \"acme\",\n * \"_exported_at\": \"2026-04-07T12:00:00.000Z\",\n * \"_exported_by\": \"alice@acme.example\",\n * \"collections\": {\n * \"invoices\": {\n * \"schema\": null,\n * \"refs\": { \"clientId\": { \"target\": \"clients\", \"mode\": \"strict\" } },\n * \"records\": [ ... ]\n * }\n * },\n * \"ledgerHead\": { \"hash\": \"...\", \"index\": 42, \"ts\": \"...\" }\n * }\n * ```\n *\n * `schema` is included for forward compatibility but is currently\n * always `null` because Standard Schema validators are not JSON-\n * serializable. Format-package serializers that need the schema\n * should use `exportStream()` directly and read `chunk.schema` (which\n * is the live validator object, not a serialization of it).\n */\n async exportJSON(opts: ExportStreamOptions = {}): Promise<string> {\n // Force per-collection granularity regardless of caller setting:\n // record-by-record output doesn't make sense in a single string.\n const collections: Record<\n string,\n {\n schema: null\n refs: Record<string, { target: string; mode: 'strict' | 'warn' | 'cascade' }>\n records: unknown[]\n }\n > = {}\n let ledgerHead: ExportChunk['ledgerHead'] | undefined\n // Merged dictionary snapshot across all collections.\n // Only populated when `resolveLabels` is not set.\n const allDictionaries: Record<\n string, // collection name\n Record<string, Record<string, Record<string, string>>>\n > = {}\n\n for await (const chunk of this.exportStream({\n granularity: 'collection',\n withLedgerHead: opts.withLedgerHead === true,\n })) {\n collections[chunk.collection] = {\n schema: null, // Standard Schema validators are not JSON-serializable\n refs: chunk.refs,\n records: chunk.records,\n }\n if (chunk.ledgerHead) ledgerHead = chunk.ledgerHead\n // Collect dictionary snapshots unless resolveLabels is set\n if (!opts.resolveLabels && chunk.dictionaries) {\n allDictionaries[chunk.collection] = chunk.dictionaries\n }\n }\n\n const hasDictionaries = Object.keys(allDictionaries).length > 0\n return JSON.stringify({\n _noydb_export: 1,\n _compartment: this.name,\n _exported_at: new Date().toISOString(),\n _exported_by: this.keyring.userId,\n collections,\n ...(hasDictionaries ? { _dictionaries: allDictionaries } : {}),\n ...(ledgerHead ? { ledgerHead } : {}),\n })\n }\n}\n\n// ─── Elevation handle ────────────────────────────────────\n\n/**\n * Reserved collection that holds the audit ledger of elevation\n * sessions. One envelope per `vault.elevate(...)` call.\n */\nexport const ELEVATION_AUDIT_COLLECTION = '_elevation_audit'\n\n/**\n * Scoped handle returned by `vault.elevate(...)`. Writes through this\n * handle land at the elevated tier with `authorization: 'elevation'`\n * stamped on the audit event; reads stay on the original `Vault`.\n *\n * The handle lazily checks its TTL on every operation, so a\n * forgotten `release()` cannot keep elevated writes alive past\n * `expiresAt` — the next call simply throws\n * {@link ElevationExpiredError}.\n *\n * Naming note: the issue's spec text used `elevated.session`\n * for this field; we name the field `handle` to avoid conflicting\n * with the codebase's existing `SessionToken` value type. The\n * semantics are unchanged.\n */\nexport class ElevatedHandle {\n /** Target tier this handle writes at. */\n readonly tier: number\n /** Audit string stamped on every cross-tier event. */\n readonly reason: string\n /** Absolute expiration in ms (Date.now()). */\n readonly expiresAt: number\n private released = false\n private readonly vault: Vault\n private readonly onRelease: () => void\n\n constructor(opts: {\n vault: Vault\n tier: number\n reason: string\n expiresAt: number\n onRelease: () => void\n }) {\n this.vault = opts.vault\n this.tier = opts.tier\n this.reason = opts.reason\n this.expiresAt = opts.expiresAt\n this.onRelease = opts.onRelease\n }\n\n /**\n * Scoped collection accessor. Returns a thin wrapper exposing the\n * single elevated operation (`put`). Reads, deletes, queries —\n * everything else — should go through the original `vault`'s\n * `collection(...)`, which keeps \"writes elevated, reads\n * unprivileged\" trivially true.\n */\n collection<T>(name: string): { put(id: string, record: T): Promise<void> } {\n // Don't gate the wrapper itself — just the operation. Adopters\n // commonly cache `const docs = elev.collection('docs')` and the\n // lazy-check still works correctly because assertActive runs at\n // every `put` call, against a fresh `Date.now()`.\n return {\n put: async (id: string, record: T): Promise<void> => {\n this.assertActive()\n await this.vault._elevatedPut<T>(name, id, record, this.tier, this.reason)\n },\n }\n }\n\n /**\n * Manually revert the elevation. Idempotent — calling twice (or\n * after the TTL expired) is a safe no-op. The vault's\n * active-elevation slot is cleared so a subsequent\n * `vault.elevate(...)` succeeds without throwing\n * {@link AlreadyElevatedError}.\n */\n async release(): Promise<void> {\n if (this.released) return\n this.released = true\n this.onRelease()\n }\n\n private assertActive(): void {\n if (this.released) {\n throw new ElevationExpiredError({ tier: this.tier, expiresAt: this.expiresAt })\n }\n if (Date.now() > this.expiresAt) {\n // Auto-release on first use past TTL so the vault's active\n // slot frees up without requiring the caller to think about\n // explicit release on expiry.\n this.released = true\n this.onRelease()\n throw new ElevationExpiredError({ tier: this.tier, expiresAt: this.expiresAt })\n }\n }\n}\n","import type { NoydbEventMap } from './types.js'\n\ntype EventHandler<T> = (data: T) => void\n\n/** Typed event emitter for NOYDB events. */\nexport class NoydbEventEmitter {\n private readonly listeners = new Map<string, Set<EventHandler<unknown>>>()\n\n on<K extends keyof NoydbEventMap>(\n event: K,\n handler: EventHandler<NoydbEventMap[K]>,\n ): void {\n let set = this.listeners.get(event as string)\n if (!set) {\n set = new Set()\n this.listeners.set(event as string, set)\n }\n set.add(handler as EventHandler<unknown>)\n }\n\n off<K extends keyof NoydbEventMap>(\n event: K,\n handler: EventHandler<NoydbEventMap[K]>,\n ): void {\n this.listeners.get(event as string)?.delete(handler as EventHandler<unknown>)\n }\n\n emit<K extends keyof NoydbEventMap>(event: K, data: NoydbEventMap[K]): void {\n const set = this.listeners.get(event as string)\n if (set) {\n for (const handler of set) {\n handler(data)\n }\n }\n }\n\n removeAllListeners(): void {\n this.listeners.clear()\n }\n}\n","/**\n * Tier-2 authenticator slot management — issue #11.\n *\n * Each slot independently wraps the SAME KEK under a method-specific\n * derived key (LUKS pattern). Enrolling adds a slot; removing drops\n * one. Both are constant-time keyring writes — no DEK re-keying.\n *\n * The crypto for each method lives in its `@noy-db/on-*` package\n * (`on-webauthn`, `on-oidc`, `on-password`); this module accepts the\n * package's `wrapped_kek` ciphertext + `meta` payload and persists it.\n *\n * @see docs/subsystems/session-tiers.md → Tier 2 — Authenticate\n *\n * @module\n */\nimport type { NoydbStore, KeyringAuthenticator } from '../types.js'\nimport { NoAccessError, ValidationError } from '../errors.js'\nimport type { UnlockedKeyring } from './keyring.js'\nimport { persistKeyring } from './keyring.js'\n\n/** Fields shared across both wrap-KEK and wrap-DEKs enroll inputs. */\ninterface EnrollAuthenticatorBase {\n readonly id: string\n readonly method: KeyringAuthenticator['method']\n /** Method-specific metadata (cred id, salt, …). */\n readonly meta: Record<string, unknown>\n /** Tier the active session held when enrolling. Defaults to 1. */\n readonly enrolled_via_tier?: 1 | 2\n}\n\n/** Wrap-KEK enroll input (WebAuthn, OIDC). */\nexport interface EnrollAuthenticatorWrappingKEKOptions extends EnrollAuthenticatorBase {\n /** Already-wrapped KEK ciphertext (base64) — produced by the on-* package. */\n readonly wrapped_kek: string\n readonly wrapKind?: 'kek'\n}\n\n/** Wrap-DEKs enroll input (password, future on-* using the unified wrap-DEKs primitive). */\nexport interface EnrollAuthenticatorWrappingDEKsOptions extends EnrollAuthenticatorBase {\n readonly wrapKind: 'deks'\n /** Base64 AES-GCM ciphertext of `{ deks: { collection: base64rawDek } }`. */\n readonly wrapped_deks: string\n /** Base64 AES-GCM IV used for the `wrapped_deks` ciphertext. */\n readonly iv: string\n}\n\n/** Discriminated union over the two enroll input shapes. */\nexport type EnrollAuthenticatorOptions =\n | EnrollAuthenticatorWrappingKEKOptions\n | EnrollAuthenticatorWrappingDEKsOptions\n\n/**\n * Append a new authenticator slot to the keyring file. Throws\n * `ValidationError` if a slot with the same id already exists — the\n * caller decides whether to remove + re-enroll.\n *\n * Accepts either wrap-KEK (WebAuthn, OIDC) or wrap-DEKs (password)\n * input. The variant is preserved verbatim into `KeyringAuthenticator`.\n */\nexport async function enrollAuthenticator(\n store: NoydbStore,\n vault: string,\n keyring: UnlockedKeyring,\n options: EnrollAuthenticatorOptions,\n): Promise<UnlockedKeyring> {\n const existing = keyring.authenticators.find((a) => a.id === options.id)\n if (existing) {\n throw new ValidationError(\n `enrollAuthenticator: slot id \"${options.id}\" already exists in vault \"${vault}\". ` +\n 'Remove the slot first or pick a unique id.',\n )\n }\n\n const base = {\n id: options.id,\n method: options.method,\n enrolled_at: new Date().toISOString(),\n enrolled_via_tier: options.enrolled_via_tier ?? 1,\n meta: options.meta,\n } as const\n\n const slot: KeyringAuthenticator = options.wrapKind === 'deks'\n ? {\n ...base,\n wrapKind: 'deks',\n wrapped_deks: options.wrapped_deks,\n iv: options.iv,\n }\n : {\n ...base,\n wrapped_kek: options.wrapped_kek,\n }\n\n const next = appendSlot(keyring, slot)\n await persistKeyring(store, vault, next)\n return next\n}\n\n/**\n * Caller payload for {@link updateAuthenticator} (#55). Mutates only\n * `meta` — the slot's id, method, and wrap material are immutable\n * through this primitive, preserving the anti-slot-swap guard.\n *\n * `meta` is **merged** at the top level: keys absent from the patch\n * are preserved, keys present overwrite. To clear a meta key, pass\n * `null` for that key explicitly. (Same semantics as #57's\n * `UserApi.updateMe`, scoped to this top-level merge — no recursion\n * into nested meta values.)\n */\nexport interface UpdateAuthenticatorOptions {\n readonly meta?: Record<string, unknown>\n}\n\n/**\n * Mutate a tier-2 authenticator slot's `meta` blob (slot rename,\n * label changes). The slot's `id`, `method`, and wrap material\n * (`wrapped_kek` for wrap-KEK; `wrapped_deks` + `iv` for wrap-DEKs)\n * are immutable through this entry point — the anti-slot-swap guard\n * is structural, not gate-driven, so even if the policy gate is\n * weakened a future caller cannot use this path to swap one slot's\n * crypto for another's.\n *\n * `meta` patch semantics:\n * - Top-level merge — absent keys preserved, present keys overwrite\n * - `null` value — delete that meta key\n * - Non-object values (string, number, boolean, array) — replace verbatim\n *\n * @throws `NoAccessError` when no slot with the given id exists.\n * @throws `ValidationError` when no patch field is provided.\n *\n * @see #55\n */\nexport async function updateAuthenticator(\n store: NoydbStore,\n vault: string,\n keyring: UnlockedKeyring,\n slotId: string,\n options: UpdateAuthenticatorOptions,\n): Promise<UnlockedKeyring> {\n if (options.meta === undefined) {\n throw new ValidationError(\n `updateAuthenticator: at least one of meta must be provided ` +\n `(slotId: \"${slotId}\").`,\n )\n }\n\n const idx = keyring.authenticators.findIndex((a) => a.id === slotId)\n if (idx === -1) {\n throw new NoAccessError(\n `updateAuthenticator: slot \"${slotId}\" not found in vault \"${vault}\".`,\n )\n }\n const existing = keyring.authenticators[idx]!\n\n // Merge at the top level. Absent keys preserved (same as #57's\n // updateMe semantics, but non-recursive — meta is a flat label\n // bag in practice, no consumer nests it).\n const mergedMeta: Record<string, unknown> = { ...existing.meta }\n for (const [k, v] of Object.entries(options.meta)) {\n if (v === undefined) continue // skip\n if (v === null) {\n delete mergedMeta[k]\n continue\n }\n mergedMeta[k] = v\n }\n\n // Reconstruct the slot preserving wrapKind discrimination. The\n // immutable fields (id, method, wrapped_kek / wrapped_deks + iv,\n // enrolled_at, enrolled_via_tier) all flow through ...existing.\n const next: KeyringAuthenticator = { ...existing, meta: mergedMeta }\n const nextSlots = [...keyring.authenticators]\n nextSlots[idx] = next\n\n const nextKeyring: UnlockedKeyring = {\n ...keyring,\n authenticators: nextSlots,\n }\n await persistKeyring(store, vault, nextKeyring)\n return nextKeyring\n}\n\n/**\n * Drop a slot by id. No-op if the slot doesn't exist (idempotent —\n * removing a non-existent slot is a recoverable retry, not an error).\n */\nexport async function removeAuthenticator(\n store: NoydbStore,\n vault: string,\n keyring: UnlockedKeyring,\n slotId: string,\n): Promise<UnlockedKeyring> {\n const filtered = keyring.authenticators.filter((a) => a.id !== slotId)\n if (filtered.length === keyring.authenticators.length) {\n return keyring // idempotent — nothing to do\n }\n const next: UnlockedKeyring = {\n ...keyring,\n authenticators: filtered,\n }\n await persistKeyring(store, vault, next)\n return next\n}\n\n/**\n * Look up a slot by id. Returns `undefined` when no slot matches.\n * Used by tier-2 unlock dispatchers to fetch the wrapped KEK + meta\n * before invoking the method-specific verifier.\n */\nexport function findAuthenticator(\n keyring: UnlockedKeyring,\n slotId: string,\n): KeyringAuthenticator | undefined {\n return keyring.authenticators.find((a) => a.id === slotId)\n}\n\nfunction appendSlot(\n keyring: UnlockedKeyring,\n slot: KeyringAuthenticator,\n): UnlockedKeyring {\n return {\n ...keyring,\n authenticators: [...keyring.authenticators, slot],\n }\n}\n","/**\n * Per-vault tier-3 (PIN / quick-resume) state — issue #11.\n *\n * The hub holds a `PinResumeState`-shaped record in memory, keyed by\n * vault. `enrollUnlock` populates it; `unlockViaPin` consumes it via\n * `@noy-db/on-pin`'s `resumePin`. The cached state is wiped when the\n * idle timer fires or `db.close()` is called.\n *\n * Importantly, this module does NOT depend on `@noy-db/on-pin` — the\n * caller passes the already-built state in. That keeps the hub's\n * `peerDependencies` empty for tier-3 and lets developers swap the\n * primitive (e.g. an OS biometric in place of a PIN).\n *\n * @module\n */\n\n/**\n * Opaque `PinResumeState`-compatible record. Mirrored from\n * `@noy-db/on-pin/PinResumeState`. The hub treats the contents as\n * a black box.\n */\nexport interface QuickUnlockState {\n readonly _noydb_on_pin: 1\n readonly salt: string\n readonly iv: string\n readonly wrappedKeyring: string\n readonly expiresAt: string\n readonly maxAttempts: number\n attempts: number\n}\n\n/** In-memory store for tier-3 unlock state, keyed by vault. */\nexport class QuickUnlockStore {\n private readonly states = new Map<string, QuickUnlockState>()\n private readonly timers = new Map<string, ReturnType<typeof setTimeout>>()\n\n /**\n * Register a quick-unlock state for a vault. Replaces any existing\n * state. Schedules an automatic clear when the state's `expiresAt`\n * elapses.\n */\n set(vault: string, state: QuickUnlockState): void {\n this.clearTimer(vault)\n this.states.set(vault, state)\n const ttl = new Date(state.expiresAt).getTime() - Date.now()\n if (ttl > 0) {\n const timer = setTimeout(() => this.delete(vault), ttl)\n this.timers.set(vault, timer)\n }\n }\n\n /** Read the state for a vault. Returns undefined when none is registered. */\n get(vault: string): QuickUnlockState | undefined {\n return this.states.get(vault)\n }\n\n /** Drop the state for a vault. Cancels the auto-clear timer. */\n delete(vault: string): void {\n this.clearTimer(vault)\n this.states.delete(vault)\n }\n\n /** Drop every cached state. Called on `db.close()`. */\n clear(): void {\n for (const vault of this.states.keys()) {\n this.clearTimer(vault)\n }\n this.states.clear()\n }\n\n private clearTimer(vault: string): void {\n const t = this.timers.get(vault)\n if (t) clearTimeout(t)\n this.timers.delete(vault)\n }\n}\n","/**\n * Strategy seam for the optional multi-record transaction subsystem.\n * `runTransaction` is only reachable through `withTransactions()`\n * exported from `@noy-db/hub/tx`. Consumers who don't use\n * `db.transaction(fn)` ship none of the ~288 LOC.\n *\n * @internal\n */\n\nimport type { Noydb } from '../noydb.js'\nimport type { TxContext, AmendmentTxOptions } from './transaction.js'\n\n/**\n * @internal\n */\nexport interface TxStrategy {\n runTransaction<T>(\n db: Noydb,\n fn: (tx: TxContext) => Promise<T> | T,\n options?: AmendmentTxOptions,\n ): Promise<T>\n}\n\nconst NOT_ENABLED = new Error(\n 'Multi-record transactions require the tx strategy. Import ' +\n '`{ withTransactions }` from \"@noy-db/hub/tx\" and pass it to ' +\n '`createNoydb({ txStrategy: withTransactions() })`.',\n)\n\n/**\n * @internal\n */\nexport const NO_TX: TxStrategy = {\n async runTransaction() { throw NOT_ENABLED },\n}\n","/**\n * Sync scheduling policy.\n *\n * ## What it controls\n *\n * A {@link SyncPolicy} has two halves:\n * - **push** ({@link PushPolicy}) — when dirty local writes are sent to the remote.\n * - **pull** ({@link PullPolicy}) — when the remote is polled for new data.\n *\n * ## Choosing a policy\n *\n * The right policy depends on the backend's operational characteristics:\n *\n * | Backend type | Recommended policy |\n * |---|---|\n * | Per-record (DynamoDB, S3, IDB) | {@link INDEXED_STORE_POLICY} — `on-change` push, `manual` pull |\n * | Bundle (Drive, WebDAV, Git) | {@link BUNDLE_STORE_POLICY} — `debounce` push, `interval` pull |\n *\n * Consumers can override via `createNoydb({ syncPolicy: { ... } })`:\n *\n * ```ts\n * const db = await createNoydb({\n * store: jsonFile({ dir: './data' }),\n * syncPolicy: {\n * push: { mode: 'debounce', debounceMs: 5_000 },\n * pull: { mode: 'on-focus' },\n * },\n * })\n * ```\n *\n * ## Scheduler lifecycle\n *\n * {@link SyncScheduler} owns all timers, debounce logic, and browser lifecycle\n * hooks (`visibilitychange`, `pagehide`, `beforeExit`). Call `scheduler.start()`\n * after opening a vault and `scheduler.stop()` when closing it. The scheduler\n * delegates actual push/pull work to {@link SyncSchedulerCallbacks} provided\n * by the {@link SyncEngine}.\n *\n * @module\n */\n\n// ─── Policy types ───────────────────────────────────────────────────────\n\n/**\n * When push operations are triggered automatically.\n *\n * - `'manual'` — only on explicit `sync.push()` calls.\n * - `'on-change'` — immediately after every local write (respecting `minIntervalMs`).\n * - `'debounce'` — after `debounceMs` of inactivity following a write.\n * - `'interval'` — on a fixed timer regardless of writes.\n */\nexport type PushMode = 'manual' | 'on-change' | 'debounce' | 'interval'\n\n/**\n * When pull operations are triggered automatically.\n *\n * - `'manual'` — only on explicit `sync.pull()` calls.\n * - `'interval'` — on a fixed `intervalMs` timer.\n * - `'on-focus'` — when the browser tab regains visibility.\n */\nexport type PullMode = 'manual' | 'interval' | 'on-focus'\n\n/**\n * Push half of a sync policy. Controls the trigger mode and timing guards\n * for outbound sync operations.\n */\nexport interface PushPolicy {\n /** Push trigger mode. */\n readonly mode: PushMode\n /** Debounce delay in ms. Only used when `mode: 'debounce'`. Default: 30_000. */\n readonly debounceMs?: number\n /** Interval in ms between automatic pushes. Used by `'interval'` and as floor for `'debounce'`. */\n readonly intervalMs?: number\n /**\n * Hard floor between pushes regardless of mode. Prevents burst writes\n * from hammering the remote. Default: 0 (no floor).\n */\n readonly minIntervalMs?: number\n /**\n * Force a push on page unload (`pagehide` / `visibilitychange → hidden`)\n * in browsers, `beforeExit` in Node. Default: true for non-manual modes.\n */\n readonly onUnload?: boolean\n}\n\n/**\n * Pull half of a sync policy. Controls when and how often inbound sync\n * operations are triggered.\n */\nexport interface PullPolicy {\n /** Pull trigger mode. */\n readonly mode: PullMode\n /** Interval in ms between automatic pulls. Used by `'interval'` mode. Default: 60_000. */\n readonly intervalMs?: number\n}\n\n/**\n * Combined push + pull sync scheduling policy for a vault.\n *\n * Pass via `createNoydb({ syncPolicy })` to override the default policy\n * derived from the active store type. Pre-built defaults are available\n * as `INDEXED_STORE_POLICY` and `BUNDLE_STORE_POLICY`.\n */\nexport interface SyncPolicy {\n readonly push: PushPolicy\n readonly pull: PullPolicy\n}\n\n// ─── Default policies by store category ─────────────────────────────────\n\n/** Default for per-record stores (DynamoDB, S3, file, IDB). */\nexport const INDEXED_STORE_POLICY: SyncPolicy = {\n push: { mode: 'on-change', minIntervalMs: 0, onUnload: true },\n pull: { mode: 'manual' },\n}\n\n/** Default for bundle stores (Drive, WebDAV, Git). */\nexport const BUNDLE_STORE_POLICY: SyncPolicy = {\n push: { mode: 'debounce', debounceMs: 30_000, minIntervalMs: 120_000, onUnload: true },\n pull: { mode: 'interval', intervalMs: 60_000 },\n}\n\n// ─── Sync scheduler ─────────────────────────────────────────────────────\n\n/**\n * Current operational state of the `SyncScheduler`.\n *\n * - `'idle'` — no pending or active sync operations.\n * - `'pending'` — local writes are queued, waiting for debounce/interval to fire.\n * - `'pushing'` — push in progress.\n * - `'pulling'` — pull in progress.\n * - `'error'` — last sync operation failed; `lastError` holds the cause.\n */\nexport type SyncSchedulerState = 'idle' | 'pending' | 'pushing' | 'pulling' | 'error'\n\n/**\n * Snapshot of the sync scheduler's state, returned by `SyncScheduler.status`.\n * Safe to expose in a reactive UI status indicator.\n */\nexport interface SyncSchedulerStatus {\n readonly state: SyncSchedulerState\n readonly lastPushAt: string | null\n readonly lastPullAt: string | null\n readonly lastError: Error | null\n readonly pendingWrites: number\n}\n\n/**\n * Callbacks injected into `SyncScheduler` by the SyncEngine.\n *\n * The scheduler owns timers and lifecycle hooks; it delegates actual push/pull\n * work to these callbacks to stay decoupled from the sync implementation.\n */\nexport interface SyncSchedulerCallbacks {\n push(): Promise<void>\n pull(): Promise<void>\n getDirtyCount(): number\n}\n\n/**\n * Manages sync timing according to a `SyncPolicy`.\n *\n * The scheduler owns all timers and lifecycle hooks. It delegates actual\n * push/pull work to callbacks provided by the SyncEngine.\n */\nexport class SyncScheduler {\n private readonly policy: SyncPolicy\n private readonly callbacks: SyncSchedulerCallbacks\n\n private _state: SyncSchedulerState = 'idle'\n private _lastPushAt: string | null = null\n private _lastPullAt: string | null = null\n private _lastError: Error | null = null\n private _lastPushTime = 0 // monotonic ms for minIntervalMs enforcement\n\n // Timers\n private debounceTimer: ReturnType<typeof setTimeout> | null = null\n private pushIntervalTimer: ReturnType<typeof setInterval> | null = null\n private pullIntervalTimer: ReturnType<typeof setInterval> | null = null\n\n // Bound handlers for cleanup\n private readonly boundOnVisibilityChange: (() => void) | null = null\n private readonly boundOnBeforeExit: (() => void) | null = null\n private readonly boundOnPageHide: (() => void) | null = null\n\n private started = false\n\n constructor(policy: SyncPolicy, callbacks: SyncSchedulerCallbacks) {\n this.policy = policy\n this.callbacks = callbacks\n\n // Pre-bind handlers\n if (this.shouldRegisterUnload()) {\n this.boundOnVisibilityChange = this.handleVisibilityChange.bind(this)\n this.boundOnPageHide = this.handlePageHide.bind(this)\n this.boundOnBeforeExit = this.handleBeforeExit.bind(this)\n }\n }\n\n /** Current scheduler status snapshot. */\n get status(): SyncSchedulerStatus {\n return {\n state: this._state,\n lastPushAt: this._lastPushAt,\n lastPullAt: this._lastPullAt,\n lastError: this._lastError,\n pendingWrites: this.callbacks.getDirtyCount(),\n }\n }\n\n /** Start the scheduler — registers timers, event listeners. */\n start(): void {\n if (this.started) return\n this.started = true\n\n // Push: interval mode\n if (this.policy.push.mode === 'interval' && this.policy.push.intervalMs) {\n this.pushIntervalTimer = setInterval(() => {\n void this.executePush()\n }, this.policy.push.intervalMs)\n }\n\n // Pull: interval mode\n if (this.policy.pull.mode === 'interval' && this.policy.pull.intervalMs) {\n this.pullIntervalTimer = setInterval(() => {\n void this.executePull()\n }, this.policy.pull.intervalMs)\n }\n\n // Pull: on-focus mode\n if (this.policy.pull.mode === 'on-focus' && typeof document !== 'undefined') {\n document.addEventListener('visibilitychange', this.handleFocusPull)\n }\n\n // Unload hooks\n if (this.shouldRegisterUnload()) {\n if (typeof document !== 'undefined' && this.boundOnVisibilityChange) {\n document.addEventListener('visibilitychange', this.boundOnVisibilityChange)\n }\n if (typeof globalThis.addEventListener === 'function' && this.boundOnPageHide) {\n globalThis.addEventListener('pagehide', this.boundOnPageHide)\n }\n if (typeof process !== 'undefined' && this.boundOnBeforeExit) {\n process.on('beforeExit', this.boundOnBeforeExit)\n }\n }\n }\n\n /** Stop the scheduler — clears timers, removes event listeners. */\n stop(): void {\n if (!this.started) return\n this.started = false\n\n if (this.debounceTimer) {\n clearTimeout(this.debounceTimer)\n this.debounceTimer = null\n }\n if (this.pushIntervalTimer) {\n clearInterval(this.pushIntervalTimer)\n this.pushIntervalTimer = null\n }\n if (this.pullIntervalTimer) {\n clearInterval(this.pullIntervalTimer)\n this.pullIntervalTimer = null\n }\n\n // Focus pull\n if (this.policy.pull.mode === 'on-focus' && typeof document !== 'undefined') {\n document.removeEventListener('visibilitychange', this.handleFocusPull)\n }\n\n // Unload hooks\n if (typeof document !== 'undefined' && this.boundOnVisibilityChange) {\n document.removeEventListener('visibilitychange', this.boundOnVisibilityChange)\n }\n if (typeof globalThis.removeEventListener === 'function' && this.boundOnPageHide) {\n globalThis.removeEventListener('pagehide', this.boundOnPageHide)\n }\n if (typeof process !== 'undefined' && this.boundOnBeforeExit) {\n process.removeListener('beforeExit', this.boundOnBeforeExit)\n }\n }\n\n /**\n * Notify the scheduler that a local write occurred.\n * For `on-change` mode: triggers immediate push (respecting minIntervalMs).\n * For `debounce` mode: resets the debounce timer.\n * For `manual` / `interval`: no-op.\n */\n notifyChange(): void {\n if (!this.started) return\n\n if (this.policy.push.mode === 'on-change') {\n void this.executePush()\n } else if (this.policy.push.mode === 'debounce') {\n this.resetDebounce()\n }\n }\n\n /** Force an immediate push, bypassing the scheduler. */\n async forcePush(): Promise<void> {\n await this.executePush()\n }\n\n /** Force an immediate pull, bypassing the scheduler. */\n async forcePull(): Promise<void> {\n await this.executePull()\n }\n\n // ─── Internal ─────────────────────────────────────────────────────\n\n private async executePush(): Promise<void> {\n if (this._state === 'pushing') return // already in progress\n\n // minIntervalMs enforcement\n const minInterval = this.policy.push.minIntervalMs ?? 0\n if (minInterval > 0) {\n const elapsed = Date.now() - this._lastPushTime\n if (elapsed < minInterval) {\n // Schedule for later if debounce mode\n if (this.policy.push.mode === 'debounce') {\n this.scheduleDebounce(minInterval - elapsed)\n }\n return\n }\n }\n\n // Nothing to push\n if (this.callbacks.getDirtyCount() === 0) {\n this._state = 'idle'\n return\n }\n\n this._state = 'pushing'\n try {\n await this.callbacks.push()\n this._lastPushAt = new Date().toISOString()\n this._lastPushTime = Date.now()\n this._lastError = null\n this._state = this.callbacks.getDirtyCount() > 0 ? 'pending' : 'idle'\n } catch (err) {\n this._lastError = err instanceof Error ? err : new Error(String(err))\n this._state = 'error'\n }\n }\n\n private async executePull(): Promise<void> {\n if (this._state === 'pulling') return\n\n const previousState = this._state\n this._state = 'pulling'\n try {\n await this.callbacks.pull()\n this._lastPullAt = new Date().toISOString()\n this._lastError = null\n this._state = previousState === 'pending' ? 'pending' : 'idle'\n } catch (err) {\n this._lastError = err instanceof Error ? err : new Error(String(err))\n this._state = 'error'\n }\n }\n\n private resetDebounce(): void {\n if (this.debounceTimer) clearTimeout(this.debounceTimer)\n const ms = this.policy.push.debounceMs ?? 30_000\n this._state = 'pending'\n this.scheduleDebounce(ms)\n }\n\n private scheduleDebounce(ms: number): void {\n if (this.debounceTimer) clearTimeout(this.debounceTimer)\n this.debounceTimer = setTimeout(() => {\n this.debounceTimer = null\n void this.executePush()\n }, ms)\n }\n\n private shouldRegisterUnload(): boolean {\n const onUnload = this.policy.push.onUnload\n if (onUnload !== undefined) return onUnload\n return this.policy.push.mode !== 'manual'\n }\n\n // ─── Event handlers ───────────────────────────────────────────────\n\n private handleVisibilityChange(): void {\n if (typeof document !== 'undefined' && document.visibilityState === 'hidden') {\n this.fireUnloadPush()\n }\n }\n\n private handlePageHide(): void {\n this.fireUnloadPush()\n }\n\n private handleBeforeExit(): void {\n this.fireUnloadPush()\n }\n\n private handleFocusPull = (): void => {\n if (typeof document !== 'undefined' && document.visibilityState === 'visible') {\n void this.executePull()\n }\n }\n\n private fireUnloadPush(): void {\n if (this.callbacks.getDirtyCount() === 0) return\n // Best-effort synchronous-ish push on unload\n void this.callbacks.push().catch(() => {})\n }\n}\n","/**\n * Strategy seam for the optional session-policy subsystem. Core\n * imports `SessionStrategy` type-only + `NO_SESSION` stub; real\n * `validateSessionPolicy`, `createEnforcer`, and `revokeAllSessions`\n * are only reachable via `withSession()` in `./active.ts`.\n *\n * Solo apps that never set `sessionPolicy` and never issue a session\n * token ship none of the ~495 LOC of policy + token machinery\n * (session-policy.ts + session.ts). Dev-unlock (~299 LOC) is a\n * separate import already tree-shake-friendly via direct named\n * imports.\n *\n * Behavior under NO_SESSION:\n *\n * - **validateSessionPolicy** — throws when called. Only fires if\n * `createNoydb({ sessionPolicy })` was passed; if you set a policy\n * you must opt into the strategy.\n * - **createEnforcer** — throws. Same gate.\n * - **revokeAllSessions** — silent no-op. Called unconditionally on\n * `db.close()`; without the strategy the global session registry\n * never recorded anything, so the no-op is correct.\n *\n * @internal\n */\n\nimport type { SessionPolicy } from '../types.js'\nimport type { PolicyEnforcer, PolicyEnforcerOptions } from './session-policy.js'\n\n/**\n * @internal\n */\nexport interface SessionStrategy {\n validateSessionPolicy(policy: SessionPolicy): void\n createEnforcer(opts: PolicyEnforcerOptions): PolicyEnforcer\n revokeAllSessions(): void\n}\n\nfunction notEnabled(op: string): Error {\n return new Error(\n `${op} requires the session strategy. Import ` +\n '`{ withSession }` from \"@noy-db/hub/session\" and pass it to ' +\n '`createNoydb({ sessionStrategy: withSession() })`.',\n )\n}\n\n/**\n * No-session stub. Policy validation + enforcer construction throw\n * with an actionable pointer; global session revocation is a silent\n * no-op (the registry was never populated).\n *\n * @internal\n */\nexport const NO_SESSION: SessionStrategy = {\n validateSessionPolicy() { throw notEnabled('sessionPolicy') },\n createEnforcer() { throw notEnabled('session policy enforcement') },\n revokeAllSessions() {},\n}\n","import type { VaultPolicy } from './types.js'\n\n/**\n * Default policy for personal vaults and SMB deployments — the gates\n * that need an off-device factor get one (TOTP / email-OTP / paper\n * recovery), the rest take a tier-1 unlock alone. Tier-3 (PIN) is the\n * floor only for `rotate-unlock` because that's the\n * \"change my PIN\" flow.\n *\n * The unspecified gates (e.g. `view-user-auth`) inherit the engine\n * default of `{ enabled: false, minTier: 1 }` — they fail closed.\n *\n * @see docs/subsystems/session-tiers.md → Built-in gates\n */\nexport const PERSONAL_POLICY: VaultPolicy = Object.freeze({\n passphrase: {\n minWords: 6,\n minWordLength: 3,\n rejectRepeatedAdjacent: true,\n },\n gates: {\n 'rotate-passphrase': {\n minTier: 1,\n // Any second factor satisfies the gate — off-device kinds (TOTP,\n // email-OTP, paper recovery, roaming WebAuthn) are the strongest;\n // platform-bound kinds (platform WebAuthn, password, PIN) are\n // accepted because requiring \"something off-device\" is overkill\n // for personal/SMB threat models. Consumers needing the off-device\n // guarantee should use STRICT_POLICY or override this gate.\n factors: [{\n anyOf: [\n 'totp', 'email-otp', 'recovery',\n 'webauthn-roaming', 'webauthn-platform',\n 'password', 'pin',\n ],\n }],\n },\n 'recover-passphrase': {\n minTier: 1,\n enabled: true,\n },\n // rotate-recovery (#121): deliberate paper-sheet regeneration\n // when the user remembers their passphrase. PERSONAL matches the\n // pre-#121 low-level flow's bar — knowing the passphrase is enough.\n 'rotate-recovery': { minTier: 1 },\n 'enroll-authenticator': { minTier: 1 },\n 'remove-authenticator': { minTier: 1 },\n // update-authenticator: meta-only mutation (slot rename, label\n // changes). Symmetric with enroll/remove under PERSONAL — tier-1\n // unlock alone. The structural anti-slot-swap guard inside the\n // implementation enforces wrap-material/id/method immutability\n // regardless of this gate's settings.\n 'update-authenticator': { minTier: 1 },\n 'rotate-unlock': { minTier: 2 },\n 'enroll-user': { minTier: 1 },\n 'revoke-user': { minTier: 1 },\n // Peer-recovery is a high-trust intentional op — co-owners\n // recovering each other should not need an off-device factor in\n // the personal/SMB threat model (the partner is already vetted by\n // virtue of being a co-owner). Tier-1 unlock is the floor; the\n // STRICT preset adds a recovery/email-OTP requirement.\n 'peer-recover-user': { minTier: 1 },\n // update-user: post-grant identity mutation (role/displayName/\n // permissions). PERSONAL_POLICY treats this on par with enroll-user\n // / revoke-user — tier-1 unlock alone. The role-elevation guard\n // inside the implementation is the structural backstop that this\n // gate's settings cannot weaken.\n 'update-user': { minTier: 1 },\n 'export-bundle': { minTier: 1 },\n 'export-plaintext': {\n minTier: 1,\n factors: [{ anyOf: ['totp', 'email-otp'] }],\n },\n 'view-user-auth': {\n minTier: 1,\n enabled: false,\n },\n // ─── User envelope gates (#22) ────────────────────────────────────\n // edit-own-profile: tier 3 floor — any active session can edit their\n // own profile/preferences. Tightening to require a TOTP for\n // profile changes is a one-line override.\n // view-team-profiles: tier 2 floor — an authenticated session can\n // read teammates' profiles (display names, avatars, locales).\n // Setting `enabled: false` makes vault.user.list() return only\n // self (privacy-strict opt-out).\n 'edit-own-profile': { minTier: 3 },\n 'view-team-profiles': { minTier: 2 },\n },\n}) as VaultPolicy\n\n/**\n * Strict policy for regulated deployments and shared workstations —\n * raises the phrase floor to 8 words, demands two distinct factors for\n * exports, and blocks export-on-shared-device. Use as a base for\n * `policy: { ...STRICT_POLICY, gates: { ...STRICT_POLICY.gates, ... } }`\n * tweaks.\n */\nexport const STRICT_POLICY: VaultPolicy = Object.freeze({\n passphrase: {\n minWords: 8,\n minWordLength: 3,\n rejectRepeatedAdjacent: true,\n },\n gates: {\n 'rotate-passphrase': {\n minTier: 1,\n factors: [{ anyOf: ['totp', 'email-otp', 'recovery'], count: 2 }],\n },\n 'recover-passphrase': {\n minTier: 1,\n enabled: true,\n },\n // rotate-recovery (#121): STRICT requires an off-device factor —\n // rotating recovery is an off-site-trust event; a stolen unlocked\n // laptop must not be able to silently mint a new sheet for the\n // attacker. Matches the `peer-recover-user` STRICT default.\n 'rotate-recovery': {\n minTier: 1,\n factors: [{ anyOf: ['totp', 'email-otp', 'webauthn-roaming'] }],\n },\n 'enroll-authenticator': {\n minTier: 1,\n factors: [{ anyOf: ['totp', 'email-otp'] }],\n },\n 'remove-authenticator': {\n minTier: 1,\n factors: [{ anyOf: ['totp', 'email-otp'] }],\n },\n // STRICT update-authenticator: same factor floor as enroll/remove.\n // Even though meta changes don't touch wrap material, a malicious\n // rename could mislead the user about which device a slot\n // corresponds to (\"MacBook Touch ID\" → \"iPhone Touch ID\" on a\n // shared workstation). STRICT requires a fresh factor proof.\n 'update-authenticator': {\n minTier: 1,\n factors: [{ anyOf: ['totp', 'email-otp'] }],\n },\n 'rotate-unlock': { minTier: 1 },\n 'enroll-user': {\n minTier: 1,\n factors: [{ anyOf: ['totp', 'email-otp'] }],\n },\n 'revoke-user': {\n minTier: 1,\n factors: [{ anyOf: ['totp', 'email-otp'] }],\n },\n // STRICT peer-recovery: the issuer must present a recovery code\n // OR a fresh off-device second factor at the moment of recovery.\n // This binds the high-trust operation to a verifiable proof\n // (recovery sheet photographed by an attacker won't suffice —\n // they'd also need tier-1 unlock first; this gate is the freshness\n // binding on top). Roaming WebAuthn (YubiKey-class hardware key)\n // accepted; platform-bound kinds (Touch ID, password, PIN)\n // intentionally excluded under STRICT because they don't survive\n // device theft — the off-device requirement is the whole point.\n 'peer-recover-user': {\n minTier: 1,\n factors: [{ anyOf: ['recovery', 'totp', 'email-otp', 'webauthn-roaming'] }],\n },\n // STRICT update-user: matches the enroll-user / revoke-user shape\n // (off-device factor required). Update-user is admin-shaped — it\n // mutates someone else's role/permissions; STRICT requires a fresh\n // off-device factor proof so the operator affirmatively re-asserts\n // identity at the moment of mutation. Platform-bound factors\n // (Touch ID / password / PIN) intentionally excluded: same logic as\n // peer-recover-user — the off-device requirement is the whole\n // point under STRICT.\n 'update-user': {\n minTier: 1,\n factors: [{ anyOf: ['totp', 'email-otp'] }],\n },\n 'export-bundle': {\n minTier: 1,\n factors: [{ anyOf: ['totp', 'email-otp'] }],\n warn: { sharedDevice: 'block' },\n },\n 'export-plaintext': {\n minTier: 1,\n factors: [{ anyOf: ['totp', 'email-otp'], count: 2 }],\n warn: { sharedDevice: 'block' },\n },\n 'view-user-auth': {\n minTier: 1,\n enabled: false,\n },\n // ─── User envelope gates (#22) ────────────────────────────────────\n // STRICT: profile edits require a TOTP/email-OTP factor (typical\n // shared-workstation hardening — your name/avatar shouldn't change\n // without a fresh second-factor proof).\n 'edit-own-profile': {\n minTier: 2,\n factors: [{ anyOf: ['totp', 'email-otp'] }],\n },\n 'view-team-profiles': { minTier: 2 },\n },\n}) as VaultPolicy\n\n/**\n * Merge a developer override onto a preset. Unspecified gates inherit;\n * specified gates fully replace the preset's entry for that gate.\n *\n * Example:\n *\n * ```ts\n * mergePolicy(PERSONAL_POLICY, {\n * gates: {\n * 'app:approve-large-payment': { minTier: 2, factors: [{ anyOf: ['totp'] }] },\n * },\n * })\n * // → PERSONAL_POLICY plus the new app gate; existing gates intact.\n * ```\n */\nexport function mergePolicy(\n base: VaultPolicy,\n override?: Partial<VaultPolicy>,\n): VaultPolicy {\n if (!override) return base\n const passphrase = override.passphrase ?? base.passphrase\n return {\n ...(passphrase !== undefined ? { passphrase } : {}),\n gates: {\n ...base.gates,\n ...(override.gates ?? {}),\n },\n }\n}\n","/**\n * Policy gate engine — the {@link checkGate} entry point.\n *\n * Given a configured {@link VaultPolicy}, an active session tier, and\n * the factor proofs an actor is presenting, decide whether the gate\n * permits the action. On denial, throws {@link PolicyDeniedError} with\n * a stable {@link PolicyDenyReason} so consumers can branch in error\n * UIs.\n *\n * @see docs/subsystems/session-tiers.md → checkGate() API\n *\n * @module\n */\nimport { PolicyDeniedError, type PolicyDenyReason } from './errors.js'\nimport type {\n ActiveTier,\n FactorProof,\n GateName,\n GatePolicy,\n VaultPolicy,\n FactorRequirement,\n} from './types.js'\n\n/** Default freshness window — 5 minutes. */\nexport const DEFAULT_FRESHNESS_MS = 5 * 60 * 1000\n\n/** Caller-supplied context for one `checkGate` invocation. */\nexport interface CheckGateContext {\n /** Tier the active session currently holds. */\n readonly activeTier: ActiveTier\n /** Proofs the actor is presenting for this gate. */\n readonly factors?: ReadonlyArray<FactorProof>\n /**\n * If the host knows the actor is on a shared device, set this to\n * `true` so the engine can apply `warn.sharedDevice` rules. Defaults\n * to `false`.\n */\n readonly sharedDevice?: boolean\n /**\n * Override `now()` for tests. Defaults to `Date.now()`.\n * @internal\n */\n readonly now?: number\n}\n\n/**\n * Decide whether `gate` permits the action under `context`. Throws\n * {@link PolicyDeniedError} on denial; resolves with `void` on success.\n *\n * Lookup rules:\n * - **Built-in gates** without a configured policy fail closed\n * (`enabled: false`).\n * - **App-defined gates** (`app:*`) without a configured policy are\n * treated as no-op (allow). The developer registered the policy if\n * they wanted enforcement; absence means the gate is informational.\n */\nexport async function checkGate(\n policy: VaultPolicy,\n gate: GateName,\n context: CheckGateContext,\n): Promise<void> {\n const configured = policy.gates[gate]\n if (!configured) {\n if (gate.startsWith('app:')) {\n // Custom app gate without a policy — the developer hasn't\n // registered one; engine treats it as an unenforced label.\n return\n }\n // Built-in gate without a policy — fail closed.\n throw deny(gate, 'disabled', { minTier: 1, enabled: false })\n }\n\n if (configured.enabled === false) {\n throw deny(gate, 'disabled', configured)\n }\n\n // Tier check first — cheap and a hard prerequisite.\n if (context.activeTier > configured.minTier) {\n // Higher number is a LOWER tier in this model (1 is most privileged).\n throw deny(gate, 'insufficient-tier', configured)\n }\n\n // Factor checks — every requirement entry must be satisfied.\n if (configured.factors && configured.factors.length > 0) {\n const presented = context.factors ?? []\n const now = context.now ?? Date.now()\n for (const requirement of configured.factors) {\n const matches = countMatchingFactors(presented, requirement, now)\n const need = requirement.count ?? 1\n if (matches.fresh < need) {\n if (matches.totalKindMatches < need) {\n throw deny(gate, 'missing-factor', configured)\n }\n // Some matched the kind list but not the freshness window.\n throw deny(gate, 'stale-proof', configured)\n }\n }\n }\n\n // Soft signals — only `'block'` raises here.\n if (configured.warn?.sharedDevice === 'block' && context.sharedDevice === true) {\n throw deny(gate, 'shared-device-blocked', configured)\n }\n}\n\n/**\n * Same as {@link checkGate} but returns a structured verdict instead\n * of throwing. Useful when an error UI wants to show the user\n * \"you'll need TOTP plus a recovery code to do that\" without first\n * triggering the action.\n */\nexport async function describeGate(\n policy: VaultPolicy,\n gate: GateName,\n context: CheckGateContext,\n): Promise<{ ok: true } | { ok: false; reason: PolicyDenyReason; required: GatePolicy }> {\n try {\n await checkGate(policy, gate, context)\n return { ok: true }\n } catch (err) {\n if (err instanceof PolicyDeniedError) {\n return { ok: false, reason: err.reason, required: err.required }\n }\n throw err\n }\n}\n\nfunction countMatchingFactors(\n presented: ReadonlyArray<FactorProof>,\n requirement: FactorRequirement,\n now: number,\n): { totalKindMatches: number; fresh: number } {\n const freshnessMs = requirement.freshnessMs ?? DEFAULT_FRESHNESS_MS\n let totalKindMatches = 0\n let fresh = 0\n for (const proof of presented) {\n if (!requirement.anyOf.includes(proof.kind)) continue\n totalKindMatches += 1\n const minted = proof.mintedAt ? Date.parse(proof.mintedAt) : now\n if (Number.isFinite(minted) && now - minted <= freshnessMs) {\n fresh += 1\n }\n }\n return { totalKindMatches, fresh }\n}\n\nfunction deny(gate: GateName, reason: PolicyDenyReason, required: GatePolicy): PolicyDeniedError {\n return new PolicyDeniedError(gate, reason, required)\n}\n","/**\n * Policy gate DSL — barrel export for the `@noy-db/hub/policy` surface.\n *\n * @see docs/subsystems/session-tiers.md → Policy gates DSL\n *\n * @module\n */\nexport type {\n FactorKind,\n FactorRequirement,\n FactorProof,\n FactorProofBundle,\n GatePolicy,\n WarningRules,\n GateName,\n BuiltInGateName,\n VaultPolicy,\n ActiveTier,\n} from './types.js'\n\nexport {\n PolicyDeniedError,\n RecoveryNotEnrolledError,\n RecoveryProfileNotImplementedError,\n ManagedRecoveryNotEnrolledError,\n} from './errors.js'\nexport type { PolicyDenyReason } from './errors.js'\n\nexport { PERSONAL_POLICY, STRICT_POLICY, mergePolicy } from './presets.js'\n\nexport { checkGate, describeGate, DEFAULT_FRESHNESS_MS } from './engine.js'\nexport type { CheckGateContext } from './engine.js'\n\nexport {\n loadVaultPolicy,\n saveVaultPolicy,\n META_COLLECTION,\n POLICY_RECORD_ID,\n} from './storage.js'\n","import type {\n NoydbOptions,\n NoydbEventMap,\n GrantOptions,\n RevokeOptions,\n UpdateUserOptions,\n UserInfo,\n PushResult,\n PullResult,\n PushOptions,\n PullOptions,\n SyncStatus,\n SyncTarget,\n NoydbStore,\n Role,\n AccessibleVault,\n ListAccessibleVaultsOptions,\n QueryAcrossOptions,\n QueryAcrossResult,\n ReAuthOperation,\n TranslatorAuditEntry,\n} from './types.js'\nimport { ValidationError, NoAccessError, InvalidKeyError, KeyringCorruptError, StoreCapabilityError, PermissionDeniedError } from './errors.js'\nimport {\n readDirectoryConfig,\n persistDirectoryConfig,\n} from './directory/storage.js'\nimport type { PassphrasePolicy } from './validation.js'\nimport {\n rotatePassphrase as keyringRotatePassphrase,\n recoverPassphrase as keyringRecoverPassphrase,\n type RotatePassphraseInput,\n type RecoverPassphraseInput,\n type RecoverPassphraseResult,\n type RotateRecoveryOptions,\n type RotateRecoveryResult,\n type EnrollRecoveryResult,\n type RecoveryEnrollmentInput,\n type RecoveryProof,\n} from './team/rotate-recover.js'\nimport {\n recoverUser as keyringRecoverUser,\n type RecoverUserOptions,\n} from './team/peer-recover.js'\nimport {\n loadPaperRecoveryEntries,\n savePaperRecoveryEntries,\n hasRecoveryEnrolled,\n hasStrongRecoveryEnrolled,\n mintPaperRecoveryEntry,\n type PaperRecoveryEntry,\n loadShamirRecoveryEntries,\n saveShamirRecoveryEntries,\n mintShamirRecoveryEntry,\n type ShamirRecoveryEntry,\n} from './team/recovery.js'\nimport { resolveManagedSecret, saveSealedPassphrase } from './team/managed-passphrase.js'\nimport type { ShamirRecoveryProvider } from './team/shamir-recovery-provider.js'\nimport { generateULID } from './bundle/ulid.js'\nimport { RecoveryNotEnrolledError, RecoveryProfileNotImplementedError, ManagedRecoveryNotEnrolledError, PolicyDeniedError } from './policy/errors.js'\nimport {\n describeAuthConfig as fnDescribeAuthConfig,\n diagramAuthConfig as fnDiagramAuthConfig,\n describeUserAuth as fnDescribeUserAuth,\n describeAllUsersAuth as fnDescribeAllUsersAuth,\n} from './auth-introspection/index.js'\nimport {\n loadPublicEnvelope,\n savePublicEnvelope,\n readPublicEnvelope as fnReadPublicEnvelope,\n resolveSchema as resolvePublicEnvelopeSchema,\n validatePublicEnvelopeInput,\n type PublicEnvelope,\n type SetPublicEnvelopeInput,\n type ResolvedPublicEnvelopeSchema,\n} from './meta/public-envelope/index.js'\nimport { Vault } from './vault.js'\nimport { NoydbEventEmitter } from './events.js'\nimport {\n loadKeyring,\n createOwnerKeyring,\n grant as keyringGrant,\n revoke as keyringRevoke,\n rotateKeys as keyringRotate,\n changeSecret as keyringChangeSecret,\n listUsers as keyringListUsers,\n updateKeyringIdentity,\n} from './team/keyring.js'\nimport type { UnlockedKeyring } from './team/keyring.js'\nimport {\n enrollAuthenticator as keyringEnrollAuthenticator,\n removeAuthenticator as keyringRemoveAuthenticator,\n updateAuthenticator as keyringUpdateAuthenticator,\n findAuthenticator,\n type EnrollAuthenticatorOptions,\n type UpdateAuthenticatorOptions,\n} from './team/authenticators.js'\nimport { QuickUnlockStore, type QuickUnlockState } from './session/unlock-state.js'\nimport type { KeyringAuthenticator } from './types.js'\nimport type { SyncEngine } from './team/sync.js'\nimport type { SyncTransaction } from './team/sync-transaction.js'\nimport { NO_SYNC, type SyncStrategy } from './team/sync-strategy.js'\nimport type { AmendmentTxOptions } from './tx/transaction.js'\nimport { TxContext } from './tx/transaction.js'\nimport { NO_TX, type TxStrategy } from './tx/strategy.js'\nimport { INDEXED_STORE_POLICY } from './store/sync-policy.js'\nimport type { PolicyEnforcer } from './session/session-policy.js'\nimport { NO_SESSION, type SessionStrategy } from './session/strategy.js'\nimport {\n checkGate as policyCheckGate,\n loadVaultPolicy,\n saveVaultPolicy,\n PERSONAL_POLICY,\n mergePolicy,\n type ActiveTier,\n type FactorProofBundle,\n type GateName,\n type VaultPolicy,\n} from './policy/index.js'\n\n/**\n * Privilege rank used by `listAccessibleVaults({ minRole })` to\n * filter the result. Higher number = more privileged. Owner is at the\n * top; client is at the bottom. Viewer outranks client because viewer\n * has read-all access while client has only explicit-collection read\n * — the ordering reflects \"how much can this principal see,\" not\n * \"how much can this principal modify.\"\n */\nconst ROLE_RANK: Record<Role, number> = {\n client: 1,\n viewer: 2,\n operator: 3,\n admin: 4,\n owner: 5,\n}\n\n/** Dummy keyring for unencrypted mode. */\nfunction createPlaintextKeyring(userId: string): UnlockedKeyring {\n return {\n userId,\n displayName: userId,\n role: 'owner',\n permissions: {},\n deks: new Map(),\n kek: null,\n salt: new Uint8Array(0),\n authenticators: [],\n }\n}\n\n/** The top-level NOYDB instance. */\nexport class Noydb {\n private readonly options: NoydbOptions\n private readonly emitter = new NoydbEventEmitter()\n private readonly vaultCache = new Map<string, Vault>()\n private readonly keyringCache = new Map<string, UnlockedKeyring>()\n private readonly syncEngines = new Map<string, SyncEngine>()\n /**\n * Per-vault active session tier — defaults to `1` after a passphrase\n * unlock; tier-2 / tier-3 unlocks (issue #11) downgrade it. Used by\n * {@link checkGate} to evaluate `gate.minTier`.\n */\n private readonly activeTier = new Map<string, ActiveTier>()\n /**\n * Per-vault loaded policy. Cached after the first\n * `_meta/policy` load; replaced by `db.updatePolicy()`.\n */\n private readonly policyCache = new Map<string, VaultPolicy>()\n /**\n * One-shot bypass for the managed-mode strong-recovery check (#195).\n * Set true by {@link openVaultAndEnrollRecovery} for the duration of\n * the bootstrap window so the keyring can be created before the\n * strong recovery is enrolled. Always cleared (try/finally).\n * @internal\n */\n private _skipNextManagedRecoveryCheck = false\n /** Per-vault tier-3 (PIN / quick-resume) state — issue #11. */\n private readonly quickUnlock = new QuickUnlockStore()\n /**\n * Resolved public-envelope schema. Lazily computed once from\n * `NoydbOptions.publicEnvelope`; `undefined` when the developer\n * didn't opt in.\n */\n private readonly publicEnvelopeSchema: ResolvedPublicEnvelopeSchema | undefined\n private closed = false\n private sessionTimer: ReturnType<typeof setTimeout> | null = null\n /** Per-vault policy enforcers. */\n private readonly policyEnforcers = new Map<string, PolicyEnforcer>()\n private readonly txStrategy: TxStrategy\n private readonly sessionStrategy: SessionStrategy\n private readonly syncStrategy: SyncStrategy\n /**\n * Currently-running multi-record transaction, set by\n * `runTransaction` at the start of Phase 2 (commit) and cleared in\n * the same function's `finally` block. Side-effect writes triggered\n * during a staged op's `Collection.put` (today: eager derivation\n * outputs) register their pre-write envelope on `_executed` here so\n * a mid-batch failure rolls them back alongside the main staged ops\n * (#133). `null` outside of Phase 2.\n * @internal\n */\n private _activeTxContext: TxContext | null = null\n\n // ─── plaintextTranslator state ─────────────────────────\n /**\n * In-process translation cache. Key is `\"${field}\\x00${collection}\\x00${from}\\x00${to}\\x00${text}\"`.\n * Cleared on `close()` alongside the KEK and DEKs.\n */\n private readonly translatorCache = new Map<string, string>()\n /** Audit log for all translator invocations in this session. Cleared on `close()`. */\n private readonly _translatorAuditLog: TranslatorAuditEntry[] = []\n\n constructor(options: NoydbOptions) {\n this.options = options\n this.txStrategy = options.txStrategy ?? NO_TX\n this.sessionStrategy = options.sessionStrategy ?? NO_SESSION\n this.syncStrategy = options.syncStrategy ?? NO_SYNC\n this.publicEnvelopeSchema = resolvePublicEnvelopeSchema(options.publicEnvelope)\n // Validate sessionPolicy at construction time (developer error if invalid).\n // The strategy's stub throws with a pointer at the subpath if the\n // consumer set a policy without opting in.\n if (options.sessionPolicy) {\n this.sessionStrategy.validateSessionPolicy(options.sessionPolicy)\n }\n this.resetSessionTimer()\n }\n\n private resetSessionTimer(): void {\n if (this.sessionTimer) clearTimeout(this.sessionTimer)\n // Honor the new sessionPolicy.idleTimeoutMs if present, fall back to\n // the legacy sessionTimeout for backwards compatibility.\n const idleMs = this.options.sessionPolicy?.idleTimeoutMs ?? this.options.sessionTimeout\n if (idleMs && idleMs > 0) {\n this.sessionTimer = setTimeout(() => {\n this.close()\n }, idleMs)\n }\n }\n\n /**\n * Attach a policy enforcer for a vault.\n * Called internally when a session is started for a vault; the\n * enforcer handles idle/absolute timeouts and background-lock behavior.\n */\n private attachPolicyEnforcer(vault: string, sessionId: string): void {\n const policy = this.options.sessionPolicy\n if (!policy) return\n\n // Tear down any previous enforcer for this vault\n this.policyEnforcers.get(vault)?.destroy()\n\n const enforcer = this.sessionStrategy.createEnforcer({\n policy,\n sessionId,\n onRevoke: (_reason) => {\n this.keyringCache.delete(vault)\n this.vaultCache.delete(vault)\n this.policyEnforcers.delete(vault)\n },\n })\n this.policyEnforcers.set(vault, enforcer)\n }\n\n /**\n * Touch the policy enforcer for a vault (records activity, resets\n * idle timer). Also touches the legacy session timer. No-op if no enforcer.\n */\n private touchPolicy(vault?: string): void {\n this.resetSessionTimer()\n if (vault) {\n this.policyEnforcers.get(vault)?.touch()\n }\n }\n\n /**\n * Check that a policy-guarded operation is permitted.\n * Throws `SessionPolicyError` if re-auth is required.\n */\n private checkPolicyOperation(vault: string, op: ReAuthOperation): void {\n this.policyEnforcers.get(vault)?.checkOperation(op)\n }\n\n /**\n * Open a vault by name.\n *\n * @param name Vault identifier.\n * @param opts Optional settings for this session.\n * @param opts.locale Default locale for i18n/dictKey field resolution\n *. Set here to avoid passing `{ locale }`\n * on every individual `get()`/`list()` call.\n */\n async openVault(\n name: string,\n opts?: { locale?: string },\n ): Promise<Vault> {\n if (this.closed) throw new ValidationError('Instance is closed')\n this.touchPolicy(name)\n\n let comp = this.vaultCache.get(name)\n if (comp) {\n // Update locale on existing cached vault if specified\n if (opts?.locale !== undefined) {\n comp.setLocale(opts.locale)\n }\n return comp\n }\n\n const keyring = await this.getKeyringInternal(name)\n // Tier-1 unlock — passphrase / getKeyring callbacks both yield the\n // most-privileged tier. Tier-2 / tier-3 unlocks (issue #11) install\n // a lower tier here when they land.\n if (!this.activeTier.has(name)) {\n this.activeTier.set(name, 1)\n }\n // Load + persist the policy document. First call: persist the\n // developer-supplied policy (or default preset). Later calls: read\n // whatever's on disk and merge any developer override on top.\n if (this.options.encrypt !== false && !this.policyCache.has(name)) {\n await this.bootstrapPolicy(name)\n }\n\n // Set up sync engine(s) — handles bare NoydbStore, SyncTarget, or SyncTarget[]\n let syncEngine: SyncEngine | undefined\n const targets = normalizeSyncTargets(this.options.sync)\n if (targets.length > 0) {\n // Primary sync engine is the first sync-peer (or first target if none)\n const primary = targets.find(t => t.role === 'sync-peer') ?? targets[0]!\n const effectivePolicy = this.options.syncPolicy ?? primary.policy ?? INDEXED_STORE_POLICY\n syncEngine = this.syncStrategy.buildSyncEngine({\n local: this.options.store,\n remote: primary.store,\n vault: name,\n strategy: this.options.conflict ?? 'version',\n emitter: this.emitter,\n syncPolicy: effectivePolicy,\n role: primary.role,\n ...(primary.label !== undefined ? { label: primary.label } : {}),\n })\n this.syncEngines.set(name, syncEngine)\n\n // Additional targets get their own engines (backup/archive are push-only)\n for (const target of targets) {\n if (target === primary) continue\n const targetPolicy = target.policy ?? this.options.syncPolicy ?? INDEXED_STORE_POLICY\n const engine = this.syncStrategy.buildSyncEngine({\n local: this.options.store,\n remote: target.store,\n vault: name,\n strategy: this.options.conflict ?? 'version',\n emitter: this.emitter,\n syncPolicy: targetPolicy,\n role: target.role,\n ...(target.label !== undefined ? { label: target.label } : {}),\n })\n const key = `${name}::${target.label ?? target.role}`\n this.syncEngines.set(key, engine)\n }\n }\n\n comp = new Vault({\n adapter: this.options.store,\n name,\n noydb: this,\n keyring,\n encrypted: this.options.encrypt !== false,\n emitter: this.emitter,\n onDirty: targets.length > 0\n ? async (coll, id, action, version) => {\n // Fan out dirty tracking to all sync engines for this vault\n for (const [key, engine] of this.syncEngines) {\n if (key === name || key.startsWith(`${name}::`)) {\n void engine.trackChange(coll, id, action, version)\n }\n }\n }\n : undefined,\n onRegisterConflictResolver: syncEngine\n ? (resolverName, resolver) => syncEngine.registerConflictResolver(resolverName, resolver)\n : undefined,\n syncAdapter: targets.length > 0 ? targets[0]!.store : undefined,\n historyConfig: this.options.history,\n ...(this.options.blobStrategy !== undefined ? { blobStrategy: this.options.blobStrategy } : {}),\n ...(this.options.indexStrategy !== undefined ? { indexStrategy: this.options.indexStrategy } : {}),\n ...(this.options.aggregateStrategy !== undefined ? { aggregateStrategy: this.options.aggregateStrategy } : {}),\n ...(this.options.crdtStrategy !== undefined ? { crdtStrategy: this.options.crdtStrategy } : {}),\n ...(this.options.consentStrategy !== undefined ? { consentStrategy: this.options.consentStrategy } : {}),\n ...(this.options.periodsStrategy !== undefined ? { periodsStrategy: this.options.periodsStrategy } : {}),\n ...(this.options.shadowStrategy !== undefined ? { shadowStrategy: this.options.shadowStrategy } : {}),\n ...(this.options.historyStrategy !== undefined ? { historyStrategy: this.options.historyStrategy } : {}),\n ...(this.options.i18nStrategy !== undefined ? { i18nStrategy: this.options.i18nStrategy } : {}),\n ...(this.options.syncStrategy !== undefined ? { syncStrategy: this.options.syncStrategy } : {}),\n ...(this.options.guardStrategies !== undefined ? { guardStrategies: this.options.guardStrategies } : {}),\n locale: opts?.locale,\n // Thread the translator hook so Collection.put() can invoke it\n plaintextTranslator: this.options.plaintextTranslator\n ? (text, from, to, field, collection) =>\n this.invokeTranslator(text, from, to, field, collection)\n : undefined,\n // Refresh callback used by Vault.load() to re-derive\n // the in-memory keyring from a freshly-loaded keyring file.\n // Encrypted compartments need this so post-load decrypts work\n // against the loaded session's wrapped DEKs; plaintext\n // compartments leave it null and load() skips the refresh.\n reloadKeyring:\n this.options.encrypt !== false && this.options.secret\n ? async () => {\n // Drop the cached keyring so the next loadKeyring\n // call reads fresh from the adapter, then update the\n // cache so subsequent openVault calls see the\n // refreshed keyring too.\n this.keyringCache.delete(name)\n const refreshed = await loadKeyring(\n this.options.store,\n name,\n this.options.user,\n this.options.secret as string,\n )\n this.keyringCache.set(name, refreshed)\n return refreshed\n }\n : undefined,\n })\n // Initialise the optional guard + derivation registries via\n // dynamic-import. Both calls are no-ops when the corresponding\n // strategies array is empty / unset, leaving the subsystem code\n // out of the floor bundle for consumers that don't use it (#130).\n await comp._initGuards(this.options.guardStrategies ?? [])\n await comp._initDerivations(this.options.derivationStrategies ?? [])\n await comp._initMaterializedViews(this.options.materializedViewStrategies ?? [])\n await comp._initOverlayedViews(this.options.overlayedViewStrategies ?? [])\n this.vaultCache.set(name, comp)\n return comp\n }\n\n /** Synchronous vault access (must call openVault first, or auto-opens). */\n vault(name: string): Vault {\n const cached = this.vaultCache.get(name)\n if (cached) return cached\n\n // For backwards compat: if not opened yet, create with cached keyring or plaintext\n if (this.options.encrypt === false) {\n const keyring = createPlaintextKeyring(this.options.user)\n const comp = new Vault({\n adapter: this.options.store,\n name,\n noydb: this,\n keyring,\n encrypted: false,\n emitter: this.emitter,\n historyConfig: this.options.history,\n ...(this.options.blobStrategy !== undefined ? { blobStrategy: this.options.blobStrategy } : {}),\n ...(this.options.indexStrategy !== undefined ? { indexStrategy: this.options.indexStrategy } : {}),\n ...(this.options.aggregateStrategy !== undefined ? { aggregateStrategy: this.options.aggregateStrategy } : {}),\n ...(this.options.crdtStrategy !== undefined ? { crdtStrategy: this.options.crdtStrategy } : {}),\n ...(this.options.consentStrategy !== undefined ? { consentStrategy: this.options.consentStrategy } : {}),\n ...(this.options.periodsStrategy !== undefined ? { periodsStrategy: this.options.periodsStrategy } : {}),\n ...(this.options.shadowStrategy !== undefined ? { shadowStrategy: this.options.shadowStrategy } : {}),\n ...(this.options.historyStrategy !== undefined ? { historyStrategy: this.options.historyStrategy } : {}),\n ...(this.options.i18nStrategy !== undefined ? { i18nStrategy: this.options.i18nStrategy } : {}),\n ...(this.options.syncStrategy !== undefined ? { syncStrategy: this.options.syncStrategy } : {}),\n ...(this.options.guardStrategies !== undefined ? { guardStrategies: this.options.guardStrategies } : {}),\n })\n this.vaultCache.set(name, comp)\n return comp\n }\n\n const keyring = this.keyringCache.get(name)\n if (!keyring) {\n throw new ValidationError(\n `Vault \"${name}\" not opened. Use await db.openVault(\"${name}\") first.`,\n )\n }\n\n const comp = new Vault({\n adapter: this.options.store,\n name,\n noydb: this,\n keyring,\n encrypted: true,\n historyConfig: this.options.history,\n ...(this.options.blobStrategy !== undefined ? { blobStrategy: this.options.blobStrategy } : {}),\n ...(this.options.indexStrategy !== undefined ? { indexStrategy: this.options.indexStrategy } : {}),\n ...(this.options.aggregateStrategy !== undefined ? { aggregateStrategy: this.options.aggregateStrategy } : {}),\n ...(this.options.crdtStrategy !== undefined ? { crdtStrategy: this.options.crdtStrategy } : {}),\n ...(this.options.consentStrategy !== undefined ? { consentStrategy: this.options.consentStrategy } : {}),\n ...(this.options.periodsStrategy !== undefined ? { periodsStrategy: this.options.periodsStrategy } : {}),\n ...(this.options.shadowStrategy !== undefined ? { shadowStrategy: this.options.shadowStrategy } : {}),\n ...(this.options.historyStrategy !== undefined ? { historyStrategy: this.options.historyStrategy } : {}),\n ...(this.options.i18nStrategy !== undefined ? { i18nStrategy: this.options.i18nStrategy } : {}),\n ...(this.options.syncStrategy !== undefined ? { syncStrategy: this.options.syncStrategy } : {}),\n ...(this.options.guardStrategies !== undefined ? { guardStrategies: this.options.guardStrategies } : {}),\n emitter: this.emitter,\n })\n this.vaultCache.set(name, comp)\n return comp\n }\n\n /**\n * Grant access to a user for a vault.\n *\n * Gated by `enroll-user`. `STRICT_POLICY` requires a TOTP / email-OTP\n * factor proof so the operator affirmatively re-asserts identity at\n * the moment of grant; `PERSONAL_POLICY` accepts a tier-1 unlock alone.\n *\n * The legacy `requireReAuthFor: ['grant']` session-policy check still\n * fires on top — both are independent opt-ins.\n */\n async grant(\n vault: string,\n options: GrantOptions,\n factors?: FactorProofBundle,\n ): Promise<void> {\n this.checkPolicyOperation(vault, 'grant')\n await this.checkGate(vault, 'enroll-user', factors)\n const keyring = await this.getKeyringInternal(vault)\n await keyringGrant(this.options.store, vault, keyring, options)\n }\n\n /**\n * Revoke a user's access to a vault.\n *\n * Gated by `revoke-user`. `STRICT_POLICY` requires a TOTP / email-OTP\n * factor proof; `PERSONAL_POLICY` accepts a tier-1 unlock alone.\n *\n * The legacy `requireReAuthFor: ['revoke']` session-policy check still\n * fires on top — both are independent opt-ins.\n */\n async revoke(\n vault: string,\n options: RevokeOptions,\n factors?: FactorProofBundle,\n ): Promise<void> {\n this.checkPolicyOperation(vault, 'revoke')\n await this.checkGate(vault, 'revoke-user', factors)\n const keyring = await this.getKeyringInternal(vault)\n await keyringRevoke(this.options.store, vault, keyring, options)\n }\n\n /**\n * Mutate post-grant identity fields on an existing keyring — `role`,\n * `displayName`, and/or `permissions`. Pure plaintext-header rewrite:\n * no DEK rewrap, no KEK required, no authenticator slots touched.\n * Tier-2 enrollments and recovery codes survive.\n *\n * Different from `db.revoke + db.grant`:\n *\n * - Same `userId`, same DEK wrappings, same `granted_by`, same\n * `_users/<keyringId>` envelope. Only the specified header\n * fields move. Last-write-wins via the standard keyring put.\n * - No cascade on role demotion (admins demoted to operator keep\n * the keyrings they previously granted; the cascade rules are\n * a `db.revoke` concern, not `db.updateUser`).\n * - Tier-2 slots NOT dropped — the wrapping is unaffected.\n *\n * Role-elevation guard: BOTH the old and new role must satisfy\n * `db.grant`'s hierarchy. Owner can do anything; admin manages\n * admin/operator/viewer/client laterally; admin cannot promote to\n * owner OR demote from owner. The guard runs regardless of the\n * `update-user` policy gate's settings — gates can only be more\n * permissive than the structural floor, never less.\n *\n * Gated by `update-user`. `STRICT_POLICY` requires a TOTP/email-OTP\n * factor proof so the operator affirmatively re-asserts identity at\n * the moment of mutation; `PERSONAL_POLICY` accepts a tier-1 unlock\n * alone.\n *\n * ```ts\n * await db.updateUser('acme', {\n * userId: 'bob',\n * role: 'operator', // promote\n * permissions: { invoices: 'rw' },\n * }, { factors: [{ kind: 'totp' }] })\n * ```\n *\n * @throws `NoAccessError` when no keyring exists for the target.\n * @throws `PermissionDeniedError` when the role hierarchy rejects.\n * @throws `ValidationError` when no field is provided.\n *\n * @see #54\n */\n async updateUser(\n vault: string,\n options: UpdateUserOptions,\n factors?: FactorProofBundle,\n ): Promise<void> {\n await this.checkGate(vault, 'update-user', factors)\n const keyring = await this.getKeyringInternal(vault)\n await updateKeyringIdentity(this.options.store, vault, keyring, options)\n // If the caller updated their own role / permissions, the cached\n // unlocked keyring is stale — drop it so the next access reloads\n // with the new header fields. (DEKs unchanged, so the cached\n // unlock still works; only the role-gated checks would diverge.)\n if (options.userId === this.options.user) {\n this.keyringCache.delete(vault)\n }\n }\n\n /**\n * Rotate the DEKs for the given collections in a vault.\n *\n * Generates fresh DEKs, re-encrypts every record in each collection,\n * and re-wraps the new DEKs into every remaining user's keyring. The\n * old DEKs become unreachable — useful as a defense-in-depth measure\n * after a suspected key leak, or as the scheduled half of a\n * key-rotation policy.\n *\n * Unlike `revoke({ rotateKeys: true })`, this call does NOT remove\n * any users — every current member keeps access, but with fresh\n * keys. This is the \"just rotate\" path; the \"revoke and rotate\"\n * path still lives in `revoke()`.\n *\n * Exposed on Noydb (rather than only on the lower-level keyring\n * module) so CLI and admin tooling can trigger rotation without\n * reaching into internals. See `noy-db rotate` for the CLI wrapper.\n */\n async rotate(vault: string, collections: string[]): Promise<void> {\n this.checkPolicyOperation(vault, 'rotate')\n const keyring = await this.getKeyringInternal(vault)\n await keyringRotate(this.options.store, vault, keyring, collections)\n // Refresh the cached keyring so subsequent operations see the\n // freshly-rotated DEKs. Without this, `ensureCollectionDEK` on\n // the next Collection access would still hold the old ones.\n this.keyringCache.set(vault, keyring)\n }\n\n /** List all users with access to a vault. */\n async listUsers(vault: string): Promise<UserInfo[]> {\n return keyringListUsers(this.options.store, vault)\n }\n\n // ─── Cross-vault queries ──────────────────────\n\n /**\n * Enumerate every vault the calling principal can unwrap,\n * optionally filtered by minimum role.\n *\n * The walk is a two-step pipeline: first ask the adapter for the\n * universe of compartments it stores, then for each one attempt to\n * load the calling user's keyring with the in-memory passphrase.\n * Compartments where the user has no keyring file (`NoAccessError`)\n * or where the passphrase doesn't unwrap (`InvalidKeyError`) are\n * silently dropped from the result — the existence of those\n * compartments is **not** confirmed in the return value.\n *\n * Requires the optional `NoydbStore.listVaults()` capability.\n * Throws `StoreCapabilityError` against stores that don't\n * implement it (today: store-aws-dynamo, store-aws-s3, store-browser-local, store-browser-idb). For those backends the\n * consumer should either pass an explicit candidate list to\n * `queryAcross()` directly, or maintain a vault index out of\n * band.\n *\n * **Privacy note.** This method's return value never reveals the\n * existence of a vault the caller cannot unwrap. The adapter\n * sees the enumeration call (it has to — it owns the storage), but\n * downstream consumers of `listAccessibleVaults()` only see\n * the filtered list. That's the boundary the existence-leak\n * guarantee draws.\n *\n * **Known edge case.** A vault whose keyring file\n * happens to have an empty wrapped-DEKs map (because the owner\n * granted access before any collection was created) will pass the\n * `loadKeyring` probe with *any* passphrase — there are no DEKs to\n * unwrap, so the integrity-checked unwrap that normally rejects\n * wrong passphrases never runs. The result is that an unrelated\n * principal who happens to know the user-id and the vault\n * name can show up in `listAccessibleVaults()` as having\n * access to that empty vault. They cannot read any actual\n * data (their DEK set is empty), so this is a metadata leak\n * (vault name + user-id), not a content leak. Hardening this\n * via a passphrase canary in the keyring file is a deferred\n * follow-up.\n *\n * **Cost.** O(compartments × keyring-load) — one `loadKeyring`\n * attempt per vault in the universe. Each attempt does one\n * adapter `get` + one PBKDF2 derivation + N AES-KW unwraps. For\n * dozens of compartments this is fine; for thousands the consumer\n * should cache the result and refresh on grant/revoke events. A\n * future optimization could batch the keyring reads via\n * `loadAll('_keyring')` if such a thing existed at the adapter\n * layer, but the contract doesn't expose that.\n *\n * @example\n * ```ts\n * // All compartments I can unwrap\n * const all = await db.listAccessibleVaults()\n *\n * // Only compartments where I'm at least admin\n * const admin = await db.listAccessibleVaults({ minRole: 'admin' })\n *\n * // Only compartments I own\n * const owned = await db.listAccessibleVaults({ minRole: 'owner' })\n * ```\n */\n async listAccessibleVaults(\n options: ListAccessibleVaultsOptions = {},\n ): Promise<AccessibleVault[]> {\n if (this.closed) throw new ValidationError('Instance is closed')\n this.resetSessionTimer()\n\n const adapter = this.options.store\n if (typeof adapter.listVaults !== 'function') {\n throw new StoreCapabilityError(\n 'listVaults',\n 'Noydb.listAccessibleVaults()',\n adapter.name,\n )\n }\n\n if (this.options.encrypt === false) {\n // Plaintext mode: no keyrings exist; every vault the\n // adapter knows about is \"accessible\" trivially as owner.\n const all = await adapter.listVaults()\n return all.map((id) => ({ id, role: 'owner' as Role }))\n }\n\n if (!this.options.secret) {\n throw new ValidationError(\n 'Noydb.listAccessibleVaults(): a secret (passphrase) is required ' +\n 'when encryption is enabled.',\n )\n }\n\n const minRank = ROLE_RANK[options.minRole ?? 'client']\n const universe = await adapter.listVaults()\n const accessible: AccessibleVault[] = []\n\n for (const vault of universe) {\n // Probe with loadKeyring directly (NOT getKeyring, which would\n // auto-create a fresh owner keyring on miss — that would\n // silently grant access to every empty vault in the\n // universe and is exactly the wrong shape for an enumeration\n // API). The two expected failure modes — no keyring file, or\n // wrong passphrase — are caught and silently dropped so the\n // return value never leaks existence.\n let keyring: UnlockedKeyring\n try {\n keyring = await loadKeyring(\n adapter,\n vault,\n this.options.user,\n this.options.secret,\n )\n } catch (err) {\n if (\n err instanceof NoAccessError ||\n err instanceof InvalidKeyError ||\n err instanceof KeyringCorruptError\n ) {\n // No accessible key material for this vault. KeyringCorruptError\n // is included so a single partially-corrupted vault does NOT\n // poison the enumeration of every other healthy vault — the\n // caller can probe a corrupted vault directly via openVault()\n // / loadKeyring() if they want to act on it.\n continue\n }\n throw err // unexpected error — surface it\n }\n\n if (ROLE_RANK[keyring.role] < minRank) continue\n accessible.push({ id: vault, role: keyring.role })\n\n // Opportunistically prime the keyring cache so a subsequent\n // openVault() doesn't have to re-derive the KEK. The cost\n // is one Map.set per vault we already paid to unwrap.\n this.keyringCache.set(vault, keyring)\n }\n\n return accessible\n }\n\n /**\n * Run a per-vault callback against a list of compartments and\n * collect the results.\n *\n * Pure orchestration — there is no new crypto, no new sync, no new\n * authorization layer. Each vault is opened via the existing\n * `openVault()` path (which honors the cache primed by\n * `listAccessibleVaults`), the callback runs against the\n * resulting `Vault` instance, and the result (or thrown\n * error) is captured into the per-vault slot.\n *\n * **Per-vault errors do not abort the fan-out.** If one\n * vault's callback throws, that vault's slot carries\n * the error and the remaining compartments still run. The caller\n * decides how to handle the partition between success and failure.\n * This is the right shape for cross-tenant reports where one\n * tenant's outage shouldn't hide the other tenants' data.\n *\n * **Concurrency** is opt-in via `options.concurrency`. The default\n * is `1` (sequential) — conservative because per-vault\n * callbacks typically do their own I/O and an unbounded fan-out\n * can exhaust adapter connections (DynamoDB throughput, S3 socket\n * limits, browser fetch concurrency). Bump to 4-8 for cloud-backed\n * adapters where parallelism is the whole point.\n *\n * @example\n * ```ts\n * // Cross-tenant invoice totals as a flat list\n * const accessible = await db.listAccessibleVaults({ minRole: 'admin' })\n * const results = await db.queryAcross(\n * accessible.map((c) => c.id),\n * async (comp) => {\n * return comp.collection<Invoice>('invoices').query()\n * .where('month', '==', '2026-03')\n * .toArray()\n * },\n * { concurrency: 4 },\n * )\n * // results: Array<{ vault, result?: Invoice[], error?: Error }>\n *\n * // Compose with exportStream() — cross-vault plaintext export\n * const exports = await db.queryAcross(accessible.map((c) => c.id), async (comp) => {\n * const out: unknown[] = []\n * for await (const chunk of comp.exportStream()) out.push(chunk)\n * return out\n * })\n * ```\n */\n async queryAcross<T>(\n vaultIds: string[],\n fn: (vault: Vault) => Promise<T>,\n options: QueryAcrossOptions = {},\n ): Promise<QueryAcrossResult<T>[]> {\n if (this.closed) throw new ValidationError('Instance is closed')\n this.resetSessionTimer()\n\n const concurrency = Math.max(1, options.concurrency ?? 1)\n const results: QueryAcrossResult<T>[] = new Array(vaultIds.length)\n\n // Tiny inline p-limit. Maintains a sliding window of `concurrency`\n // in-flight promises and schedules the next vault as each\n // one settles. No external dep. Index-keyed result array so the\n // output preserves caller-supplied order even when concurrency\n // > 1 lets later compartments finish before earlier ones.\n let nextIndex = 0\n const inFlight: Set<Promise<void>> = new Set()\n\n const launch = (): Promise<void> | null => {\n if (nextIndex >= vaultIds.length) return null\n const idx = nextIndex++\n const vaultId = vaultIds[idx]!\n const task = (async () => {\n try {\n const comp = await this.openVault(vaultId)\n const result = await fn(comp)\n results[idx] = { vault: vaultId, result }\n } catch (err) {\n results[idx] = {\n vault: vaultId,\n error: err instanceof Error ? err : new Error(String(err)),\n }\n }\n })()\n inFlight.add(task)\n // Fire-and-forget cleanup. The task itself never rejects (the\n // try/catch above swallows everything into the result slot), so\n // there's no rejection to handle here — `void` tells the linter\n // we know what we're doing.\n void task.finally(() => inFlight.delete(task))\n return task\n }\n\n // Prime the window.\n for (let i = 0; i < concurrency; i++) {\n if (launch() === null) break\n }\n\n // Drain. As each task settles, kick off the next one until the\n // input is exhausted. `Promise.race` against the live set is the\n // simplest way to \"wake up on whichever finishes first\" without\n // pulling in p-limit / async-pool / etc.\n while (inFlight.size > 0) {\n await Promise.race(inFlight)\n while (inFlight.size < concurrency && nextIndex < vaultIds.length) {\n if (launch() === null) break\n }\n }\n\n return results\n }\n\n /**\n * Change the current user's passphrase for a vault.\n *\n * Validates the new passphrase against the strength rules. Pass\n * `{ allowWeakPassphrase: true }` to skip — typically only useful for\n * fixtures and migrations. Pass a `PassphrasePolicy` to override the\n * default rules (e.g. consumer-tunable `pattern` / `customValidator`).\n */\n async changeSecret(\n vault: string,\n newPassphrase: string,\n options?: PassphrasePolicy & { allowWeakPassphrase?: boolean },\n ): Promise<void> {\n this.checkPolicyOperation(vault, 'changeSecret')\n const keyring = await this.getKeyringInternal(vault)\n const updated = await keyringChangeSecret(\n this.options.store,\n vault,\n keyring,\n newPassphrase,\n options,\n )\n this.keyringCache.set(vault, updated)\n }\n\n // ─── Sync ──────────────────────────────────────────────────────\n\n /** Push local changes to remote for a vault. */\n async push(vault: string, options?: PushOptions): Promise<PushResult> {\n const engine = this.getSyncEngine(vault)\n return engine.push(options)\n }\n\n /** Pull remote changes to local for a vault. */\n async pull(vault: string, options?: PullOptions): Promise<PullResult> {\n const engine = this.getSyncEngine(vault)\n return engine.pull(options)\n }\n\n /**\n * Bidirectional sync: pull then push for all targets.\n * `sync-peer` targets do pull+push; `backup`/`archive` targets do push-only.\n */\n async sync(vault: string, options?: { push?: PushOptions; pull?: PullOptions }): Promise<{ pull: PullResult; push: PushResult }> {\n const primary = this.getSyncEngine(vault)\n const result = await primary.sync(options)\n\n // Fan out push to backup/archive targets (fire-and-mark-dirty)\n for (const [key, engine] of this.syncEngines) {\n if (key === vault) continue\n if (!key.startsWith(`${vault}::`)) continue\n if (engine.role === 'sync-peer') {\n await engine.sync(options).catch((err: Error) => {\n this.emitter.emit('sync:backup-error', {\n vault,\n target: engine.label ?? engine.role,\n error: err,\n })\n })\n } else {\n // backup/archive: push-only\n await engine.push(options?.push).catch((err: Error) => {\n this.emitter.emit('sync:backup-error', {\n vault,\n target: engine.label ?? engine.role,\n error: err,\n })\n })\n }\n }\n\n return result\n }\n\n /**\n * Multi-record atomic transaction.\n *\n * The callback stages writes across any number of vaults /\n * collections; on return the hub pre-flights version checks, then\n * commits every staged op. If the body throws, nothing is\n * persisted. If any staged op fails its `expectedVersion` check,\n * the batch throws `ConflictError` with zero writes performed. If a\n * mid-commit failure occurs after one or more ops have already\n * written, each executed op is reverted best-effort (see\n * `runTransaction` for the crash-window caveat).\n *\n * Distinct from `transaction(vault: string) → SyncTransaction`\n * which batches push/pull across sync peers.\n */\n transaction<T>(fn: (tx: TxContext) => Promise<T> | T): Promise<T>\n /**\n * Open an amendment-mode transaction. Requires `admin` or `owner`\n * role on every vault touched by the body; throws\n * `AmendmentForbiddenError` on first non-privileged `tx.vault(name)`\n * call. Guard `check` callbacks are SKIPPED inside an amendment —\n * the staged change-set is fed to each guard's `amendment.invariant`\n * after the body returns, and the multi-record summary is appended\n * to the vault's ledger as `op: 'amendment'`.\n */\n transaction<T>(\n options: AmendmentTxOptions,\n fn: (tx: TxContext) => Promise<T> | T,\n ): Promise<T>\n /**\n * Create a sync transaction for the given vault.\n * The vault must already be open via `openVault()`.\n * Call `tx.put()` / `tx.delete()` to stage changes, then `tx.commit()`\n * to write all locally and push atomically to remote.\n */\n transaction(vault: string): SyncTransaction\n transaction<T>(\n arg: string | AmendmentTxOptions | ((tx: TxContext) => Promise<T> | T),\n maybeFn?: (tx: TxContext) => Promise<T> | T,\n ): SyncTransaction | Promise<T> {\n if (typeof arg === 'function') {\n return this.txStrategy.runTransaction(this, arg)\n }\n if (typeof arg === 'object' && arg !== null && arg.amendment === true) {\n // Two-arg amendment form. We forward `arg` as the options bag —\n // the executor handles reason validation + per-vault role check.\n if (typeof maybeFn !== 'function') {\n throw new ValidationError(\n 'db.transaction({ amendment: true }, fn) requires the callback as the second argument.',\n )\n }\n return this.txStrategy.runTransaction(this, maybeFn, arg)\n }\n const vault = arg as string\n const comp = this.vaultCache.get(vault)\n if (!comp) {\n throw new ValidationError(\n `Vault \"${vault}\" is not open. Call openVault() first.`,\n )\n }\n const engine = this.getSyncEngine(vault)\n return this.syncStrategy.buildSyncTransaction(comp, engine)\n }\n\n /**\n * Internal accessor for the primary store — used by the tx\n * executor to perform raw adapter reads for pre-flight CAS and\n * raw writes for rollback. Not part of the public API.\n *\n * @internal\n */\n get _store(): NoydbStore {\n return this.options.store\n }\n\n /**\n * Currently-running multi-record transaction, or `null` outside\n * Phase 2. `Collection.dispatchDerivations` consults this so a\n * recursive derived-output write inside `Collection.put` can register\n * its envelope onto `ctx._executed` and roll back with the main\n * staged ops on mid-batch failure (#133).\n *\n * @internal\n */\n get _activeTxContextOrNull(): TxContext | null {\n return this._activeTxContext\n }\n\n /**\n * Called by `runTransaction` at Phase 2 start, and by\n * `Collection.putManyAtomic` (via `derivationSource.setActiveTxContext`)\n * for its own Phase 2 loop. Nested or concurrent (non-nested)\n * transactions on the same Noydb instance are NOT supported —\n * overwriting an active context means another transaction is still\n * running and its `_executed` list would be cross-contaminated by\n * the nested writes. We tolerate the overwrite (best-effort, no\n * throw) to keep the rare interleaving from breaking consumers who\n * currently get lucky with timing, but applications should ensure\n * their multi-record commits are serialised on a single Noydb.\n *\n * @internal\n */\n _setActiveTxContext(ctx: TxContext): void {\n this._activeTxContext = ctx\n }\n\n /**\n * Factory for a transient `TxContext` bound to this Noydb. Used by\n * `Collection.putManyAtomic` (via `derivationSource.createTxContext`)\n * to publish an active context for the duration of its bulk-atomic\n * Phase 2 loop, so recursive derivation-output writes register on\n * `ctx._executed` and roll back together with the source ops (#133).\n *\n * @internal\n */\n _createTxContext(): TxContext {\n return new TxContext(this)\n }\n\n /**\n * Called by `runTransaction` in its `finally`. Only clears when the\n * passed ctx matches the active one — a defensive no-op if some\n * other code path already cleared it.\n *\n * @internal\n */\n _clearActiveTxContext(ctx: TxContext): void {\n if (this._activeTxContext === ctx) {\n this._activeTxContext = null\n }\n }\n\n /** Get sync status for a vault. */\n syncStatus(vault: string): SyncStatus {\n const engine = this.syncEngines.get(vault)\n if (!engine) {\n return { dirty: 0, lastPush: null, lastPull: null, online: true }\n }\n return engine.status()\n }\n\n private requireShamirProvider(): ShamirRecoveryProvider {\n const p = this.options.shamirRecovery\n if (!p) {\n throw new Error(\n \"shamir recovery requires a ShamirRecoveryProvider — pass \"\n + \"shamirRecovery: shamirRecoveryProvider() from '@noy-db/on-shamir' to createNoydb()\",\n )\n }\n return p\n }\n\n private getSyncEngine(vault: string): SyncEngine {\n const engine = this.syncEngines.get(vault)\n if (!engine) {\n throw new ValidationError('No sync adapter configured. Pass a `sync` adapter to createNoydb().')\n }\n return engine\n }\n\n // ─── Events ────────────────────────────────────────────────────\n\n on<K extends keyof NoydbEventMap>(event: K, handler: (data: NoydbEventMap[K]) => void): void {\n this.emitter.on(event, handler)\n }\n\n off<K extends keyof NoydbEventMap>(event: K, handler: (data: NoydbEventMap[K]) => void): void {\n this.emitter.off(event, handler)\n }\n\n /**\n * Soft-lock a single vault: clear its in-memory keyring, DEKs, vault\n * instance, sync engine, policy enforcer, and active-tier entry —\n * WITHOUT destroying the `Noydb` instance.\n *\n * Designed for \"lock screen\" UX: the user taps **Lock** and DEKs are\n * scrubbed from memory immediately, but the same `Noydb` instance can\n * be re-unlocked via {@link unlockViaAuthenticator} (tier 2) or\n * {@link unlockViaPin} (tier 3) without re-running `createNoydb`.\n *\n * **QuickUnlock state is preserved.** That's the whole point — the\n * user can still resume via PIN without a full credential re-prompt.\n * The on-disk `_meta/policy` document is also kept in cache (it\n * survives lock; nothing about it changes when DEKs are scrubbed).\n *\n * No-op when `vault` is not currently in cache (idempotent).\n *\n * Unblocks vLannaAi/niwat#33.\n *\n * @see #17\n */\n lockVault(vault: string): void {\n // Sync engine: stop autosync + drop the engine so the next openVault\n // builds a fresh one against the freshly-loaded keyring.\n this.syncEngines.get(vault)?.stopAutoSync()\n this.syncEngines.delete(vault)\n // Policy enforcer: cancels its idle timer and any visibility listener.\n this.policyEnforcers.get(vault)?.destroy()\n this.policyEnforcers.delete(vault)\n // Live caches: scrub DEKs, vault instance, active tier.\n this.keyringCache.delete(vault)\n this.vaultCache.delete(vault)\n this.activeTier.delete(vault)\n // Intentionally NOT cleared:\n // - this.quickUnlock — preserves PIN resume (#17 contract).\n // - this.policyCache — vault policy is on-disk data, survives lock.\n // - this.sessionStrategy — no per-vault revoke; close() handles bulk.\n }\n\n close(): void {\n this.closed = true\n if (this.sessionTimer) {\n clearTimeout(this.sessionTimer)\n this.sessionTimer = null\n }\n // Destroy all policy enforcers (cancels timers + visibility listeners)\n for (const enforcer of this.policyEnforcers.values()) {\n enforcer.destroy()\n }\n this.policyEnforcers.clear()\n // Revoke all in-memory session keys\n this.sessionStrategy.revokeAllSessions()\n // Stop all sync engines\n for (const engine of this.syncEngines.values()) {\n engine.stopAutoSync()\n }\n this.syncEngines.clear()\n this.keyringCache.clear()\n this.vaultCache.clear()\n this.activeTier.clear()\n this.policyCache.clear()\n this.quickUnlock.clear()\n this.emitter.removeAllListeners()\n // Clear translator state — same lifetime as KEK/DEKs\n this.translatorCache.clear()\n this._translatorAuditLog.length = 0\n }\n\n /**\n * Returns a snapshot of all translator invocations since the last\n * `close()`. Useful for testing and compliance auditing. The log is\n * in-memory only — it is cleared when `db.close()` is called.\n *\n * Entries deliberately omit content hashes. See `TranslatorAuditEntry`\n * and issue for the rationale.\n */\n translatorAuditLog(): readonly TranslatorAuditEntry[] {\n return [...this._translatorAuditLog]\n }\n\n /**\n * Invoke the configured `plaintextTranslator` (or serve from cache).\n * Records one `TranslatorAuditEntry` per call regardless of cache hit.\n * Called by `Vault` during `put()` for `autoTranslate: true` fields.\n *\n * @internal — not part of the public API surface\n */\n async invokeTranslator(\n text: string,\n from: string,\n to: string,\n field: string,\n collection: string,\n ): Promise<string> {\n const cacheKey = `${field}\\x00${collection}\\x00${from}\\x00${to}\\x00${text}`\n const translatorName = this.options.plaintextTranslatorName ?? 'anonymous'\n\n const cached = this.translatorCache.get(cacheKey)\n if (cached !== undefined) {\n this._translatorAuditLog.push({\n type: 'translator-invocation',\n field,\n collection,\n fromLocale: from,\n toLocale: to,\n translatorName,\n timestamp: new Date().toISOString(),\n cached: true,\n })\n return cached\n }\n\n const result = await this.options.plaintextTranslator!({ text, from, to, field, collection })\n this.translatorCache.set(cacheKey, result)\n this._translatorAuditLog.push({\n type: 'translator-invocation',\n field,\n collection,\n fromLocale: from,\n toLocale: to,\n translatorName,\n timestamp: new Date().toISOString(),\n })\n return result\n }\n\n // ─── Policy gates (issue #9) ──────────────────────────────────\n /**\n * Read the active policy for a vault. Loads from `_meta/policy` on\n * first call; subsequent calls hit the in-memory cache. Throws\n * `ValidationError` if the vault has not been opened.\n */\n async getPolicy(vault: string): Promise<VaultPolicy> {\n if (this.closed) throw new ValidationError('Instance is closed')\n const cached = this.policyCache.get(vault)\n if (cached) return cached\n await this.bootstrapPolicy(vault)\n return this.policyCache.get(vault) ?? PERSONAL_POLICY\n }\n\n /**\n * Replace the policy document at `_meta/policy` and update the\n * in-memory cache. Gated by the `enroll-user` policy (a policy\n * change is fundamentally a privilege-management action).\n */\n async updatePolicy(vault: string, override: Partial<VaultPolicy>): Promise<VaultPolicy> {\n if (this.closed) throw new ValidationError('Instance is closed')\n const current = await this.getPolicy(vault)\n const merged = mergePolicy(current, override)\n if (this.options.encrypt !== false) {\n await saveVaultPolicy(this.options.store, vault, merged)\n }\n this.policyCache.set(vault, merged)\n return merged\n }\n\n /**\n * Read the current vault-level user-directory toggle (#122). Returns\n * the default-on shape (`{ enabled: true }`) when no `_meta/directory`\n * document has been persisted yet.\n *\n * No role gate — anyone who can open the vault can read the toggle.\n */\n async getDirectoryEnabled(vault: string): Promise<boolean> {\n if (this.closed) throw new ValidationError('Instance is closed')\n const persisted = await readDirectoryConfig(this.options.store, vault)\n return persisted?.enabled ?? true\n }\n\n /**\n * Toggle the vault's user-directory listing on or off (#122).\n * Owner-only. When disabled, `listUsersWithEnvelopes()` throws\n * {@link import('./errors.js').DirectoryDisabledError} for callers\n * whose role is neither `owner` nor `admin`.\n *\n * Honest caveat: this is a UX flag, not a privacy guarantee. The\n * keyring file at `_keyring/<userId>` and the envelope ciphertext at\n * `_users/<keyringId>` remain observable to anyone with direct store\n * read access — only the hub-level enumeration is gated. See\n * `docs/subsystems/user-envelope.md` → \"Directory visibility\".\n */\n async setDirectoryEnabled(vault: string, enabled: boolean): Promise<void> {\n if (this.closed) throw new ValidationError('Instance is closed')\n const keyring = await this.getKeyringInternal(vault)\n if (keyring.role !== 'owner') {\n throw new PermissionDeniedError(\n `setDirectoryEnabled requires owner role; caller has role \"${keyring.role}\"`,\n )\n }\n await persistDirectoryConfig(this.options.store, vault, { enabled })\n }\n\n /**\n * Evaluate a policy gate against the active session tier and the\n * presented factor proofs. Throws {@link PolicyDeniedError} on\n * denial; resolves with `void` on success.\n *\n * @param vault The vault whose policy applies.\n * @param gate Gate name — built-in (e.g. `'rotate-passphrase'`)\n * or app-defined (`app:*`).\n * @param presented Caller-supplied factor proofs.\n */\n async checkGate(\n vault: string,\n gate: GateName,\n factors?: FactorProofBundle,\n ): Promise<void> {\n const policy = await this.getPolicy(vault)\n const tier = this.activeTier.get(vault) ?? 1\n await policyCheckGate(policy, gate, {\n activeTier: tier,\n ...(factors?.factors !== undefined ? { factors: factors.factors } : {}),\n ...(factors?.sharedDevice !== undefined\n ? { sharedDevice: factors.sharedDevice }\n : {}),\n })\n }\n\n /** Read or persist the vault policy at `_meta/policy` on first open. */\n private async bootstrapPolicy(\n vault: string,\n opts?: { skipManagedCheck?: boolean },\n ): Promise<void> {\n const onDisk = await loadVaultPolicy(this.options.store, vault)\n if (onDisk) {\n // Honour the on-disk document; developer overrides cannot\n // weaken what the vault committed to at creation time.\n this.policyCache.set(vault, onDisk)\n await this.assertRecoveryEnrolled(vault, onDisk, opts)\n return\n }\n // First time — persist the developer's policy (or default preset).\n const initial = this.options.policy\n ? mergePolicy(PERSONAL_POLICY, this.options.policy)\n : PERSONAL_POLICY\n await saveVaultPolicy(this.options.store, vault, initial)\n this.policyCache.set(vault, initial)\n await this.assertRecoveryEnrolled(vault, initial, opts)\n }\n\n /**\n * Throw {@link RecoveryNotEnrolledError} or\n * {@link ManagedRecoveryNotEnrolledError} when recovery enrollment\n * is missing.\n *\n * Two enforcement modes:\n *\n * 1. **Managed-mode mandatory strong-recovery (#195).** When\n * `passphraseMode === 'managed'`, the vault MUST have at least\n * one **strong** recovery profile (Shamir today). Paper alone is\n * rejected because under managed mode the user has no memorized\n * passphrase, so losing the paper sheet = losing every record.\n * This check is unconditional — independent of `requireRecovery`\n * and the `recover-passphrase` gate.\n *\n * 2. **Opt-in strict mandatory-recovery.** When\n * `requireRecovery: true` is set on createNoydb (and the gate is\n * not explicitly disabled), require ANY recovery profile (paper\n * or shamir). This is the v0.x default-off behavior; v1.0 may\n * flip it default-on.\n *\n * The managed-mode check fires from {@link bootstrapPolicy} unless\n * the `skipManagedCheck` flag is set (used by\n * {@link openVaultAndEnrollRecovery} to allow atomic create-and-enroll).\n */\n private async assertRecoveryEnrolled(\n vault: string,\n policy: VaultPolicy,\n opts?: { skipManagedCheck?: boolean },\n ): Promise<void> {\n const skipManaged = (opts?.skipManagedCheck ?? false) || this._skipNextManagedRecoveryCheck\n if (this.options.passphraseMode === 'managed' && !skipManaged) {\n const enrolled = await hasStrongRecoveryEnrolled(this.options.store, vault)\n if (!enrolled) {\n throw new ManagedRecoveryNotEnrolledError(vault)\n }\n }\n if (this.options.requireRecovery !== true) return\n const gate = policy.gates['recover-passphrase']\n if (gate?.enabled === false) return\n const enrolled = await hasRecoveryEnrolled(this.options.store, vault)\n if (enrolled) return\n throw new RecoveryNotEnrolledError()\n }\n\n /**\n * Internal accessor used by tier-2/tier-3 unlock paths (issue #11)\n * to mark the active session tier.\n * @internal\n */\n _setActiveTier(vault: string, tier: ActiveTier): void {\n this.activeTier.set(vault, tier)\n }\n\n // ─── Tier-2 enroll / remove (issue #11) ────────────────────────\n /**\n * Add a tier-2 authenticator slot to the calling user's keyring.\n * Each slot independently wraps the SAME KEK under a method-specific\n * key — adding a slot is a constant-time keyring write.\n *\n * The wrapping ciphertext is produced by the corresponding\n * `@noy-db/on-*` package (e.g. `enrollPasswordAuthenticator` from\n * `@noy-db/on-password`); the hub persists the result.\n *\n * Gated by `enroll-authenticator`; `presented` carries any factor\n * proofs the active policy demands.\n */\n async enrollAuthenticator(\n vault: string,\n options: EnrollAuthenticatorOptions,\n factors?: FactorProofBundle,\n ): Promise<void> {\n await this.checkGate(vault, 'enroll-authenticator', factors)\n const keyring = await this.getKeyringInternal(vault)\n const next = await keyringEnrollAuthenticator(this.options.store, vault, keyring, options)\n this.keyringCache.set(vault, next)\n }\n\n /**\n * Remove a tier-2 authenticator slot. Idempotent — removing a\n * non-existent slot is a successful no-op. Gated by\n * `remove-authenticator`.\n */\n async removeAuthenticator(\n vault: string,\n slotId: string,\n factors?: FactorProofBundle,\n ): Promise<void> {\n await this.checkGate(vault, 'remove-authenticator', factors)\n const keyring = await this.getKeyringInternal(vault)\n const next = await keyringRemoveAuthenticator(this.options.store, vault, keyring, slotId)\n this.keyringCache.set(vault, next)\n }\n\n /** Read the slot list for a vault. Internal — `describeAuthConfig` (#13) consumes this. */\n async listAuthenticators(vault: string): Promise<ReadonlyArray<KeyringAuthenticator>> {\n const keyring = await this.getKeyringInternal(vault)\n return keyring.authenticators\n }\n\n /**\n * Mutate the `meta` blob on an existing authenticator slot — slot\n * rename, label change, attachment of UI hints. The slot's `id`,\n * `method`, and wrap material (`wrapped_kek` / `wrapped_deks` + `iv`)\n * are immutable through this method. Anti-slot-swap is structural,\n * not gate-driven.\n *\n * `meta` patch semantics (#57-aligned):\n * - Top-level merge — absent keys preserved\n * - `null` value — delete that meta key\n * - Other values — replace verbatim\n *\n * Use case: per-slot nickname for \"iPhone Touch ID\" vs \"MacBook\n * Touch ID\" disambiguation in admin UIs. The slot id (auto-derived\n * from credentialId prefix) is not human-friendly; `meta.nickname`\n * is.\n *\n * Gated by `update-authenticator`. PERSONAL_POLICY: tier-1 unlock\n * alone (matches enroll/remove). STRICT_POLICY: tier-1 +\n * TOTP/email-OTP factor proof — a malicious rename on a shared\n * workstation could mislead the user about which device a slot\n * corresponds to, so STRICT requires fresh factor binding.\n *\n * @throws `NoAccessError` when no slot with the given id exists.\n * @throws `ValidationError` when no patch field is provided.\n *\n * @see #55\n */\n async updateAuthenticator(\n vault: string,\n slotId: string,\n options: UpdateAuthenticatorOptions,\n factors?: FactorProofBundle,\n ): Promise<void> {\n await this.checkGate(vault, 'update-authenticator', factors)\n const keyring = await this.getKeyringInternal(vault)\n const next = await keyringUpdateAuthenticator(this.options.store, vault, keyring, slotId, options)\n this.keyringCache.set(vault, next)\n }\n\n /**\n * Native WebAuthn enrollment using the **real** internal keyring (#16).\n *\n * Why this exists: when a consumer is using `createNoydb({ secret })`,\n * they cannot reach the live `UnlockedKeyring` to feed it to\n * `enrollWebAuthn(keyring, vault, opts)` from `@noy-db/on-webauthn`.\n * Constructing a synthetic keyring (the previous workaround) produces\n * a slot whose `wrapped_kek` references the synthetic payload, not\n * the live session — so `unlockViaAuthenticator()` later replaces the\n * live DEK map with stale wrapped DEKs and every decrypt fails.\n *\n * This method runs `ceremony` with the REAL keyring (still in\n * `keyringCache`). The ceremony performs the WebAuthn enrollment and\n * returns the slot options that hub then persists via the standard\n * tier-2 enrollAuthenticator path.\n *\n * Layering note: hub does not import `@noy-db/on-webauthn` (that\n * would invert the dep graph). The consumer wires it in:\n *\n * ```ts\n * import { enrollWebAuthn } from '@noy-db/on-webauthn'\n *\n * await db.enrollWebAuthn('demo', async (keyring) => {\n * const e = await enrollWebAuthn(keyring, 'demo', { rp: {...} })\n * return {\n * id: `webauthn-${e.credentialId.slice(0, 8)}`,\n * method: 'webauthn',\n * wrapped_kek: e.wrappedPayload,\n * meta: {\n * credentialId: e.credentialId,\n * wrapIv: e.wrapIv,\n * prfUsed: e.prfUsed,\n * beFlag: e.beFlag,\n * requireSingleDevice: e.requireSingleDevice,\n * },\n * }\n * })\n * ```\n *\n * Returns the WebAuthn `credentialId` (extracted from `meta.credentialId`)\n * for the caller's lookup index (a bootstrap vault, a PublicEnvelope,\n * a server-side allowlist).\n *\n * Gated by `enroll-authenticator` like `enrollAuthenticator()` itself.\n *\n * @see #16\n */\n async enrollWebAuthn(\n vault: string,\n ceremony: (keyring: UnlockedKeyring) => Promise<EnrollAuthenticatorOptions>,\n factors?: FactorProofBundle,\n ): Promise<{ credentialId: string }> {\n await this.checkGate(vault, 'enroll-authenticator', factors)\n const keyring = await this.getKeyringInternal(vault)\n const slotOptions = await ceremony(keyring)\n if (slotOptions.method !== 'webauthn') {\n throw new ValidationError(\n `enrollWebAuthn: ceremony returned method \"${slotOptions.method}\"; expected \"webauthn\". ` +\n 'Use db.enrollAuthenticator() for non-webauthn methods.',\n )\n }\n const credentialId = (slotOptions.meta as { credentialId?: unknown }).credentialId\n if (typeof credentialId !== 'string' || credentialId.length === 0) {\n throw new ValidationError(\n 'enrollWebAuthn: ceremony result must include `meta.credentialId` (base64 string). ' +\n 'See @noy-db/on-webauthn enrollWebAuthn() return shape.',\n )\n }\n const next = await keyringEnrollAuthenticator(this.options.store, vault, keyring, slotOptions)\n this.keyringCache.set(vault, next)\n return { credentialId }\n }\n\n /**\n * Filter the slot list to webauthn-method slots only. Useful for\n * \"you have N WebAuthn credentials enrolled\" UI surfaces and for\n * deciding when a new device prompt should appear. Identity is\n * `id` + `enrolled_at`; the `meta.credentialId` (base64) is used by\n * `allowCredentials` at unlock time.\n *\n * @see #16\n */\n async listWebAuthnSlots(vault: string): Promise<ReadonlyArray<{\n id: string\n enrolledAt: string\n credentialId: string\n }>> {\n const keyring = await this.getKeyringInternal(vault)\n return keyring.authenticators\n .filter((a) => a.method === 'webauthn')\n .map((a) => {\n const credentialId = (a.meta as { credentialId?: unknown }).credentialId\n return {\n id: a.id,\n enrolledAt: a.enrolled_at,\n credentialId: typeof credentialId === 'string' ? credentialId : '',\n }\n })\n }\n\n /**\n * Resolve a slot by id, then hand the wrapped-KEK ciphertext + meta\n * to the caller-supplied verifier. The verifier is the\n * `unlockWith*` function from the corresponding `@noy-db/on-*`\n * package, e.g. `unlockWithPassword(slot, password)`.\n *\n * On success, mark the active session tier as 2 — subsequent\n * `checkGate` calls see a tier-2 unlock.\n */\n async unlockViaAuthenticator(\n vault: string,\n slotId: string,\n verify: (slot: KeyringAuthenticator) => Promise<UnlockedKeyring>,\n ): Promise<UnlockedKeyring> {\n const keyring = await this.getKeyringInternal(vault)\n const slot = findAuthenticator(keyring, slotId)\n if (!slot) {\n throw new ValidationError(\n `unlockViaAuthenticator: no slot with id \"${slotId}\" in vault \"${vault}\".`,\n )\n }\n const unlocked = await verify(slot)\n this.keyringCache.set(vault, unlocked)\n this.activeTier.set(vault, 2)\n return unlocked\n }\n\n // ─── Public envelope (docs/subsystems/public-envelope.md) ──────\n /**\n * Set the owner-curated public envelope for a vault. Throws\n * `ValidationError` if the developer did not opt the hub into\n * `publicEnvelope` via `NoydbOptions`, or if the input violates\n * the resolved schema (oversized icon, disallowed MIME, oversized\n * string, unknown field).\n *\n * `createdAt` is set on the first write and preserved on every\n * subsequent write. `updatedAt` is refreshed on every write.\n * `version` is monotonic — increments on every successful write.\n */\n async setPublicEnvelope(\n vault: string,\n input: SetPublicEnvelopeInput,\n ): Promise<PublicEnvelope> {\n if (!this.publicEnvelopeSchema) {\n throw new ValidationError(\n 'setPublicEnvelope: the public-envelope feature is not enabled. ' +\n 'Pass `publicEnvelope: true` (or a schema object) to `createNoydb`.',\n )\n }\n validatePublicEnvelopeInput(input, this.publicEnvelopeSchema)\n\n const now = new Date().toISOString()\n const existing = await loadPublicEnvelope(this.options.store, vault)\n const next: PublicEnvelope = {\n _noydb_public: 1,\n version: (existing?.version ?? 0) + 1,\n ...(existing?.createdAt !== undefined ? { createdAt: existing.createdAt } : { createdAt: now }),\n updatedAt: now,\n ...(input.name !== undefined ? { name: input.name } : (existing?.name !== undefined ? { name: existing.name } : {})),\n ...(input.description !== undefined ? { description: input.description } : (existing?.description !== undefined ? { description: existing.description } : {})),\n ...(input.icon !== undefined ? { icon: input.icon } : (existing?.icon !== undefined ? { icon: existing.icon } : {})),\n ...(input.defaultLocale !== undefined ? { defaultLocale: input.defaultLocale } : (existing?.defaultLocale !== undefined ? { defaultLocale: existing.defaultLocale } : {})),\n }\n await savePublicEnvelope(this.options.store, vault, next)\n return next\n }\n\n /**\n * Read the public envelope for a vault. Returns `undefined` when\n * none has been written. Pass `locale` to resolve any locale-map\n * fields to plain strings; omitting `locale` returns the raw map.\n *\n * Works even when the developer didn't enable\n * `publicEnvelope` — reads are passive and never throw on a\n * missing schema (the envelope is plaintext and exists on disk\n * regardless).\n */\n async getPublicEnvelope(\n vault: string,\n opts: { readonly locale?: string } = {},\n ): Promise<PublicEnvelope | undefined> {\n return fnReadPublicEnvelope(this.options.store, vault, opts)\n }\n\n // ─── Auth introspection (issue #13) ────────────────────────────\n /** English summary of the configured auth model. */\n async describeAuthConfig(vault: string): Promise<string> {\n return fnDescribeAuthConfig(this.options.store, vault)\n }\n\n /** Mermaid `flowchart TB` source for the auth graph. */\n async diagramAuthConfig(vault: string): Promise<string> {\n return fnDiagramAuthConfig(this.options.store, vault)\n }\n\n /**\n * Per-user enrollment summary. Gated by `view-user-auth` (default:\n * disabled). Sanitization is allowlist-based — never renders cred\n * ids, password hashes, secrets, or any field outside the allowlist.\n */\n async describeUserAuth(\n vault: string,\n userId: string,\n factors?: FactorProofBundle,\n ): Promise<string> {\n await this.checkGate(vault, 'view-user-auth', factors)\n return fnDescribeUserAuth(this.options.store, vault, userId)\n }\n\n /** Bulk variant for owner dashboards. Gated by `view-user-auth`. */\n async describeAllUsersAuth(\n vault: string,\n factors?: FactorProofBundle,\n ): Promise<Array<{ userId: string; description: string }>> {\n await this.checkGate(vault, 'view-user-auth', factors)\n return fnDescribeAllUsersAuth(this.options.store, vault)\n }\n\n // ─── Tier-1 change flows (issue #10) ───────────────────────────\n /**\n * Rotate the user's passphrase (user remembers old). Validates the\n * new phrase against the configured `passphrase` policy, runs the\n * `rotate-passphrase` gate, then re-derives + re-wraps every DEK.\n *\n * Tier-2 authenticator slots are dropped — each slot wraps the old\n * KEK and would need its derivation key to be re-presented. Re-enrol\n * via `db.enrollAuthenticator` after rotation. Tracked as a\n * v0.1.0-pre.5 limitation.\n *\n * @throws `WeakPassphraseError` on a weak new phrase.\n * @throws `PolicyDeniedError` when the gate denies (missing factor, …).\n * @throws `InvalidKeyError` when `oldPassphrase` is wrong.\n */\n async rotatePassphrase(\n vault: string,\n input: RotatePassphraseInput,\n factors?: FactorProofBundle,\n ): Promise<void> {\n // Managed-passphrase mode (#14): the user does NOT know the\n // current passphrase (hub generated it and sealed it under the\n // provider). Manual rotation via this method is impossible by\n // construction — surface a clear error rather than fail mid-way\n // with InvalidKeyError once `oldPassphrase` doesn't match the\n // hub-generated one. Recovery-under-managed (which mints a fresh\n // sealed passphrase via the provider) is the supported path; it\n // lands in a follow-up.\n if (this.options.passphraseMode === 'managed') {\n throw new PolicyDeniedError(\n 'rotate-passphrase',\n 'disabled',\n { minTier: 1, enabled: false },\n 'Managed-passphrase mode (#14): the passphrase is hub-generated '\n + 'and sealed under the SealingKeyProvider — there is no '\n + 'plaintext to rotate. Use the recovery flow (follow-up issue) '\n + 'to mint a fresh sealed passphrase.',\n )\n }\n await this.checkGate(vault, 'rotate-passphrase', factors)\n const userId = this.options.user\n const next = await keyringRotatePassphrase(this.options.store, vault, userId, input)\n this.keyringCache.set(vault, next)\n }\n\n /**\n * Reset the passphrase using a recovery proof (user forgot the old).\n * v0.1.0-pre.5 supports the `'paper'` profile end-to-end; the\n * other three profiles throw {@link RecoveryProfileNotImplementedError}.\n *\n * Burns the used recovery entry on success.\n */\n async recoverPassphrase(\n vault: string,\n input: RecoverPassphraseInput,\n factors?: FactorProofBundle,\n ): Promise<RecoverPassphraseResult> {\n await this.checkGate(vault, 'recover-passphrase', factors)\n const userId = this.options.user\n\n // Snapshot the entries BEFORE recovery — the team function burns\n // exactly one entry, so post-recovery `_meta/recovery-paper`\n // contains `entriesBeforeRecovery.length - 1` entries (the ones\n // the user did NOT just consume). Those are what we replace\n // under the auto-rotation logic from #36.\n const entriesBeforeRecovery = await loadPaperRecoveryEntries(this.options.store, vault)\n\n const next = await keyringRecoverPassphrase(this.options.shamirRecovery, this.options.store, vault, userId, input)\n this.keyringCache.set(vault, next)\n\n const rotateRemaining = input.rotateRemainingCodes ?? true\n const remainingAfterBurn = Math.max(0, entriesBeforeRecovery.length - 1)\n if (!rotateRemaining || remainingAfterBurn === 0) {\n return { newCodes: [] }\n }\n\n // Auto-rotate: replace the remaining entries with a fresh set\n // minted under the new keyring's DEKs. Wraps the same DEK set the\n // recovered keyring just got, so the new codes round-trip through\n // a future `db.recoverPassphrase` cleanly.\n //\n // If this step fails (store error mid-mint), we leave the existing\n // post-burn entries in place — the user falls back to the\n // pre-#36 behavior (remaining N-1 codes still valid). Strictly\n // safer than wiping then failing.\n const codeGen = input.codeGenerator ?? generateULID\n const newCodeCount = input.newCodeCount ?? remainingAfterBurn\n const codes: string[] = []\n const newEntries: PaperRecoveryEntry[] = []\n for (let i = 0; i < newCodeCount; i++) {\n const rawCode = codeGen()\n const entry = await mintPaperRecoveryEntry(next.deks, rawCode, generateULID())\n codes.push(rawCode)\n newEntries.push(entry)\n }\n // Single replace-all write — `savePaperRecoveryEntries` overwrites\n // `_meta/recovery-paper` atomically (one envelope `put`).\n await savePaperRecoveryEntries(this.options.store, vault, newEntries)\n\n return { newCodes: codes }\n }\n\n /**\n * Deliberate paper-recovery-code regeneration (#121). User knows their\n * passphrase but wants a fresh sheet — they lost the printout or\n * suspect compromise of the off-site copy.\n *\n * Symmetric to {@link rotatePassphrase} for the recovery profile:\n * gated, audit-trackable, ergonomic. Replaces (not appends) the\n * paper sheet under `_meta/recovery-paper` in a single envelope `put`.\n *\n * Gated by the `rotate-recovery` policy gate:\n * - PERSONAL_POLICY: `{ minTier: 1 }` — knowing the passphrase\n * suffices, matching the pre-#121 low-level flow's bar.\n * - STRICT_POLICY: `{ minTier: 1, factors: [{ anyOf: ['totp',\n * 'email-otp', 'webauthn-roaming'] }] }` — rotation is an\n * off-site-trust event; require an off-device factor so a\n * stolen unlocked laptop cannot silently mint a sheet for the\n * attacker.\n *\n * Defaults `count` to the existing sheet size so consumers aren't\n * surprised by a different code count. Explicit `count` overrides.\n *\n * @throws {@link RecoveryProfileNotImplementedError} when `profile`\n * is anything other than `'paper'` (v1 dispatch limit).\n * @throws {@link PolicyDeniedError} when the gate denies (missing\n * factor, tier mismatch, ...).\n * @throws on missing paper sheet — \"nothing to rotate\" surfaces as\n * an error rather than silently minting an entire new sheet.\n *\n * @example Default count + show-once UI\n * ```ts\n * const { newCodes } = await db.rotateRecovery('acme', { profile: 'paper' })\n * showCodesToUser(newCodes)\n * ```\n *\n * @example STRICT-policy site with TOTP factor proof\n * ```ts\n * await db.rotateRecovery(\n * 'acme',\n * { profile: 'paper', count: 10 },\n * { factors: [{ kind: 'totp', proof: '123456' }] },\n * )\n * ```\n */\n async rotateRecovery(\n vault: string,\n options: RotateRecoveryOptions,\n factors?: FactorProofBundle,\n ): Promise<RotateRecoveryResult> {\n if (options.profile === 'paper') {\n return this.rotateRecoveryPaper(vault, options, factors)\n }\n if (options.profile === 'shamir') {\n return this.rotateRecoveryShamir(vault, options, factors)\n }\n // Defense-in-depth for `as unknown as ...` bypass.\n throw new RecoveryProfileNotImplementedError(\n (options as { profile: string }).profile,\n '#196',\n )\n }\n\n private async rotateRecoveryPaper(\n vault: string,\n options: Extract<RotateRecoveryOptions, { profile: 'paper' }>,\n factors?: FactorProofBundle,\n ): Promise<RotateRecoveryResult> {\n await this.checkGate(vault, 'rotate-recovery', factors)\n\n const existing = await loadPaperRecoveryEntries(this.options.store, vault)\n if (existing.length === 0) {\n throw new Error(\n `db.rotateRecovery: no recovery codes are enrolled for vault \"${vault}\". ` +\n `Call db.enrollRecovery({ profile: 'paper', entries }) first; ` +\n `rotateRecovery replaces an existing sheet rather than minting one from scratch.`,\n )\n }\n\n const keyring = await this.getKeyring(vault)\n const codeGen = options.codeGenerator ?? generateULID\n const count = options.count ?? existing.length\n\n const codes: string[] = []\n const newEntries: PaperRecoveryEntry[] = []\n for (let i = 0; i < count; i++) {\n const rawCode = codeGen()\n const entry = await mintPaperRecoveryEntry(keyring.deks, rawCode, generateULID())\n codes.push(rawCode)\n newEntries.push(entry)\n }\n // Atomic replace — `savePaperRecoveryEntries` overwrites\n // `_meta/recovery-paper` in a single envelope `put`.\n await savePaperRecoveryEntries(this.options.store, vault, newEntries)\n\n return { newCodes: codes, entryId: 'paper-batch' }\n }\n\n private async rotateRecoveryShamir(\n vault: string,\n options: Extract<RotateRecoveryOptions, { profile: 'shamir' }>,\n factors?: FactorProofBundle,\n ): Promise<RotateRecoveryResult> {\n await this.checkGate(vault, 'rotate-recovery', factors)\n\n const existing = await loadShamirRecoveryEntries(this.options.store, vault)\n if (existing.length === 0) {\n throw new Error(\n `db.rotateRecovery: no Shamir recovery entry is enrolled for vault \"${vault}\". ` +\n `Call db.enrollRecovery({ profile: 'shamir', k, n }) first; ` +\n `rotateRecovery replaces an existing entry rather than minting one from scratch.`,\n )\n }\n\n // Pick which entry to rotate.\n let targetEntryId: string\n if (options.entryId !== undefined) {\n const found = existing.find(e => e.entryId === options.entryId)\n if (!found) {\n throw new Error(\n `db.rotateRecovery: no Shamir entry with entryId=\"${options.entryId}\" found `\n + `in vault \"${vault}\". Available: ${existing.map(e => `\"${e.entryId}\"`).join(', ')}.`,\n )\n }\n targetEntryId = options.entryId\n } else {\n if (existing.length > 1) {\n throw new Error(\n `db.rotateRecovery: vault \"${vault}\" has ${existing.length} Shamir entries `\n + `enrolled (${existing.map(e => `\"${e.entryId}\"`).join(', ')}). `\n + `Pass \\`entryId\\` to disambiguate which one to rotate; ambiguous rotation `\n + `would risk replacing the wrong entry.`,\n )\n }\n targetEntryId = existing[0]!.entryId\n }\n\n const keyring = await this.getKeyring(vault)\n const { entry, shareStrings } = await mintShamirRecoveryEntry(\n this.requireShamirProvider(),\n keyring.deks,\n targetEntryId,\n options.k,\n options.n,\n options.label,\n )\n\n // Atomic single-doc replace: drop the old entry, insert the new one.\n const next: ShamirRecoveryEntry[] = existing\n .filter(e => e.entryId !== targetEntryId)\n .concat(entry)\n await saveShamirRecoveryEntries(this.options.store, vault, next)\n\n return { newShares: shareStrings, entryId: targetEntryId }\n }\n\n /**\n * **Atomic create-and-enroll for managed-mode vaults (#195).**\n *\n * Bootstraps a managed-mode vault and enrolls strong recovery in\n * a single ceremony. Under `passphraseMode: 'managed'`, every\n * `openVault` call requires a strong recovery profile (Shamir\n * today) to be enrolled — otherwise it throws\n * {@link ManagedRecoveryNotEnrolledError}. This method bypasses\n * the check temporarily so the keyring can be created, enrolls\n * the supplied recovery profile(s), then returns the vault.\n *\n * For Shamir enrollments, the show-once share strings come back\n * in `recoveryEnrollments[i].shares`. The hub never retains them\n * — the caller MUST display them to the user (once) before any\n * subsequent operation.\n *\n * Paper alone is NOT a strong profile under managed mode; passing\n * `{ profile: 'paper', ... }` without an accompanying shamir entry\n * is rejected at validation time.\n *\n * ```ts\n * const db = await createNoydb({\n * store, user: 'alice',\n * passphraseMode: 'managed',\n * sealingKey: macosKeychainSealingProvider({ ... }),\n * })\n *\n * const { vault, recoveryEnrollments } = await db.openVaultAndEnrollRecovery('acme', {\n * recovery: [{ profile: 'shamir', k: 2, n: 3 }],\n * })\n * for (const r of recoveryEnrollments) {\n * if (r.shares) showSharesToUser(r.shares) // ONCE\n * }\n * ```\n *\n * @throws ValidationError if recovery is empty, or contains no\n * strong profile under managed mode.\n */\n async openVaultAndEnrollRecovery(\n vault: string,\n opts: {\n readonly recovery: ReadonlyArray<RecoveryEnrollmentInput>\n readonly locale?: string\n },\n ): Promise<{\n readonly vault: Vault\n readonly recoveryEnrollments: ReadonlyArray<EnrollRecoveryResult>\n }> {\n if (opts.recovery.length === 0) {\n throw new ValidationError(\n 'openVaultAndEnrollRecovery: at least one recovery enrollment is required.',\n )\n }\n\n // Validate \"at least one strong\" when managed mode is on.\n if (this.options.passphraseMode === 'managed') {\n const hasStrong = opts.recovery.some(r => r.profile === 'shamir')\n if (!hasStrong) {\n throw new ValidationError(\n 'openVaultAndEnrollRecovery: managed-mode vaults require at least one strong '\n + 'recovery profile in the `recovery` array. Paper alone is not strong under '\n + 'managed mode (no user passphrase to fall back on). Include '\n + '{ profile: \"shamir\", k, n } in `recovery`.',\n )\n }\n }\n\n // Temporarily bypass the managed-mode strong-recovery check so\n // openVault can create the keyring. Recovery enrollment happens\n // inside this window; the check is restored at the end.\n this._skipNextManagedRecoveryCheck = true\n let vaultHandle: Vault\n try {\n vaultHandle = await this.openVault(vault, opts.locale !== undefined ? { locale: opts.locale } : undefined)\n } finally {\n this._skipNextManagedRecoveryCheck = false\n }\n\n // Enroll each recovery profile.\n const recoveryEnrollments: EnrollRecoveryResult[] = []\n for (const enrollment of opts.recovery) {\n recoveryEnrollments.push(await this.enrollRecovery(vault, enrollment))\n }\n\n // Belt-and-braces final check — by now, strong recovery must be on disk.\n if (this.options.passphraseMode === 'managed') {\n const policy = this.policyCache.get(vault)\n if (policy) {\n await this.assertRecoveryEnrolled(vault, policy)\n }\n }\n\n return { vault: vaultHandle, recoveryEnrollments }\n }\n\n /**\n * **Recovery flow under managed-passphrase mode (#195).**\n *\n * Replaces the sealed passphrase of a managed-mode vault with a\n * fresh 256-bit random, sealed under the configured\n * `SealingKeyProvider`. The user never sees the new passphrase.\n *\n * Internally:\n * 1. Verify the recovery proof (Shamir today) and unwrap the\n * DEK set.\n * 2. Mint a fresh 256-bit random as the new effective passphrase.\n * 3. Rewrap the DEK set under a fresh KEK derived from the new\n * passphrase (via the existing `recoverPassphrase` path).\n * 4. Seal the random bytes under the provider and overwrite\n * `_meta/sealed-passphrase`.\n * 5. Drop the keyring cache so the next operation re-derives.\n *\n * The vault's strong-recovery enrollment is preserved across\n * recovery (Shamir entries are not burned on use — see #196).\n *\n * @throws ValidationError if the Noydb instance is not in managed mode.\n */\n async recoverManagedPassphrase(\n vault: string,\n options: {\n readonly recoveryProof: RecoveryProof\n readonly passphrasePolicy?: PassphrasePolicy\n },\n ): Promise<void> {\n if (this.options.passphraseMode !== 'managed') {\n throw new ValidationError(\n 'recoverManagedPassphrase: this method only applies to vaults opened '\n + 'in managed-passphrase mode. For standard mode, use db.recoverPassphrase.',\n )\n }\n const provider = this.options.sealingKey\n if (!provider) {\n throw new ValidationError(\n 'recoverManagedPassphrase: createNoydb({ passphraseMode: \"managed\" }) requires '\n + '`sealingKey` to be supplied; without it the new sealed passphrase cannot '\n + 'be persisted.',\n )\n }\n\n // Mint fresh 256-bit random; base64 it for use as the new\n // effective passphrase. AES-GCM auth-tag failures in the\n // managed-mode envelope catch tampering.\n const randomBytes = new Uint8Array(32)\n globalThis.crypto.getRandomValues(randomBytes)\n let binary = ''\n for (let i = 0; i < randomBytes.length; i++) binary += String.fromCharCode(randomBytes[i]!)\n const newPassphrase = btoa(binary)\n\n try {\n // Seal first; if the provider fails (KMS down, keychain locked),\n // we don't touch the keyring. Then run recoverPassphrase which\n // rewraps DEKs under the new KEK derived from the random bytes.\n const sealed = await provider.seal(randomBytes)\n await keyringRecoverPassphrase(\n this.options.shamirRecovery,\n this.options.store,\n vault,\n this.options.user,\n {\n newPassphrase,\n recoveryProof: options.recoveryProof,\n // The new passphrase IS 256 bits of random; policy gates on\n // length/entropy don't apply.\n allowWeakPassphrase: true,\n ...(options.passphrasePolicy !== undefined\n ? { passphrasePolicy: options.passphrasePolicy }\n : {}),\n },\n )\n // Update _meta/sealed-passphrase with the freshly sealed random.\n // The previous envelope is overwritten by saveSealedPassphrase.\n await saveSealedPassphrase(this.options.store, vault, {\n providerId: provider.id,\n sealed,\n })\n } finally {\n // Best-effort zero of the in-memory random buffer.\n randomBytes.fill(0)\n }\n\n // Drop the keyring cache so the next openVault re-derives from\n // the new sealed envelope.\n this.keyringCache.delete(vault)\n }\n\n /**\n * Atomic peer-recovery — re-wraps an EXISTING user's keyring under\n * a fresh temp passphrase in a single store write. Closes #34's\n * partial-failure window (the previous compose-from-primitives\n * pattern was `db.revoke + db.grant`, two writes — if the issuer\n * cancelled between them the target was locked out entirely).\n *\n * Different from `db.revoke + db.grant`:\n *\n * - Same `userId`, role, permissions, capabilities preserved.\n * - DEKs unchanged → every other principal in the vault keeps\n * access. No key rotation.\n * - Allows owner→owner natively (#33). The existing\n * `db.revoke` retains its block — peer-recovery is a separate,\n * intentionally-named operation.\n * - Tier-2 slots dropped (they wrap the old KEK).\n *\n * Gated by `peer-recover-user`; `STRICT_POLICY` requires a\n * recovery / TOTP / email-OTP factor proof at the moment of\n * recovery, so the issuer affirmatively re-asserts identity.\n *\n * The recipient should call `db.rotatePassphrase` on first session\n * to choose their own phrase — the temp acts as a single-use\n * bridge.\n *\n * ```ts\n * await db.recoverUser('acme', {\n * userId: 'bob',\n * passphrase: 'temporary-correct-horse-battery-staple-printer',\n * }, { factors: [{ kind: 'recovery' }] })\n * // Bob opens createNoydb({ user: 'bob', secret: tempPhrase })\n * // and immediately calls db.rotatePassphrase to set his own.\n * ```\n *\n * @throws `NoAccessError` when no keyring exists for the target.\n * @throws `PermissionDeniedError` when the caller's role can't\n * recover the target's role (admin→owner is blocked even\n * under recovery).\n * @throws `PrivilegeEscalationError` when the caller lacks a DEK\n * the target previously had access to.\n *\n * @see #33 #34 — the issues this method closes.\n */\n async recoverUser(\n vault: string,\n options: RecoverUserOptions,\n factors?: FactorProofBundle,\n ): Promise<void> {\n await this.checkGate(vault, 'peer-recover-user', factors)\n const callerKeyring = await this.getKeyringInternal(vault)\n await keyringRecoverUser(this.options.store, vault, callerKeyring, options)\n // If the caller is recovering THEIR OWN keyring (rare but\n // possible — e.g. a self-recovery flow that bypasses the password\n // ceremony), the keyringCache entry is now stale. Drop it so the\n // next access reloads with the fresh wrapping.\n if (options.userId === this.options.user) {\n this.keyringCache.delete(vault)\n }\n }\n\n /**\n * Persist a recovery enrollment. v0.1.0-pre.5 accepts the `'paper'`\n * profile.\n *\n * The hub wraps the user's DEK set (not the KEK) under a code-derived\n * AES-GCM key — see `team/recovery.ts` for the rationale. The mint\n * helper {@link mintPaperRecoveryEntry} is the canonical primitive;\n * pair it with `db.getKeyring(vault)` to obtain the live DEK set:\n *\n * ```ts\n * import { mintPaperRecoveryEntry } from '@noy-db/hub'\n *\n * const keyring = await db.getKeyring('acme')\n * const codes: string[] = ['CORRECT-HORSE-1', 'BATTERY-STAPLE-2', ...]\n * const entries = await Promise.all(\n * codes.map((code, i) => mintPaperRecoveryEntry(keyring.deks, code, `code-${i}`)),\n * )\n * await db.enrollRecovery('acme', { profile: 'paper', entries })\n * showCodesToUser(codes)\n * ```\n *\n * As of pre.8, `@noy-db/on-recovery`'s `generateRecoveryCodeSet`\n * delegates to `mintPaperRecoveryEntry` internally — its output is\n * fed directly to this API. Pick whichever fits your code-gen layer:\n *\n * ```ts\n * import { generateRecoveryCodeSet } from '@noy-db/on-recovery'\n * const { codes, entries } = await generateRecoveryCodeSet({ deks: keyring.deks, count: 8 })\n * await db.enrollRecovery('acme', { profile: 'paper', entries })\n * ```\n */\n async enrollRecovery(\n vault: string,\n enrollment: RecoveryEnrollmentInput,\n ): Promise<EnrollRecoveryResult> {\n if (enrollment.profile === 'paper') {\n const existing = await loadPaperRecoveryEntries(this.options.store, vault)\n await savePaperRecoveryEntries(this.options.store, vault, [\n ...existing,\n ...enrollment.entries,\n ])\n // Paper enrollments don't have a single entryId — callers\n // pre-mint with their own ids. Return a stable sentinel so the\n // result type is consistent for both profiles.\n return { entryId: 'paper-batch' }\n }\n if (enrollment.profile === 'shamir') {\n const keyring = await this.getKeyring(vault)\n const entryId = enrollment.entryId ?? generateULID()\n const { entry, shareStrings } = await mintShamirRecoveryEntry(\n this.requireShamirProvider(),\n keyring.deks,\n entryId,\n enrollment.k,\n enrollment.n,\n enrollment.label,\n )\n const existing = await loadShamirRecoveryEntries(this.options.store, vault)\n // If a Shamir entry with this id already exists, replace it\n // (allows callers to be idempotent on `entryId`); otherwise append.\n const next: ShamirRecoveryEntry[] = existing.filter(e => e.entryId !== entryId).concat(entry)\n await saveShamirRecoveryEntries(this.options.store, vault, next)\n return { entryId, shares: shareStrings }\n }\n // Defense-in-depth for `as unknown as ...` bypass at the call site.\n throw new RecoveryProfileNotImplementedError(\n (enrollment as { profile: string }).profile,\n '#196',\n )\n }\n\n /** Read the persisted recovery entries (paper + Shamir). Used by `describeAuthConfig` (#13). */\n async listRecoveryEntries(\n vault: string,\n ): Promise<{\n paper: ReadonlyArray<PaperRecoveryEntry>\n shamir: ReadonlyArray<ShamirRecoveryEntry>\n }> {\n const paper = await loadPaperRecoveryEntries(this.options.store, vault)\n const shamir = await loadShamirRecoveryEntries(this.options.store, vault)\n return { paper, shamir }\n }\n\n // ─── Tier-3 enroll / unlock (issue #11) ────────────────────────\n /**\n * Register a tier-3 quick-unlock state for the vault. The state is\n * an opaque blob produced by `@noy-db/on-pin/enrollPin` (or any\n * compatible primitive). It is held in memory only — never persisted\n * — and auto-clears when its `expiresAt` elapses.\n *\n * Gated by `rotate-unlock` (the same gate covers \"set\" and \"rotate\"\n * because tier-3 is a single-slot rolling secret).\n */\n async enrollUnlock(\n vault: string,\n state: QuickUnlockState,\n factors?: FactorProofBundle,\n ): Promise<void> {\n await this.checkGate(vault, 'rotate-unlock', factors)\n this.quickUnlock.set(vault, state)\n }\n\n /**\n * Resume a session via the registered tier-3 state. The verifier is\n * `@noy-db/on-pin/resumePin` (or compatible). On success, mark the\n * active session tier as 3 — every operation must re-authenticate at\n * tier 2 to elevate.\n *\n * Returns `undefined` (caller should fall back to tier 2) when no\n * tier-3 state is registered.\n */\n async unlockViaPin(\n vault: string,\n resume: (state: QuickUnlockState) => Promise<UnlockedKeyring>,\n ): Promise<UnlockedKeyring | undefined> {\n const state = this.quickUnlock.get(vault)\n if (!state) return undefined\n const keyring = await resume(state)\n this.keyringCache.set(vault, keyring)\n this.activeTier.set(vault, 3)\n return keyring\n }\n\n /** Drop the tier-3 state for a vault — explicit logout. */\n clearQuickUnlock(vault: string): void {\n this.quickUnlock.delete(vault)\n }\n\n /**\n * Public accessor for the unlocked keyring of a vault — issue #28.\n *\n * Returns a **defensive shallow copy** so consumers can read the DEK\n * map and authenticator list without the risk of mutating the hub's\n * internal cache (#88). Internal hub code paths use a live reference\n * via `getKeyringInternal`; ceremonies and external consumers always\n * get a snapshot.\n *\n * The CryptoKey values inside `deks` are not cloned — Web Crypto\n * keys are opaque handles, and a shared handle is intentional\n * (encrypt / decrypt go through the same key the cache holds).\n * Only the container Map / authenticator array is fresh.\n *\n * Used by `@noy-db/on-*` ceremonies that need the live DEK set\n * (paper recovery via {@link mintPaperRecoveryEntry}, tier-3 PIN\n * enrolment via on-pin's `enrollPin`, custom on-* ceremonies that\n * don't have a hub-side wrapper).\n *\n * No new permission gate — this is an accessor over already-unlocked\n * state. The keyring is materialized only after the calling session\n * has unlocked the vault at tier 1, 2, or 3, so exposing it does not\n * widen access. Throws `ValidationError` when encryption is enabled\n * and no `secret` / `getKeyring` is configured.\n *\n * ```ts\n * const keyring = await db.getKeyring('acme')\n * // keyring.deks: Map<collection, CryptoKey>\n * // keyring.kek: CryptoKey | null (null for tier-3 / wrap-DEKs sessions)\n * // keyring.role / .permissions / .authenticators\n * ```\n */\n async getKeyring(vault: string): Promise<UnlockedKeyring> {\n const live = await this.getKeyringInternal(vault)\n // Deep-ish defensive copy. Each container the consumer might\n // reasonably mutate is freshly cloned. CryptoKey handles inside\n // `deks` are intentionally shared — they're opaque references that\n // both encrypt and decrypt go through. `salt` (Uint8Array) is left\n // as-is: no realistic mutation path. (#88, extended in #114.)\n return {\n ...live,\n deks: new Map(live.deks),\n permissions: { ...live.permissions },\n authenticators: live.authenticators.map((a) => ({\n ...a,\n meta: { ...a.meta },\n })),\n ...(live.policy !== undefined ? { policy: { ...live.policy } } : {}),\n ...(live.exportCapability !== undefined\n ? { exportCapability: { ...live.exportCapability } }\n : {}),\n ...(live.importCapability !== undefined\n ? { importCapability: { ...live.importCapability } }\n : {}),\n }\n }\n\n /**\n * Live-reference variant used by the hub's own code paths. Internal\n * mutations on `deks` (e.g. {@link ensureCollectionDEK} adding a\n * collection key) need to land on the cached keyring so subsequent\n * accesses see them. Not exposed publicly — callers outside hub\n * should use {@link getKeyring}, which returns a defensive copy.\n */\n private async getKeyringInternal(vault: string): Promise<UnlockedKeyring> {\n if (this.options.encrypt === false) {\n return createPlaintextKeyring(this.options.user)\n }\n\n const cached = this.keyringCache.get(vault)\n if (cached) return cached\n\n // Custom unlock path (e.g. WebAuthn / OIDC / Shamir): caller-supplied\n // callback owns \"open existing vs create new\" — no automatic NoAccessError\n // fallback because the callback owner has the UI context for that choice.\n if (this.options.getKeyring) {\n const keyring = await this.options.getKeyring(vault)\n this.keyringCache.set(vault, keyring)\n return keyring\n }\n\n // Managed-passphrase mode (#14) — resolve the effective secret\n // before falling into the normal load/create path. The first call\n // mints + seals + persists; subsequent calls unseal what's there.\n // The returned string takes the place of `options.secret` for the\n // rest of this method (and is NOT persisted on `this.options`).\n let effectiveSecret: string | undefined\n if (this.options.passphraseMode === 'managed') {\n // sealingKey presence was validated at createNoydb time.\n \n effectiveSecret = await resolveManagedSecret(\n this.options.store,\n vault,\n this.options.sealingKey!,\n )\n } else {\n effectiveSecret = this.options.secret\n }\n\n if (!effectiveSecret) {\n throw new ValidationError('A secret (passphrase) or getKeyring callback is required when encryption is enabled')\n }\n\n let keyring: UnlockedKeyring\n try {\n keyring = await loadKeyring(this.options.store, vault, this.options.user, effectiveSecret)\n } catch (err) {\n if (err instanceof NoAccessError) {\n // No keyring on disk — first boot or cleared store.\n keyring = await createOwnerKeyring(\n this.options.store,\n vault,\n this.options.user,\n effectiveSecret,\n {\n // Managed mode generates 256-bit base64 strings that don't satisfy\n // the human-passphrase strength rules (no spaces, no \"words\").\n // Skip validation in managed mode — the entropy floor is already\n // 256 bits by construction.\n validate: this.options.passphraseMode === 'managed'\n ? false\n : this.options.validatePassphrase === true,\n },\n )\n } else if (err instanceof InvalidKeyError && this.options.onInvalidKey === 'reset') {\n // Stale keyring: exists in the store but the current credentials can't\n // decrypt it (e.g. the data records were cleared while the _keyring row\n // survived, or a WebAuthn credential was rotated between sessions).\n // The caller opted into reset — delete the stale row and start fresh.\n await this.options.store.delete(vault, '_keyring', this.options.user)\n keyring = await createOwnerKeyring(\n this.options.store,\n vault,\n this.options.user,\n effectiveSecret,\n {\n validate: this.options.passphraseMode === 'managed'\n ? false\n : this.options.validatePassphrase === true,\n },\n )\n } else {\n throw err\n }\n }\n\n this.keyringCache.set(vault, keyring)\n return keyring\n }\n}\n\n/** Create a new NOYDB instance. */\nexport async function createNoydb(options: NoydbOptions): Promise<Noydb> {\n const encrypted = options.encrypt !== false\n const managed = options.passphraseMode === 'managed'\n\n if (options.secret && options.getKeyring) {\n throw new ValidationError('Provide either `secret` or `getKeyring`, not both')\n }\n\n // Managed-passphrase mode (#14) — mutually exclusive with both\n // `secret` (the whole point is hub generates and seals; the user\n // doesn't supply one) and `getKeyring` (a custom unlock path that\n // bypasses the sealing flow entirely). Requires a SealingKeyProvider.\n if (managed) {\n if (options.secret) {\n throw new ValidationError(\n '`passphraseMode: \"managed\"` is mutually exclusive with `secret` — '\n + 'managed mode generates the passphrase itself. Drop `secret`.',\n )\n }\n if (options.getKeyring) {\n throw new ValidationError(\n '`passphraseMode: \"managed\"` is mutually exclusive with `getKeyring` — '\n + 'a custom unlock callback would bypass the sealing flow. Drop `getKeyring`.',\n )\n }\n if (!options.sealingKey) {\n throw new ValidationError(\n '`passphraseMode: \"managed\"` requires `sealingKey: SealingKeyProvider` '\n + '(see @noy-db/seal-macos-keychain / @noy-db/seal-aws-kms / etc.).',\n )\n }\n }\n\n if (encrypted && !managed && !options.secret && !options.getKeyring) {\n throw new ValidationError('A secret (passphrase) or getKeyring callback is required when encryption is enabled')\n }\n\n return new Noydb(options)\n}\n\n// ─── Internal helpers ─────────────────────────────────────────────────\n\n/**\n * Normalize `NoydbOptions.sync` to a `SyncTarget[]`.\n * Accepts a bare NoydbStore, a SyncTarget, or an array.\n */\nfunction normalizeSyncTargets(\n sync: NoydbOptions['sync'],\n): SyncTarget[] {\n if (!sync) return []\n if (Array.isArray(sync)) return sync\n // SyncTarget has a `role` property; bare NoydbStore does not\n if ('role' in sync && typeof sync.role === 'string') {\n return [sync]\n }\n // Bare NoydbStore — wrap as sync-peer\n return [{ store: sync as NoydbStore, role: 'sync-peer' }]\n}\n","/**\n * @noy-db/hub/bundle — opt-in .noydb container format subsystem.\n *\n * @category capability\n *\n * The `.noydb` binary wrapper around `vault.dump()` for safe\n * cloud-storage drops: 10-byte magic prefix + JSON header +\n * compressed body. Consumers that don't export/import bundles can\n * omit this subpath and save ~805 LOC of format code + Brotli/gzip\n * wiring.\n */\n\nexport {\n writeNoydbBundle,\n readNoydbBundle,\n readNoydbBundleHeader,\n resetBrotliSupportCache,\n} from './bundle.js'\nexport type {\n WriteNoydbBundleOptions,\n ReadNoydbBundleOptions,\n NoydbBundleReadResult,\n} from './bundle.js'\n\nexport {\n NOYDB_BUNDLE_MAGIC,\n NOYDB_BUNDLE_PREFIX_BYTES,\n NOYDB_BUNDLE_FORMAT_VERSION,\n FLAG_COMPRESSED,\n FLAG_HAS_INTEGRITY_HASH,\n COMPRESSION_NONE,\n COMPRESSION_GZIP,\n COMPRESSION_BROTLI,\n validateBundleHeader,\n encodeBundleHeader,\n} from './format.js'\nexport type {\n CompressionAlgo,\n NoydbBundleHeader,\n} from './format.js'\n\nexport { generateULID, isULID } from './ulid.js'\n\n// ─── Partition extraction (#198 epic) ───────────────────\nexport { walkClosure } from './walk-closure.js'\nexport type { WalkClosureOptions, ClosureResult } from './walk-closure.js'\nexport { describeExtraction } from './describe-extraction.js'\nexport type { ExtractionPreview } from './describe-extraction.js'\nexport { extractPartition } from './extract-partition.js'\nexport type { ExtractPartitionResult } from './extract-partition.js'\nexport { adoptPartition, unsealDeks, createOwnerOnAdoptedPartition } from './adopt-partition.js'\nexport type {\n AdoptPartitionOptions,\n AdoptPartitionResult,\n CreateOwnerResult,\n CreateOwnerOptions,\n CreateOwnerStandardOptions,\n CreateOwnerManagedOptions,\n} from './adopt-partition.js'\nexport { TransferSealError, AdoptionStateError } from '../errors.js'\n\n// ─── Bundle / backup errors ─────────────────────────────\n// Re-exported from the central errors module so subpath consumers can\n// `instanceof BundleIntegrityError` without falling back to the root barrel.\nexport {\n BundleIntegrityError,\n BundleSealMismatchError,\n BundleVersionConflictError,\n BackupLedgerError,\n BackupCorruptedError,\n PartitionExtractionError,\n} from '../errors.js'\n","/**\n * `.noydb` container format — byte layout, header schema, validators.\n *\n *. Wraps a `vault.dump()` JSON string in a thin\n * binary container with a magic-byte prefix, a minimum-disclosure\n * unencrypted header, and a compressed body.\n *\n * **Byte layout** (read in order from offset 0):\n *\n * ```\n * +--------+--------+--------+--------+\n * | N=78 | D=68 | B=66 | 1=49 | Magic 'NDB1' (4 bytes)\n * +--------+--------+--------+--------+\n * | flags | compr | header_length (uint32 BE) |\n * +--------+--------+--------+--------+--------+--------+--------+\n * | header_length bytes of UTF-8 JSON header ...\n * +--------+--------+\n * | compressed body bytes ...\n * ```\n *\n * Total fixed prefix before the header JSON is **10 bytes**:\n * - 4 bytes magic\n * - 1 byte flags\n * - 1 byte compression algorithm\n * - 4 bytes header length (uint32 big-endian)\n *\n * **Why a binary container** at all? `vault.dump()` already\n * produces a JSON string with encrypted records inside. Wrapping it\n * again seems redundant — but the wrap is what makes the file safe\n * to drop into cloud storage (Drive, Dropbox, iCloud) without\n * leaking the vault name and exporter identity through the\n * cloud's metadata API. The minimum-disclosure header is the only\n * thing visible without downloading and decompressing the body.\n * The dump JSON inside the body still contains the original\n * metadata, but that's only readable by someone who already has the\n * file bytes — the same person who could read the encrypted records\n * with the right passphrase.\n *\n * **Why minimum disclosure** in the header? Because consumers will\n * inevitably store these in services where the filename, file size,\n * and any unencrypted metadata are indexed for search. A field like\n * `vault: \"Acme Corp\"` would let an attacker (or a curious\n * cloud admin) enumerate which compartments exist and who exported\n * them, even with zero access to the encrypted body. The header\n * carries only what's needed to identify the file as a NOYDB\n * bundle and verify its integrity — nothing about the contents.\n */\n\nimport type { PublicEnvelope } from '../meta/public-envelope/types.js'\n\n/** Magic bytes 'NDB1' (ASCII), identifying a NOYDB bundle. */\nexport const NOYDB_BUNDLE_MAGIC = new Uint8Array([0x4e, 0x44, 0x42, 0x31])\n\n/** Total fixed prefix before the header JSON: 4+1+1+4 bytes. */\nexport const NOYDB_BUNDLE_PREFIX_BYTES = 10\n\n/** Current bundle format version. Bumped on layout changes. */\nexport const NOYDB_BUNDLE_FORMAT_VERSION = 1\n\n/**\n * Bitfield interpretation of the flags byte.\n *\n * Bit 0 — body is compressed (0 = raw, 1 = compressed)\n * Bit 1 — header carries an integrity hash over the body bytes\n * Bits 2-7 — reserved, must be 0 in\n */\nexport const FLAG_COMPRESSED = 0b0000_0001\nexport const FLAG_HAS_INTEGRITY_HASH = 0b0000_0010\n\n/**\n * Compression algorithm encoding for the byte at offset 5.\n *\n * `none` is admitted for round-trip testing and for callers that\n * want to bundle without compression (e.g. when piping into a\n * separately compressed transport). `gzip` is the universally\n * available baseline (Node 18+, all modern browsers). `brotli` is\n * preferred when the runtime supports it — typically 30-50% smaller\n * for JSON payloads — but Node 22+ / Chrome 124+ / Firefox 122+\n * are required, so the writer feature-detects at runtime and falls\n * back to gzip. The reader must handle all three.\n */\nexport const COMPRESSION_NONE = 0\nexport const COMPRESSION_GZIP = 1\nexport const COMPRESSION_BROTLI = 2\n\nexport type CompressionAlgo = 0 | 1 | 2\n\n/**\n * The unencrypted header carried in every `.noydb` bundle.\n *\n * **Minimum-disclosure rules:** these are the ONLY allowed keys.\n * Any other key in a parsed header causes\n * `validateBundleHeader` to throw. The set is kept short to\n * minimize attack surface from cloud-storage metadata indexing —\n * see the file-level doc comment for the rationale.\n *\n * Forbidden in particular:\n * - `vault` / `_compartment` — would leak the tenant name\n * - `exporter` / `_exported_by` — would leak user identity\n * - `timestamp` / `_exported_at` — would leak activity timing\n * - `kdfParams` / salt fields — would leak crypto config that\n * could narrow brute-force search space\n * - any field starting with `_` (reserved by the dump format)\n */\nexport interface NoydbBundleHeader {\n /** Bundle format version — bumped on layout changes. */\n readonly formatVersion: number\n /**\n * Opaque ULID identifier — generated once per vault and\n * stable across re-exports of the same vault. Does not\n * leak any information about contents (the timestamp prefix is\n * just monotonicity for sortability, not exporter activity —\n * see `bundle/ulid.ts` for the design notes).\n */\n readonly handle: string\n /** Compressed body length in bytes. Lets readers verify completeness without decompressing. */\n readonly bodyBytes: number\n /** SHA-256 of the compressed body bytes (lowercase hex). Lets readers verify integrity without decompressing. */\n readonly bodySha256: string\n /**\n * Owner-curated public envelope (`docs/subsystems/public-envelope.md`).\n * Optional — present only when the source vault has a\n * `_meta/public-envelope` document AND the writer's hub is opted\n * into the feature. Treat as **untrusted hint**; the body's\n * encrypted contents remain the source of truth.\n *\n * The envelope deliberately widens the minimum-disclosure rule\n * for explicit, owner-curated label fields (name, icon, …). Every\n * other unknown header key still rejects at parse time.\n */\n readonly publicEnvelope?: PublicEnvelope\n /**\n * Auto-unlock material indicator (#197). When present, the bundle\n * body wraps the dump JSON in a structure carrying per-user\n * passphrases — either plaintext (`'unsealed'`, public-by-design)\n * or sealed under a `SealingKeyProvider` (`'sealed'`, requires\n * matching provider on the recipient side).\n *\n * Visible pre-decompression so cloud listing UIs can warn before\n * download: \"this bundle opens itself for anyone holding the file\"\n * (unsealed) or \"this bundle is sealed for a specific provider\"\n * (sealed).\n *\n * Absent → the body is a raw `vault.dump()` JSON string (the\n * pre-#197 shape; back-compatible).\n */\n readonly autoUnlock?: 'unsealed' | 'sealed'\n /**\n * Bundle's role in the source → destination lifecycle (#203).\n * - omitted / 'snapshot' (default): backup/copy of an existing vault.\n * - 'extracted-partition': re-keyed projection awaiting adoption.\n */\n readonly bundleKind?: 'snapshot' | 'extracted-partition'\n /**\n * Transfer-seal INDICATOR (#206) — metadata only, no payload (the\n * sealed DEKs live in the body). Present iff\n * bundleKind === 'extracted-partition'.\n */\n readonly transferSeal?: {\n readonly v: 1\n readonly alg: 'aes-256-gcm-pre-shared'\n readonly sealId: string\n }\n}\n\n/**\n * Allowlist of header keys. Any key not in this set is forbidden\n * and causes `validateBundleHeader` to throw. Kept as a Set for\n * O(1) lookup; the validator iterates over the parsed header and\n * checks each key against this set.\n */\nconst ALLOWED_HEADER_KEYS: ReadonlySet<string> = new Set([\n 'formatVersion',\n 'handle',\n 'bodyBytes',\n 'bodySha256',\n 'publicEnvelope',\n 'autoUnlock',\n 'bundleKind',\n 'transferSeal',\n])\n\n/**\n * Validate a parsed bundle header. Throws on any deviation from\n * the minimum-disclosure schema:\n *\n * - Missing required field\n * - Wrong type for any field\n * - Any extra key not in `ALLOWED_HEADER_KEYS`\n * - Unsupported `formatVersion`\n * - Negative or non-integer `bodyBytes`\n * - Malformed `handle` (must be 26-char Crockford base32)\n * - Malformed `bodySha256` (must be 64-char lowercase hex)\n *\n * The error messages name the offending field so consumers can\n * fix the producer rather than the reader.\n */\nexport function validateBundleHeader(\n parsed: unknown,\n): asserts parsed is NoydbBundleHeader {\n if (parsed === null || typeof parsed !== 'object') {\n throw new Error(\n `.noydb bundle header must be a JSON object, got ${parsed === null ? 'null' : typeof parsed}`,\n )\n }\n // Disallow any unknown key — minimum disclosure means we reject\n // forward-compat extension keys at the format layer; new fields\n // require a format version bump and a new validator.\n for (const key of Object.keys(parsed)) {\n if (!ALLOWED_HEADER_KEYS.has(key)) {\n throw new Error(\n `.noydb bundle header contains forbidden key \"${key}\". ` +\n `Only minimum-disclosure fields are allowed: ` +\n `${[...ALLOWED_HEADER_KEYS].join(', ')}.`,\n )\n }\n }\n const h = parsed as Record<string, unknown>\n if (typeof h['formatVersion'] !== 'number' || h['formatVersion'] !== NOYDB_BUNDLE_FORMAT_VERSION) {\n throw new Error(\n `.noydb bundle header.formatVersion must be ${NOYDB_BUNDLE_FORMAT_VERSION}, ` +\n `got ${String(h['formatVersion'])}. The reader does not support ` +\n `forward-compat versions; upgrade the reader to handle newer bundles.`,\n )\n }\n if (typeof h['handle'] !== 'string' || !/^[0-9A-HJKMNP-TV-Z]{26}$/.test(h['handle'])) {\n throw new Error(\n `.noydb bundle header.handle must be a 26-character Crockford base32 ULID, ` +\n `got ${typeof h['handle'] === 'string' ? `\"${h['handle']}\"` : String(h['handle'])}.`,\n )\n }\n if (typeof h['bodyBytes'] !== 'number' || !Number.isInteger(h['bodyBytes']) || h['bodyBytes'] < 0) {\n throw new Error(\n `.noydb bundle header.bodyBytes must be a non-negative integer, ` +\n `got ${String(h['bodyBytes'])}.`,\n )\n }\n if (typeof h['bodySha256'] !== 'string' || !/^[0-9a-f]{64}$/.test(h['bodySha256'])) {\n throw new Error(\n `.noydb bundle header.bodySha256 must be a 64-character lowercase hex string, ` +\n `got ${typeof h['bodySha256'] === 'string' ? `\"${h['bodySha256']}\"` : String(h['bodySha256'])}.`,\n )\n }\n if (h['publicEnvelope'] !== undefined) {\n const env = h['publicEnvelope']\n if (env === null || typeof env !== 'object' || Array.isArray(env)) {\n throw new Error(\n `.noydb bundle header.publicEnvelope must be a JSON object when present, got ${typeof env}.`,\n )\n }\n const e = env as Record<string, unknown>\n if (e['_noydb_public'] !== 1) {\n throw new Error(\n `.noydb bundle header.publicEnvelope._noydb_public must be 1, got ${String(e['_noydb_public'])}.`,\n )\n }\n if (typeof e['version'] !== 'number' || !Number.isInteger(e['version']) || e['version'] < 1) {\n throw new Error(\n `.noydb bundle header.publicEnvelope.version must be a positive integer, got ${String(e['version'])}.`,\n )\n }\n }\n if (h['autoUnlock'] !== undefined) {\n if (h['autoUnlock'] !== 'unsealed' && h['autoUnlock'] !== 'sealed') {\n const got = typeof h['autoUnlock'] === 'string' ? `\"${h['autoUnlock']}\"` : typeof h['autoUnlock']\n throw new Error(\n `.noydb bundle header.autoUnlock must be 'unsealed' or 'sealed' when present, got ${got}.`,\n )\n }\n }\n if (h['bundleKind'] !== undefined) {\n if (h['bundleKind'] !== 'snapshot' && h['bundleKind'] !== 'extracted-partition') {\n const got = typeof h['bundleKind'] === 'string' ? `\"${h['bundleKind']}\"` : typeof h['bundleKind']\n throw new Error(\n `.noydb bundle header.bundleKind must be 'snapshot' or 'extracted-partition' when present, got ${got}.`,\n )\n }\n }\n if (h['transferSeal'] !== undefined) {\n const ts = h['transferSeal']\n if (ts === null || typeof ts !== 'object' || Array.isArray(ts)) {\n throw new Error(`.noydb bundle header.transferSeal must be a JSON object when present, got ${typeof ts}.`)\n }\n const t = ts as Record<string, unknown>\n if (t['v'] !== 1) {\n throw new Error(`.noydb bundle header.transferSeal.v must be 1, got ${String(t['v'])}.`)\n }\n if (t['alg'] !== 'aes-256-gcm-pre-shared') {\n throw new Error(`.noydb bundle header.transferSeal.alg must be 'aes-256-gcm-pre-shared', got ${String(t['alg'])}.`)\n }\n if (typeof t['sealId'] !== 'string' || t['sealId'].length === 0) {\n throw new Error(`.noydb bundle header.transferSeal.sealId must be a non-empty string, got ${String(t['sealId'])}.`)\n }\n }\n // Cross-field invariant: the seal indicator and the extracted-partition\n // kind imply each other. An extracted partition is unlocked via its\n // transfer seal; a seal without the kind is a malformed header.\n const isExtracted = h['bundleKind'] === 'extracted-partition'\n const hasSeal = h['transferSeal'] !== undefined\n if (hasSeal && !isExtracted) {\n throw new Error(\n `.noydb bundle header.transferSeal requires bundleKind === 'extracted-partition'.`,\n )\n }\n if (isExtracted && !hasSeal) {\n throw new Error(\n `.noydb bundle header with bundleKind === 'extracted-partition' must carry a transferSeal indicator.`,\n )\n }\n // An extracted partition's unlock path IS the transfer seal. A parallel\n // autoUnlock credential would create two unlock paths and weaken the\n // one-time-seal guarantee (spec §12.3). Reject the combination.\n if (isExtracted && h['autoUnlock'] !== undefined) {\n throw new Error(\n `.noydb bundle header cannot carry both autoUnlock and bundleKind === 'extracted-partition' — `\n + `an extracted partition is unlocked via its transfer seal, not an auto-credential.`,\n )\n }\n}\n\n/**\n * Encode a header object to UTF-8 JSON bytes after validating\n * minimum disclosure. Used by the writer to serialize the header\n * region of the container.\n */\nexport function encodeBundleHeader(header: NoydbBundleHeader): Uint8Array {\n validateBundleHeader(header)\n // Stable key ordering — JSON.stringify with no replacer uses\n // insertion order, which is fine here because we control the\n // object construction. Stable ordering means two bundles with\n // identical contents produce byte-identical headers.\n const json = JSON.stringify({\n formatVersion: header.formatVersion,\n handle: header.handle,\n bodyBytes: header.bodyBytes,\n bodySha256: header.bodySha256,\n ...(header.publicEnvelope !== undefined ? { publicEnvelope: header.publicEnvelope } : {}),\n ...(header.autoUnlock !== undefined ? { autoUnlock: header.autoUnlock } : {}),\n ...(header.bundleKind !== undefined ? { bundleKind: header.bundleKind } : {}),\n ...(header.transferSeal !== undefined ? { transferSeal: header.transferSeal } : {}),\n })\n return new TextEncoder().encode(json)\n}\n\n/**\n * Parse a bundle header from its UTF-8 JSON bytes. Throws on\n * invalid JSON or any minimum-disclosure violation.\n */\nexport function decodeBundleHeader(bytes: Uint8Array): NoydbBundleHeader {\n const json = new TextDecoder('utf-8', { fatal: true }).decode(bytes)\n let parsed: unknown\n try {\n parsed = JSON.parse(json)\n } catch (err) {\n throw new Error(\n `.noydb bundle header is not valid JSON: ${(err as Error).message}`,\n )\n }\n validateBundleHeader(parsed)\n return parsed\n}\n\n/**\n * Read a uint32 from `bytes` at `offset` in big-endian byte order.\n * No bounds check — callers must guarantee `offset + 4 <= bytes.length`.\n * Used to decode the header length field; kept inline so the parser\n * doesn't depend on DataView allocation per call.\n */\nexport function readUint32BE(bytes: Uint8Array, offset: number): number {\n return (\n (bytes[offset]! << 24 >>> 0) +\n (bytes[offset + 1]! << 16) +\n (bytes[offset + 2]! << 8) +\n bytes[offset + 3]!\n )\n}\n\n/**\n * Write a uint32 to `bytes` at `offset` in big-endian byte order.\n * No bounds check — callers must guarantee `offset + 4 <= bytes.length`.\n */\nexport function writeUint32BE(bytes: Uint8Array, offset: number, value: number): void {\n bytes[offset] = (value >>> 24) & 0xff\n bytes[offset + 1] = (value >>> 16) & 0xff\n bytes[offset + 2] = (value >>> 8) & 0xff\n bytes[offset + 3] = value & 0xff\n}\n\n/**\n * Verify the magic prefix of a bundle. Returns true if the first\n * 4 bytes match `NDB1`. Used by readers as a fast file-type check\n * before any further parsing.\n */\nexport function hasNoydbBundleMagic(bytes: Uint8Array): boolean {\n if (bytes.length < NOYDB_BUNDLE_MAGIC.length) return false\n for (let i = 0; i < NOYDB_BUNDLE_MAGIC.length; i++) {\n if (bytes[i] !== NOYDB_BUNDLE_MAGIC[i]) return false\n }\n return true\n}\n","/**\n * `.noydb` container primitives — write, read, header-only read.\n *\n *. Wraps a `vault.dump()` JSON string in the\n * binary container described in `format.ts`.\n *\n * **Three primitives:**\n *\n * - `writeNoydbBundle(vault, opts?)` — produces the\n * full container bytes ready to write to disk or upload\n * - `readNoydbBundleHeader(bytes)` — parses just the header\n * without decompressing the body, fast file-type and\n * metadata read for cloud listing UIs\n * - `readNoydbBundle(bytes)` — full read: validates magic,\n * header, integrity hash, and decompresses the body to\n * return the original `dump()` JSON string for use with\n * `vault.load()`\n *\n * **Compression strategy:** brotli when available (Node 22+,\n * Chrome 124+, Firefox 122+), gzip fallback elsewhere. The\n * algorithm choice is encoded in the format byte at offset 5,\n * so readers handle either transparently. Brotli wins ~30-50%\n * on JSON payloads with repeated keys (which vault dumps\n * are).\n *\n * **Why split read/load?** `readNoydbBundle` returns the\n * *unwrapped JSON string*, not a Vault object. The caller\n * is responsible for piping that JSON into\n * `vault.load(json, passphrase)`. Splitting the layers\n * keeps the bundle module free of any crypto/passphrase\n * concerns — it's purely a format layer. The same `readNoydbBundle`\n * call can also feed verification tools, format inspectors, or\n * archive utilities that don't care about decryption.\n */\n\nimport {\n COMPRESSION_BROTLI,\n COMPRESSION_GZIP,\n COMPRESSION_NONE,\n FLAG_COMPRESSED,\n FLAG_HAS_INTEGRITY_HASH,\n NOYDB_BUNDLE_FORMAT_VERSION,\n NOYDB_BUNDLE_MAGIC,\n NOYDB_BUNDLE_PREFIX_BYTES,\n decodeBundleHeader,\n encodeBundleHeader,\n hasNoydbBundleMagic,\n readUint32BE,\n writeUint32BE,\n type CompressionAlgo,\n type NoydbBundleHeader,\n} from './format.js'\nimport { BundleIntegrityError, BundleSealMismatchError, ValidationError } from '../errors.js'\nimport type { Vault } from '../vault.js'\nimport type { BundleRecipient } from '../team/keyring.js'\nimport { pickLocale } from '../meta/public-envelope/storage.js'\nimport type { PublicEnvelope } from '../meta/public-envelope/types.js'\nimport type { SealingKeyProvider, RecipientSealer, RecipientHint } from '../team/managed-passphrase.js'\n\n// ─── #215 auto-credential types ───────────────────────────────────────────────\n\n/**\n * The credential kinds that can be bundled for auto-unlock.\n * WebAuthn is intentionally excluded — it is hardware-bound and\n * cannot be embedded as a portable credential.\n */\nexport type AutoCredentialKind = 'passphrase' | 'password' | 'pin'\n\n/**\n * A typed credential for auto-unlock. Carries the credential `kind`\n * alongside the plaintext `value`, so consumers can dispatch the\n * correct login/prefill path rather than treating all credentials\n * as passphrases.\n *\n * `bundle.ts` is a pure format layer — it carries the credential\n * without interpreting it. The consumer is responsible for\n * dispatching on `kind`.\n */\nexport interface AutoCredential {\n readonly kind: AutoCredentialKind\n readonly value: string\n}\n\n/**\n * Options accepted by `writeNoydbBundle`.\n *\n * - `compression: 'auto'` (default) — try brotli, fall back to gzip\n * - `compression: 'brotli'` — force brotli, throw if unsupported\n * - `compression: 'gzip'` — force gzip\n * - `compression: 'none'` — no compression (round-trip testing only)\n *\n * **Slice filtering** (added in ):\n * - `collections` — allowlist of collection names to include. Internal\n * collections (keyrings, ledger) and excluded user collections are\n * dropped from the bundle. Records inside included collections are\n * carried through verbatim.\n * - `since` — only records whose envelope `_ts` is on/after the given\n * instant survive. Operates on the unencrypted envelope timestamp,\n * so plaintext access to records is not required.\n *\n * Both filters intersect (AND). When neither is provided the bundle is\n * a whole-vault snapshot, identical to today's behaviour.\n */\nexport interface WriteNoydbBundleOptions {\n readonly compression?: 'auto' | 'brotli' | 'gzip' | 'none'\n /** Allowlist of user-collection names to include. */\n readonly collections?: readonly string[]\n /**\n * Drop records whose envelope `_ts` is strictly older than this\n * instant. Accepts a `Date` or any ISO-8601 string parseable by\n * `new Date()`.\n */\n readonly since?: Date | string\n /**\n * Plaintext-pipeline record predicate. Decrypts each record\n * with the vault's per-collection DEK, runs the predicate, and\n * keeps the original ciphertext for survivors (no re-encrypt —\n * preserves zero-knowledge cleanly). Records the predicate returns\n * `false` for are dropped from the bundle.\n *\n * Async predicates are supported. Mutating the record from inside\n * the predicate is undefined behaviour.\n */\n readonly where?: (\n record: unknown,\n ctx: { collection: string; id: string },\n ) => boolean | Promise<boolean>\n /**\n * Hierarchical-tier ceiling. Records whose envelope `_tier`\n * is strictly greater than this number are dropped. Operates on the\n * envelope `_tier` (no decryption needed) — vault.exportStream is\n * referenced in the issue body for symmetry, but the tier value\n * lives on the unencrypted envelope. Vault without tiers is a no-op.\n */\n readonly tierAtMost?: number\n /**\n * Single-recipient re-keying shorthand. When set, the\n * bundle's keyring is replaced with one freshly-derived entry sealed\n * with this passphrase. The recipient inherits the source keyring's\n * userId, role, and permissions. Mutually exclusive with `recipients`.\n */\n readonly exportPassphrase?: string\n /**\n * Multi-recipient re-keying. Replaces the bundle's keyring\n * map with one slot per recipient, each sealed with its own\n * passphrase. DEKs are unwrapped from the source keyring once and\n * re-wrapped per recipient — record ciphertext is unchanged.\n *\n * Mutually exclusive with `exportPassphrase`. When neither is set,\n * the bundle inherits the source keyring as-is (today's behaviour,\n * suited to personal backup-and-restore).\n */\n readonly recipients?: readonly BundleRecipient[]\n /**\n * Auto-unlock — unsealed per-user credentials (#215).\n *\n * Generalises `autoPassphrases` to support any bundleable credential\n * kind (`passphrase` | `password` | `pin`).\n *\n * Public-by-design: anyone holding the bundle bytes can read these\n * plaintext credentials. Use for demo data, sample vaults,\n * prospect onboarding.\n *\n * The `policy: 'public-by-design'` discriminant is mandatory. A\n * bare `{ perUser }` without it is rejected at write time — the\n * safety net against a careless call against a production vault.\n *\n * Mutually exclusive with `sealedCredentials`, `autoPassphrases`,\n * and `sealedPassphrases`.\n */\n readonly autoCredentials?: {\n readonly policy: 'public-by-design'\n readonly perUser: Record<string, AutoCredential>\n }\n /**\n * Auto-unlock — per-user credentials sealed under a\n * {@link SealingKeyProvider} (#215).\n *\n * Generalises `sealedPassphrases` to support any bundleable\n * credential kind (`passphrase` | `password` | `pin`).\n *\n * The hub seals each user's plaintext credential under `provider`\n * and embeds the resulting sealed envelopes in the bundle. The\n * recipient must hold a provider with a matching `pid` (i.e.,\n * `provider.id`) to auto-unseal on import.\n *\n * `mode: 'self-target'` — sender and recipient share the same\n * provider identity (same iCloud Keychain entry, same\n * MDM-provisioned bundle id, same KMS account, etc.).\n *\n * `mode: 'recipient-target'` — asymmetric sealing via a\n * {@link RecipientSealer}. Each user entry carries a\n * `credential` and a `hint` (the recipient's public material).\n * The bundle can only be unsealed by the holder of the matching\n * private key.\n *\n * Mutually exclusive with `autoCredentials`, `autoPassphrases`,\n * and `sealedPassphrases`.\n */\n readonly sealedCredentials?:\n | {\n readonly mode: 'self-target'\n readonly provider: SealingKeyProvider\n readonly perUser: Record<string, AutoCredential>\n }\n | {\n readonly mode: 'recipient-target'\n readonly provider: RecipientSealer\n readonly perUser: Record<string, { readonly credential: AutoCredential; readonly hint: RecipientHint }>\n }\n /**\n * @deprecated Use `autoCredentials` instead (#215).\n *\n * Auto-unlock — unsealed per-user passphrases (#197 slice 1).\n *\n * Public-by-design: anyone holding the bundle bytes can read these\n * plaintext credentials. Use for demo data, sample vaults,\n * prospect onboarding.\n *\n * The `policy: 'public-by-design'` discriminant is mandatory. A\n * bare `{ perUser }` without it is rejected at write time — the\n * safety net against a careless call against a production vault.\n *\n * Mutually exclusive with `autoCredentials`, `sealedCredentials`,\n * and `sealedPassphrases`.\n */\n readonly autoPassphrases?: {\n readonly policy: 'public-by-design'\n readonly perUser: Record<string, string>\n }\n /**\n * @deprecated Use `sealedCredentials` instead (#215).\n *\n * Auto-unlock — per-user passphrases sealed under a\n * {@link SealingKeyProvider} (#197 slice 1, self-target only).\n *\n * The hub seals each user's plaintext passphrase under `provider`\n * and embeds the resulting sealed envelopes in the bundle. The\n * recipient must hold a provider with a matching `pid` (i.e.,\n * `provider.id`) to auto-unseal on import.\n *\n * `mode: 'self-target'` is the only mode for `sealedPassphrases` — sender\n * and recipient share the same provider identity (same iCloud Keychain\n * entry, same MDM-provisioned bundle id, same KMS account, etc.).\n * For recipient-target sealing via the `RecipientSealer` interface,\n * use `sealedCredentials` with `mode: 'recipient-target'` (§11.4).\n *\n * Mutually exclusive with `autoCredentials`, `sealedCredentials`,\n * and `autoPassphrases`.\n */\n readonly sealedPassphrases?: {\n readonly mode: 'self-target'\n readonly provider: SealingKeyProvider\n readonly perUser: Record<string, string>\n }\n}\n\n/**\n * Result returned by `readNoydbBundle`. The caller is expected to\n * pass `dumpJson` into `vault.load(json, passphrase)` to\n * actually restore a vault. Splitting the layers keeps the\n * bundle module free of crypto concerns — see file-level docs.\n */\nexport interface NoydbBundleReadResult {\n readonly header: NoydbBundleHeader\n readonly dumpJson: string\n /**\n * Auto-unlock material (#197, widened in #215). Present only when\n * the header's `autoUnlock` flag is set AND the body's wrapped\n * structure survived parsing. Values are typed credentials — either\n * delivered plain (`kind: 'unsealed'`) or unsealed at read time\n * using one of the supplied `sealingProviders` (`kind: 'sealed'`).\n *\n * Consumers dispatch on `cred.kind` to choose the correct login /\n * prefill path. Pre-0.2 bundles (bare string entries) are coerced\n * to `{ kind: 'passphrase', value }` on read for back-compat.\n *\n * For `kind: 'sealed'` bundles read without `sealingProviders`, the\n * `value` field is the raw base64 sealed bytes — opaque to the\n * consumer until unsealed elsewhere.\n */\n readonly autoUnlock?: {\n readonly kind: 'unsealed' | 'sealed'\n readonly perUser: Record<string, AutoCredential>\n }\n}\n\n/**\n * Sealed credential entry as it appears in the bundle body's\n * `_autoUnlock.perUser` map when the bundle was written with\n * `sealedCredentials` (or the deprecated `sealedPassphrases`).\n * Provider's sealed output is base64-encoded; the `pid` is the\n * dispatch key matched against recipient-supplied\n * `SealingKeyProvider.id`. The `kind` carries the plaintext-tier\n * metadata so the consumer can dispatch on credential type without\n * unsealing first.\n *\n * Back-compat: `kind` is absent in pre-0.2 bundles — readers must\n * default to `'passphrase'` when not present.\n */\ninterface SealedAutoUnlockEntry {\n readonly pid: string\n readonly sealed: string\n readonly alg: 'aes-256-gcm'\n readonly kind?: AutoCredentialKind\n /**\n * Recipient-target only: the RecipientHint the sender used to seal.\n * Carried for recipient verifiability (\"yes this was sealed against\n * my published hint\"). Self-target entries omit it. Pre-0.2 readers\n * ignore unknown fields, so this is back-compatible.\n */\n readonly hint?: RecipientHint\n}\n\n/**\n * Discriminated wrapper carried in the bundle body when the header's\n * `autoUnlock` flag is set. Without the flag, the body is the raw\n * `vault.dump()` JSON string (the pre-#197 shape).\n *\n * Back-compat: pre-0.2 bundles carry bare `string` values in the\n * unsealed `perUser` map. Readers must coerce those to\n * `{ kind: 'passphrase', value }`.\n */\ninterface AutoUnlockBody {\n readonly _noydb_bundle_body: 1\n readonly dump: string\n readonly _autoUnlock:\n | { readonly kind: 'unsealed'; readonly perUser: Record<string, AutoCredential | string> }\n | { readonly kind: 'sealed'; readonly perUser: Record<string, SealedAutoUnlockEntry> }\n}\n\n/**\n * Options accepted by {@link readNoydbBundle} for the #197\n * auto-unlock paths. Without these the reader behaves exactly as\n * pre-#197 (header parsed; body returned as `dumpJson`).\n */\nexport interface ReadNoydbBundleOptions {\n /**\n * Recipient-side sealing providers used to unseal entries from\n * `sealedPassphrases`. The reader picks the one whose `.id`\n * matches each entry's `pid`. Multiple providers may be supplied\n * (different users may seal under different identities).\n *\n * When unset and the bundle carries sealed envelopes, the\n * `autoUnlock.perUser` map remains the SEALED entries unmodified\n * — callers can inspect them or unseal elsewhere.\n */\n readonly sealingProviders?: readonly SealingKeyProvider[]\n /**\n * Opt-in trial mode for unsealing — when an entry's `pid` doesn't\n * match a registered provider, try each provider whose alg\n * matches. Default `false` (strict-pid dispatch per foundation\n * §11.9.2). Surfaces extra credential prompts; use deliberately.\n */\n readonly attemptUnsealAcrossProviders?: boolean\n}\n\n// ─── #197/#215 auto-unlock helpers ────────────────────────────────────────────\n\n/**\n * Internal normalized form of the auto-unlock options, computed once\n * from the four public-facing fields (autoCredentials, sealedCredentials,\n * autoPassphrases, sealedPassphrases). Callers work against this shape\n * so the build + validate paths share a single normalizer.\n */\ninterface NormalizedAutoUnlock {\n readonly mode: 'unsealed' | 'sealed-self' | 'sealed-recipient'\n readonly provider?: SealingKeyProvider | RecipientSealer\n readonly perUser: Record<string, AutoCredential>\n /** Present only for `sealed-recipient`. Same key set as `perUser`. */\n readonly hints?: Record<string, RecipientHint>\n}\n\n/**\n * Coerce a `Record<string, string>` (legacy passphrase-only map) into\n * a `Record<string, AutoCredential>` by tagging each entry as\n * `kind: 'passphrase'`. Used by the normalizer to promote the deprecated\n * `autoPassphrases`/`sealedPassphrases` sugar.\n */\nfunction toAutoCredentials(m: Record<string, string>): Record<string, AutoCredential> {\n return Object.fromEntries(\n Object.entries(m).map(([u, value]) => [u, { kind: 'passphrase' as const, value }]),\n )\n}\n\n/**\n * Normalize the four auto-unlock option fields into a single\n * `NormalizedAutoUnlock` (or `null` when none is set). Enforces mutual\n * exclusion — exactly one of the four may be present. Promotes the\n * deprecated sugar fields to `AutoCredential` shape.\n *\n * Does NOT validate field-level constraints (policy marker, perUser\n * length, mode, provider presence, kind allowlist) — those are checked\n * in `validateAutoUnlockOptions` after normalization.\n */\nfunction normalizeAutoUnlock(opts: WriteNoydbBundleOptions): NormalizedAutoUnlock | null {\n const set = [\n opts.autoCredentials,\n opts.sealedCredentials,\n opts.autoPassphrases,\n opts.sealedPassphrases,\n ].filter(v => v !== undefined).length\n if (set === 0) return null\n if (set > 1) {\n throw new ValidationError(\n 'writeNoydbBundle: only one of autoCredentials / sealedCredentials / '\n + 'autoPassphrases / sealedPassphrases may be set.',\n )\n }\n if (opts.autoCredentials !== undefined) {\n return { mode: 'unsealed', perUser: opts.autoCredentials.perUser }\n }\n if (opts.autoPassphrases !== undefined) {\n return { mode: 'unsealed', perUser: toAutoCredentials(opts.autoPassphrases.perUser) }\n }\n if (opts.sealedCredentials !== undefined) {\n if (opts.sealedCredentials.mode === 'recipient-target') {\n const perUser: Record<string, AutoCredential> = {}\n const hints: Record<string, RecipientHint> = {}\n for (const [userId, entry] of Object.entries(opts.sealedCredentials.perUser)) {\n perUser[userId] = entry.credential\n hints[userId] = entry.hint\n }\n return { mode: 'sealed-recipient', provider: opts.sealedCredentials.provider, perUser, hints }\n }\n return { mode: 'sealed-self', provider: opts.sealedCredentials.provider, perUser: opts.sealedCredentials.perUser }\n }\n // sealedPassphrases — only remaining option\n return {\n mode: 'sealed-self',\n provider: opts.sealedPassphrases!.provider,\n perUser: toAutoCredentials(opts.sealedPassphrases!.perUser),\n }\n}\n\n/**\n * Validate the auto-unlock options and return the resulting header\n * `autoUnlock` value (or null when no auto-unlock requested).\n *\n * Takes the pre-computed `NormalizedAutoUnlock` so the caller (i.e.\n * `writeNoydbBundle`) can pass the same object to `buildAutoUnlockWrapper`\n * without a second `normalizeAutoUnlock` call.\n *\n * Validation per spec (#197 + #215 §3):\n * - (mutual exclusion already enforced by normalizeAutoUnlock)\n * - unsealed path: `policy: 'public-by-design'` marker required\n * - non-empty `perUser` maps\n * - sealed path: provider present; both `mode: 'self-target'` and `mode: 'recipient-target'` accepted; recipient-target requires a `RecipientSealer` provider and per-user `hint` (§11.4)\n * - every AutoCredential.kind ∈ {passphrase, password, pin}\n * (WebAuthn is hardware-bound and cannot be bundled)\n *\n * Throws {@link ValidationError} on any violation.\n */\nfunction validateAutoUnlockOptions(\n opts: WriteNoydbBundleOptions,\n normalized: NormalizedAutoUnlock | null,\n): 'unsealed' | 'sealed' | null {\n if (normalized === null) return null\n\n const VALID_KINDS: ReadonlySet<string> = new Set(['passphrase', 'password', 'pin'])\n\n // Validate every credential kind before any further checks.\n for (const [userId, cred] of Object.entries(normalized.perUser)) {\n if (!VALID_KINDS.has(cred.kind)) {\n throw new ValidationError(\n `writeNoydbBundle: credential for user '${userId}' has unsupported kind '${cred.kind}'. `\n + 'auto-unlock supports passphrase/password/pin only; WebAuthn is hardware-bound '\n + 'and cannot be bundled.',\n )\n }\n }\n\n if (normalized.mode === 'unsealed') {\n // Read the policy marker from whichever active option carries it.\n const policy = opts.autoCredentials?.policy ?? opts.autoPassphrases?.policy\n if (policy !== 'public-by-design') {\n throw new ValidationError(\n 'writeNoydbBundle: `autoCredentials` (or `autoPassphrases`) requires '\n + '`policy: \"public-by-design\"`. '\n + 'This is an explicit opt-in marker — bundling plaintext credentials is '\n + 'safe only when those credentials are intended to be public (demo data, '\n + 'sample vaults). For production credentials, use `sealedCredentials` instead.',\n )\n }\n const userCount = Object.keys(normalized.perUser).length\n if (userCount === 0) {\n throw new ValidationError(\n 'writeNoydbBundle: `autoCredentials.perUser` (or `autoPassphrases.perUser`) '\n + 'must have at least one entry.',\n )\n }\n return 'unsealed'\n }\n\n // Sealed path — branch on mode.\n if (normalized.mode === 'sealed-recipient') {\n const provider = normalized.provider\n if (provider === undefined || typeof (provider as RecipientSealer).publishRecipientHint !== 'function'\n || typeof (provider as RecipientSealer).sealForRecipient !== 'function') {\n throw new ValidationError(\n 'writeNoydbBundle: `sealedCredentials.provider` for mode \\'recipient-target\\' must be a '\n + 'RecipientSealer (publishRecipientHint + sealForRecipient). Self-only providers '\n + '(MemorySealingKeyProvider, at-macos-keychain, etc.) do not satisfy this contract.',\n )\n }\n const hints = normalized.hints\n if (hints === undefined) {\n throw new Error('unreachable — sealed-recipient normalization must populate hints')\n }\n for (const userId of Object.keys(normalized.perUser)) {\n const hint = hints[userId]\n if (hint === undefined) {\n throw new ValidationError(\n `writeNoydbBundle: \\`sealedCredentials.perUser['${userId}']\\` missing required \\`hint\\` for mode 'recipient-target'.`,\n )\n }\n if (hint.v !== 1) {\n throw new ValidationError(\n `writeNoydbBundle: \\`sealedCredentials.perUser['${userId}'].hint.v\\` must be 1 (got ${String(hint.v)}).`,\n )\n }\n if (typeof hint.pid !== 'string' || hint.pid.length === 0) {\n throw new ValidationError(\n `writeNoydbBundle: \\`sealedCredentials.perUser['${userId}'].hint.pid\\` must be a non-empty string identifying the recipient.`,\n )\n }\n if (hint.alg !== 'rsa-oaep-sha256') {\n throw new ValidationError(\n `writeNoydbBundle: \\`sealedCredentials.perUser['${userId}'].hint.alg\\` must be 'rsa-oaep-sha256' in slice 1 (got '${String(hint.alg)}').`,\n )\n }\n // Note: hint.pid identifies the recipient, not the sender — no pid===sender.id check here.\n // The sender holds a RecipientSealer that calls sealForRecipient(plaintext, hint);\n // the hint's pid is the dispatch key on the reader side (matched against recipient providers).\n }\n const userCount = Object.keys(normalized.perUser).length\n if (userCount === 0) {\n throw new ValidationError(\n 'writeNoydbBundle: `sealedCredentials.perUser` must have at least one entry.',\n )\n }\n return 'sealed'\n }\n\n // mode === 'sealed-self'\n const selfTargetMode = opts.sealedCredentials?.mode ?? opts.sealedPassphrases?.mode\n if (selfTargetMode !== 'self-target') {\n throw new ValidationError(\n `writeNoydbBundle: \\`sealedCredentials.mode\\` (or \\`sealedPassphrases.mode\\`) must be `\n + `'self-target' or 'recipient-target' (got '${String(selfTargetMode)}').`,\n )\n }\n if (normalized.provider === undefined) {\n throw new ValidationError(\n 'writeNoydbBundle: `sealedCredentials.provider` (or `sealedPassphrases.provider`) '\n + 'is required (a `SealingKeyProvider`).',\n )\n }\n const userCount = Object.keys(normalized.perUser).length\n if (userCount === 0) {\n throw new ValidationError(\n 'writeNoydbBundle: `sealedCredentials.perUser` (or `sealedPassphrases.perUser`) '\n + 'must have at least one entry.',\n )\n }\n return 'sealed'\n}\n\n/**\n * Build the body wrapper carrying the dump + `_autoUnlock` blob.\n * Takes the pre-computed `NormalizedAutoUnlock` so both validate and\n * build work off the same normalized form (no double-normalize).\n */\nasync function buildAutoUnlockWrapper(\n dumpJson: string,\n normalized: NormalizedAutoUnlock,\n): Promise<AutoUnlockBody> {\n if (normalized.mode === 'unsealed') {\n return {\n _noydb_bundle_body: 1,\n dump: dumpJson,\n _autoUnlock: {\n kind: 'unsealed',\n perUser: { ...normalized.perUser },\n },\n }\n }\n // Sealed path — branch on mode.\n const provider = normalized.provider\n if (provider === undefined) {\n throw new Error('unreachable — validation should have caught this')\n }\n const sealedPerUser: Record<string, SealedAutoUnlockEntry> = {}\n const encoder = new TextEncoder()\n\n if (normalized.mode === 'sealed-recipient') {\n const recipientSealer = provider as RecipientSealer\n const hints = normalized.hints\n if (hints === undefined) {\n throw new Error('unreachable — sealed-recipient normalization must populate hints')\n }\n for (const [userId, cred] of Object.entries(normalized.perUser)) {\n const hint = hints[userId]!\n const sealed = await recipientSealer.sealForRecipient(encoder.encode(cred.value), hint)\n sealedPerUser[userId] = {\n pid: hint.pid, // use the recipient's pid, not the sender's\n sealed: bytesToBase64(sealed),\n alg: 'aes-256-gcm',\n kind: cred.kind,\n hint,\n }\n }\n } else {\n // mode === 'sealed-self'\n const selfSealer = provider as SealingKeyProvider\n for (const [userId, cred] of Object.entries(normalized.perUser)) {\n const sealed = await selfSealer.seal(encoder.encode(cred.value))\n sealedPerUser[userId] = {\n pid: selfSealer.id,\n sealed: bytesToBase64(sealed),\n alg: 'aes-256-gcm',\n kind: cred.kind,\n }\n }\n }\n\n return {\n _noydb_bundle_body: 1,\n dump: dumpJson,\n _autoUnlock: { kind: 'sealed', perUser: sealedPerUser },\n }\n}\n\n/**\n * Parse the body bytes when the header signaled an auto-unlock.\n * Returns the inner `dump` JSON string + the `_autoUnlock` blob;\n * throws if the wrapper structure is malformed.\n */\nfunction parseAutoUnlockBody(bodyString: string): { dump: string; blob: AutoUnlockBody['_autoUnlock'] } {\n let parsed: unknown\n try {\n parsed = JSON.parse(bodyString)\n } catch (err) {\n throw new BundleIntegrityError(\n 'header declared autoUnlock but body could not be parsed as JSON wrapper: '\n + (err instanceof Error ? err.message : String(err)),\n )\n }\n if (typeof parsed !== 'object' || parsed === null) {\n throw new BundleIntegrityError('autoUnlock body is not a JSON object')\n }\n const obj = parsed as Record<string, unknown>\n if (obj['_noydb_bundle_body'] !== 1) {\n throw new BundleIntegrityError(\n 'autoUnlock body missing `_noydb_bundle_body: 1` discriminator',\n )\n }\n if (typeof obj['dump'] !== 'string') {\n throw new BundleIntegrityError('autoUnlock body must carry a string `dump` field')\n }\n const blob = obj['_autoUnlock']\n if (typeof blob !== 'object' || blob === null) {\n throw new BundleIntegrityError('autoUnlock body missing `_autoUnlock` blob')\n }\n const blobObj = blob as Record<string, unknown>\n const kind = blobObj['kind']\n if (kind !== 'unsealed' && kind !== 'sealed') {\n throw new BundleIntegrityError(\n `autoUnlock blob has invalid kind ${String(kind)}; expected 'unsealed' or 'sealed'`,\n )\n }\n return {\n dump: obj['dump'],\n blob: blob as AutoUnlockBody['_autoUnlock'],\n }\n}\n\n/**\n * Transfer-seal payload (#206). The destination DEKs, exported to raw\n * bytes and AES-256-GCM-sealed *as a set* under the one-time transfer\n * key. `adoptPartition` (#207) unseals this; `createOwnerOnAdoptedPartition`\n * (#208) re-wraps the raw DEKs under the recipient's KEK.\n */\nexport interface TransferSealPayload {\n readonly v: 1\n readonly alg: 'aes-256-gcm-pre-shared'\n readonly sealId: string\n /** base64(AES-256-GCM(transferKey, JSON of { collection: base64(rawDEK) })) — iv ‖ ct ‖ tag. */\n readonly payload: string\n}\n\n/**\n * Body wrapper for an extracted, transfer-sealed partition (#203/#206).\n * Sibling to {@link AutoUnlockBody}; selected by `header.bundleKind ===\n * 'extracted-partition'`. The inner `dump` is a re-keyed projection with\n * an empty `keyrings` map.\n */\nexport interface ExtractedPartitionBody {\n readonly _noydb_bundle_body: 1\n readonly dump: string\n readonly _transferSeal: TransferSealPayload\n}\n\nexport function buildExtractedPartitionWrapper(\n dumpJson: string,\n seal: TransferSealPayload,\n): ExtractedPartitionBody {\n return { _noydb_bundle_body: 1, dump: dumpJson, _transferSeal: seal }\n}\n\nexport function parseExtractedPartitionBody(\n bodyString: string,\n): { dump: string; seal: TransferSealPayload } {\n let parsed: unknown\n try {\n parsed = JSON.parse(bodyString)\n } catch (err) {\n throw new BundleIntegrityError(\n 'header declared extracted-partition but body could not be parsed as JSON wrapper: '\n + (err instanceof Error ? err.message : String(err)),\n )\n }\n if (typeof parsed !== 'object' || parsed === null) {\n throw new BundleIntegrityError('extracted-partition body is not a JSON object')\n }\n const obj = parsed as Record<string, unknown>\n if (obj['_noydb_bundle_body'] !== 1) {\n throw new BundleIntegrityError(\n 'extracted-partition body missing `_noydb_bundle_body: 1` discriminator',\n )\n }\n if (typeof obj['dump'] !== 'string') {\n throw new BundleIntegrityError('extracted-partition body must carry a string `dump` field')\n }\n const seal = obj['_transferSeal']\n if (typeof seal !== 'object' || seal === null) {\n throw new BundleIntegrityError('extracted-partition body missing `_transferSeal` blob')\n }\n const s = seal as Record<string, unknown>\n if (s['v'] !== 1 || s['alg'] !== 'aes-256-gcm-pre-shared'\n || typeof s['sealId'] !== 'string' || typeof s['payload'] !== 'string') {\n throw new BundleIntegrityError('extracted-partition `_transferSeal` blob is malformed')\n }\n return { dump: obj['dump'], seal: seal as TransferSealPayload }\n}\n\n/**\n * Coerce an unsealed perUser entry to `AutoCredential`. Pre-0.2 bundles\n * store bare strings; 0.2+ bundles store `{ kind, value }` objects.\n */\nfunction coerceUnsealed(entry: AutoCredential | string): AutoCredential {\n if (typeof entry === 'string') return { kind: 'passphrase', value: entry }\n return entry\n}\n\n/**\n * Resolve the `_autoUnlock` blob into a typed per-user credential map.\n *\n * - For `kind: 'unsealed'`: pass through, coercing pre-0.2 bare strings\n * to `{ kind: 'passphrase', value }`.\n * - For `kind: 'sealed'`: pick a `SealingKeyProvider` from\n * `opts.sealingProviders` whose `.id` matches each entry's `pid`;\n * unseal to `AutoCredential`. When no provider matches AND strict mode\n * (default), throw `BundleSealMismatchError`. With\n * `attemptUnsealAcrossProviders: true`, try each provider whose\n * `alg` matches the envelope.\n * Exception: if an unmatched entry carries a `hint` field (recipient-target\n * entries), it passes through as `{ kind, value: base64sealed }` rather than\n * throwing — multi-recipient bundles have N-1 unmatched entries from each\n * recipient's perspective, and the consumer is expected to ignore entries\n * not addressed to them.\n * - When `sealingProviders` is unset entirely on a `'sealed'` bundle,\n * pass through the SEALED entries as `{ kind, value: base64sealed }` —\n * the caller can inspect or unseal elsewhere.\n *\n * Pre-0.2 sealed entries missing `kind` default to `'passphrase'`.\n */\nasync function resolveAutoUnlock(\n blob: AutoUnlockBody['_autoUnlock'],\n opts: ReadNoydbBundleOptions,\n): Promise<{ kind: 'unsealed' | 'sealed'; perUser: Record<string, AutoCredential> }> {\n if (blob.kind === 'unsealed') {\n const resolved: Record<string, AutoCredential> = {}\n for (const [userId, entry] of Object.entries(blob.perUser)) {\n resolved[userId] = coerceUnsealed(entry)\n }\n return { kind: 'unsealed', perUser: resolved }\n }\n // Sealed path.\n if (opts.sealingProviders === undefined || opts.sealingProviders.length === 0) {\n // Inspection mode — pass the sealed payload through as a typed\n // credential whose `value` is the opaque base64 sealed bytes.\n // The caller is signalled by `kind: 'sealed'` on the outer result.\n const passthrough: Record<string, AutoCredential> = {}\n for (const [userId, entry] of Object.entries(blob.perUser)) {\n passthrough[userId] = { kind: entry.kind ?? 'passphrase', value: entry.sealed }\n }\n return { kind: 'sealed', perUser: passthrough }\n }\n const providersByPid = new Map<string, SealingKeyProvider>()\n for (const p of opts.sealingProviders) providersByPid.set(p.id, p)\n\n const decoder = new TextDecoder()\n const unsealedMap: Record<string, AutoCredential> = {}\n\n for (const [userId, entry] of Object.entries(blob.perUser)) {\n const credKind: AutoCredentialKind = entry.kind ?? 'passphrase'\n const provider = providersByPid.get(entry.pid)\n if (provider === undefined) {\n if (opts.attemptUnsealAcrossProviders === true) {\n // Try each provider; first that succeeds wins.\n let opened: string | null = null\n for (const candidate of opts.sealingProviders) {\n try {\n const plaintextBytes = await candidate.unseal(base64ToBytes(entry.sealed))\n opened = decoder.decode(plaintextBytes)\n break\n } catch {\n // try next\n }\n }\n if (opened === null) {\n if (entry.hint !== undefined) {\n // Recipient-target entry not addressed to any held key — pass through sealed.\n // Other recipients' entries in a multi-recipient bundle are opaque to us.\n unsealedMap[userId] = { kind: credKind, value: entry.sealed }\n continue\n }\n throw new BundleSealMismatchError(userId, entry.pid)\n }\n unsealedMap[userId] = { kind: credKind, value: opened }\n continue\n }\n if (entry.hint !== undefined) {\n // Recipient-target entry not addressed to any held key — pass through sealed.\n // Multi-recipient bundles deliberately seal each user's entry under their own\n // public key; a reader holding only alice's key will not match bob's pid.\n unsealedMap[userId] = { kind: credKind, value: entry.sealed }\n continue\n }\n throw new BundleSealMismatchError(userId, entry.pid)\n }\n const plaintextBytes = await provider.unseal(base64ToBytes(entry.sealed))\n unsealedMap[userId] = { kind: credKind, value: decoder.decode(plaintextBytes) }\n }\n return { kind: 'sealed', perUser: unsealedMap }\n}\n\nfunction bytesToBase64(bytes: Uint8Array): string {\n let binary = ''\n for (let i = 0; i < bytes.length; i++) binary += String.fromCharCode(bytes[i]!)\n return btoa(binary)\n}\n\nfunction base64ToBytes(b64: string): Uint8Array {\n const binary = atob(b64)\n const out = new Uint8Array(binary.length)\n for (let i = 0; i < binary.length; i++) out[i] = binary.charCodeAt(i)\n return out\n}\n\n/**\n * Detect whether the runtime's `CompressionStream` supports brotli.\n *\n * Brotli requires Node 22+ / Chrome 124+ / Firefox 122+. The\n * detection runs the `CompressionStream` constructor in a\n * try/catch — unsupported formats throw `TypeError` synchronously,\n * making this a safe one-shot check that we cache for the\n * lifetime of the process.\n */\nlet cachedBrotliSupport: boolean | null = null\nfunction supportsBrotliCompression(): boolean {\n if (cachedBrotliSupport !== null) return cachedBrotliSupport\n try {\n new CompressionStream('br' as CompressionFormat)\n cachedBrotliSupport = true\n } catch {\n cachedBrotliSupport = false\n }\n return cachedBrotliSupport\n}\n\n/** Test-only: reset the brotli detection cache between tests. */\nexport function resetBrotliSupportCache(): void {\n cachedBrotliSupport = null\n}\n\n/**\n * Pick the compression algorithm and the corresponding format byte\n * from a user option. Throws if the user explicitly requests brotli\n * on a runtime that doesn't support it — a silent fallback would\n * make the produced bundle smaller-than-expected and confuse\n * size-bound tests.\n */\nfunction selectCompression(option: WriteNoydbBundleOptions['compression']): {\n format: CompressionAlgo\n streamFormat: CompressionFormat | null\n} {\n const choice = option ?? 'auto'\n if (choice === 'none') return { format: COMPRESSION_NONE, streamFormat: null }\n if (choice === 'gzip') return { format: COMPRESSION_GZIP, streamFormat: 'gzip' }\n if (choice === 'brotli') {\n if (!supportsBrotliCompression()) {\n throw new Error(\n `writeNoydbBundle({ compression: 'brotli' }) is not supported on this ` +\n `runtime. Brotli requires Node 22+, Chrome 124+, or Firefox 122+. ` +\n `Use { compression: 'auto' } to fall back to gzip silently, or ` +\n `{ compression: 'gzip' } to be explicit.`,\n )\n }\n return { format: COMPRESSION_BROTLI, streamFormat: 'br' as CompressionFormat }\n }\n // 'auto' — prefer brotli, fall back to gzip\n if (supportsBrotliCompression()) {\n return { format: COMPRESSION_BROTLI, streamFormat: 'br' as CompressionFormat }\n }\n return { format: COMPRESSION_GZIP, streamFormat: 'gzip' }\n}\n\n/**\n * Pump a Uint8Array through a CompressionStream / DecompressionStream\n * and collect the output. Both APIs are universally available in\n * Node 18+ and modern browsers; the only variance is which\n * formats they support, handled by `selectCompression` above.\n *\n * Implementation: build a single-chunk ReadableStream from the\n * input, pipe through the transform, then drain the resulting\n * ReadableStream into a single concatenated Uint8Array. This is\n * O(N) memory in the input + output sizes, which is fine for the\n * dump-sized payloads (typically <50MB) targets.\n */\nasync function pumpThroughStream(\n input: Uint8Array,\n stream: CompressionStream | DecompressionStream,\n): Promise<Uint8Array> {\n const readable = new Blob([input as BlobPart]).stream().pipeThrough(stream)\n const reader = readable.getReader()\n const chunks: Uint8Array[] = []\n let total = 0\n for (;;) {\n const { value, done } = await reader.read()\n if (done) break\n if (value) {\n chunks.push(value as Uint8Array)\n total += value.length\n }\n }\n const out = new Uint8Array(total)\n let offset = 0\n for (const chunk of chunks) {\n out.set(chunk, offset)\n offset += chunk.length\n }\n return out\n}\n\n/**\n * SHA-256 hex digest of `bytes`. Used for the bundle integrity\n * hash carried in the header. Web Crypto API only — no Node\n * crypto module, no third-party hash library.\n *\n * The output format is lowercase hex (64 chars for SHA-256). The\n * format validator pins this — uppercase or mixed-case digests\n * are rejected, so the writer and reader agree on canonicalization.\n */\nasync function sha256Hex(bytes: Uint8Array): Promise<string> {\n // Copy into a fresh ArrayBuffer-backed Uint8Array. The\n // underlying buffer of `bytes` may be SharedArrayBuffer (e.g.\n // from a worker), which `subtle.digest` rejects via TypeScript's\n // BufferSource type. Allocating a fresh ArrayBuffer-backed view\n // sidesteps the type narrowing and is portable across all\n // runtimes — the copy cost is O(N) but bundle bodies are\n // typically <50MB, well below the threshold where the copy\n // matters.\n const copy = new Uint8Array(bytes.length)\n copy.set(bytes)\n const digest = await crypto.subtle.digest('SHA-256', copy)\n const view = new Uint8Array(digest)\n let hex = ''\n for (let i = 0; i < view.length; i++) {\n hex += view[i]!.toString(16).padStart(2, '0')\n }\n return hex\n}\n\n/**\n * Concatenate any number of Uint8Arrays into a single new buffer.\n * Used to assemble the final bundle from its prefix + header +\n * body parts.\n */\nfunction concatBytes(parts: readonly Uint8Array[]): Uint8Array {\n let total = 0\n for (const p of parts) total += p.length\n const out = new Uint8Array(total)\n let offset = 0\n for (const p of parts) {\n out.set(p, offset)\n offset += p.length\n }\n return out\n}\n\n/**\n * Replace the bundle's keyrings with freshly built recipient slots,\n * one per supplied recipient. No-op when neither `exportPassphrase`\n * nor `recipients` is set — the source keyring is inherited as-is.\n *\n * The single-passphrase shorthand creates a one-recipient list whose\n * id, role, and permissions inherit from the source vault — useful\n * for \"back up to a different passphrase\" without changing role\n * semantics. The multi-recipient form wraps each slot independently\n * with its declared role + permissions.\n *\n * @internal\n */\nasync function applyRecipientRewrap(\n vault: Vault,\n dumpJson: string,\n opts: WriteNoydbBundleOptions,\n): Promise<string> {\n if (opts.exportPassphrase === undefined && opts.recipients === undefined) {\n return dumpJson\n }\n\n const recipients: readonly BundleRecipient[] =\n opts.recipients ?? [\n {\n id: vault.userId,\n passphrase: opts.exportPassphrase as string,\n role: vault.role,\n },\n ]\n\n const recipientKeyrings = await vault.buildBundleRecipientKeyrings(recipients)\n\n const backup = JSON.parse(dumpJson) as { keyrings: unknown; [k: string]: unknown }\n backup.keyrings = recipientKeyrings\n return JSON.stringify(backup)\n}\n\n/**\n * Apply opt-in slice filters to a vault dump JSON string. Filters that\n * narrow the bundle without crossing the encryption boundary — both\n * operate on metadata (collection name, envelope `_ts`) and never need\n * to decrypt records. When neither filter is set, the dump is returned\n * unchanged so the no-arg path stays a pure passthrough.\n *\n * Internal-collection filtering: when a `collections` allowlist is\n * provided, the bundle still carries `_internal` (ledger entries) and\n * the keyrings — they're necessary for the receiver to verify and\n * unlock the bundle. The allowlist applies to the user-collection\n * map only.\n *\n * @internal\n */\nfunction applySliceFilters(\n dumpJson: string,\n opts: WriteNoydbBundleOptions,\n): string {\n const collectionsFilter = opts.collections\n ? new Set(opts.collections)\n : null\n const sinceMs =\n opts.since !== undefined ? new Date(opts.since).getTime() : null\n if (collectionsFilter === null && sinceMs === null) return dumpJson\n\n // Parse, prune, re-serialize. The dump shape is stable\n // (VaultBackup) so this is a one-off allocation; for vaults beyond\n // the documented 1K–50K target a streaming variant would be a\n // follow-up, but the simple parse path keeps the slice path\n // type-safe and trivially auditable.\n const backup = JSON.parse(dumpJson) as {\n collections?: Record<string, Record<string, { _ts?: string }>>\n [k: string]: unknown\n }\n\n if (backup.collections && typeof backup.collections === 'object') {\n const next: Record<string, Record<string, unknown>> = {}\n for (const [name, records] of Object.entries(backup.collections)) {\n if (collectionsFilter && !collectionsFilter.has(name)) continue\n if (sinceMs === null) {\n next[name] = records\n continue\n }\n const kept: Record<string, unknown> = {}\n for (const [id, env] of Object.entries(records)) {\n const envTs = env._ts ? new Date(env._ts).getTime() : NaN\n if (Number.isFinite(envTs) && envTs >= sinceMs) {\n kept[id] = env\n }\n }\n next[name] = kept\n }\n backup.collections = next as typeof backup.collections\n }\n\n return JSON.stringify(backup)\n}\n\n/**\n * Apply opt-in plaintext-tier filters\n * to a vault dump. Operates BEFORE `applySliceFilters` so the metadata\n * pass sees the trimmed record set.\n *\n * The filter never re-encrypts: surviving records carry their original\n * envelope unchanged. Failing records are dropped from the\n * `collections` map. Internal collections (ledger, deltas) and the\n * keyrings map are untouched.\n *\n * @internal\n */\nasync function applyPlaintextFilters(\n vault: Vault,\n dumpJson: string,\n opts: WriteNoydbBundleOptions,\n): Promise<string> {\n if (opts.where === undefined && opts.tierAtMost === undefined) {\n return dumpJson\n }\n\n type Env = { _ts?: string; _tier?: number; _iv: string; _data: string }\n const backup = JSON.parse(dumpJson) as {\n collections?: Record<string, Record<string, Env>>\n [k: string]: unknown\n }\n if (!backup.collections || typeof backup.collections !== 'object') {\n return dumpJson\n }\n\n const tierCeiling = opts.tierAtMost\n const where = opts.where\n\n const next: Record<string, Record<string, Env>> = {}\n for (const [collName, records] of Object.entries(backup.collections)) {\n const kept: Record<string, Env> = {}\n for (const [id, env] of Object.entries(records)) {\n // Tier ceiling — runs FIRST so we don't waste a decrypt on\n // records about to be dropped anyway. Envelope tier defaults to\n // 0 when absent (matches Vault's tier-0 conventions).\n if (tierCeiling !== undefined) {\n const tier = env._tier ?? 0\n if (tier > tierCeiling) continue\n }\n // Plaintext predicate — decrypt, run, keep on truthy. Errors\n // from inside the predicate propagate (callers want to see why\n // their filter blew up rather than getting a silent passthrough).\n if (where !== undefined) {\n const record = await vault._decryptEnvelopeForBundleFilter(\n env as never,\n collName,\n )\n const ok = await where(record, { collection: collName, id })\n if (!ok) continue\n }\n kept[id] = env\n }\n next[collName] = kept\n }\n backup.collections = next\n return JSON.stringify(backup)\n}\n\n/**\n * Write a `.noydb` bundle for the given vault.\n *\n * Pipeline:\n * 1. Resolve or create the compartment's stable bundle handle\n * via `vault.getBundleHandle()` — same handle on\n * every export from the same vault instance, so cloud\n * adapters can use it as a primary key.\n * 2. `vault.dump()` → JSON string with encrypted records\n * inside.\n * 3. UTF-8 encode the dump string.\n * 4. Compress (brotli if available, gzip fallback by default).\n * 5. Compute SHA-256 of the compressed body for integrity.\n * 6. Build the minimum-disclosure header from format version,\n * handle, body length, body sha.\n * 7. Serialize: magic (4) + flags (1) + algo (1) + headerLen (4)\n * + header JSON (N) + compressed body (M).\n *\n * The output is a single `Uint8Array`. Consumers writing to disk\n * pass it to `fs.writeFile`; consumers uploading to cloud storage\n * pass it as the request body. The `@noy-db/file` adapter wraps\n * this with a `saveBundle(path, vault)` helper.\n */\n/**\n * Assemble the final `.noydb` container bytes from a body JSON string +\n * header extras. Shared by `writeNoydbBundle` and `extractPartition`\n * so both producers go through one compress/hash/prefix path.\n *\n * @internal\n */\nexport async function assembleBundleContainer(opts: {\n handle: string\n bodyJsonStr: string\n compression: WriteNoydbBundleOptions['compression']\n /** Header fields beyond the always-present four. */\n headerExtras?: Partial<Pick<NoydbBundleHeader, 'publicEnvelope' | 'autoUnlock' | 'bundleKind' | 'transferSeal'>>\n}): Promise<Uint8Array> {\n const dumpBytes = new TextEncoder().encode(opts.bodyJsonStr)\n const { format, streamFormat } = selectCompression(opts.compression)\n const body = streamFormat === null\n ? dumpBytes\n : await pumpThroughStream(dumpBytes, new CompressionStream(streamFormat))\n const bodySha256 = await sha256Hex(body)\n\n const header: NoydbBundleHeader = {\n formatVersion: NOYDB_BUNDLE_FORMAT_VERSION,\n handle: opts.handle,\n bodyBytes: body.length,\n bodySha256,\n ...(opts.headerExtras?.publicEnvelope !== undefined ? { publicEnvelope: opts.headerExtras.publicEnvelope } : {}),\n ...(opts.headerExtras?.autoUnlock !== undefined ? { autoUnlock: opts.headerExtras.autoUnlock } : {}),\n ...(opts.headerExtras?.bundleKind !== undefined ? { bundleKind: opts.headerExtras.bundleKind } : {}),\n ...(opts.headerExtras?.transferSeal !== undefined ? { transferSeal: opts.headerExtras.transferSeal } : {}),\n }\n const headerBytes = encodeBundleHeader(header)\n\n const prefix = new Uint8Array(NOYDB_BUNDLE_PREFIX_BYTES)\n prefix.set(NOYDB_BUNDLE_MAGIC, 0)\n prefix[4] = (streamFormat === null ? 0 : FLAG_COMPRESSED) | FLAG_HAS_INTEGRITY_HASH\n prefix[5] = format\n writeUint32BE(prefix, 6, headerBytes.length)\n\n return concatBytes([prefix, headerBytes, body])\n}\n\nexport async function writeNoydbBundle(\n vault: Vault,\n opts: WriteNoydbBundleOptions = {},\n): Promise<Uint8Array> {\n if (opts.exportPassphrase !== undefined && opts.recipients !== undefined) {\n throw new Error(\n 'writeNoydbBundle: pass either exportPassphrase or recipients, not both',\n )\n }\n\n // #197/#215 — auto-unlock: normalize once, validate + build from the\n // same NormalizedAutoUnlock object so there's no double-normalize call.\n const normalizedAutoUnlock = normalizeAutoUnlock(opts)\n const autoUnlockMode = validateAutoUnlockOptions(opts, normalizedAutoUnlock)\n\n const handle = await vault.getBundleHandle()\n const dumpJson = await vault.dump()\n\n // Re-keying: when caller supplied recipients (or the single-recipient\n // shorthand), substitute the bundle's `keyrings` map with freshly\n // built recipient slots before slice filters run.\n const rekeyed = await applyRecipientRewrap(vault, dumpJson, opts)\n // Plaintext-tier filters run BEFORE\n // the metadata-only slice — that way the metadata pass sees the\n // already-trimmed record set and the two filter chains compose\n // cleanly.\n const plainFiltered = await applyPlaintextFilters(vault, rekeyed, opts)\n const filtered = applySliceFilters(plainFiltered, opts)\n\n // If no auto-unlock requested, body remains the raw dump JSON\n // (pre-#197 shape). Otherwise build the wrapped body containing the\n // dump + `_autoUnlock` blob and serialize.\n const bodyJsonStr = normalizedAutoUnlock === null\n ? filtered\n : JSON.stringify(await buildAutoUnlockWrapper(filtered, normalizedAutoUnlock))\n // Snapshot the source vault's public envelope into the header\n // when one is persisted. `Vault.getPublicEnvelope` tolerates a\n // missing document and returns undefined, which we propagate as\n // \"no envelope in the header.\" Vaults without a\n // `_meta/public-envelope` document produce minimum-disclosure\n // headers exactly like before, preserving back-compat.\n const publicEnvelope = await vault.getPublicEnvelope()\n\n return assembleBundleContainer({\n handle,\n bodyJsonStr,\n compression: opts.compression,\n headerExtras: {\n ...(publicEnvelope !== undefined ? { publicEnvelope } : {}),\n ...(autoUnlockMode !== null ? { autoUnlock: autoUnlockMode } : {}),\n },\n })\n}\n\n/**\n * Internal helper shared by both readers — parses just the prefix\n * + header region of a bundle without touching the body. Returns\n * the parsed header plus the offset where the body starts and the\n * compression algorithm needed to decompress it.\n *\n * Throws on any format violation: missing/invalid magic, truncated\n * prefix, header length larger than the file, or unknown\n * compression algorithm.\n */\nfunction parsePrefixAndHeader(bytes: Uint8Array): {\n header: NoydbBundleHeader\n bodyOffset: number\n algo: CompressionAlgo\n flags: number\n} {\n if (!hasNoydbBundleMagic(bytes)) {\n throw new Error(\n `Not a .noydb bundle: missing 'NDB1' magic prefix. The first 4 bytes ` +\n `are ${[...bytes.slice(0, 4)].map((b) => b.toString(16).padStart(2, '0')).join(' ')}.`,\n )\n }\n if (bytes.length < NOYDB_BUNDLE_PREFIX_BYTES) {\n throw new Error(\n `Truncated .noydb bundle: file is only ${bytes.length} bytes, ` +\n `which is less than the ${NOYDB_BUNDLE_PREFIX_BYTES}-byte fixed prefix.`,\n )\n }\n const flags = bytes[4]!\n const algo = bytes[5]!\n if (algo !== COMPRESSION_NONE && algo !== COMPRESSION_GZIP && algo !== COMPRESSION_BROTLI) {\n throw new Error(\n `.noydb bundle declares unknown compression algorithm ${algo}. ` +\n `Known values: 0 (none), 1 (gzip), 2 (brotli).`,\n )\n }\n const headerLength = readUint32BE(bytes, 6)\n const bodyOffset = NOYDB_BUNDLE_PREFIX_BYTES + headerLength\n if (bodyOffset > bytes.length) {\n throw new Error(\n `Truncated .noydb bundle: declared header length ${headerLength} ` +\n `would extend past end of file (${bytes.length} bytes).`,\n )\n }\n const headerBytes = bytes.slice(NOYDB_BUNDLE_PREFIX_BYTES, bodyOffset)\n const header = decodeBundleHeader(headerBytes)\n return { header, bodyOffset, algo: algo as CompressionAlgo, flags }\n}\n\n/**\n * Read just the bundle header — no body decompression, no\n * integrity verification. Intended for cloud-listing UIs that want\n * to show the handle and size before downloading the full body.\n *\n * Returns the same `NoydbBundleHeader` shape as the writer, with\n * minimum-disclosure validation already applied.\n *\n * **Cost** — O(prefix + header bytes). The header is normally well\n * under 1 KB, but may grow to roughly 256 KB when a `publicEnvelope`\n * with an inline icon is present. Cloud-listing UIs that previously\n * assumed sub-KB header reads should account for this when sizing\n * range requests against bundles that may carry icons.\n */\nexport function readNoydbBundleHeader(bytes: Uint8Array): NoydbBundleHeader {\n return parsePrefixAndHeader(bytes).header\n}\n\n/**\n * Read just the bundle's public envelope (`docs/subsystems/public-envelope.md`)\n * — without verifying the body or even parsing the dump JSON. Pass\n * the raw bundle bytes; receive the owner-curated metadata or\n * `undefined` if the bundle was written without one.\n *\n * Locale-resolves any `name` / `description` map fields when `locale`\n * is supplied. Omitting `locale` returns the raw envelope.\n *\n * Same security caveat as the on-vault read path — the public\n * envelope is **untrusted hint** in v1; the encrypted body remains\n * the source of truth for vault contents.\n */\nexport function readNoydbBundlePublicEnvelope(\n bytes: Uint8Array,\n opts: { readonly locale?: string } = {},\n): PublicEnvelope | undefined {\n const header = parsePrefixAndHeader(bytes).header\n const env = header.publicEnvelope\n if (!env) return undefined\n if (opts.locale === undefined) return env\n return {\n ...env,\n ...(env.name !== undefined ? { name: pickLocale(env.name, opts.locale, env.defaultLocale) } : {}),\n ...(env.description !== undefined ? { description: pickLocale(env.description, opts.locale, env.defaultLocale) } : {}),\n }\n}\n\n/**\n * Read a full `.noydb` bundle: validate magic + header, verify\n * integrity hash over the body bytes, decompress, and return the\n * original `vault.dump()` JSON string ready to pass to\n * `vault.load()`.\n *\n * Throws `BundleIntegrityError` if the body's actual SHA-256 does\n * not match the value declared in the header. Distinct from a\n * format error so consumers can pattern-match in catch blocks\n * (corrupted-in-transit vs malformed-by-producer).\n *\n * Note: this function does NOT take a passphrase. The dump JSON\n * inside the body still contains encrypted records — restoring\n * the vault requires `vault.load(dumpJson, passphrase)`\n * after this call. Splitting the layers keeps the bundle module\n * free of crypto concerns and lets the same code feed format\n * inspectors that never decrypt anything.\n */\nexport async function readNoydbBundle(\n bytes: Uint8Array,\n opts: ReadNoydbBundleOptions = {},\n): Promise<NoydbBundleReadResult> {\n const { header, bodyOffset, algo } = parsePrefixAndHeader(bytes)\n const body = bytes.slice(bodyOffset)\n\n // Length check before hash check — a length mismatch is the\n // cheapest tamper signal and produces a more actionable error.\n if (body.length !== header.bodyBytes) {\n throw new BundleIntegrityError(\n `body length ${body.length} does not match header.bodyBytes ` +\n `${header.bodyBytes}. The bundle was truncated or padded ` +\n `between write and read.`,\n )\n }\n\n const actualSha = await sha256Hex(body)\n if (actualSha !== header.bodySha256) {\n throw new BundleIntegrityError(\n `body sha256 ${actualSha} does not match header.bodySha256 ` +\n `${header.bodySha256}. The bundle bytes were modified between ` +\n `write and read — refuse to decompress.`,\n )\n }\n\n let dumpBytes: Uint8Array\n if (algo === COMPRESSION_NONE) {\n dumpBytes = body\n } else {\n const streamFormat: CompressionFormat =\n algo === COMPRESSION_BROTLI ? ('br' as CompressionFormat) : 'gzip'\n try {\n dumpBytes = await pumpThroughStream(body, new DecompressionStream(streamFormat))\n } catch (err) {\n throw new BundleIntegrityError(\n `decompression failed: ${(err as Error).message}. The bundle ` +\n `passed the integrity hash but the body is not valid ` +\n `${streamFormat} data — likely a producer bug.`,\n )\n }\n }\n\n const bodyString = new TextDecoder('utf-8', { fatal: true }).decode(dumpBytes)\n\n // #197 — when the header signaled an auto-unlock, the body is a\n // JSON wrapper carrying the dump string + the auto-unlock blob.\n // When absent, the body IS the raw dump JSON (pre-#197 shape).\n if (header.autoUnlock === undefined) {\n return { header, dumpJson: bodyString }\n }\n const { dump, blob } = parseAutoUnlockBody(bodyString)\n const autoUnlock = await resolveAutoUnlock(blob, opts)\n return { header, dumpJson: dump, autoUnlock }\n}\n","/**\n * Transitive-closure FK walker (#201). Computes the set of\n * (collection, id) tuples reachable from seed predicates, so a\n * partition extraction ships a referentially-complete subset.\n *\n * Two-phase, plaintext, read-only (runs inside the unlocked vault\n * session — see foundation §13.4 / spec invariant 7):\n * 1. INBOUND expansion: from selected records, pull every record\n * that references them (children travel with parents), to a\n * fixed point.\n * 2. OUTBOUND completion: pull every parent the selected set\n * references (no dangling FKs), transitively, WITHOUT\n * re-expanding inbound from those parents (bounds the closure).\n *\n * The FK graph is auto-derived from the vault's existing RefRegistry\n * (the `ref('target')` declarations on collections) — no hand-written\n * edge list. See the design spec §4.1.\n *\n * @module\n */\nimport type { Vault } from '../vault.js'\nimport { PartitionExtractionError } from '../errors.js'\n\n/** Seed predicate per collection. Records that return true become roots. */\nexport interface WalkClosureOptions {\n readonly seeds: Record<\n string,\n (record: Record<string, unknown>) => boolean | Promise<boolean>\n >\n /** Max fixed-point iterations before throwing. Default 16. */\n readonly maxDepth?: number\n}\n\nexport interface ClosureResult {\n /** collection → set of record ids that travel together. */\n readonly closure: Map<string, Set<string>>\n readonly graph: {\n /** Fixed-point iterations the walk needed to converge. */\n readonly depth: number\n /** True if an edge pointed back to an already-selected node. */\n readonly cyclesDetected: boolean\n }\n}\n\nexport async function walkClosure(\n vault: Vault,\n opts: WalkClosureOptions,\n): Promise<ClosureResult> {\n const closure = new Map<string, Set<string>>()\n\n // Records carry a string `id` by construction (Collection.put(id: string)).\n // A non-string id during the walk means a malformed record — fail loud\n // rather than silently dropping it from the closure (which would leave a\n // dangling FK or a missing child in the extracted bundle).\n const requireStringId = (collection: string, record: Record<string, unknown>): string => {\n const id = record['id']\n if (typeof id !== 'string') {\n throw new PartitionExtractionError(\n `walkClosure: record in collection \"${collection}\" has a non-string ` +\n `id (${typeof id}); cannot include it in the partition closure.`,\n )\n }\n return id\n }\n\n const add = (collection: string, id: string): boolean => {\n let set = closure.get(collection)\n if (!set) {\n set = new Set<string>()\n closure.set(collection, set)\n }\n if (set.has(id)) return false\n set.add(id)\n return true\n }\n\n // Phase 0: evaluate seed predicates.\n for (const [collectionName, predicate] of Object.entries(opts.seeds)) {\n const coll = vault.collection<Record<string, unknown>>(collectionName)\n const records = await coll.list()\n for (const record of records) {\n if (await predicate(record)) {\n add(collectionName, requireStringId(collectionName, record))\n }\n }\n }\n\n const { refRegistry } = vault._introspectState()\n const maxDepth = opts.maxDepth ?? 16\n let cyclesDetected = false\n\n // `depth` counts PRODUCTIVE expansion generations (rounds that added at\n // least one new record), taken as the max over the two phases — i.e. the\n // FK hop-distance the closure needed, not the raw loop-iteration count.\n // The terminal draining pass that adds nothing does not count.\n let inboundDepth = 0\n let outboundDepth = 0\n\n // Phase 1 — INBOUND expansion. Worklist of newly-added (collection,id)\n // whose children we still need to pull.\n let frontier: Array<[string, string]> = []\n for (const [c, ids] of closure) for (const id of ids) frontier.push([c, id])\n\n while (frontier.length > 0) {\n const next: Array<[string, string]> = []\n for (const [collectionName, id] of frontier) {\n // Which collections reference THIS collection, and via which field?\n for (const inbound of refRegistry.getInbound(collectionName)) {\n const childColl = vault.collection<Record<string, unknown>>(inbound.collection)\n // TODO(perf): re-scans the full inbound collection on every frontier\n // element. O(frontier · inboundCollections · records) per depth. Fine\n // at consumer-firm scale (foundation §13.4); revisit with an index or\n // pagination if extraction over very large vaults gets slow.\n const childRecords = await childColl.list()\n for (const child of childRecords) {\n const fk = child[inbound.field]\n // Only scalar FK values can match an id; skip null/objects\n // (mirrors checkIntegrity's scalar guard, vault.ts).\n if (typeof fk !== 'string' && typeof fk !== 'number') continue\n if (String(fk) !== id) continue\n const childId = requireStringId(inbound.collection, child)\n if (add(inbound.collection, childId)) {\n next.push([inbound.collection, childId])\n } else {\n cyclesDetected = true\n }\n }\n }\n }\n if (next.length > 0 && ++inboundDepth > maxDepth) {\n throw new PartitionExtractionError(\n `walkClosure exceeded maxDepth=${maxDepth}; the FK graph may be ` +\n `unexpectedly deep or cyclic. Raise maxDepth or narrow the seeds.`,\n )\n }\n frontier = next\n }\n\n // Phase 2 — OUTBOUND completion. Pull referenced parents so no FK\n // dangles. Transitive over outbound edges only; parents are NOT\n // inbound-expanded (that would drag in unrelated siblings).\n let outboundFrontier: Array<[string, string]> = []\n for (const [c, ids] of closure) for (const id of ids) outboundFrontier.push([c, id])\n\n while (outboundFrontier.length > 0) {\n const next: Array<[string, string]> = []\n for (const [collectionName, id] of outboundFrontier) {\n const outbound = refRegistry.getOutbound(collectionName)\n if (Object.keys(outbound).length === 0) continue\n const coll = vault.collection<Record<string, unknown>>(collectionName)\n const record = await coll.get(id)\n if (!record) continue\n for (const [field, descriptor] of Object.entries(outbound)) {\n const rawId = record[field]\n // Only scalar FK values reference a parent id; skip null/objects.\n if (typeof rawId !== 'string' && typeof rawId !== 'number') continue\n const parentId = String(rawId)\n // Reaching an already-selected parent here is normal DAG\n // convergence (a child referencing its in-scope parent), not a\n // cycle — so do NOT flag cyclesDetected in the outbound phase.\n if (add(descriptor.target, parentId)) {\n next.push([descriptor.target, parentId])\n }\n }\n }\n if (next.length > 0 && ++outboundDepth > maxDepth) {\n throw new PartitionExtractionError(\n `walkClosure exceeded maxDepth=${maxDepth} during outbound completion.`,\n )\n }\n outboundFrontier = next\n }\n\n const depth = Math.max(inboundDepth, outboundDepth)\n\n return { closure, graph: { depth, cyclesDetected } }\n}\n","/**\n * Partition-extraction dry-run (#202). Read-only preview of what an\n * `extractPartition` would move: record counts, byte totals, and the\n * timestamp span per collection — computed from raw encrypted\n * envelopes WITHOUT decrypting them. Writes nothing, mutates nothing.\n *\n * @module\n */\nimport type { Vault } from '../vault.js'\nimport { walkClosure, type WalkClosureOptions } from './walk-closure.js'\n\nexport interface ExtractionPreview {\n readonly totalRecords: number\n /** Sum of serialized encrypted-envelope sizes (bytes). */\n readonly totalBytes: number\n readonly byCollection: ReadonlyArray<{\n readonly name: string\n readonly recordCount: number\n readonly bytes: number\n /** Earliest envelope `_ts` in this collection (lexicographic). */\n readonly oldestTs?: string\n readonly newestTs?: string\n }>\n readonly graph: { readonly depth: number; readonly cyclesDetected: boolean }\n /** Records the walk reached but whose envelope couldn't be read. */\n readonly inaccessible: ReadonlyArray<{ readonly collection: string; readonly id: string }>\n}\n\nexport async function describeExtraction(\n vault: Vault,\n opts: WalkClosureOptions,\n): Promise<ExtractionPreview> {\n const { closure, graph } = await walkClosure(vault, opts)\n\n const { name: vaultName, adapter } = vault._introspectState()\n const encoder = new TextEncoder()\n\n const byCollection: Array<{\n name: string; recordCount: number; bytes: number; oldestTs?: string; newestTs?: string\n }> = []\n const inaccessible: Array<{ collection: string; id: string }> = []\n let totalBytes = 0\n let totalRecords = 0\n\n for (const [collectionName, ids] of closure) {\n let bytes = 0\n let oldestTs: string | undefined\n let newestTs: string | undefined\n let recordCount = 0\n\n for (const id of ids) {\n const env = await adapter.get(vaultName, collectionName, id)\n if (!env) {\n // Walk reached it (via decrypted list) but the raw store read\n // returned nothing — surface rather than miscount.\n inaccessible.push({ collection: collectionName, id })\n continue\n }\n recordCount++\n bytes += encoder.encode(JSON.stringify(env)).length\n const ts = env._ts\n if (oldestTs === undefined || ts < oldestTs) oldestTs = ts\n if (newestTs === undefined || ts > newestTs) newestTs = ts\n }\n\n byCollection.push({\n name: collectionName,\n recordCount,\n bytes,\n // Spread conditionally — exactOptionalPropertyTypes forbids an\n // explicit `undefined` on an optional property.\n ...(oldestTs !== undefined ? { oldestTs } : {}),\n ...(newestTs !== undefined ? { newestTs } : {}),\n })\n totalBytes += bytes\n totalRecords += recordCount\n }\n\n byCollection.sort((a, b) => a.name.localeCompare(b.name))\n\n return Object.freeze({\n totalRecords,\n totalBytes,\n byCollection,\n graph,\n inaccessible,\n })\n}\n","/**\n * Partition extraction (#203 + #206). Walks the FK closure, re-encrypts\n * the selected records under fresh per-collection DEKs, seals those DEKs\n * under a one-time transfer key, and serializes an unowned\n * `extracted-partition` bundle.\n *\n * @module\n */\nimport type { Vault } from '../vault.js'\nimport type { EncryptedEnvelope } from '../types.js'\nimport { NOYDB_BACKUP_VERSION } from '../types.js'\nimport { decrypt, encrypt, generateDEK, bufferToBase64 } from '../crypto.js'\nimport { PartitionExtractionError } from '../errors.js'\nimport { walkClosure, type WalkClosureOptions } from './walk-closure.js'\nimport { generateULID } from './ulid.js'\nimport { SCHEMAS_COLLECTION } from '../persisted-schemas/storage.js'\nimport { NOYDB_FORMAT_VERSION } from '../types.js'\nimport { LEDGER_COLLECTION } from '../history/ledger/constants.js'\nimport { canonicalJson, hashEntry } from '../history/ledger/entry.js'\nimport type { LedgerEntry } from '../history/ledger/entry.js'\nimport { envelopePayloadHash } from '../history/ledger/hash.js'\nimport {\n assembleBundleContainer,\n buildExtractedPartitionWrapper,\n type TransferSealPayload,\n} from './bundle.js'\n\n/** Re-keyed collections snapshot + the fresh DEKs used. */\nexport interface ReKeyResult {\n readonly collections: Record<string, Record<string, EncryptedEnvelope>>\n readonly deks: Map<string, CryptoKey>\n}\n\n/**\n * Re-encrypt every record in `closure` under a fresh per-collection DEK.\n * Reads raw source envelopes, decrypts under the source DEK, re-encrypts\n * under the new DEK. Plaintext-pipeline: requires an unlocked vault.\n */\nexport async function reKeyClosure(\n vault: Vault,\n closure: Map<string, Set<string>>,\n): Promise<ReKeyResult> {\n const { name: vaultName, adapter, getDEK } = vault._introspectState()\n const collections: Record<string, Record<string, EncryptedEnvelope>> = {}\n const deks = new Map<string, CryptoKey>()\n\n for (const [collectionName, ids] of closure) {\n const srcDek = await getDEK(collectionName)\n const destDek = await generateDEK()\n deks.set(collectionName, destDek)\n const out: Record<string, EncryptedEnvelope> = {}\n\n for (const id of ids) {\n const env = await adapter.get(vaultName, collectionName, id)\n if (!env) continue\n const plaintext = await decrypt(env._iv, env._data, srcDek)\n const { iv, data } = await encrypt(plaintext, destDek)\n out[id] = { ...env, _iv: iv, _data: data }\n }\n collections[collectionName] = out\n }\n\n return { collections, deks }\n}\n\n/**\n * Re-key the persisted JSON Schemas (`_schemas/<collection>`) for the\n * closure collections under the destination DEKs (#204). Returns a\n * `{ collection: envelope }` map for the carried collections that actually\n * have a schema; collections without one are omitted.\n */\nexport async function reKeySchemas(\n vault: Vault,\n closure: Map<string, Set<string>>,\n destDeks: Map<string, CryptoKey>,\n): Promise<Record<string, EncryptedEnvelope>> {\n const { name: vaultName, adapter, getDEK } = vault._introspectState()\n const out: Record<string, EncryptedEnvelope> = {}\n\n for (const collectionName of closure.keys()) {\n const env = await adapter.get(vaultName, SCHEMAS_COLLECTION, collectionName)\n if (!env) continue // collection has no persisted schema — skip\n const destDek = destDeks.get(collectionName)\n if (!destDek) continue\n const srcDek = await getDEK(collectionName)\n const plaintext = await decrypt(env._iv, env._data, srcDek)\n const { iv, data } = await encrypt(plaintext, destDek)\n out[collectionName] = { ...env, _iv: iv, _data: data }\n }\n return out\n}\n\nconst paddedIndex = (n: number): string => String(n).padStart(10, '0')\n\nexport interface ReKeyLedgerResult {\n /** { paddedIndex: re-encrypted entry envelope } for backup._internal._ledger. */\n readonly entries: Record<string, EncryptedEnvelope>\n /** Recomputed ledgerHead for the carried chain (index -1 when empty). */\n readonly head: { hash: string; index: number; ts: string }\n}\n\n/**\n * Build the carried `_ledger` chain for an extracted partition (#205, slice 1).\n * Filters source entries to the closure, RE-CHAINS them (fresh index + prevHash),\n * and re-encrypts under `ledgerDek`. The `payloadHash` is recomputed against the\n * re-keyed envelope ONLY for the latest `put` per (collection,id) — the entry\n * `verifyBackupIntegrity` cross-checks; earlier puts + deletes keep their source\n * `payloadHash` verbatim (recomputing an intermediate put would assert a false\n * hash for an older version). Amendments + out-of-closure entries are dropped;\n * `_ledger_deltas`/`_history` are deferred to slice 2.\n */\nexport async function reKeyLedger(\n vault: Vault,\n closure: Map<string, Set<string>>,\n reKeyedCollections: Record<string, Record<string, EncryptedEnvelope>>,\n ledgerDek: CryptoKey,\n): Promise<ReKeyLedgerResult> {\n const { name: vaultName, adapter, getDEK } = vault._introspectState()\n const srcLedgerDek = await getDEK(LEDGER_COLLECTION)\n\n // 1. Load + decrypt source entries in index order.\n const ids = (await adapter.list(vaultName, LEDGER_COLLECTION)).sort()\n const srcEntries: LedgerEntry[] = []\n for (const id of ids) {\n const env = await adapter.get(vaultName, LEDGER_COLLECTION, id)\n if (!env) continue\n srcEntries.push(JSON.parse(await decrypt(env._iv, env._data, srcLedgerDek)) as LedgerEntry)\n }\n\n // 2. Keep closure put/delete entries (drop amendments + out-of-closure).\n const kept = srcEntries.filter(\n (e) => (e.op === 'put' || e.op === 'delete') && (closure.get(e.collection)?.has(e.id) ?? false),\n )\n\n // 3a. Reverse pass: index of the LATEST put per (collection,id).\n const latestPutIndex = new Map<string, number>()\n for (let i = kept.length - 1; i >= 0; i--) {\n const e = kept[i]!\n if (e.op !== 'put') continue\n const key = `${e.collection}/${e.id}`\n if (!latestPutIndex.has(key)) latestPutIndex.set(key, i)\n }\n\n // 3b. Forward re-chain + re-encrypt.\n const entries: Record<string, EncryptedEnvelope> = {}\n let prevHash = ''\n let last: LedgerEntry | undefined\n for (let i = 0; i < kept.length; i++) {\n const src = kept[i]!\n const key = `${src.collection}/${src.id}`\n const isLatestPut = src.op === 'put' && latestPutIndex.get(key) === i\n const reKeyedEnv = reKeyedCollections[src.collection]?.[src.id]\n const payloadHash = isLatestPut && reKeyedEnv\n ? await envelopePayloadHash(reKeyedEnv)\n : src.payloadHash\n const entry: LedgerEntry = {\n index: i,\n prevHash,\n op: src.op,\n collection: src.collection,\n id: src.id,\n version: src.version,\n ts: src.ts,\n actor: src.actor,\n payloadHash,\n ...(src.reason !== undefined ? { reason: src.reason } : {}),\n }\n const { iv, data } = await encrypt(canonicalJson(entry), ledgerDek)\n entries[paddedIndex(i)] = {\n _noydb: NOYDB_FORMAT_VERSION, _v: i + 1, _ts: entry.ts, _iv: iv, _data: data, _by: entry.actor,\n }\n prevHash = await hashEntry(entry)\n last = entry\n }\n\n return {\n entries,\n head: last ? { hash: prevHash, index: last.index, ts: last.ts } : { hash: '', index: -1, ts: '' },\n }\n}\n\n/** A minted transfer key (raw 32 bytes) + the seal carrying the DEK set. */\nexport interface SealResult {\n readonly seal: TransferSealPayload\n readonly transferKey: Uint8Array\n}\n\n/**\n * Mint a random 32-byte transfer key, export each DEK to raw bytes, and\n * AES-256-GCM-seal the `{ collection: base64(rawDEK) }` map under the\n * transfer key. The transfer key is returned to the caller out-of-band;\n * only the sealed bytes travel in the bundle. Layout: iv(12) ‖ ct ‖ tag.\n */\nexport async function sealDeks(deks: Map<string, CryptoKey>): Promise<SealResult> {\n const dekMap: Record<string, string> = {}\n for (const [collection, dek] of deks) {\n const raw = await crypto.subtle.exportKey('raw', dek)\n dekMap[collection] = bufferToBase64(raw)\n }\n\n const transferKey = crypto.getRandomValues(new Uint8Array(32))\n const key = await crypto.subtle.importKey('raw', transferKey, 'AES-GCM', false, ['encrypt'])\n const iv = crypto.getRandomValues(new Uint8Array(12))\n const plaintext = new TextEncoder().encode(JSON.stringify(dekMap))\n const ct = await crypto.subtle.encrypt({ name: 'AES-GCM', iv }, key, plaintext)\n\n const combined = new Uint8Array(iv.byteLength + ct.byteLength)\n combined.set(iv, 0)\n combined.set(new Uint8Array(ct), iv.byteLength)\n\n const sealId = bufferToBase64(crypto.getRandomValues(new Uint8Array(12)))\n return {\n seal: { v: 1, alg: 'aes-256-gcm-pre-shared', sealId, payload: bufferToBase64(combined) },\n transferKey,\n }\n}\n\nexport interface ExtractPartitionResult {\n readonly bundleBytes: Uint8Array\n /** Raw 32-byte transfer key — deliver out-of-band; required to adopt. */\n readonly transferKey: Uint8Array\n readonly sealId: string\n}\n\n/**\n * Extract a re-keyed, transfer-sealed partition (#203 + #206). Owner-only\n * (#198 invariant 5): producing a standalone re-keyed vault is an\n * ownership operation. Non-destructive on the source.\n */\nexport async function extractPartition(\n vault: Vault,\n opts: WalkClosureOptions & {\n readonly compression?: 'auto' | 'brotli' | 'gzip' | 'none'\n readonly carrySchemas?: boolean\n readonly carryLedger?: boolean\n },\n): Promise<ExtractPartitionResult> {\n if (vault.role !== 'owner') {\n throw new PartitionExtractionError(\n `extractPartition requires the 'owner' role on the source vault; caller is '${vault.role}'. `\n + `Producing a re-keyed standalone partition is an ownership operation.`,\n )\n }\n\n // Persisted-schema writes (collection({ persistJsonSchema: true })) are fire-\n // and-forget queued onto vault._pendingSchemaWrites — a caller that does\n // `collection() → put() → extractPartition({ carrySchemas: true })` in quick\n // succession can hit a window where _schemas/<col> is not yet on disk and\n // reKeySchemas silently drops the row. Drain BEFORE reKeySchemas reads.\n if (opts.carrySchemas) await vault._drainPendingSchemaWrites()\n\n const { closure } = await walkClosure(vault, opts)\n const { collections, deks } = await reKeyClosure(vault, closure)\n\n // carryLedger (#205): mint a fresh _ledger DEK, build the carried chain, and\n // SEAL the ledger DEK alongside the data DEKs so #208 wraps it into the\n // recipient keyring (lets them decrypt + verify the chain). Must run BEFORE\n // sealDeks.\n let ledgerHead: { hash: string; index: number; ts: string } | undefined\n let ledgerEntries: Record<string, EncryptedEnvelope> | undefined\n if (opts.carryLedger && vault._getLedgerOrNull() !== null) {\n // Skip when the source vault has no history strategy: reKeyLedger's first\n // `getDEK(LEDGER_COLLECTION)` would auto-mint and persist a phantom\n // _ledger DEK on the source keyring (contradicting \"non-destructive on\n // the source\"), and there's nothing to carry anyway. Mirrors the same\n // null-guard the source audit-append uses below.\n const ledgerDek = await generateDEK()\n const built = await reKeyLedger(vault, closure, collections, ledgerDek)\n if (built.head.index >= 0) {\n ledgerEntries = built.entries\n ledgerHead = built.head\n deks.set(LEDGER_COLLECTION, ledgerDek)\n }\n }\n\n // Build _internal (schemas #204 + ledger #205). reKeySchemas reads data-\n // collection DEKs only, so it is unaffected by the _ledger DEK added above.\n const internalSchemas = opts.carrySchemas ? await reKeySchemas(vault, closure, deks) : {}\n const internal: Record<string, Record<string, EncryptedEnvelope>> = {}\n if (Object.keys(internalSchemas).length > 0) internal[SCHEMAS_COLLECTION] = internalSchemas\n if (ledgerEntries) internal[LEDGER_COLLECTION] = ledgerEntries\n const hasInternal = Object.keys(internal).length > 0\n\n const { seal, transferKey } = await sealDeks(deks)\n\n // Source-side audit (#226 / spec §4.2 / invariant 4): record that a partition\n // was handed over. Non-destructive — an audit append, no record touched.\n // No-op when the source vault has no history strategy. append() fills\n // index/prevHash/ts and (since actor is '') the ledger's configured actor.\n await vault._getLedgerOrNull()?.append({\n op: 'lifecycle',\n collection: '',\n id: '',\n version: 0,\n actor: '',\n payloadHash: '',\n reason: `partition-handed-over:${seal.sealId}`,\n })\n\n // Build the dump JSON: unowned (empty keyrings), empty ledger (default),\n // re-keyed collections only.\n const { name: vaultName } = vault._introspectState()\n const backup = {\n _noydb_backup: NOYDB_BACKUP_VERSION,\n _compartment: vaultName,\n _exported_at: new Date().toISOString(),\n _exported_by: '', // unowned — no source user travels\n keyrings: {},\n collections,\n ...(hasInternal ? { _internal: internal } : {}),\n ...(ledgerHead ? { ledgerHead: { hash: ledgerHead.hash, index: ledgerHead.index, ts: ledgerHead.ts } } : {}),\n }\n const bodyJsonStr = JSON.stringify(buildExtractedPartitionWrapper(JSON.stringify(backup), seal))\n\n // An extracted partition is a NEW vault, not a re-export of the source —\n // mint a fresh handle rather than reusing the source's stable ULID\n // (which would collide if a recipient imports both source + partition).\n const handle = generateULID()\n const bundleBytes = await assembleBundleContainer({\n handle,\n bodyJsonStr,\n compression: opts.compression,\n headerExtras: {\n bundleKind: 'extracted-partition',\n transferSeal: { v: seal.v, alg: seal.alg, sealId: seal.sealId }, // indicator only\n },\n })\n\n return { bundleBytes, transferKey, sealId: seal.sealId }\n}\n","/**\n * Partition adoption (#207). Recipient side: verify an extracted bundle,\n * validate the transfer key, import the re-keyed collections into a\n * destination store, and record an `_meta/adoption` marker. The bundle\n * stays UNOWNED after adoption — `createOwnerOnAdoptedPartition` (#208)\n * mints the owner; `#209` destroys the seal.\n *\n * @module\n */\nimport { base64ToBuffer, wrapKey } from '../crypto.js'\nimport { TransferSealError, AdoptionStateError, ValidationError } from '../errors.js'\nimport type { NoydbStore, VaultSnapshot, KeyringFile } from '../types.js'\nimport { createOwnerKeyring } from '../team/keyring.js'\nimport { resolveManagedSecret } from '../team/managed-passphrase.js'\nimport type { SealingKeyProvider } from '../team/managed-passphrase.js'\nimport type { ShamirRecoveryProvider } from '../team/shamir-recovery-provider.js'\nimport type { RecoveryEnrollmentInput } from '../team/rotate-recover.js'\nimport { LedgerStore } from '../history/ledger/store.js'\nimport { LEDGER_COLLECTION } from '../history/ledger/constants.js'\nimport type { TransferSealPayload } from './bundle.js'\nimport { readNoydbBundleHeader, readNoydbBundle, parseExtractedPartitionBody } from './bundle.js'\n\n/**\n * Reverse of `sealDeks` (#206). Imports the transfer key, decrypts the\n * sealed `{ collection: base64(rawDEK) }` map (layout iv(12)‖ct‖tag), and\n * re-imports each DEK as an AES-GCM key. Throws `TransferSealError` on a\n * wrong key (AES-GCM auth-tag failure) or malformed payload.\n */\nexport async function unsealDeks(\n seal: TransferSealPayload,\n transferKey: Uint8Array,\n): Promise<Map<string, CryptoKey>> {\n if (transferKey.byteLength !== 32) {\n throw new TransferSealError(\n `transfer key must be 32 bytes, got ${transferKey.byteLength}.`,\n )\n }\n const key = await crypto.subtle.importKey('raw', transferKey as BufferSource, 'AES-GCM', false, ['decrypt'])\n const raw = base64ToBuffer(seal.payload)\n let plaintext: ArrayBuffer\n try {\n plaintext = await crypto.subtle.decrypt(\n { name: 'AES-GCM', iv: raw.slice(0, 12) as BufferSource },\n key,\n raw.slice(12) as BufferSource,\n )\n } catch {\n throw new TransferSealError(\n 'transfer seal could not be opened — wrong transfer key (AES-GCM authentication failed).',\n )\n }\n let dekMap: Record<string, string>\n try {\n dekMap = JSON.parse(new TextDecoder().decode(plaintext)) as Record<string, string>\n } catch {\n throw new TransferSealError('transfer seal payload is not valid JSON after decryption.')\n }\n const deks = new Map<string, CryptoKey>()\n for (const [collection, b64] of Object.entries(dekMap)) {\n // Extractable: the recipient must be able to re-wrap these under their\n // own KEK (AES-KW) at owner-creation (#208). Matches generateDEK.\n const dek = await crypto.subtle.importKey('raw', base64ToBuffer(b64) as BufferSource, 'AES-GCM', true, ['encrypt', 'decrypt'])\n deks.set(collection, dek)\n }\n return deks\n}\n\nexport interface AdoptPartitionOptions {\n readonly transferKey: Uint8Array\n readonly destinationStore: NoydbStore\n readonly vaultName: string\n}\n\nexport interface AdoptPartitionResult {\n readonly vaultName: string\n readonly needsOwner: true\n readonly sealId: string\n}\n\nexport async function adoptPartition(\n bundleBytes: Uint8Array,\n opts: AdoptPartitionOptions,\n): Promise<AdoptPartitionResult> {\n const { transferKey, destinationStore, vaultName } = opts\n\n const header = readNoydbBundleHeader(bundleBytes)\n if (header.bundleKind !== 'extracted-partition' || header.transferSeal === undefined) {\n throw new ValidationError(\n 'adoptPartition requires an extracted-partition bundle with a transfer seal. '\n + 'For ordinary backups use readNoydbBundle + vault.load.',\n )\n }\n\n const { dumpJson } = await readNoydbBundle(bundleBytes)\n const { dump, seal } = parseExtractedPartitionBody(dumpJson)\n\n // Validate the transfer key by unsealing in memory; throws\n // TransferSealError on mismatch. DEKs are discarded here — they stay\n // sealed at rest (in _meta/adoption) until #208 wraps them under the\n // recipient's KEK.\n await unsealDeks(seal, transferKey)\n\n // Single-occupancy per vaultName: an `_meta/adoption` marker already present\n // means this slot holds a partition (adopted-and-unowned, or already owned).\n // saveAll below would overwrite its data and replace the marker, stranding the\n // prior adoption's transfer seal. Refuse regardless of sealId — re-adopting the\n // SAME bundle is a redundant call, and adopting a DIFFERENT bundle here would\n // clobber the existing partition. Either way, pick a fresh vaultName.\n const existing = await destinationStore.get(vaultName, '_meta', 'adoption')\n if (existing) {\n const prior = JSON.parse(existing._data) as { sealId?: string }\n if (prior.sealId === seal.sealId) {\n throw new AdoptionStateError(\n `partition (sealId ${seal.sealId}) is already adopted into vault \"${vaultName}\".`,\n )\n }\n throw new AdoptionStateError(\n `vault \"${vaultName}\" already holds an adopted partition (sealId ${prior.sealId}); `\n + `adopting a different partition (sealId ${seal.sealId}) here would overwrite it. `\n + `Adopt into a fresh vaultName instead.`,\n )\n }\n\n // The marker-only check above misses a worse case: a vaultName already in use\n // by an ORDINARY vault (createNoydb + openVault) carries no `_meta/adoption`,\n // yet `saveAll` below is destructive on SQL adapters (`DELETE FROM ... WHERE\n // vault = ?` followed by upsert) and would wipe the legitimate keyring +\n // data. Refuse adoption into ANY occupied slot — a fresh vaultName is the\n // documented precondition.\n const existingKeyring = await destinationStore.list(vaultName, '_keyring')\n if (existingKeyring.length > 0) {\n throw new AdoptionStateError(\n `vault \"${vaultName}\" already holds a keyring (an unrelated owner exists at this slot); `\n + `adoptPartition requires a fresh vaultName to avoid destructive saveAll on SQL adapters.`,\n )\n }\n\n const backup = JSON.parse(dump) as { collections: VaultSnapshot; _internal?: VaultSnapshot }\n await destinationStore.saveAll(vaultName, backup.collections)\n\n // Import carried internal collections (e.g. _schemas from #204 carrySchemas).\n // saveAll only writes data collections; _internal is written per-record.\n if (backup._internal) {\n for (const [collection, records] of Object.entries(backup._internal)) {\n for (const [id, envelope] of Object.entries(records)) {\n await destinationStore.put(vaultName, collection, id, envelope)\n }\n }\n }\n\n const adoptedAt = new Date().toISOString()\n const adoption = { sealId: seal.sealId, adoptedAt, needsOwner: true as const, transferSeal: seal }\n await destinationStore.put(vaultName, '_meta', 'adoption', {\n _noydb: 1, _v: 1, _ts: adoptedAt, _iv: '', _data: JSON.stringify(adoption),\n })\n\n return { vaultName, needsOwner: true, sealId: seal.sealId }\n}\n\nexport interface CreateOwnerResult {\n readonly vaultName: string\n readonly userId: string\n}\n\n/** Standard-mode owner: recipient supplies the passphrase. */\nexport interface CreateOwnerStandardOptions {\n readonly userId: string\n readonly passphrase: string\n readonly transferKey: Uint8Array\n}\n\n/**\n * Managed-mode owner (#208 follow-up): the passphrase is minted + sealed under\n * a `SealingKeyProvider` (e.g. an `at-*` OS keychain) so the partition\n * auto-unlocks on the recipient's device. Managed mode mandates a strong\n * (Shamir) recovery profile at creation (#195), which needs the\n * `shamirRecovery` provider injected.\n */\nexport interface CreateOwnerManagedOptions {\n readonly userId: string\n readonly passphraseMode: 'managed'\n readonly sealingKey: SealingKeyProvider\n readonly recovery: ReadonlyArray<RecoveryEnrollmentInput>\n readonly shamirRecovery: ShamirRecoveryProvider\n readonly transferKey: Uint8Array\n}\n\nexport type CreateOwnerOptions = CreateOwnerStandardOptions | CreateOwnerManagedOptions\n\nfunction isManaged(o: CreateOwnerOptions): o is CreateOwnerManagedOptions {\n return 'passphraseMode' in o && o.passphraseMode === 'managed'\n}\n\n/**\n * Mint the first owner keyring on an adopted-but-unowned partition (#208),\n * then destroy the transfer seal (#209).\n *\n * Standard mode: the recipient supplies a passphrase. Managed mode: the\n * passphrase is minted + sealed under a `SealingKeyProvider` and a strong\n * (Shamir) recovery profile is enrolled (#195) — orchestrated via the existing\n * `openVaultAndEnrollRecovery` ceremony.\n *\n * Either way, reuses `createOwnerKeyring` to derive the KEK + write the base\n * keyring, then wraps the partition's DEKs (recovered from the seal) under that\n * KEK and re-persists the merged keyring file.\n *\n * Idempotent under retry: the seal is destroyed LAST (Stage D), after the\n * keyring (Stage A), the ledger transition (Stage B), and — in managed mode —\n * strong-recovery enrollment (Stage C). A failure in the fallible enrollment\n * step leaves the seal intact, and re-running with the same `userId` +\n * `transferKey` resumes from the first incomplete stage. (Multi-profile recovery\n * arrays may re-enroll an already-enrolled profile on retry; managed mode's\n * mandated single Shamir profile does not.)\n */\nexport async function createOwnerOnAdoptedPartition(\n store: NoydbStore,\n vaultName: string,\n opts: CreateOwnerOptions,\n): Promise<CreateOwnerResult> {\n const { userId, transferKey } = opts\n\n // Managed mode requires a strong (Shamir) recovery profile, validated BEFORE\n // any disk write (#195) — same gate as createNoydb.\n if (isManaged(opts) && !opts.recovery.some((r) => r.profile === 'shamir')) {\n throw new AdoptionStateError(\n 'managed-mode adoption requires at least one strong (shamir) recovery profile in '\n + '`recovery` — paper alone is not strong when there is no user passphrase to fall back on.',\n )\n }\n\n // 1. Verify adopted-unowned state.\n const adoptionEnv = await store.get(vaultName, '_meta', 'adoption')\n if (!adoptionEnv) {\n throw new AdoptionStateError(\n `vault \"${vaultName}\" is not an adopted partition (no _meta/adoption). `\n + `createOwnerOnAdoptedPartition only applies to vaults created via adoptPartition.`,\n )\n }\n const adoption = JSON.parse(adoptionEnv._data) as {\n sealId: string; adoptedAt: string; needsOwner?: boolean\n consumedAt?: string; transferSeal?: TransferSealPayload\n }\n if (adoption.consumedAt !== undefined || adoption.transferSeal === undefined) {\n throw new AdoptionStateError(\n `vault \"${vaultName}\" already has an owner (transfer seal consumed at ${adoption.consumedAt}).`,\n )\n }\n\n // 2. Recover the partition DEKs from the seal (throws on wrong key) BEFORE\n // writing any keyring, so a bad transfer key leaves no trace. Always\n // validated, including when resuming a partial prior call.\n const partitionDeks = await unsealDeks(adoption.transferSeal, transferKey)\n\n // The ceremony below is split into stages so a failure in the fallible\n // managed-enrollment step (network/provider outage) leaves the call RETRYABLE\n // — the seal is destroyed only once everything durable is in place. Each stage\n // detects its own prior completion rather than relying on a single resume bit.\n\n // A keyring present for a DIFFERENT user (with the seal still unconsumed) is a\n // genuine second-owner attempt — refuse it. A same-user keyring is a resumed\n // partial call and is handled by the stage checks below.\n const existingKeyring = await store.get(vaultName, '_keyring', userId)\n const otherOwners = (await store.list(vaultName, '_keyring')).filter((u) => u !== userId)\n if (otherOwners.length > 0) {\n throw new AdoptionStateError(\n `vault \"${vaultName}\" already has a keyring for a different owner; cannot create owner \"${userId}\".`,\n )\n }\n\n // Stage A — mint the owner keyring + merge the partition DEKs. Considered done\n // only when the keyring already holds every partition DEK. createOwnerKeyring\n // overwrites (fresh KEK + fresh _users DEK), so re-running is safe ONLY while\n // no recovery has been enrolled yet — guaranteed here because enrollment\n // (Stage C) runs strictly after Stage A completes.\n const partitionCollections = [...partitionDeks.keys()]\n const priorDeks = existingKeyring ? (JSON.parse(existingKeyring._data) as KeyringFile).deks : {}\n const ownerMinted = existingKeyring !== null && partitionCollections.every((c) => c in priorDeks)\n if (!ownerMinted) {\n // Resolve the owner passphrase. Managed mode mints a random passphrase, seals\n // it under the provider, and persists _meta/sealed-passphrase (so the\n // partition auto-unlocks on the recipient's device); standard mode uses the\n // caller's passphrase. Idempotent under retry — resolveManagedSecret's reopen\n // arm reuses an already-sealed passphrase.\n const passphrase = isManaged(opts)\n ? await resolveManagedSecret(store, vaultName, opts.sealingKey)\n : opts.passphrase\n\n // Mint the owner keyring (KEK + _users DEK + canary, written to disk).\n const unlocked = await createOwnerKeyring(store, vaultName, userId, passphrase)\n\n // Merge the partition DEKs (wrapped under the new KEK) into the keyring.\n const env = await store.get(vaultName, '_keyring', userId)\n if (!env) throw new AdoptionStateError(`keyring write for \"${userId}\" did not persist`)\n const keyringFile = JSON.parse(env._data) as KeyringFile\n const kek = unlocked.kek\n if (!kek) throw new AdoptionStateError(`owner keyring for \"${userId}\" has no KEK to wrap partition DEKs under`)\n const mergedDeks: Record<string, string> = { ...keyringFile.deks }\n for (const [collection, dek] of partitionDeks) {\n mergedDeks[collection] = await wrapKey(dek, kek)\n }\n const mergedFile: KeyringFile = { ...keyringFile, deks: mergedDeks }\n await store.put(vaultName, '_keyring', userId, { ...env, _data: JSON.stringify(mergedFile) })\n }\n\n // Stage B — (#226 destination) record the ownership transition on the carried\n // audit chain (carryLedger sealed the _ledger DEK). No-op without that DEK.\n // Idempotent: appended only if the closing `transfer-seal-consumed` entry is\n // absent, so a retry does not duplicate the pair.\n const ledgerDek = partitionDeks.get(LEDGER_COLLECTION)\n if (ledgerDek) {\n const ledger = new LedgerStore({\n adapter: store,\n vault: vaultName,\n encrypted: true,\n getDEK: async () => ledgerDek,\n actor: userId,\n })\n const creationReason = `creation-of-new-owner:${userId}`\n const consumedReason = `transfer-seal-consumed:${adoption.sealId}`\n // Gate each append on its own presence — a crash or store error strictly\n // between the two adjacent puts would otherwise re-append the first one\n // on retry. The pair is the audit record, not a single transaction.\n const recordedReasons = new Set((await ledger.loadAllEntries()).map((e) => e.reason))\n if (!recordedReasons.has(creationReason)) {\n await ledger.append({ op: 'lifecycle', collection: '', id: '', version: 0, actor: '', payloadHash: '', reason: creationReason })\n }\n if (!recordedReasons.has(consumedReason)) {\n await ledger.append({ op: 'lifecycle', collection: '', id: '', version: 0, actor: '', payloadHash: '', reason: consumedReason })\n }\n }\n\n // Stage C — Managed mode (#208 follow-up): enroll the mandatory strong recovery\n // (#195) by orchestrating the existing public ceremony. The partition is\n // now a managed-mode vault on disk (sealed passphrase + keyring), so we\n // open it as a normal client and let openVaultAndEnrollRecovery do the\n // gate-bypass + enroll + re-assert. Dynamic import keeps the Noydb class\n // out of the @noy-db/hub/bundle static graph. Runs BEFORE seal destruction\n // so a failure here leaves the seal intact and the call retryable.\n if (isManaged(opts)) {\n const { createNoydb } = await import('../noydb.js')\n const db = await createNoydb({\n store,\n user: userId,\n passphraseMode: 'managed',\n sealingKey: opts.sealingKey,\n shamirRecovery: opts.shamirRecovery,\n })\n await db.openVaultAndEnrollRecovery(vaultName, { recovery: opts.recovery })\n }\n\n // Stage D — (#209) Destroy the transfer seal LAST — the commit point. Everything\n // above is either idempotent or resumable, so the seal is only consumed\n // once the owner keyring (and, in managed mode, strong recovery) is\n // durably in place. Retain sealId + consumedAt for audit.\n const consumed = { sealId: adoption.sealId, adoptedAt: adoption.adoptedAt, consumedAt: new Date().toISOString() }\n await store.put(vaultName, '_meta', 'adoption', { ...adoptionEnv, _data: JSON.stringify(consumed) })\n\n return { vaultName, userId }\n}\n"],"mappings":"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","names":["sha256Hex","init_storage","sha256Hex","sha256Hex","init_storage","init_storage","init_types","init_storage","init_types","init_types","init_storage","bytesToBase64","base64ToBytes","sha256Hex","init_errors","SALT_BYTES","IV_BYTES","subtle","bytesToBase64","base64ToBytes","PBKDF2_ITERATIONS","bytesToBase64","writeKeyringFile","deks","init_errors","META_COLLECTION","init_storage","init_storage","init_types","init_types","init_strategy","init_schema","notEnabled","init_strategy","NOT_ENABLED","init_strategy","init_strategy","coerceRefKey","recordId","sortRecords","compareValues","EMPTY_PLAN","init_strategy","notEnabled","init_strategy","DerivationExecutor","executor_exports","init_executor","_staleByRegistry","init_stale","executor_exports","init_executor","init_strategy","init_schema","resolveStaleMVOnRead","GuardExecutor","version","envelope","DerivationExecutor","loadFanoutSidecar","saveFanoutSidecar","previousEnvelope","recordId","NOT_ENABLED","init_strategy","init_strategy","NOT_ENABLED","init_strategy","init_periods","recordId","recordId","init_errors","init_storage","checkGate","sha256Hex","init_storage","init_storage","import_attestation","import_attestation","registry_exports","init_registry","registry_exports","init_registry","registry_exports","init_registry","init_strategy","init_periods","init_storage","init_types","issueAttestationCore","loadSigner","loadOrCreateSigner","revokeDocCore","unrevokeDocCore","getRevokedDocIdsCore","publishRevocationListCore","GuardRegistry","ReadOnlyVaultFacade","DerivationRegistry","MaterializedViewRegistry","OverlayedViewRegistry","MaterializedViewExecutor","clearMVStale","DerivationExecutor","loadFanoutSidecar","saveFanoutSidecar","recordId","issueDelegation","DELEGATIONS_COLLECTION","revokeDelegation","record","handle","generateULID","readPublicEnvelope","sha256Hex","NOT_ENABLED","init_strategy","notEnabled","init_strategy","init_errors","init_policy","init_storage","init_storage","init_errors","init_strategy","init_policy","keyring","comp","enrolled","userCount","plaintextBytes","init_storage","paddedIndex","createNoydb"]}