@noy-db/hub 0.1.0-pre.14 → 0.1.0-pre.16

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 (232) hide show
  1. package/dist/aggregate/index.cjs +91 -36
  2. package/dist/aggregate/index.cjs.map +1 -1
  3. package/dist/aggregate/index.d.cts +2 -2
  4. package/dist/aggregate/index.d.ts +2 -2
  5. package/dist/aggregate/index.js +15 -8
  6. package/dist/aggregate/index.js.map +1 -1
  7. package/dist/blobs/index.cjs.map +1 -1
  8. package/dist/blobs/index.d.cts +5 -4
  9. package/dist/blobs/index.d.ts +5 -4
  10. package/dist/blobs/index.js +4 -4
  11. package/dist/bundle/index.cjs +214 -7
  12. package/dist/bundle/index.cjs.map +1 -1
  13. package/dist/bundle/index.d.cts +5 -4
  14. package/dist/bundle/index.d.ts +5 -4
  15. package/dist/bundle/index.js +6 -4
  16. package/dist/{chunk-CX2S2IBB.js → chunk-23TTQXVO.js} +53 -79
  17. package/dist/chunk-23TTQXVO.js.map +1 -0
  18. package/dist/{chunk-WLZWITX3.js → chunk-2AXFIYHT.js} +1 -1
  19. package/dist/chunk-2AXFIYHT.js.map +1 -0
  20. package/dist/{chunk-SBOSKCXD.js → chunk-2R6G2WST.js} +209 -13
  21. package/dist/chunk-2R6G2WST.js.map +1 -0
  22. package/dist/{chunk-HENWE2QQ.js → chunk-34YSDCDP.js} +2 -2
  23. package/dist/{chunk-3J5PYYUL.js → chunk-5DWL3JBF.js} +2 -2
  24. package/dist/{chunk-562AT3V6.js → chunk-5NGHX6ME.js} +5 -5
  25. package/dist/chunk-5ZGZ6HIZ.js +100 -0
  26. package/dist/chunk-5ZGZ6HIZ.js.map +1 -0
  27. package/dist/{chunk-ZJICDHNH.js → chunk-6HPZY4ON.js} +5 -4
  28. package/dist/chunk-6HPZY4ON.js.map +1 -0
  29. package/dist/{chunk-LTKFPLTW.js → chunk-A3K2ECAM.js} +192 -11
  30. package/dist/chunk-A3K2ECAM.js.map +1 -0
  31. package/dist/{chunk-W7M3E5M5.js → chunk-ADQ5MQ54.js} +48 -1
  32. package/dist/{chunk-W7M3E5M5.js.map → chunk-ADQ5MQ54.js.map} +1 -1
  33. package/dist/{chunk-Q3OWJPJI.js → chunk-DPMFBCV6.js} +32 -19
  34. package/dist/chunk-DPMFBCV6.js.map +1 -0
  35. package/dist/{chunk-AD4FMCXZ.js → chunk-DYBQG5PQ.js} +2 -2
  36. package/dist/{chunk-PNTWORBA.js → chunk-DYECX3IX.js} +2 -2
  37. package/dist/chunk-EGQYGYIU.js +51 -0
  38. package/dist/chunk-EGQYGYIU.js.map +1 -0
  39. package/dist/{chunk-RGHN6WV7.js → chunk-FCXOFQAJ.js} +2 -2
  40. package/dist/{chunk-PMMNH7VZ.js → chunk-H7XROQJM.js} +1 -1
  41. package/dist/chunk-H7XROQJM.js.map +1 -0
  42. package/dist/chunk-HB3Z2GCR.js +124 -0
  43. package/dist/chunk-HB3Z2GCR.js.map +1 -0
  44. package/dist/{chunk-CRNMA2FB.js → chunk-I6MX32UC.js} +4 -4
  45. package/dist/{chunk-PHSGPPV7.js → chunk-KESP7GOK.js} +3 -3
  46. package/dist/{chunk-FB34MDSJ.js → chunk-KJJKHKFP.js} +4 -4
  47. package/dist/{chunk-J3E2UMET.js → chunk-MIQHZESA.js} +3 -3
  48. package/dist/{chunk-TNYWH7Z2.js → chunk-MKSA2V7A.js} +2 -2
  49. package/dist/{chunk-E4ZSYZSN.js → chunk-NVBUGT76.js} +4 -4
  50. package/dist/{chunk-XFVOTOYQ.js → chunk-O4RHR7FV.js} +6 -6
  51. package/dist/{chunk-EEEFQTYN.js → chunk-OMLIZL2P.js} +2 -2
  52. package/dist/{chunk-GVPFL6XF.js → chunk-P7EQ2S5O.js} +2 -2
  53. package/dist/{chunk-E3VAKWJ7.js → chunk-QQUUAXDY.js} +7 -6
  54. package/dist/chunk-QQUUAXDY.js.map +1 -0
  55. package/dist/chunk-RD5LYKD6.js +82 -0
  56. package/dist/chunk-RD5LYKD6.js.map +1 -0
  57. package/dist/{chunk-7QGJ7RTL.js → chunk-RRMDLTIL.js} +4 -4
  58. package/dist/{chunk-432R4RDC.js → chunk-SIZWEV2Y.js} +35 -5
  59. package/dist/chunk-SIZWEV2Y.js.map +1 -0
  60. package/dist/{chunk-U7YDBNFC.js → chunk-UZXLQCHP.js} +2 -2
  61. package/dist/{chunk-PPJA7KL6.js → chunk-V6SGP2KX.js} +3 -3
  62. package/dist/{chunk-7AQKLLI5.js → chunk-WCA2NROQ.js} +2 -2
  63. package/dist/{chunk-ERV4AXVO.js → chunk-XGSOTWYX.js} +90 -131
  64. package/dist/chunk-XGSOTWYX.js.map +1 -0
  65. package/dist/{chunk-5Y4AKEZ6.js → chunk-Z72JH4KG.js} +2 -2
  66. package/dist/{chunk-IXFP66OV.js → chunk-ZNOEIM6Y.js} +2 -2
  67. package/dist/consent/index.cjs.map +1 -1
  68. package/dist/consent/index.d.cts +5 -4
  69. package/dist/consent/index.d.ts +5 -4
  70. package/dist/consent/index.js +3 -3
  71. package/dist/{crypto-VTEMMJK6.js → crypto-A7FRXYHC.js} +3 -3
  72. package/dist/{delegation-HYQULPZR.js → delegation-YBA4X4JN.js} +5 -5
  73. package/dist/derivations/index.cjs +97 -1
  74. package/dist/derivations/index.cjs.map +1 -1
  75. package/dist/derivations/index.d.cts +37 -10
  76. package/dist/derivations/index.d.ts +37 -10
  77. package/dist/derivations/index.js +6 -4
  78. package/dist/{dev-unlock-CpJ9rHW2.d.ts → dev-unlock-C8u6MRfS.d.ts} +1 -1
  79. package/dist/{dev-unlock-DEUUpWoR.d.cts → dev-unlock-gBjwzB6A.d.cts} +1 -1
  80. package/dist/executor-7E3VFGW7.js +11 -0
  81. package/dist/executor-CEWX2FQI.js +8 -0
  82. package/dist/executor-X4SQ3ZLC.js +8 -0
  83. package/dist/fanout-sidecar-ACJNXFU6.js +51 -0
  84. package/dist/fanout-sidecar-ACJNXFU6.js.map +1 -0
  85. package/dist/guards/index.cjs.map +1 -1
  86. package/dist/guards/index.d.cts +6 -5
  87. package/dist/guards/index.d.ts +6 -5
  88. package/dist/guards/index.js +6 -6
  89. package/dist/{hash-_9VyW44M.d.cts → hash-BXy5DuTD.d.cts} +1 -1
  90. package/dist/{hash-CJ9LfuY5.d.ts → hash-C6KOPK9Q.d.ts} +1 -1
  91. package/dist/history/index.cjs +2 -1
  92. package/dist/history/index.cjs.map +1 -1
  93. package/dist/history/index.d.cts +6 -5
  94. package/dist/history/index.d.ts +6 -5
  95. package/dist/history/index.js +6 -6
  96. package/dist/i18n/index.cjs.map +1 -1
  97. package/dist/i18n/index.d.cts +5 -4
  98. package/dist/i18n/index.d.ts +5 -4
  99. package/dist/i18n/index.js +6 -6
  100. package/dist/{index-s31g3m-D.d.cts → index-BA7A-MJs.d.cts} +96 -4
  101. package/dist/{index-BWAXR9tV.d.ts → index-CLK67MZE.d.ts} +96 -4
  102. package/dist/{index-7nd15fgy.d.ts → index-CNwA-B6-.d.ts} +70 -2
  103. package/dist/{index-CFJKJXWa.d.cts → index-CmVgTkqk.d.cts} +70 -2
  104. package/dist/index.cjs +2400 -571
  105. package/dist/index.cjs.map +1 -1
  106. package/dist/index.d.cts +128 -18
  107. package/dist/index.d.ts +128 -18
  108. package/dist/index.js +1217 -107
  109. package/dist/index.js.map +1 -1
  110. package/dist/indexing/index.cjs.map +1 -1
  111. package/dist/indexing/index.js +2 -2
  112. package/dist/{ledger-LHLIVPJ3.js → ledger-GHIZQLKR.js} +6 -6
  113. package/dist/materialized-views/index.cjs +255 -21
  114. package/dist/materialized-views/index.cjs.map +1 -1
  115. package/dist/materialized-views/index.d.cts +7 -6
  116. package/dist/materialized-views/index.d.ts +7 -6
  117. package/dist/materialized-views/index.js +9 -5
  118. package/dist/overlay-views/index.cjs.map +1 -1
  119. package/dist/overlay-views/index.d.cts +6 -5
  120. package/dist/overlay-views/index.d.ts +6 -5
  121. package/dist/overlay-views/index.js +3 -3
  122. package/dist/periods/index.cjs +2 -1
  123. package/dist/periods/index.cjs.map +1 -1
  124. package/dist/periods/index.d.cts +5 -4
  125. package/dist/periods/index.d.ts +5 -4
  126. package/dist/periods/index.js +6 -6
  127. package/dist/{public-envelope-66ZBXVXK.js → public-envelope-CRJAVW3E.js} +4 -4
  128. package/dist/query/index.cjs +139 -113
  129. package/dist/query/index.cjs.map +1 -1
  130. package/dist/query/index.d.cts +2 -2
  131. package/dist/query/index.d.ts +2 -2
  132. package/dist/query/index.js +13 -9
  133. package/dist/{registry-JYALMHGM.js → registry-3L3N3PTG.js} +3 -3
  134. package/dist/registry-O47PUPSY.js +8 -0
  135. package/dist/registry-WLLMODKN.js +8 -0
  136. package/dist/session/index.cjs.map +1 -1
  137. package/dist/session/index.d.cts +6 -5
  138. package/dist/session/index.d.ts +6 -5
  139. package/dist/session/index.js +3 -3
  140. package/dist/shadow/index.cjs.map +1 -1
  141. package/dist/shadow/index.d.cts +5 -4
  142. package/dist/shadow/index.d.ts +5 -4
  143. package/dist/shadow/index.js +2 -2
  144. package/dist/{stale-EET5YFYL.js → stale-HSC5YO2O.js} +2 -2
  145. package/dist/store/index.cjs.map +1 -1
  146. package/dist/store/index.d.cts +5 -4
  147. package/dist/store/index.d.ts +5 -4
  148. package/dist/store/index.js +2 -2
  149. package/dist/{strategy-D-SrOLCl.d.cts → strategy-DSTrsZ8t.d.cts} +72 -19
  150. package/dist/{strategy-D-SrOLCl.d.ts → strategy-DSTrsZ8t.d.ts} +72 -19
  151. package/dist/sync/index.cjs.map +1 -1
  152. package/dist/sync/index.d.cts +4 -3
  153. package/dist/sync/index.d.ts +4 -3
  154. package/dist/sync/index.js +4 -4
  155. package/dist/team/index.cjs +136 -6
  156. package/dist/team/index.cjs.map +1 -1
  157. package/dist/team/index.d.cts +5 -4
  158. package/dist/team/index.d.ts +5 -4
  159. package/dist/team/index.js +7 -7
  160. package/dist/tx/index.cjs +2 -1
  161. package/dist/tx/index.cjs.map +1 -1
  162. package/dist/tx/index.d.cts +5 -4
  163. package/dist/tx/index.d.ts +5 -4
  164. package/dist/tx/index.js +2 -2
  165. package/dist/{types-2JGVRzO2.d.cts → types-DDy02bWN.d.cts} +1159 -257
  166. package/dist/{types-DIOLYPUq.d.ts → types-DImsOlJe.d.ts} +1159 -257
  167. package/dist/util/index.cjs.map +1 -1
  168. package/dist/util/index.js +1 -1
  169. package/dist/{with-derivation-ClhfQvRa.d.ts → with-derivation-3AdLeSw8.d.ts} +1 -1
  170. package/dist/{with-derivation-3JX8SMxY.d.cts → with-derivation-CkCHx_J8.d.cts} +1 -1
  171. package/dist/{with-guard-Br3YPxRW.d.ts → with-guard-Br9zLEdi.d.ts} +1 -1
  172. package/dist/{with-guard-fJVfWBLs.d.cts → with-guard-DtKsFExw.d.cts} +1 -1
  173. package/dist/with-materialized-view-CMhEX4FK.d.cts +27 -0
  174. package/dist/with-materialized-view-Zal2bE1T.d.ts +27 -0
  175. package/dist/{with-overlayed-view-BLUKxdiJ.d.cts → with-overlayed-view-CA6zHrBt.d.cts} +1 -1
  176. package/dist/{with-overlayed-view-CtvJxhU3.d.ts → with-overlayed-view-R4BNG9OB.d.ts} +1 -1
  177. package/package.json +14 -2
  178. package/dist/chunk-432R4RDC.js.map +0 -1
  179. package/dist/chunk-CX2S2IBB.js.map +0 -1
  180. package/dist/chunk-E3VAKWJ7.js.map +0 -1
  181. package/dist/chunk-ERV4AXVO.js.map +0 -1
  182. package/dist/chunk-GV47JHIF.js +0 -29
  183. package/dist/chunk-GV47JHIF.js.map +0 -1
  184. package/dist/chunk-LTKFPLTW.js.map +0 -1
  185. package/dist/chunk-PMMNH7VZ.js.map +0 -1
  186. package/dist/chunk-Q2L5ZYX7.js +0 -66
  187. package/dist/chunk-Q2L5ZYX7.js.map +0 -1
  188. package/dist/chunk-Q3OWJPJI.js.map +0 -1
  189. package/dist/chunk-SBK6CETA.js +0 -30
  190. package/dist/chunk-SBK6CETA.js.map +0 -1
  191. package/dist/chunk-SBOSKCXD.js.map +0 -1
  192. package/dist/chunk-WLZWITX3.js.map +0 -1
  193. package/dist/chunk-ZJICDHNH.js.map +0 -1
  194. package/dist/executor-3H3NSIVZ.js +0 -8
  195. package/dist/executor-7ZOXGY7P.js +0 -8
  196. package/dist/executor-A5LTUAG2.js +0 -9
  197. package/dist/registry-J2UI37XJ.js +0 -8
  198. package/dist/registry-UMAL72WL.js +0 -8
  199. package/dist/with-materialized-view-CHho83hN.d.cts +0 -15
  200. package/dist/with-materialized-view-CgPphnza.d.ts +0 -15
  201. /package/dist/{chunk-HENWE2QQ.js.map → chunk-34YSDCDP.js.map} +0 -0
  202. /package/dist/{chunk-3J5PYYUL.js.map → chunk-5DWL3JBF.js.map} +0 -0
  203. /package/dist/{chunk-562AT3V6.js.map → chunk-5NGHX6ME.js.map} +0 -0
  204. /package/dist/{chunk-AD4FMCXZ.js.map → chunk-DYBQG5PQ.js.map} +0 -0
  205. /package/dist/{chunk-PNTWORBA.js.map → chunk-DYECX3IX.js.map} +0 -0
  206. /package/dist/{chunk-RGHN6WV7.js.map → chunk-FCXOFQAJ.js.map} +0 -0
  207. /package/dist/{chunk-CRNMA2FB.js.map → chunk-I6MX32UC.js.map} +0 -0
  208. /package/dist/{chunk-PHSGPPV7.js.map → chunk-KESP7GOK.js.map} +0 -0
  209. /package/dist/{chunk-FB34MDSJ.js.map → chunk-KJJKHKFP.js.map} +0 -0
  210. /package/dist/{chunk-J3E2UMET.js.map → chunk-MIQHZESA.js.map} +0 -0
  211. /package/dist/{chunk-TNYWH7Z2.js.map → chunk-MKSA2V7A.js.map} +0 -0
  212. /package/dist/{chunk-E4ZSYZSN.js.map → chunk-NVBUGT76.js.map} +0 -0
  213. /package/dist/{chunk-XFVOTOYQ.js.map → chunk-O4RHR7FV.js.map} +0 -0
  214. /package/dist/{chunk-EEEFQTYN.js.map → chunk-OMLIZL2P.js.map} +0 -0
  215. /package/dist/{chunk-GVPFL6XF.js.map → chunk-P7EQ2S5O.js.map} +0 -0
  216. /package/dist/{chunk-7QGJ7RTL.js.map → chunk-RRMDLTIL.js.map} +0 -0
  217. /package/dist/{chunk-U7YDBNFC.js.map → chunk-UZXLQCHP.js.map} +0 -0
  218. /package/dist/{chunk-PPJA7KL6.js.map → chunk-V6SGP2KX.js.map} +0 -0
  219. /package/dist/{chunk-7AQKLLI5.js.map → chunk-WCA2NROQ.js.map} +0 -0
  220. /package/dist/{chunk-5Y4AKEZ6.js.map → chunk-Z72JH4KG.js.map} +0 -0
  221. /package/dist/{chunk-IXFP66OV.js.map → chunk-ZNOEIM6Y.js.map} +0 -0
  222. /package/dist/{crypto-VTEMMJK6.js.map → crypto-A7FRXYHC.js.map} +0 -0
  223. /package/dist/{delegation-HYQULPZR.js.map → delegation-YBA4X4JN.js.map} +0 -0
  224. /package/dist/{executor-3H3NSIVZ.js.map → executor-7E3VFGW7.js.map} +0 -0
  225. /package/dist/{executor-7ZOXGY7P.js.map → executor-CEWX2FQI.js.map} +0 -0
  226. /package/dist/{executor-A5LTUAG2.js.map → executor-X4SQ3ZLC.js.map} +0 -0
  227. /package/dist/{ledger-LHLIVPJ3.js.map → ledger-GHIZQLKR.js.map} +0 -0
  228. /package/dist/{public-envelope-66ZBXVXK.js.map → public-envelope-CRJAVW3E.js.map} +0 -0
  229. /package/dist/{registry-J2UI37XJ.js.map → registry-3L3N3PTG.js.map} +0 -0
  230. /package/dist/{registry-JYALMHGM.js.map → registry-O47PUPSY.js.map} +0 -0
  231. /package/dist/{registry-UMAL72WL.js.map → registry-WLLMODKN.js.map} +0 -0
  232. /package/dist/{stale-EET5YFYL.js.map → stale-HSC5YO2O.js.map} +0 -0
@@ -1,8 +1,9 @@
1
1
  import { I as IndexStrategy, d as LazyQuery } from './lazy-builder-C-rPfWG0.cjs';
2
- import { A as AggregateStrategy } from './strategy-D-SrOLCl.cjs';
2
+ import { b as AggregateSpec, A as AggregateStrategy } from './strategy-DSTrsZ8t.cjs';
3
3
  import { C as CrdtStrategy, a as CrdtMode, b as CrdtState } from './strategy-BSxFXGzb.cjs';
4
- import { N as NoydbError, Q as Query, ae as RefDescriptor, X as JoinableSource, aj as RefViolation, ak as ScanBuilder } from './index-CFJKJXWa.cjs';
4
+ import { N as NoydbError, Q as Query, ak as RefRegistry, ah as RefDescriptor, _ as JoinableSource, am as RefViolation, an as ScanBuilder } from './index-CmVgTkqk.cjs';
5
5
  import { F as FieldClause, I as IndexDef, C as CollectionIndexes } from './predicate-Dnu81tsS.cjs';
6
+ import { RawShare } from '@noy-db/on-shamir';
6
7
 
7
8
  /**
8
9
  * Standard Schema v1 integration.
@@ -819,6 +820,17 @@ interface LedgerEntry {
819
820
  * the file docstring.
820
821
  */
821
822
  readonly payloadHash: string;
823
+ /**
824
+ * Optional human-readable tag describing why this mutation happened
825
+ * (#1). Threaded through `collection.put(_, _, { reason })`. Common
826
+ * values include `'import:csv'`, `'import:json'`, `'import:xlsx'` from
827
+ * `as-*` ImportPlan.apply(), but consumers can use any string for
828
+ * domain-specific audit filtering. Auto-strip via `canonicalJson` —
829
+ * absent on the wire, never serialized as `null`.
830
+ *
831
+ * Audit consumers filter: `entries.filter(e => e.reason?.startsWith('import:'))`.
832
+ */
833
+ readonly reason?: string;
822
834
  /**
823
835
  * Optional hex-encoded sha256 of the encrypted JSON Patch delta
824
836
  * blob stored alongside this entry in `_ledger_deltas/`. Present
@@ -1086,6 +1098,13 @@ interface AppendInput {
1086
1098
  * resulting ledger entry.
1087
1099
  */
1088
1100
  amendment?: LedgerEntry['amendment'];
1101
+ /**
1102
+ * Optional human-readable tag describing why this mutation happened
1103
+ * (#1). Threaded from `collection.put(_, _, { reason })`.
1104
+ * Carried verbatim onto the resulting ledger entry's `reason` field;
1105
+ * omitted from canonical JSON when undefined.
1106
+ */
1107
+ reason?: string;
1089
1108
  }
1090
1109
  /**
1091
1110
  * Result of `LedgerStore.verify()`. On success, `head` is the hash of
@@ -3072,6 +3091,266 @@ declare class SyncEngine {
3072
3091
  private persistMeta;
3073
3092
  }
3074
3093
 
3094
+ /**
3095
+ * **Wrap-DEKs primitive (#44)** — a single canonical shape for the
3096
+ * pattern of "serialize a DEK set, encrypt it under a credential-derived
3097
+ * AES-GCM key." Used by:
3098
+ *
3099
+ * - **tier-0** — paper recovery entries (`_meta/recovery-paper`),
3100
+ * credential = the printed code.
3101
+ * - **tier-2** — password authenticator slots (`KeyringFile.authenticators`,
3102
+ * `wrapKind: 'deks'`), credential = the user's password.
3103
+ *
3104
+ * **Not** used by `@noy-db/on-pin` — tier-3 wraps the DEK set under
3105
+ * the same conceptual pattern but at **100,000 PBKDF2 iterations**
3106
+ * (vs the 600,000 here), because the protection window for a PIN
3107
+ * slot is short (idle-timeout-bounded, typically 15 min) and 600k
3108
+ * iterations would make every PIN-resume noticeably slow. The wire
3109
+ * formats are deliberately incompatible. See `@noy-db/on-pin`'s
3110
+ * `PIN_PBKDF2_ITERATIONS` and the threat-model rationale in its
3111
+ * module docstring.
3112
+ *
3113
+ * Before #44, the same crypto lived in two places: `mintPaperRecoveryEntry`
3114
+ * (in `team/recovery.ts`) and `enrollPasswordAuthenticator` (in
3115
+ * `@noy-db/on-password`). Both functions did identical work — PBKDF2
3116
+ * the credential, AES-GCM-encrypt the JSON-serialized DEK set — but
3117
+ * their implementations had drifted apart enough that fixing a bug
3118
+ * in one wouldn't fix the other.
3119
+ *
3120
+ * This module owns the canonical implementation. Consumers compose:
3121
+ *
3122
+ * - `mintPaperRecoveryEntry` is now a thin wrapper that calls
3123
+ * `mintWrappedDeksBlob` and adds `{ codeId, enrolledAt }`.
3124
+ * - `enrollPasswordAuthenticator` calls `mintWrappedDeksBlob` and
3125
+ * wraps the result in the slot envelope.
3126
+ *
3127
+ * @module
3128
+ */
3129
+ /**
3130
+ * The wrap-DEKs primitive — a serialized + AES-GCM-encrypted DEK set
3131
+ * keyed under a credential-derived key.
3132
+ *
3133
+ * All three fields are base64-encoded so the blob is JSON-safe and
3134
+ * round-trips through `_meta/*` envelopes (which carry plaintext
3135
+ * JSON in `_data`).
3136
+ *
3137
+ * Composition: `PaperRecoveryEntry extends WrappedDeksBlob` plus
3138
+ * `{ codeId, enrolledAt }`. `KeyringAuthenticatorWrappingDEKs`
3139
+ * carries the same three fields with `salt` stored in `meta` for
3140
+ * slot-format back-compat (#44 defers moving it to top-level).
3141
+ */
3142
+ interface WrappedDeksBlob {
3143
+ /** Base64 PBKDF2 salt for the credential-derived wrapping key. */
3144
+ readonly salt: string;
3145
+ /** Base64 AES-GCM IV used for the `wrappedDeks` ciphertext. */
3146
+ readonly iv: string;
3147
+ /** Base64 AES-GCM ciphertext of `{ deks: { collection: base64rawDek } }`. */
3148
+ readonly wrappedDeks: string;
3149
+ }
3150
+ /**
3151
+ * Mint a fresh `WrappedDeksBlob` from a DEK set + a string credential.
3152
+ *
3153
+ * Generates a random salt + IV, derives a 256-bit AES-GCM key via
3154
+ * PBKDF2-SHA256(credential, salt, 600K), serializes the DEK set as
3155
+ * `{ deks: { coll: rawBase64 } }`, and AES-GCM-encrypts.
3156
+ *
3157
+ * The `credential` is the user-typed string (recovery code, password,
3158
+ * PIN). Caller normalization rules apply (e.g. paper
3159
+ * recovery uppercase-strips the code before reaching this function).
3160
+ *
3161
+ * @param deks - DEK set to wrap. Each DEK must be exportable via
3162
+ * `subtle.exportKey('raw', dek)` (the hub mints DEKs
3163
+ * this way; consumers feeding non-extractable keys
3164
+ * will get `InvalidAccessError` from WebCrypto).
3165
+ * @param credential - String input the consumer minted (paper code,
3166
+ * password, PIN). Treated as opaque bytes by PBKDF2.
3167
+ */
3168
+ declare function mintWrappedDeksBlob(deks: Map<string, CryptoKey>, credential: string): Promise<WrappedDeksBlob>;
3169
+ /**
3170
+ * Reverse of {@link mintWrappedDeksBlob}. Re-derives the wrapping key
3171
+ * from the credential + stored salt, AES-GCM-decrypts the wrapped DEK
3172
+ * set, and re-imports each DEK as an extractable AES-GCM CryptoKey.
3173
+ *
3174
+ * Throws (AES-GCM auth tag failure) when the credential doesn't
3175
+ * match the blob. Callers iterating over multiple blobs (e.g. paper
3176
+ * recovery's "try every entry until one matches") should catch.
3177
+ */
3178
+ declare function unwrapDeksFromBlob(blob: WrappedDeksBlob, credential: string): Promise<Map<string, CryptoKey>>;
3179
+
3180
+ /**
3181
+ * Recovery profile persistence + dispatch — issue #10.
3182
+ *
3183
+ * v0.1.0-pre.5 wires the **paper** profile end-to-end through
3184
+ * `@noy-db/on-recovery`. The other three profiles (Shamir,
3185
+ * multi-channel, admin-mediated) ship the API surface and throw
3186
+ * {@link RecoveryProfileNotImplementedError} during use; per-profile
3187
+ * dispatch lands in follow-up issues.
3188
+ *
3189
+ * Storage layout:
3190
+ *
3191
+ * ```
3192
+ * _meta/recovery-paper — JSON { entries: RecoveryCodeEntry[] } produced by `on-recovery`.
3193
+ * _meta/recovery-shamir — reserved
3194
+ * _meta/recovery-multi — reserved
3195
+ * _meta/recovery-admin — reserved
3196
+ * ```
3197
+ *
3198
+ * Like `_meta/policy` and `_meta/handle`, the documents are plain JSON
3199
+ * with empty `_iv` — the recovery-code wrapping is what protects the
3200
+ * KEK; the entries themselves are inert without the user's code.
3201
+ *
3202
+ * @module
3203
+ */
3204
+
3205
+ /**
3206
+ * One paper recovery code as persisted in `_meta/recovery-paper`.
3207
+ *
3208
+ * The hub's KEK is intentionally non-extractable (see `crypto.ts`),
3209
+ * so the recovery entry can't AES-KW-wrap the KEK directly. Instead
3210
+ * we wrap a serialized DEK set: the entry holds the AES-GCM
3211
+ * ciphertext of `{ deks: { collection: rawDekBase64 } }`. Recovery
3212
+ * deserializes the DEK set, then mints a fresh KEK from the new
3213
+ * passphrase and rewraps the DEKs under it.
3214
+ *
3215
+ * This is the same pattern `@noy-db/on-pin` uses for tier-3 quick
3216
+ * resume — the cryptographic guarantee is identical (AES-GCM with a
3217
+ * PBKDF2-derived key), and it sidesteps the non-extractable-KEK
3218
+ * constraint cleanly.
3219
+ *
3220
+ * Type-level composition (#44): `PaperRecoveryEntry extends
3221
+ * WrappedDeksBlob` — the three crypto fields (`salt`, `iv`,
3222
+ * `wrappedDeks`) come from the shared primitive; `codeId` and
3223
+ * `enrolledAt` are paper-recovery's own metadata. Wire format
3224
+ * unchanged.
3225
+ */
3226
+ interface PaperRecoveryEntry extends WrappedDeksBlob {
3227
+ readonly codeId: string;
3228
+ readonly enrolledAt: string;
3229
+ }
3230
+ interface PaperRecoveryDoc {
3231
+ readonly _noydb_recovery: 1;
3232
+ readonly profile: 'paper';
3233
+ readonly entries: ReadonlyArray<PaperRecoveryEntry>;
3234
+ }
3235
+ /** Read the paper-recovery entries. Returns empty array when absent. */
3236
+ declare function loadPaperRecoveryEntries(store: NoydbStore, vault: string): Promise<ReadonlyArray<PaperRecoveryEntry>>;
3237
+ /** Replace the paper-recovery entries (used after burn-on-recovery). */
3238
+ declare function savePaperRecoveryEntries(store: NoydbStore, vault: string, entries: ReadonlyArray<PaperRecoveryEntry>): Promise<void>;
3239
+ /** Drop a single paper-recovery entry (burn-on-use). */
3240
+ declare function burnPaperRecoveryEntry(store: NoydbStore, vault: string, codeId: string): Promise<void>;
3241
+ /** Whether at least one recovery profile has any enrolled entries. */
3242
+ declare function hasRecoveryEnrolled(store: NoydbStore, vault: string): Promise<boolean>;
3243
+ /**
3244
+ * One Shamir-recovery entry as persisted in `_meta/recovery-shamir`.
3245
+ *
3246
+ * Like {@link PaperRecoveryEntry}, the entry composes
3247
+ * {@link WrappedDeksBlob} (DEKs wrapped under a fresh ephemeral
3248
+ * recovery secret) with profile-specific metadata. Unlike paper, the
3249
+ * "credential" was never visible to the user — it was 32 random
3250
+ * bytes split into N Shamir shares at enrollment. The shares ARE
3251
+ * the credential; the user holds them, the hub never sees them
3252
+ * again after `enrollRecovery` returns.
3253
+ *
3254
+ * Per the spec §5: the recovery secret is base64-encoded and
3255
+ * passed as the `credential` arg to
3256
+ * {@link mintWrappedDeksBlob} / {@link unwrapDeksFromBlob}. The
3257
+ * PBKDF2 round over high-entropy input is harmless overhead — it
3258
+ * keeps the shared primitive unchanged while letting Shamir reuse
3259
+ * the same wrapping pipeline as paper.
3260
+ */
3261
+ interface ShamirRecoveryEntry extends WrappedDeksBlob {
3262
+ /** Stable id for this entry. Allows multiple Shamir splits to coexist. */
3263
+ readonly entryId: string;
3264
+ /** Threshold — minimum shares to reconstruct. */
3265
+ readonly k: number;
3266
+ /** Total shares minted at enrollment. */
3267
+ readonly n: number;
3268
+ /** x-coordinates of the n minted shares (audit metadata; not used at unlock). */
3269
+ readonly xCoords: ReadonlyArray<number>;
3270
+ /** ISO timestamp. */
3271
+ readonly enrolledAt: string;
3272
+ /** Optional caller-supplied label (e.g., "2-of-3 board escrow"). */
3273
+ readonly label?: string;
3274
+ }
3275
+ interface ShamirRecoveryDoc {
3276
+ readonly _noydb_recovery: 1;
3277
+ readonly profile: 'shamir';
3278
+ readonly entries: ReadonlyArray<ShamirRecoveryEntry>;
3279
+ }
3280
+ /** Read the Shamir-recovery entries. Returns empty array when absent. */
3281
+ declare function loadShamirRecoveryEntries(store: NoydbStore, vault: string): Promise<ReadonlyArray<ShamirRecoveryEntry>>;
3282
+ /** Replace the Shamir-recovery entries (used by enrollment and rotation). */
3283
+ declare function saveShamirRecoveryEntries(store: NoydbStore, vault: string, entries: ReadonlyArray<ShamirRecoveryEntry>): Promise<void>;
3284
+ /**
3285
+ * Mint a fresh Shamir recovery entry from a DEK set.
3286
+ *
3287
+ * 1. Generates a 32-byte recovery secret.
3288
+ * 2. Wraps the DEK set under that secret via
3289
+ * {@link mintWrappedDeksBlob} (the recovery secret is base64-
3290
+ * encoded as the credential string — PBKDF2 over high-entropy
3291
+ * input is harmless overhead).
3292
+ * 3. Splits the recovery secret via Shamir into `n` shares with
3293
+ * threshold `k`.
3294
+ * 4. Zeros the in-memory recovery secret after wrapping + splitting.
3295
+ *
3296
+ * Returns:
3297
+ * - `entry` — the {@link ShamirRecoveryEntry} to persist.
3298
+ * - `shareStrings` — the `n` Base32-encoded share strings to
3299
+ * return to the caller. The HUB MUST NOT PERSIST THESE; once
3300
+ * returned they are the user's responsibility.
3301
+ *
3302
+ * @param deks - DEK set to wrap.
3303
+ * @param entryId - Stable id for this entry (caller-supplied or
3304
+ * hub-generated).
3305
+ * @param k - Threshold (>= 2).
3306
+ * @param n - Total shares (k <= n <= 255).
3307
+ * @param label - Optional caller label.
3308
+ */
3309
+ declare function mintShamirRecoveryEntry(deks: Map<string, CryptoKey>, entryId: string, k: number, n: number, label?: string): Promise<{
3310
+ entry: ShamirRecoveryEntry;
3311
+ shareStrings: string[];
3312
+ }>;
3313
+ /**
3314
+ * Decrypt a Shamir recovery entry to recover the raw DEK set.
3315
+ *
3316
+ * Combines K or more `shares`, reconstructs the recovery secret,
3317
+ * unwraps the DEKs via {@link unwrapDeksFromBlob}.
3318
+ *
3319
+ * Throws (AES-GCM auth-tag mismatch) when the shares don't combine
3320
+ * to the secret originally used to mint the entry — typically
3321
+ * because they came from a different enrollment or were tampered
3322
+ * with. Callers iterating multiple entries should catch.
3323
+ */
3324
+ declare function unwrapDeksFromShamirEntry(entry: ShamirRecoveryEntry, shares: readonly RawShare[]): Promise<Map<string, CryptoKey>>;
3325
+
3326
+ /**
3327
+ * Generate one paper-recovery entry from an unlocked DEK set.
3328
+ *
3329
+ * Returns the serializable entry (persisted via
3330
+ * {@link savePaperRecoveryEntries}). The recovery flow unwraps the
3331
+ * DEK set, then mints a fresh KEK from the user's new passphrase.
3332
+ *
3333
+ * Thin wrapper over {@link mintWrappedDeksBlob} (#44) — the crypto
3334
+ * lives in the shared primitive; this function just adds paper-
3335
+ * recovery's own metadata (`codeId`, `enrolledAt`).
3336
+ *
3337
+ * @param deks Map of collection-name → DEK (extractable).
3338
+ * @param code The plaintext recovery code (caller-supplied;
3339
+ * pair this with `@noy-db/on-recovery`'s code
3340
+ * generator/parser if available).
3341
+ * @param codeId Stable id used by `burnPaperRecoveryEntry`.
3342
+ */
3343
+ declare function mintPaperRecoveryEntry(deks: Map<string, CryptoKey>, code: string, codeId: string): Promise<PaperRecoveryEntry>;
3344
+ /**
3345
+ * Decrypt a recovery entry to recover the raw DEK set. Used by the
3346
+ * `recoverPassphrase` flow after the user's code has been parsed.
3347
+ *
3348
+ * Thin wrapper over {@link unwrapDeksFromBlob} (#44).
3349
+ *
3350
+ * @throws when the code does not match the entry (AES-GCM auth tag fail).
3351
+ */
3352
+ declare function unwrapDeksFromPaperEntry(entry: PaperRecoveryEntry, code: string): Promise<Map<string, CryptoKey>>;
3353
+
3075
3354
  /**
3076
3355
  * Tier-2 authenticator slot management — issue #11.
3077
3356
  *
@@ -3272,20 +3551,26 @@ declare function rotatePassphrase(store: NoydbStore, vault: string, userId: stri
3272
3551
  /**
3273
3552
  * Caller payload for {@link recoverPassphrase}.
3274
3553
  *
3275
- * **Narrowed to `'paper'` only (#86).** The other three profiles
3276
- * (`shamir`, `multi-channel`, `admin-mediated`) are documented in the
3277
- * spec but not yet wired end-to-end. Matching the discipline of
3278
- * {@link db.enrollRecovery}, the type rejects them at compile time
3279
- * rather than accepting them and throwing at runtime. The runtime
3280
- * guard ({@link RecoveryProfileNotImplementedError}) remains so
3281
- * consumers who bypass TS via `as unknown as RecoveryProof` still
3282
- * receive a clear error.
3554
+ * As of #196 slice 1, `paper` and `shamir` are wired end-to-end.
3555
+ * The remaining two profiles (`multi-channel`, `admin-mediated`)
3556
+ * stay outside the union and throw
3557
+ * {@link RecoveryProfileNotImplementedError} at the runtime guard
3558
+ * when bypassed via `as unknown as RecoveryProof`.
3283
3559
  */
3284
3560
  type RecoveryProof = {
3285
3561
  readonly profile: 'paper';
3286
3562
  readonly payload: {
3287
3563
  readonly code: string;
3288
3564
  };
3565
+ } | {
3566
+ readonly profile: 'shamir';
3567
+ readonly payload: {
3568
+ /** Optional disambiguator when multiple Shamir entries are enrolled.
3569
+ * When omitted, hub tries each entry until one combines. */
3570
+ readonly entryId?: string;
3571
+ /** K or more share strings (Base32-encoded per @noy-db/on-shamir). */
3572
+ readonly shares: ReadonlyArray<string>;
3573
+ };
3289
3574
  };
3290
3575
  interface RecoverPassphraseInput {
3291
3576
  readonly newPassphrase: string;
@@ -3345,6 +3630,83 @@ interface RecoverPassphraseInput {
3345
3630
  interface RecoverPassphraseResult {
3346
3631
  readonly newCodes: readonly string[];
3347
3632
  }
3633
+ /**
3634
+ * Input for {@link Noydb.rotateRecovery} (#121) — deliberate
3635
+ * recovery-credential regeneration when the user knows their
3636
+ * passphrase but wants a fresh sheet (paper) or fresh shares
3637
+ * (shamir). Symmetric to {@link RotatePassphraseInput}.
3638
+ */
3639
+ type RotateRecoveryOptions = {
3640
+ readonly profile: 'paper';
3641
+ /** How many fresh codes to mint. Default: existing sheet size. */
3642
+ readonly count?: number;
3643
+ /** Optional code generator — see {@link RecoverPassphraseInput.codeGenerator}. */
3644
+ readonly codeGenerator?: () => string;
3645
+ } | {
3646
+ readonly profile: 'shamir';
3647
+ /** New threshold. */
3648
+ readonly k: number;
3649
+ /** New total share count. */
3650
+ readonly n: number;
3651
+ /** Disambiguator when multiple Shamir entries exist; required if there are 2+. */
3652
+ readonly entryId?: string;
3653
+ /** Optional updated label. */
3654
+ readonly label?: string;
3655
+ };
3656
+ /**
3657
+ * Result of {@link Noydb.rotateRecovery}. Shape varies by profile:
3658
+ *
3659
+ * - `paper` → `{ newCodes: string[] }` (and `entryId === 'paper-batch'`)
3660
+ * - `shamir` → `{ newShares: string[], entryId }`
3661
+ *
3662
+ * `newCodes` is populated for paper rotations; `newShares` for
3663
+ * Shamir rotations. Both are show-once — the hub does not
3664
+ * retain them.
3665
+ */
3666
+ interface RotateRecoveryResult {
3667
+ readonly newCodes?: readonly string[];
3668
+ readonly newShares?: readonly string[];
3669
+ readonly entryId?: string;
3670
+ }
3671
+ /**
3672
+ * Result of {@link Noydb.enrollRecovery}. Shape varies by profile:
3673
+ *
3674
+ * - `paper` → `{ entryId: 'paper-batch' }` (caller minted the
3675
+ * entries; this is a sentinel since paper enrollments are batch-shaped).
3676
+ * - `shamir` → `{ entryId, shares: string[] }` — shares are
3677
+ * show-once; the hub does not retain them.
3678
+ */
3679
+ interface EnrollRecoveryResult {
3680
+ readonly entryId: string;
3681
+ readonly shares?: readonly string[];
3682
+ }
3683
+ /**
3684
+ * Input shape for {@link Noydb.enrollRecovery} and
3685
+ * {@link Noydb.openVaultAndEnrollRecovery} (#195). Discriminated
3686
+ * union over recovery profiles.
3687
+ *
3688
+ * - `paper`: caller pre-mints entries (typically via
3689
+ * `mintPaperRecoveryEntry` or `@noy-db/on-recovery`'s
3690
+ * `generateRecoveryCodeSet`) and passes them in. The hub stores
3691
+ * them and surfaces an opaque batch id.
3692
+ * - `shamir`: hub mints the recovery secret + the shares at
3693
+ * enrollment time. The shares are returned in
3694
+ * {@link EnrollRecoveryResult.shares} (show-once); the hub never
3695
+ * retains them.
3696
+ *
3697
+ * Multi-channel and admin-mediated will be added when the respective
3698
+ * dispatch slices ship.
3699
+ */
3700
+ type RecoveryEnrollmentInput = {
3701
+ readonly profile: 'paper';
3702
+ readonly entries: ReadonlyArray<PaperRecoveryEntry>;
3703
+ } | {
3704
+ readonly profile: 'shamir';
3705
+ readonly k: number;
3706
+ readonly n: number;
3707
+ readonly label?: string;
3708
+ readonly entryId?: string;
3709
+ };
3348
3710
  /**
3349
3711
  * Reset the user's passphrase using a recovery proof. v0.1.0-pre.5
3350
3712
  * supports the `'paper'` profile via `@noy-db/on-recovery` entries
@@ -3385,233 +3747,56 @@ declare function recoverPassphrase(store: NoydbStore, vault: string, userId: str
3385
3747
  * their own phrase.
3386
3748
  *
3387
3749
  * Caller must be at least as privileged as the target. The hub
3388
- * `db.recoverUser` method gates this with the `peer-recover-user`
3389
- * policy gate (#33's factor-proof requirement); the function below
3390
- * enforces only the role + anti-privilege-escalation invariants.
3391
- *
3392
- * @module
3393
- */
3394
-
3395
- /** Input shape for {@link recoverUser}. */
3396
- interface RecoverUserOptions {
3397
- /** Target user id whose keyring is being recovered. */
3398
- readonly userId: string;
3399
- /**
3400
- * Temporary passphrase under which the new keyring is wrapped.
3401
- * The recipient should call `db.rotatePassphrase` immediately on
3402
- * acceptance to choose their own phrase — this temp acts as a
3403
- * single-use bridge in invite / peer-recovery flows.
3404
- */
3405
- readonly passphrase: string;
3406
- /** Override the target's role. Defaults to the existing target's role. */
3407
- readonly role?: Role;
3408
- /** Override the target's display name. Defaults to existing. */
3409
- readonly displayName?: string;
3410
- /** Validate phrase strength against the configured policy. */
3411
- readonly validatePassphrase?: boolean;
3412
- /**
3413
- * Skip phrase strength validation even when `validatePassphrase` is
3414
- * set. The escape hatch matches `grant`'s shape — used when the
3415
- * temp phrase is a high-entropy one-shot string that doesn't need
3416
- * to satisfy the human-typeable rules.
3417
- */
3418
- readonly allowWeakPassphrase?: boolean;
3419
- /**
3420
- * Optional explicit phrase policy override (passed through to
3421
- * `assertStrongPassphrase`). Mirrors how `grant` accepts a custom
3422
- * `PassphrasePolicy` for app-specific tightening.
3423
- */
3424
- readonly passphrasePolicy?: PassphrasePolicy;
3425
- }
3426
- /**
3427
- * Atomically rewrap the target user's keyring under a fresh temp
3428
- * passphrase. Single store write; no revoke step; no key rotation.
3429
- *
3430
- * Caller's responsibilities (NOT enforced here):
3431
- * - Run the `peer-recover-user` policy gate first via
3432
- * `Noydb.checkGate` to enforce the freshness factor proof.
3433
- * - Communicate the temp passphrase to the recipient via a secure
3434
- * channel (URL fragment, in-person, etc.) — the hub does not
3435
- * transport secrets.
3436
- */
3437
- declare function recoverUser(store: NoydbStore, vault: string, callerKeyring: UnlockedKeyring, options: RecoverUserOptions): Promise<void>;
3438
-
3439
- /**
3440
- * **Wrap-DEKs primitive (#44)** — a single canonical shape for the
3441
- * pattern of "serialize a DEK set, encrypt it under a credential-derived
3442
- * AES-GCM key." Used by:
3443
- *
3444
- * - **tier-0** — paper recovery entries (`_meta/recovery-paper`),
3445
- * credential = the printed code.
3446
- * - **tier-2** — password authenticator slots (`KeyringFile.authenticators`,
3447
- * `wrapKind: 'deks'`), credential = the user's password.
3448
- *
3449
- * **Not** used by `@noy-db/on-pin` — tier-3 wraps the DEK set under
3450
- * the same conceptual pattern but at **100,000 PBKDF2 iterations**
3451
- * (vs the 600,000 here), because the protection window for a PIN
3452
- * slot is short (idle-timeout-bounded, typically 15 min) and 600k
3453
- * iterations would make every PIN-resume noticeably slow. The wire
3454
- * formats are deliberately incompatible. See `@noy-db/on-pin`'s
3455
- * `PIN_PBKDF2_ITERATIONS` and the threat-model rationale in its
3456
- * module docstring.
3457
- *
3458
- * Before #44, the same crypto lived in two places: `mintPaperRecoveryEntry`
3459
- * (in `team/recovery.ts`) and `enrollPasswordAuthenticator` (in
3460
- * `@noy-db/on-password`). Both functions did identical work — PBKDF2
3461
- * the credential, AES-GCM-encrypt the JSON-serialized DEK set — but
3462
- * their implementations had drifted apart enough that fixing a bug
3463
- * in one wouldn't fix the other.
3464
- *
3465
- * This module owns the canonical implementation. Consumers compose:
3466
- *
3467
- * - `mintPaperRecoveryEntry` is now a thin wrapper that calls
3468
- * `mintWrappedDeksBlob` and adds `{ codeId, enrolledAt }`.
3469
- * - `enrollPasswordAuthenticator` calls `mintWrappedDeksBlob` and
3470
- * wraps the result in the slot envelope.
3471
- *
3472
- * @module
3473
- */
3474
- /**
3475
- * The wrap-DEKs primitive — a serialized + AES-GCM-encrypted DEK set
3476
- * keyed under a credential-derived key.
3477
- *
3478
- * All three fields are base64-encoded so the blob is JSON-safe and
3479
- * round-trips through `_meta/*` envelopes (which carry plaintext
3480
- * JSON in `_data`).
3481
- *
3482
- * Composition: `PaperRecoveryEntry extends WrappedDeksBlob` plus
3483
- * `{ codeId, enrolledAt }`. `KeyringAuthenticatorWrappingDEKs`
3484
- * carries the same three fields with `salt` stored in `meta` for
3485
- * slot-format back-compat (#44 defers moving it to top-level).
3486
- */
3487
- interface WrappedDeksBlob {
3488
- /** Base64 PBKDF2 salt for the credential-derived wrapping key. */
3489
- readonly salt: string;
3490
- /** Base64 AES-GCM IV used for the `wrappedDeks` ciphertext. */
3491
- readonly iv: string;
3492
- /** Base64 AES-GCM ciphertext of `{ deks: { collection: base64rawDek } }`. */
3493
- readonly wrappedDeks: string;
3494
- }
3495
- /**
3496
- * Mint a fresh `WrappedDeksBlob` from a DEK set + a string credential.
3497
- *
3498
- * Generates a random salt + IV, derives a 256-bit AES-GCM key via
3499
- * PBKDF2-SHA256(credential, salt, 600K), serializes the DEK set as
3500
- * `{ deks: { coll: rawBase64 } }`, and AES-GCM-encrypts.
3501
- *
3502
- * The `credential` is the user-typed string (recovery code, password,
3503
- * PIN). Caller normalization rules apply (e.g. paper
3504
- * recovery uppercase-strips the code before reaching this function).
3505
- *
3506
- * @param deks - DEK set to wrap. Each DEK must be exportable via
3507
- * `subtle.exportKey('raw', dek)` (the hub mints DEKs
3508
- * this way; consumers feeding non-extractable keys
3509
- * will get `InvalidAccessError` from WebCrypto).
3510
- * @param credential - String input the consumer minted (paper code,
3511
- * password, PIN). Treated as opaque bytes by PBKDF2.
3512
- */
3513
- declare function mintWrappedDeksBlob(deks: Map<string, CryptoKey>, credential: string): Promise<WrappedDeksBlob>;
3514
- /**
3515
- * Reverse of {@link mintWrappedDeksBlob}. Re-derives the wrapping key
3516
- * from the credential + stored salt, AES-GCM-decrypts the wrapped DEK
3517
- * set, and re-imports each DEK as an extractable AES-GCM CryptoKey.
3518
- *
3519
- * Throws (AES-GCM auth tag failure) when the credential doesn't
3520
- * match the blob. Callers iterating over multiple blobs (e.g. paper
3521
- * recovery's "try every entry until one matches") should catch.
3522
- */
3523
- declare function unwrapDeksFromBlob(blob: WrappedDeksBlob, credential: string): Promise<Map<string, CryptoKey>>;
3524
-
3525
- /**
3526
- * Recovery profile persistence + dispatch — issue #10.
3527
- *
3528
- * v0.1.0-pre.5 wires the **paper** profile end-to-end through
3529
- * `@noy-db/on-recovery`. The other three profiles (Shamir,
3530
- * multi-channel, admin-mediated) ship the API surface and throw
3531
- * {@link RecoveryProfileNotImplementedError} during use; per-profile
3532
- * dispatch lands in follow-up issues.
3533
- *
3534
- * Storage layout:
3535
- *
3536
- * ```
3537
- * _meta/recovery-paper — JSON { entries: RecoveryCodeEntry[] } produced by `on-recovery`.
3538
- * _meta/recovery-shamir — reserved
3539
- * _meta/recovery-multi — reserved
3540
- * _meta/recovery-admin — reserved
3541
- * ```
3542
- *
3543
- * Like `_meta/policy` and `_meta/handle`, the documents are plain JSON
3544
- * with empty `_iv` — the recovery-code wrapping is what protects the
3545
- * KEK; the entries themselves are inert without the user's code.
3546
- *
3547
- * @module
3548
- */
3549
-
3550
- /**
3551
- * One paper recovery code as persisted in `_meta/recovery-paper`.
3552
- *
3553
- * The hub's KEK is intentionally non-extractable (see `crypto.ts`),
3554
- * so the recovery entry can't AES-KW-wrap the KEK directly. Instead
3555
- * we wrap a serialized DEK set: the entry holds the AES-GCM
3556
- * ciphertext of `{ deks: { collection: rawDekBase64 } }`. Recovery
3557
- * deserializes the DEK set, then mints a fresh KEK from the new
3558
- * passphrase and rewraps the DEKs under it.
3559
- *
3560
- * This is the same pattern `@noy-db/on-pin` uses for tier-3 quick
3561
- * resume — the cryptographic guarantee is identical (AES-GCM with a
3562
- * PBKDF2-derived key), and it sidesteps the non-extractable-KEK
3563
- * constraint cleanly.
3564
- *
3565
- * Type-level composition (#44): `PaperRecoveryEntry extends
3566
- * WrappedDeksBlob` — the three crypto fields (`salt`, `iv`,
3567
- * `wrappedDeks`) come from the shared primitive; `codeId` and
3568
- * `enrolledAt` are paper-recovery's own metadata. Wire format
3569
- * unchanged.
3570
- */
3571
- interface PaperRecoveryEntry extends WrappedDeksBlob {
3572
- readonly codeId: string;
3573
- readonly enrolledAt: string;
3574
- }
3575
- interface PaperRecoveryDoc {
3576
- readonly _noydb_recovery: 1;
3577
- readonly profile: 'paper';
3578
- readonly entries: ReadonlyArray<PaperRecoveryEntry>;
3579
- }
3580
- /** Read the paper-recovery entries. Returns empty array when absent. */
3581
- declare function loadPaperRecoveryEntries(store: NoydbStore, vault: string): Promise<ReadonlyArray<PaperRecoveryEntry>>;
3582
- /** Replace the paper-recovery entries (used after burn-on-recovery). */
3583
- declare function savePaperRecoveryEntries(store: NoydbStore, vault: string, entries: ReadonlyArray<PaperRecoveryEntry>): Promise<void>;
3584
- /** Drop a single paper-recovery entry (burn-on-use). */
3585
- declare function burnPaperRecoveryEntry(store: NoydbStore, vault: string, codeId: string): Promise<void>;
3586
- /** Whether at least one recovery profile has any enrolled entries. */
3587
- declare function hasRecoveryEnrolled(store: NoydbStore, vault: string): Promise<boolean>;
3588
- /**
3589
- * Generate one paper-recovery entry from an unlocked DEK set.
3590
- *
3591
- * Returns the serializable entry (persisted via
3592
- * {@link savePaperRecoveryEntries}). The recovery flow unwraps the
3593
- * DEK set, then mints a fresh KEK from the user's new passphrase.
3594
- *
3595
- * Thin wrapper over {@link mintWrappedDeksBlob} (#44) — the crypto
3596
- * lives in the shared primitive; this function just adds paper-
3597
- * recovery's own metadata (`codeId`, `enrolledAt`).
3750
+ * `db.recoverUser` method gates this with the `peer-recover-user`
3751
+ * policy gate (#33's factor-proof requirement); the function below
3752
+ * enforces only the role + anti-privilege-escalation invariants.
3598
3753
  *
3599
- * @param deks Map of collection-name → DEK (extractable).
3600
- * @param code The plaintext recovery code (caller-supplied;
3601
- * pair this with `@noy-db/on-recovery`'s code
3602
- * generator/parser if available).
3603
- * @param codeId Stable id used by `burnPaperRecoveryEntry`.
3754
+ * @module
3604
3755
  */
3605
- declare function mintPaperRecoveryEntry(deks: Map<string, CryptoKey>, code: string, codeId: string): Promise<PaperRecoveryEntry>;
3756
+
3757
+ /** Input shape for {@link recoverUser}. */
3758
+ interface RecoverUserOptions {
3759
+ /** Target user id whose keyring is being recovered. */
3760
+ readonly userId: string;
3761
+ /**
3762
+ * Temporary passphrase under which the new keyring is wrapped.
3763
+ * The recipient should call `db.rotatePassphrase` immediately on
3764
+ * acceptance to choose their own phrase — this temp acts as a
3765
+ * single-use bridge in invite / peer-recovery flows.
3766
+ */
3767
+ readonly passphrase: string;
3768
+ /** Override the target's role. Defaults to the existing target's role. */
3769
+ readonly role?: Role;
3770
+ /** Override the target's display name. Defaults to existing. */
3771
+ readonly displayName?: string;
3772
+ /** Validate phrase strength against the configured policy. */
3773
+ readonly validatePassphrase?: boolean;
3774
+ /**
3775
+ * Skip phrase strength validation even when `validatePassphrase` is
3776
+ * set. The escape hatch matches `grant`'s shape — used when the
3777
+ * temp phrase is a high-entropy one-shot string that doesn't need
3778
+ * to satisfy the human-typeable rules.
3779
+ */
3780
+ readonly allowWeakPassphrase?: boolean;
3781
+ /**
3782
+ * Optional explicit phrase policy override (passed through to
3783
+ * `assertStrongPassphrase`). Mirrors how `grant` accepts a custom
3784
+ * `PassphrasePolicy` for app-specific tightening.
3785
+ */
3786
+ readonly passphrasePolicy?: PassphrasePolicy;
3787
+ }
3606
3788
  /**
3607
- * Decrypt a recovery entry to recover the raw DEK set. Used by the
3608
- * `recoverPassphrase` flow after the user's code has been parsed.
3609
- *
3610
- * Thin wrapper over {@link unwrapDeksFromBlob} (#44).
3789
+ * Atomically rewrap the target user's keyring under a fresh temp
3790
+ * passphrase. Single store write; no revoke step; no key rotation.
3611
3791
  *
3612
- * @throws when the code does not match the entry (AES-GCM auth tag fail).
3792
+ * Caller's responsibilities (NOT enforced here):
3793
+ * - Run the `peer-recover-user` policy gate first via
3794
+ * `Noydb.checkGate` to enforce the freshness factor proof.
3795
+ * - Communicate the temp passphrase to the recipient via a secure
3796
+ * channel (URL fragment, in-person, etc.) — the hub does not
3797
+ * transport secrets.
3613
3798
  */
3614
- declare function unwrapDeksFromPaperEntry(entry: PaperRecoveryEntry, code: string): Promise<Map<string, CryptoKey>>;
3799
+ declare function recoverUser(store: NoydbStore, vault: string, callerKeyring: UnlockedKeyring, options: RecoverUserOptions): Promise<void>;
3615
3800
 
3616
3801
  /**
3617
3802
  * Public envelope — owner-curated plaintext metadata, readable
@@ -3833,6 +4018,12 @@ interface StagedOp {
3833
4018
  id: string;
3834
4019
  record?: unknown;
3835
4020
  expectedVersion?: number;
4021
+ /**
4022
+ * Optional human-readable tag forwarded to the resulting ledger
4023
+ * entry's `reason` field (#1). Set by callers via
4024
+ * `tx.vault(v).collection(c).put(id, record, { reason })`.
4025
+ */
4026
+ reason?: string;
3836
4027
  }
3837
4028
  /**
3838
4029
  * One executed op (main staged op or recursive side-effect like a
@@ -3922,6 +4113,7 @@ declare class TxCollection<T> {
3922
4113
  */
3923
4114
  put(id: string, record: T, options?: {
3924
4115
  expectedVersion?: number;
4116
+ reason?: string;
3925
4117
  }): void;
3926
4118
  /**
3927
4119
  * Stage a delete. Does not write until the transaction body returns.
@@ -4026,6 +4218,16 @@ interface GatePolicy {
4026
4218
  * configured policy as "no gate" (no-op).
4027
4219
  */
4028
4220
  type BuiltInGateName = 'rotate-passphrase' | 'recover-passphrase' | 'enroll-authenticator' | 'remove-authenticator'
4221
+ /**
4222
+ * Authorize a deliberate paper-recovery-code regeneration —
4223
+ * `db.rotateRecovery` (#121). Symmetric to `rotate-passphrase` for
4224
+ * the case where the user remembers their passphrase but wants a
4225
+ * fresh sheet (lost the printout, suspect compromise of the off-site
4226
+ * copy). PERSONAL allows tier-1; STRICT requires an off-device
4227
+ * factor so a stolen unlocked laptop cannot silently mint a new
4228
+ * sheet for an attacker.
4229
+ */
4230
+ | 'rotate-recovery'
4029
4231
  /**
4030
4232
  * Authorize a meta-only mutation on an existing authenticator slot —
4031
4233
  * `db.updateAuthenticator` (#55). The slot's wrap material, id, and
@@ -4118,6 +4320,14 @@ declare class Noydb {
4118
4320
  * `_meta/policy` load; replaced by `db.updatePolicy()`.
4119
4321
  */
4120
4322
  private readonly policyCache;
4323
+ /**
4324
+ * One-shot bypass for the managed-mode strong-recovery check (#195).
4325
+ * Set true by {@link openVaultAndEnrollRecovery} for the duration of
4326
+ * the bootstrap window so the keyring can be created before the
4327
+ * strong recovery is enrolled. Always cleared (try/finally).
4328
+ * @internal
4329
+ */
4330
+ private _skipNextManagedRecoveryCheck;
4121
4331
  /** Per-vault tier-3 (PIN / quick-resume) state — issue #11. */
4122
4332
  private readonly quickUnlock;
4123
4333
  /**
@@ -4581,15 +4791,29 @@ declare class Noydb {
4581
4791
  /** Read or persist the vault policy at `_meta/policy` on first open. */
4582
4792
  private bootstrapPolicy;
4583
4793
  /**
4584
- * Throw {@link RecoveryNotEnrolledError} when the developer
4585
- * explicitly opts into strict mandatory-recovery enforcement
4586
- * (`createNoydb({ requireRecovery: true })`) and no recovery
4587
- * entries are persisted.
4794
+ * Throw {@link RecoveryNotEnrolledError} or
4795
+ * {@link ManagedRecoveryNotEnrolledError} when recovery enrollment
4796
+ * is missing.
4588
4797
  *
4589
- * The default behavior is lenient — `recover-passphrase` is enabled
4590
- * in `PERSONAL_POLICY` but the hub does not block vault open on
4591
- * missing enrollment. v1.0 will flip the default to strict; for now,
4592
- * apps that want the spec-mandated check turn it on per-vault.
4798
+ * Two enforcement modes:
4799
+ *
4800
+ * 1. **Managed-mode mandatory strong-recovery (#195).** When
4801
+ * `passphraseMode === 'managed'`, the vault MUST have at least
4802
+ * one **strong** recovery profile (Shamir today). Paper alone is
4803
+ * rejected because under managed mode the user has no memorized
4804
+ * passphrase, so losing the paper sheet = losing every record.
4805
+ * This check is unconditional — independent of `requireRecovery`
4806
+ * and the `recover-passphrase` gate.
4807
+ *
4808
+ * 2. **Opt-in strict mandatory-recovery.** When
4809
+ * `requireRecovery: true` is set on createNoydb (and the gate is
4810
+ * not explicitly disabled), require ANY recovery profile (paper
4811
+ * or shamir). This is the v0.x default-off behavior; v1.0 may
4812
+ * flip it default-on.
4813
+ *
4814
+ * The managed-mode check fires from {@link bootstrapPolicy} unless
4815
+ * the `skipManagedCheck` flag is set (used by
4816
+ * {@link openVaultAndEnrollRecovery} to allow atomic create-and-enroll).
4593
4817
  */
4594
4818
  private assertRecoveryEnrolled;
4595
4819
  /**
@@ -4785,6 +5009,123 @@ declare class Noydb {
4785
5009
  * Burns the used recovery entry on success.
4786
5010
  */
4787
5011
  recoverPassphrase(vault: string, input: RecoverPassphraseInput, factors?: FactorProofBundle): Promise<RecoverPassphraseResult>;
5012
+ /**
5013
+ * Deliberate paper-recovery-code regeneration (#121). User knows their
5014
+ * passphrase but wants a fresh sheet — they lost the printout or
5015
+ * suspect compromise of the off-site copy.
5016
+ *
5017
+ * Symmetric to {@link rotatePassphrase} for the recovery profile:
5018
+ * gated, audit-trackable, ergonomic. Replaces (not appends) the
5019
+ * paper sheet under `_meta/recovery-paper` in a single envelope `put`.
5020
+ *
5021
+ * Gated by the `rotate-recovery` policy gate:
5022
+ * - PERSONAL_POLICY: `{ minTier: 1 }` — knowing the passphrase
5023
+ * suffices, matching the pre-#121 low-level flow's bar.
5024
+ * - STRICT_POLICY: `{ minTier: 1, factors: [{ anyOf: ['totp',
5025
+ * 'email-otp', 'webauthn-roaming'] }] }` — rotation is an
5026
+ * off-site-trust event; require an off-device factor so a
5027
+ * stolen unlocked laptop cannot silently mint a sheet for the
5028
+ * attacker.
5029
+ *
5030
+ * Defaults `count` to the existing sheet size so consumers aren't
5031
+ * surprised by a different code count. Explicit `count` overrides.
5032
+ *
5033
+ * @throws {@link RecoveryProfileNotImplementedError} when `profile`
5034
+ * is anything other than `'paper'` (v1 dispatch limit).
5035
+ * @throws {@link PolicyDeniedError} when the gate denies (missing
5036
+ * factor, tier mismatch, ...).
5037
+ * @throws on missing paper sheet — "nothing to rotate" surfaces as
5038
+ * an error rather than silently minting an entire new sheet.
5039
+ *
5040
+ * @example Default count + show-once UI
5041
+ * ```ts
5042
+ * const { newCodes } = await db.rotateRecovery('acme', { profile: 'paper' })
5043
+ * showCodesToUser(newCodes)
5044
+ * ```
5045
+ *
5046
+ * @example STRICT-policy site with TOTP factor proof
5047
+ * ```ts
5048
+ * await db.rotateRecovery(
5049
+ * 'acme',
5050
+ * { profile: 'paper', count: 10 },
5051
+ * { factors: [{ kind: 'totp', proof: '123456' }] },
5052
+ * )
5053
+ * ```
5054
+ */
5055
+ rotateRecovery(vault: string, options: RotateRecoveryOptions, factors?: FactorProofBundle): Promise<RotateRecoveryResult>;
5056
+ private rotateRecoveryPaper;
5057
+ private rotateRecoveryShamir;
5058
+ /**
5059
+ * **Atomic create-and-enroll for managed-mode vaults (#195).**
5060
+ *
5061
+ * Bootstraps a managed-mode vault and enrolls strong recovery in
5062
+ * a single ceremony. Under `passphraseMode: 'managed'`, every
5063
+ * `openVault` call requires a strong recovery profile (Shamir
5064
+ * today) to be enrolled — otherwise it throws
5065
+ * {@link ManagedRecoveryNotEnrolledError}. This method bypasses
5066
+ * the check temporarily so the keyring can be created, enrolls
5067
+ * the supplied recovery profile(s), then returns the vault.
5068
+ *
5069
+ * For Shamir enrollments, the show-once share strings come back
5070
+ * in `recoveryEnrollments[i].shares`. The hub never retains them
5071
+ * — the caller MUST display them to the user (once) before any
5072
+ * subsequent operation.
5073
+ *
5074
+ * Paper alone is NOT a strong profile under managed mode; passing
5075
+ * `{ profile: 'paper', ... }` without an accompanying shamir entry
5076
+ * is rejected at validation time.
5077
+ *
5078
+ * ```ts
5079
+ * const db = await createNoydb({
5080
+ * store, user: 'alice',
5081
+ * passphraseMode: 'managed',
5082
+ * sealingKey: macosKeychainSealingProvider({ ... }),
5083
+ * })
5084
+ *
5085
+ * const { vault, recoveryEnrollments } = await db.openVaultAndEnrollRecovery('acme', {
5086
+ * recovery: [{ profile: 'shamir', k: 2, n: 3 }],
5087
+ * })
5088
+ * for (const r of recoveryEnrollments) {
5089
+ * if (r.shares) showSharesToUser(r.shares) // ONCE
5090
+ * }
5091
+ * ```
5092
+ *
5093
+ * @throws ValidationError if recovery is empty, or contains no
5094
+ * strong profile under managed mode.
5095
+ */
5096
+ openVaultAndEnrollRecovery(vault: string, opts: {
5097
+ readonly recovery: ReadonlyArray<RecoveryEnrollmentInput>;
5098
+ readonly locale?: string;
5099
+ }): Promise<{
5100
+ readonly vault: Vault;
5101
+ readonly recoveryEnrollments: ReadonlyArray<EnrollRecoveryResult>;
5102
+ }>;
5103
+ /**
5104
+ * **Recovery flow under managed-passphrase mode (#195).**
5105
+ *
5106
+ * Replaces the sealed passphrase of a managed-mode vault with a
5107
+ * fresh 256-bit random, sealed under the configured
5108
+ * `SealingKeyProvider`. The user never sees the new passphrase.
5109
+ *
5110
+ * Internally:
5111
+ * 1. Verify the recovery proof (Shamir today) and unwrap the
5112
+ * DEK set.
5113
+ * 2. Mint a fresh 256-bit random as the new effective passphrase.
5114
+ * 3. Rewrap the DEK set under a fresh KEK derived from the new
5115
+ * passphrase (via the existing `recoverPassphrase` path).
5116
+ * 4. Seal the random bytes under the provider and overwrite
5117
+ * `_meta/sealed-passphrase`.
5118
+ * 5. Drop the keyring cache so the next operation re-derives.
5119
+ *
5120
+ * The vault's strong-recovery enrollment is preserved across
5121
+ * recovery (Shamir entries are not burned on use — see #196).
5122
+ *
5123
+ * @throws ValidationError if the Noydb instance is not in managed mode.
5124
+ */
5125
+ recoverManagedPassphrase(vault: string, options: {
5126
+ readonly recoveryProof: RecoveryProof;
5127
+ readonly passphrasePolicy?: PassphrasePolicy;
5128
+ }): Promise<void>;
4788
5129
  /**
4789
5130
  * Atomic peer-recovery — re-wraps an EXISTING user's keyring under
4790
5131
  * a fresh temp passphrase in a single store write. Closes #34's
@@ -4860,13 +5201,11 @@ declare class Noydb {
4860
5201
  * await db.enrollRecovery('acme', { profile: 'paper', entries })
4861
5202
  * ```
4862
5203
  */
4863
- enrollRecovery(vault: string, enrollment: {
4864
- profile: 'paper';
4865
- entries: ReadonlyArray<PaperRecoveryEntry>;
4866
- }): Promise<void>;
4867
- /** Read the persisted paper-recovery entries. Used by `describeAuthConfig` (#13). */
5204
+ enrollRecovery(vault: string, enrollment: RecoveryEnrollmentInput): Promise<EnrollRecoveryResult>;
5205
+ /** Read the persisted recovery entries (paper + Shamir). Used by `describeAuthConfig` (#13). */
4868
5206
  listRecoveryEntries(vault: string): Promise<{
4869
5207
  paper: ReadonlyArray<PaperRecoveryEntry>;
5208
+ shamir: ReadonlyArray<ShamirRecoveryEntry>;
4870
5209
  }>;
4871
5210
  /**
4872
5211
  * Register a tier-3 quick-unlock state for the vault. The state is
@@ -5062,6 +5401,39 @@ interface GuardStrategyHandle<T extends Record<string, unknown>> {
5062
5401
  readonly __noydb_strategy: 'guard';
5063
5402
  readonly spec: GuardStrategy<T>;
5064
5403
  }
5404
+ /**
5405
+ * Existential erasure of `GuardStrategyHandle<T>` — used as the
5406
+ * element type of `ReadonlyArray<>` fields where the per-handle T
5407
+ * differs (e.g. `guardStrategies: [invoiceGuard, disbursementGuard]`).
5408
+ *
5409
+ * Background: `GuardStrategyHandle<T>` is INVARIANT in T because T
5410
+ * appears in callback positions on the spec (`check(incoming: T, ctx)`,
5411
+ * `invariant(changes: ReadonlyArray<GuardChange<T>>, ctx)`). So
5412
+ * `Handle<Invoice>` is not assignable to `Handle<Record<string, unknown>>`.
5413
+ * A bounded existential ("there exists some T satisfying the constraint
5414
+ * such that this is a Handle<T>") is the right shape; TypeScript has
5415
+ * no first-class existentials, so we fake it with a structurally narrow
5416
+ * interface that ERASES T from both the discriminant and the spec.
5417
+ *
5418
+ * Consumers continue to construct typed handles via `withGuard<T>(...)`
5419
+ * which returns `GuardStrategyHandle<T>`. Both `Handle<Invoice>` and
5420
+ * `Handle<Disbursement>` structurally assign to `GuardStrategyHandleAny`,
5421
+ * so an array of them is `GuardStrategyHandleAny[]`.
5422
+ *
5423
+ * Internal code that needs T re-narrows via the runtime discriminant
5424
+ * (`__noydb_strategy === 'guard'`) plus per-handle type information
5425
+ * carried by the registry.
5426
+ *
5427
+ * NOT exported from the public barrel — keeping this internal
5428
+ * discourages consumers from constructing it directly. Used only as
5429
+ * the array-element type on `Vault` / `NoydbOptions.guardStrategies`.
5430
+ *
5431
+ * @internal
5432
+ */
5433
+ interface GuardStrategyHandleAny {
5434
+ readonly __noydb_strategy: 'guard';
5435
+ readonly spec: GuardStrategy<any>;
5436
+ }
5065
5437
  /** Public registration shape. See `withGuard()`. */
5066
5438
  interface GuardStrategy<T extends Record<string, unknown>> {
5067
5439
  collection: string;
@@ -5154,8 +5526,8 @@ interface DerivedFromMeta {
5154
5526
  */
5155
5527
  readonly strategyHash: string;
5156
5528
  }
5157
- /** Per-output declaration. v1: only `'record'` shape. */
5158
- interface OutputSpec {
5529
+ /** Record-shape output one source row produces (optionally) one output row at the source's id. */
5530
+ interface RecordOutputSpec {
5159
5531
  shape: 'record';
5160
5532
  collection: string;
5161
5533
  /**
@@ -5169,6 +5541,50 @@ interface OutputSpec {
5169
5541
  */
5170
5542
  optional?: boolean;
5171
5543
  }
5544
+ /**
5545
+ * Array-shape output (#200) — one source row produces a variable-length
5546
+ * list of output rows, each with its own id (from the `key` extractor).
5547
+ *
5548
+ * On every source-row change, the dispatcher diffs the previously
5549
+ * emitted key set against the new one: removed keys are deleted via
5550
+ * `_internalDelete`, new and unchanged keys are upserted via
5551
+ * `Collection.put`. Strict-mode rollback is preserved via the existing
5552
+ * `_executed` tracking.
5553
+ *
5554
+ * Storage of the per-source-row key set lives at
5555
+ * `_meta/derivations-fanout/<source>/<sourceId>/<outputKey>` as a
5556
+ * plain JSON sidecar — keeps dispatch cost O(1) per source row.
5557
+ *
5558
+ * **Slice 1 limitation**: only `lifecycle: 'eager'` is supported.
5559
+ * Registering an array-shape output with `lifecycle: 'lazy'` throws
5560
+ * at `withDerivation` construction time.
5561
+ */
5562
+ interface ArrayOutputSpec {
5563
+ shape: 'array';
5564
+ collection: string;
5565
+ /**
5566
+ * Stable identity extractor for each derived row. Called on every
5567
+ * row returned by `derive`. The string MUST be unique within a
5568
+ * single invocation — duplicate keys throw
5569
+ * `DerivationOutputShapeError`.
5570
+ *
5571
+ * Type is intentionally `(out: Record<string, unknown>) => string`
5572
+ * (not generic) because OutputSpec is type-erased at the registry
5573
+ * level. Strategy-level inference still produces typed `out`
5574
+ * through the strategy's `outputs` map.
5575
+ */
5576
+ key: (output: Record<string, unknown>) => string;
5577
+ /**
5578
+ * Cap on derived rows per source-row invocation. Defaults to 64.
5579
+ * Raise for carry-forward cases (e.g. monthly expansion of
5580
+ * multi-year contracts). Exceeding the cap throws
5581
+ * `DerivationCapExceededError` BEFORE any writes — partial fanout
5582
+ * is never persisted.
5583
+ */
5584
+ maxFanout?: number;
5585
+ }
5586
+ /** Discriminated union — record + array. */
5587
+ type OutputSpec = RecordOutputSpec | ArrayOutputSpec;
5172
5588
  /**
5173
5589
  * Registration shape passed to `withDerivation()`.
5174
5590
  *
@@ -5298,6 +5714,28 @@ interface MaterializedViewOutput {
5298
5714
  value: unknown;
5299
5715
  };
5300
5716
  }
5717
+ /**
5718
+ * One arm of a UNION materialized view. Reads rows from `collection`,
5719
+ * then maps each into the MV's row shape via `map`.
5720
+ *
5721
+ * The per-source `map` is the schema-unification boundary — sibling
5722
+ * collections can have different schemas, and `map` is where they
5723
+ * meet the MV's row type. The hub does NOT compare schemas across
5724
+ * arms; consumer responsibility is that every arm's `map` returns
5725
+ * the same shape (the strategy's `TRow` type parameter enforces this
5726
+ * at compile time).
5727
+ */
5728
+ interface UnionSource<TRow extends Record<string, unknown>> {
5729
+ /** Source collection name. Must exist in the vault. */
5730
+ readonly collection: string;
5731
+ /**
5732
+ * Pure function from a source row to the unified MV row shape.
5733
+ * Called once per source row at materialization time. Each arm's
5734
+ * mapped output is concatenated into a single stream before
5735
+ * `groupBy` + `aggregate` run.
5736
+ */
5737
+ readonly map: (sourceRow: Record<string, unknown>) => TRow;
5738
+ }
5301
5739
  /**
5302
5740
  * Registration shape passed to `withMaterializedView()`.
5303
5741
  *
@@ -5310,16 +5748,59 @@ interface MaterializedViewStrategy<TRow extends Record<string, unknown>> {
5310
5748
  */
5311
5749
  name: string;
5312
5750
  /**
5313
- * Declared query. Called at registration time with a vault-shaped
5314
- * accessor so the closure can compose collections without
5315
- * pre-existing in-scope references; called again at each refresh.
5751
+ * Declared query (single-source mode). Called at registration time
5752
+ * with a vault-shaped accessor so the closure can compose collections
5753
+ * without pre-existing in-scope references; called again at each
5754
+ * refresh.
5316
5755
  *
5317
5756
  * Built via the same `Query<T>` chainable builder used elsewhere —
5318
5757
  * `.where()`, `.join()`, `.groupBy()`, `.aggregate()`. The
5319
5758
  * dependency analyzer walks the returned plan to determine source
5320
5759
  * collections.
5760
+ *
5761
+ * Mutually exclusive with {@link unionSources}: a strategy must
5762
+ * declare exactly one of `query` (single-source) or `unionSources`
5763
+ * (multi-source UNION). Registration throws
5764
+ * `MaterializedViewConfigError` if both are set or neither is set.
5765
+ */
5766
+ query?: (db: MVQueryContext) => Query<TRow>;
5767
+ /**
5768
+ * UNION-form sources (#165): an explicit list of sibling collections
5769
+ * that contribute rows to a single MV. Each arm's `map` projects a
5770
+ * source row into the MV's unified row shape; the mapped streams are
5771
+ * concatenated, then {@link groupBy} + {@link aggregate} run on the
5772
+ * combined output.
5773
+ *
5774
+ * Mutually exclusive with {@link query}. Registration throws
5775
+ * `MaterializedViewConfigError` if both are set, if `unionSources`
5776
+ * has fewer than 2 arms, or if two arms name the same `collection`.
5777
+ *
5778
+ * UNION mode replaces the dependency-analyzer path: the source
5779
+ * collections come directly from `unionSources[].collection`, and
5780
+ * {@link sources} is ignored.
5781
+ */
5782
+ unionSources?: ReadonlyArray<UnionSource<TRow>>;
5783
+ /**
5784
+ * Group-key field(s) for UNION mode (#165). Applied to the
5785
+ * concatenated mapped-row stream from {@link unionSources} before
5786
+ * {@link aggregate} runs. Accepts a single field name or a tuple of
5787
+ * field names for multi-key grouping (same shape as
5788
+ * `Query.groupBy(...fields)`).
5789
+ *
5790
+ * UNION-mode only. Ignored if {@link query} is set — single-source
5791
+ * grouping is expressed inside the `Query<T>` returned from `query()`
5792
+ * via `.groupBy(...).aggregate(...)`.
5793
+ */
5794
+ groupBy?: string | ReadonlyArray<string>;
5795
+ /**
5796
+ * Aggregation spec for UNION mode (#165). Applied per-group after
5797
+ * {@link groupBy} buckets the concatenated mapped-row stream from
5798
+ * {@link unionSources}. Same shape as the `AggregateSpec` passed to
5799
+ * `Query.aggregate()`.
5800
+ *
5801
+ * UNION-mode only. Ignored if {@link query} is set.
5321
5802
  */
5322
- query: (db: MVQueryContext) => Query<TRow>;
5803
+ aggregate?: AggregateSpec;
5323
5804
  /**
5324
5805
  * Pure function from a materialized row → stable id used in the
5325
5806
  * output collection. Required — explicit always beats default-with-pitfalls
@@ -6268,6 +6749,159 @@ declare class UserApi {
6268
6749
  private fireChange;
6269
6750
  }
6270
6751
 
6752
+ /**
6753
+ * Persisted-schema envelope shape.
6754
+ *
6755
+ * Stored encrypted under `_schemas/<collection>` with the same DEK as the
6756
+ * collection's records. Auditors who can unlock the collection's data can
6757
+ * also read its schema; nothing more.
6758
+ *
6759
+ * @see docs/superpowers/specs/2026-05-22-schema-dump-design.md
6760
+ *
6761
+ * @module
6762
+ */
6763
+ /** Family of Standard Schema v1 validator the persisted snapshot was derived from. */
6764
+ type PersistedSchemaKind = 'Zod' | 'Valibot' | 'ArkType' | 'Effect' | 'Unknown';
6765
+ /**
6766
+ * Plaintext payload encrypted into the `_data` field of the
6767
+ * `_schemas/<collection>` envelope. The wrapper `EncryptedEnvelope` adds
6768
+ * `_noydb`, `_v`, `_ts`, `_iv`, `_data` per the standard noy-db record
6769
+ * format.
6770
+ */
6771
+ interface PersistedSchemaEnvelope {
6772
+ readonly _noydb_schema: 1;
6773
+ /** Detected validator family. */
6774
+ readonly kind: PersistedSchemaKind;
6775
+ /**
6776
+ * JSON Schema (Draft 2020-12) derived from the validator. Null when
6777
+ * derivation isn't yet supported for `kind`; in that case `reason` is
6778
+ * populated.
6779
+ */
6780
+ readonly jsonSchema: object | null;
6781
+ /** SHA-256 (hex) of the canonicalised JSON Schema, or null when unavailable. */
6782
+ readonly hash: string | null;
6783
+ /** Human-readable reason when `jsonSchema` is null. */
6784
+ readonly reason?: string;
6785
+ /** ISO-8601 timestamp of the most recent derivation write. */
6786
+ readonly derivedAt: string;
6787
+ }
6788
+
6789
+ /**
6790
+ * Types for {@link VaultSchemaSnapshot} — the structured object returned
6791
+ * by `vault.dumpSchema()`. Consumed by the upcoming `noydb describe`
6792
+ * CLI to emit human-readable YAML/JSON audit output.
6793
+ *
6794
+ * @see docs/superpowers/specs/2026-05-22-schema-dump-design.md
6795
+ *
6796
+ * @module
6797
+ */
6798
+
6799
+ /** Where the field-level info in the snapshot came from. */
6800
+ type FieldSource = 'persisted' | 'live-validator' | 'sampled' | 'unknown';
6801
+ interface FieldDescriptor {
6802
+ /** Inferred type tag: 'string' | 'number' | 'boolean' | 'enum' | 'object' | 'array' | 'null' | 'opaque'. */
6803
+ readonly type: string;
6804
+ /** Where this field info was sourced from. */
6805
+ readonly source: FieldSource;
6806
+ /** Optional constraints — minLength, maxLength, enum values, gt, etc. */
6807
+ readonly constraints?: Record<string, unknown>;
6808
+ /** True when the schema marks this field optional. */
6809
+ readonly optional?: boolean;
6810
+ /** Foreign-key target as `<collection>.<field>` when declared. */
6811
+ readonly references?: string;
6812
+ }
6813
+ interface CollectionStats {
6814
+ readonly records: number;
6815
+ readonly bytes: number;
6816
+ readonly bytesAvg: number;
6817
+ readonly bytesMin: number;
6818
+ readonly bytesMax: number;
6819
+ /** ISO-8601 from min(_ts) across envelopes. Empty string when no records. */
6820
+ readonly oldest: string;
6821
+ /** ISO-8601 from max(_ts) across envelopes. Empty string when no records. */
6822
+ readonly newest: string;
6823
+ }
6824
+ interface CollectionDescriptor {
6825
+ readonly fields: Record<string, FieldDescriptor>;
6826
+ readonly indexes: ReadonlyArray<{
6827
+ readonly fields: ReadonlyArray<string>;
6828
+ readonly unique?: boolean;
6829
+ }>;
6830
+ readonly refs: Record<string, {
6831
+ readonly target: string;
6832
+ readonly mode: 'strict' | 'warn' | 'cascade';
6833
+ }>;
6834
+ readonly validator?: {
6835
+ readonly kind: PersistedSchemaKind;
6836
+ readonly source: 'persisted' | 'live-validator';
6837
+ };
6838
+ readonly stats?: CollectionStats;
6839
+ }
6840
+ interface MaterializedViewDescriptor {
6841
+ readonly sources: ReadonlyArray<string>;
6842
+ readonly groupBy?: ReadonlyArray<string>;
6843
+ readonly aggregate?: Record<string, string>;
6844
+ readonly refresh: string;
6845
+ readonly stats?: CollectionStats;
6846
+ }
6847
+ interface OverlayViewDescriptor {
6848
+ readonly base: string;
6849
+ readonly overlay: string;
6850
+ }
6851
+ interface DerivationDescriptor {
6852
+ readonly source: string;
6853
+ readonly outputs: ReadonlyArray<string>;
6854
+ }
6855
+ interface InternalCollectionStats {
6856
+ readonly records: number;
6857
+ readonly bytes: number;
6858
+ }
6859
+ interface VaultSchemaSnapshot {
6860
+ readonly _noydb_snapshot: 1;
6861
+ readonly vault: string;
6862
+ readonly emittedAt: string;
6863
+ readonly subsystems: Record<string, boolean>;
6864
+ readonly aclRoles?: ReadonlyArray<string>;
6865
+ readonly collections: Record<string, CollectionDescriptor>;
6866
+ readonly materializedViews: Record<string, MaterializedViewDescriptor>;
6867
+ readonly overlayViews: Record<string, OverlayViewDescriptor>;
6868
+ readonly derivations: Record<string, DerivationDescriptor>;
6869
+ /** Only present when `dumpSchema({ withStats: true })` was called. */
6870
+ readonly internal?: Record<string, InternalCollectionStats>;
6871
+ }
6872
+ interface DumpSchemaOptions {
6873
+ /** When true, walk every collection's envelopes to compute counters. Default `false`. */
6874
+ readonly withStats?: boolean;
6875
+ /** Sample N records per collection lacking a persisted/live schema. Default 50. `0` disables sampling. */
6876
+ readonly sampleSize?: number;
6877
+ }
6878
+
6879
+ /**
6880
+ * Orchestrate the structural walk of a Vault, producing a
6881
+ * {@link VaultSchemaSnapshot}. Called from `Vault.dumpSchema()`.
6882
+ *
6883
+ * @module
6884
+ */
6885
+
6886
+ /**
6887
+ * The minimal slice of Vault internal state the walker needs.
6888
+ * Exposed via `vault._introspectState()` to keep the public Vault
6889
+ * surface narrow.
6890
+ *
6891
+ * @internal
6892
+ */
6893
+ interface VaultIntrospectState {
6894
+ readonly name: string;
6895
+ readonly adapter: NoydbStore;
6896
+ readonly collectionCache: Map<string, Collection<unknown>>;
6897
+ readonly refRegistry: RefRegistry;
6898
+ readonly getDEK: (collectionName: string) => Promise<CryptoKey>;
6899
+ readonly subsystems: Record<string, boolean>;
6900
+ readonly mvRegistry: unknown;
6901
+ readonly overlayRegistry: unknown;
6902
+ readonly derivationRegistry: unknown;
6903
+ }
6904
+
6271
6905
  /** A vault (tenant namespace) containing collections. */
6272
6906
  declare class Vault {
6273
6907
  private readonly adapter;
@@ -6391,6 +7025,16 @@ declare class Vault {
6391
7025
  * docstring.
6392
7026
  */
6393
7027
  private ledgerStore;
7028
+ /**
7029
+ * Background writes for persisted-schema envelopes (#schema-dump v0
7030
+ * slice 1). One promise per `collection({ persistJsonSchema: true })`
7031
+ * registration that actually fired a derive call. Fire-and-forget
7032
+ * from the collection factory; tests await
7033
+ * {@link _drainPendingSchemaWrites} before asserting on storage.
7034
+ * Production code does not need to drain — the writes are
7035
+ * idempotent fingerprints, not correctness invariants.
7036
+ */
7037
+ private _pendingSchemaWrites;
6394
7038
  /**
6395
7039
  * Per-vault foreign-key reference registry. Collections
6396
7040
  * register their `refs` option here on construction; the
@@ -6502,7 +7146,7 @@ declare class Vault {
6502
7146
  historyStrategy?: HistoryStrategy | undefined;
6503
7147
  i18nStrategy?: I18nStrategy | undefined;
6504
7148
  syncStrategy?: SyncStrategy | undefined;
6505
- guardStrategies?: ReadonlyArray<GuardStrategyHandle<any>> | undefined;
7149
+ guardStrategies?: ReadonlyArray<GuardStrategyHandleAny> | undefined;
6506
7150
  });
6507
7151
  /**
6508
7152
  * Construct (or reconstruct) the lazy DEK resolver. Captures the
@@ -6575,7 +7219,26 @@ declare class Vault {
6575
7219
  tiers?: readonly number[];
6576
7220
  /** — how lower-tier reads see above-tier records. */
6577
7221
  tierMode?: TierMode;
7222
+ /**
7223
+ * Opt-in persisted JSON Schema. When `true` AND a Zod `schema` is
7224
+ * provided, hub derives a JSON Schema via `zod-to-json-schema`
7225
+ * (optional peer-dep) and writes an encrypted snapshot to
7226
+ * `_schemas/<collectionName>`. Re-runs on every open; hash-skip
7227
+ * avoids write churn when the schema is unchanged.
7228
+ *
7229
+ * Default: `false`. Non-Zod Standard Schema validators receive a
7230
+ * stub envelope flagging the kind without a JSON Schema body.
7231
+ *
7232
+ * @see docs/superpowers/specs/2026-05-22-schema-dump-design.md
7233
+ */
7234
+ persistJsonSchema?: boolean;
6578
7235
  }): Collection<T>;
7236
+ /**
7237
+ * Await all background persisted-schema writes triggered by
7238
+ * `collection({ persistJsonSchema: true })` calls on this vault.
7239
+ * Used in tests; production code does not need to call this.
7240
+ */
7241
+ _drainPendingSchemaWrites(): Promise<void>;
6579
7242
  /**
6580
7243
  * Validate i18nText fields on a `put()`. Called by Collection just
6581
7244
  * before the adapter write, after schema validation. Throws
@@ -6876,7 +7539,7 @@ declare class Vault {
6876
7539
  * accessor `_getReadOnlyFacade()` (called from the tx amendment
6877
7540
  * runner) stays synchronous.
6878
7541
  */
6879
- _initGuards(handles: ReadonlyArray<GuardStrategyHandle<any>>): Promise<void>;
7542
+ _initGuards(handles: ReadonlyArray<GuardStrategyHandleAny>): Promise<void>;
6880
7543
  /**
6881
7544
  * @internal — Collection.put calls into this. Returns `null` for
6882
7545
  * vaults that never registered any guard strategy. Callers MUST
@@ -7186,6 +7849,34 @@ declare class Vault {
7186
7849
  private _decryptPeriodRecord;
7187
7850
  /** List all collection names in this vault. */
7188
7851
  collections(): Promise<string[]>;
7852
+ /**
7853
+ * Emit a structured introspection snapshot of this vault — vault name,
7854
+ * subsystem opt-in matrix, collections + their fields, materialized
7855
+ * views, overlay views, derivations. With `withStats: true`, walks
7856
+ * every collection's envelopes to compute record counts, byte totals,
7857
+ * and oldest/newest timestamps.
7858
+ *
7859
+ * Consumed by the `noydb describe` CLI to produce human-readable
7860
+ * audit YAML/JSON from a `.noydb` bundle.
7861
+ *
7862
+ * Field provenance:
7863
+ * - `persisted`: read from `_schemas/<col>` envelope (Route B opt-in)
7864
+ * - `live-validator`: derived in-process from a Zod schema attached
7865
+ * to the live `Collection`
7866
+ * - `sampled`: inferred from decrypted records (deferred to a follow-up)
7867
+ * - `unknown`: no schema info available
7868
+ *
7869
+ * @see docs/superpowers/specs/2026-05-22-schema-dump-design.md
7870
+ */
7871
+ dumpSchema(opts?: DumpSchemaOptions): Promise<VaultSchemaSnapshot>;
7872
+ /**
7873
+ * Internal accessor for {@link dumpVaultSchema}. Exposes the structural
7874
+ * state the walker needs (collection cache, registries, ref registry,
7875
+ * adapter) without widening the public Vault surface.
7876
+ *
7877
+ * @internal
7878
+ */
7879
+ _introspectState(): VaultIntrospectState;
7189
7880
  /**
7190
7881
  * Return the stable opaque bundle handle for this vault,
7191
7882
  * generating and persisting a fresh ULID on first call.
@@ -8241,8 +8932,20 @@ declare class Collection<T> {
8241
8932
  staleMs?: number;
8242
8933
  pollIntervalMs?: number;
8243
8934
  }): PresenceHandle<P>;
8244
- /** Create or update a record. */
8245
- put(id: string, record: T): Promise<void>;
8935
+ /**
8936
+ * Create or update a record.
8937
+ *
8938
+ * @param id Record identifier.
8939
+ * @param record The record body (validated by the collection's schema
8940
+ * if one was attached at `vault.collection(...)` time).
8941
+ * @param options Optional metadata for audit + import workflows.
8942
+ * `reason` is stamped onto the resulting ledger entry
8943
+ * (see #1) so audit consumers can filter via
8944
+ * `entries.filter(e => e.reason?.startsWith('import:'))`.
8945
+ */
8946
+ put(id: string, record: T, options?: {
8947
+ readonly reason?: string;
8948
+ }): Promise<void>;
8246
8949
  /**
8247
8950
  * Fire registered MV strategies whose dependency set includes this
8248
8951
  * collection. Eager-mode MVs re-materialize inline via
@@ -8310,6 +9013,28 @@ declare class Collection<T> {
8310
9013
  */
8311
9014
  _internalDelete(id: string, txCtx?: TxContext | null): Promise<void>;
8312
9015
  private _doDelete;
9016
+ /**
9017
+ * Cascade deletes of array-shape derived rows when a source row is
9018
+ * deleted (#200). Reads each registered strategy's fanout sidecar
9019
+ * for this source id, deletes every listed derived row, then
9020
+ * deletes the sidecar itself.
9021
+ *
9022
+ * Record-shape derivations are skipped — see _doDelete's comment
9023
+ * for why the asymmetry is correct.
9024
+ *
9025
+ * @internal
9026
+ */
9027
+ private dispatchArrayDerivationsOnDelete;
9028
+ /**
9029
+ * Mirror of {@link dispatchMaterializedViews} for the delete path
9030
+ * (#181). No record content is available (it's gone), so the
9031
+ * `_materializedFrom` skip used by the put-side dispatch doesn't
9032
+ * apply here — instead, the recursion guard is the `internal` gate
9033
+ * at the `_doDelete` call site above.
9034
+ *
9035
+ * @internal
9036
+ */
9037
+ private dispatchMaterializedViewsOnDelete;
8313
9038
  /**
8314
9039
  * List all records in the collection.
8315
9040
  *
@@ -9138,6 +9863,160 @@ interface SessionStrategy {
9138
9863
  revokeAllSessions(): void;
9139
9864
  }
9140
9865
 
9866
+ /**
9867
+ * Managed-passphrase mode — issue #14, rubber-hose-resistant vaults.
9868
+ *
9869
+ * A vault mode where the passphrase is machine-generated and never
9870
+ * exposed to the user, sealed under a developer-provided
9871
+ * {@link SealingKeyProvider} (macOS Keychain, Windows Credential
9872
+ * Manager, libsecret, AWS KMS, …). The user has no secret to give
9873
+ * up to coercion — they can't reveal what they don't know.
9874
+ *
9875
+ * ## Components in this file
9876
+ *
9877
+ * - {@link SealingKeyProvider} — the interface concrete providers
9878
+ * implement. Provider implementations live OUTSIDE hub (per-
9879
+ * platform packages).
9880
+ * - {@link MemorySealingKeyProvider} — in-memory test provider; uses
9881
+ * a deterministic per-instance "key" so two providers with
9882
+ * different ids cannot unseal each other's outputs.
9883
+ * - {@link loadSealedPassphrase} / {@link saveSealedPassphrase} —
9884
+ * plaintext envelope storage at `_meta/sealed-passphrase`.
9885
+ * Mirrors the `_meta/handle` and `_meta/public-envelope` AES-
9886
+ * GCM-bypassed patterns. The sealing layer (provider's job)
9887
+ * is the security boundary; hub doesn't have a key to encrypt
9888
+ * with at this layer — that's the whole point of the design.
9889
+ * - {@link resolveManagedSecret} — orchestrates the "generate +
9890
+ * seal + persist on first open; unseal on reopen" flow.
9891
+ * Returns the plaintext passphrase string that the rest of the
9892
+ * `createNoydb` keyring path consumes.
9893
+ *
9894
+ * Slice 1 of #14. Deferred to follow-ups:
9895
+ * - Block `rotate-passphrase` policy gate under managed mode.
9896
+ * - Mandatory strong-recovery enforcement (depends on #10).
9897
+ * - Recovery flow under managed mode (generates fresh sealed phrase).
9898
+ *
9899
+ * @see docs/subsystems/session-tiers.md → Managed-passphrase mode
9900
+ *
9901
+ * @module
9902
+ */
9903
+
9904
+ /**
9905
+ * The contract concrete providers (per-platform key stores) implement
9906
+ * to seal and unseal a hub-generated random passphrase. The plaintext
9907
+ * passphrase NEVER leaves hub-controlled memory in unsealed form —
9908
+ * the provider receives the bytes, returns opaque sealed bytes, and
9909
+ * later reverses the operation. Hub treats the sealed bytes as
9910
+ * fully opaque.
9911
+ *
9912
+ * Implementations live OUTSIDE `@noy-db/hub` (separate packages
9913
+ * per the issue's "Concrete providers (live outside hub)" note):
9914
+ *
9915
+ * | Platform | Package (TBD) | Backing |
9916
+ * |---|---|---|
9917
+ * | macOS | `@noy-db/seal-macos-keychain` | Security.framework |
9918
+ * | Windows | `@noy-db/seal-wincred` | Credential Manager |
9919
+ * | Linux | `@noy-db/seal-libsecret` | libsecret / secret-service |
9920
+ * | Cloud / server | `@noy-db/seal-aws-kms` | AWS KMS Decrypt |
9921
+ */
9922
+ interface SealingKeyProvider {
9923
+ /**
9924
+ * Non-sensitive identifier disclosed in the persisted envelope.
9925
+ * Surfaced to consumers via `loadSealedPassphrase().providerId` so
9926
+ * a vault opened with the wrong provider class can detect the
9927
+ * mismatch and surface a clear error. NOT secret — fine to log.
9928
+ *
9929
+ * Suggested format: `<family>:<scope>` — e.g. `macos-keychain:com.acme.app`,
9930
+ * `aws-kms:arn:aws:kms:us-east-1:123:key/abc`. The hub never
9931
+ * parses this; it's purely audit metadata.
9932
+ */
9933
+ readonly id: string;
9934
+ /** Seal raw passphrase bytes. Output bytes are opaque to hub. */
9935
+ seal(passphrase: Uint8Array): Promise<Uint8Array>;
9936
+ /**
9937
+ * Reverse {@link seal}. MUST throw on tamper, wrong-provider, or
9938
+ * any other failure — hub treats a thrown error as "this provider
9939
+ * cannot unlock this vault" and surfaces it to the caller.
9940
+ */
9941
+ unseal(sealed: Uint8Array): Promise<Uint8Array>;
9942
+ }
9943
+ /**
9944
+ * In-memory test provider. NOT secure — uses a deterministic
9945
+ * per-instance "key" (16-byte SHA-256 of `id`) XOR'd over the
9946
+ * passphrase plus a 4-byte provider-id fingerprint prefix. The XOR is
9947
+ * sufficient to make different `id` values produce mutually-unsealable
9948
+ * outputs (the contract tests for that), but offers ZERO real
9949
+ * confidentiality — never use outside tests.
9950
+ *
9951
+ * Replace with a real platform provider in production.
9952
+ */
9953
+ declare class MemorySealingKeyProvider implements SealingKeyProvider {
9954
+ readonly id: string;
9955
+ private readonly fingerprint;
9956
+ private readonly keyBytes;
9957
+ constructor(opts: {
9958
+ id: string;
9959
+ });
9960
+ seal(passphrase: Uint8Array): Promise<Uint8Array>;
9961
+ unseal(sealed: Uint8Array): Promise<Uint8Array>;
9962
+ }
9963
+ /** Reserved id for the managed-passphrase envelope under `_meta`. */
9964
+ declare const SEALED_PASSPHRASE_RECORD_ID: "sealed-passphrase";
9965
+ /** Plaintext payload stored inside the `_meta/sealed-passphrase` envelope. */
9966
+ interface SealedPassphrase {
9967
+ readonly _noydb_sealed: 1;
9968
+ readonly providerId: string;
9969
+ /** Sealed bytes. Base64-encoded on the wire; decoded on load. */
9970
+ readonly sealed: Uint8Array;
9971
+ }
9972
+ /**
9973
+ * Wire-format envelope persisted at `_meta/sealed-passphrase` for
9974
+ * managed-mode vaults. The provider produces raw sealed bytes via
9975
+ * {@link SealingKeyProvider.seal}; this wrapper carries the dispatch
9976
+ * metadata hub needs to pick the right provider on the unseal path.
9977
+ *
9978
+ * Stability boundary: once shipped, the wire format only grows by
9979
+ * adding optional fields. See the at-* sealing dimension foundation
9980
+ * doc, §11.9.1.
9981
+ *
9982
+ * v1 shape (this release): `{ v: 1, _noydb_sealed: 1, pid, payload }`.
9983
+ *
9984
+ * Legacy shape (pre.14, pre.15): `{ _noydb_sealed: 1, providerId, sealed }`
9985
+ * — accepted on read for backwards compatibility; never produced on
9986
+ * write going forward.
9987
+ */
9988
+ interface SealedEnvelope {
9989
+ /** Envelope schema version. v1 is the shape shipped in pre.16. */
9990
+ readonly v: 1;
9991
+ /** Magic marker for forensics + legacy-shape detection. */
9992
+ readonly _noydb_sealed: 1;
9993
+ /** Matches the producing provider's `.id`. Dispatch key on unseal. */
9994
+ readonly pid: string;
9995
+ /** Sealed bytes from the provider, base64-encoded on the wire. */
9996
+ readonly payload: string;
9997
+ }
9998
+ /**
9999
+ * Parse a `_meta/sealed-passphrase` `_data` JSON string into the
10000
+ * in-memory {@link SealedPassphrase} representation. Accepts both:
10001
+ *
10002
+ * 1. v1 wire format `{ v: 1, _noydb_sealed: 1, pid, payload }` —
10003
+ * the shape produced from pre.16 onward.
10004
+ * 2. Legacy wire format `{ _noydb_sealed: 1, providerId, sealed }` —
10005
+ * the shape produced in pre.14/pre.15. Read-only; never written
10006
+ * going forward.
10007
+ *
10008
+ * Returns `undefined` for any input that doesn't match either shape,
10009
+ * so callers can fall back to "no managed-mode envelope present."
10010
+ *
10011
+ * @internal — exported only for the migration safety-net test suite.
10012
+ */
10013
+ declare function parseSealedEnvelope(raw: unknown): SealedPassphrase | undefined;
10014
+ declare function saveSealedPassphrase(store: NoydbStore, vault: string, payload: {
10015
+ readonly providerId: string;
10016
+ readonly sealed: Uint8Array;
10017
+ }): Promise<void>;
10018
+ declare function loadSealedPassphrase(store: NoydbStore, vault: string): Promise<SealedPassphrase | undefined>;
10019
+
9141
10020
  /**
9142
10021
  * Core types — the {@link NoydbStore} interface, envelope format, roles, and
9143
10022
  * all configuration shapes consumed by {@link createNoydb}.
@@ -10656,7 +11535,7 @@ interface NoydbOptions {
10656
11535
  * Multiple guards per collection are allowed; they are dispatched
10657
11536
  * in registration order on `collection.put()`.
10658
11537
  */
10659
- readonly guardStrategies?: ReadonlyArray<GuardStrategyHandle<any>>;
11538
+ readonly guardStrategies?: ReadonlyArray<GuardStrategyHandleAny>;
10660
11539
  /**
10661
11540
  * Optional derivation strategies — source-to-output projections that
10662
11541
  * fire on `collection.put()`. Each handle is the output of
@@ -10726,6 +11605,29 @@ interface NoydbOptions {
10726
11605
  * subsequent sessions.
10727
11606
  */
10728
11607
  readonly getKeyring?: (vault: string) => Promise<UnlockedKeyring>;
11608
+ /**
11609
+ * Passphrase mode (#14). Default `'standard'`.
11610
+ *
11611
+ * - `'standard'` — the legacy flow. `secret` supplies the
11612
+ * plaintext passphrase, the user knows it, and the policy gate
11613
+ * `rotate-passphrase` is enabled.
11614
+ * - `'managed'` — rubber-hose-resistant mode. Hub generates a
11615
+ * 256-bit random passphrase at first open and seals it under
11616
+ * the provided `sealingKey`. The user never sees or types the
11617
+ * passphrase, defeating the $5-wrench attack. Mutually
11618
+ * exclusive with `secret` and `getKeyring`.
11619
+ *
11620
+ * @see docs/subsystems/session-tiers.md → Managed-passphrase mode
11621
+ */
11622
+ readonly passphraseMode?: 'standard' | 'managed';
11623
+ /**
11624
+ * Provider that seals/unseals the auto-generated managed-mode
11625
+ * passphrase. Required when `passphraseMode === 'managed'`; ignored
11626
+ * otherwise. Implementations live in per-platform packages
11627
+ * (`@noy-db/seal-macos-keychain`, `@noy-db/seal-wincred`,
11628
+ * `@noy-db/seal-libsecret`, `@noy-db/seal-aws-kms`, …).
11629
+ */
11630
+ readonly sealingKey?: SealingKeyProvider;
10729
11631
  /** Auth method. Default: 'passphrase'. */
10730
11632
  readonly auth?: 'passphrase' | 'biometric';
10731
11633
  /** Enable encryption. Default: true. */
@@ -10929,4 +11831,4 @@ interface DeleteManyResult {
10929
11831
  }>;
10930
11832
  }
10931
11833
 
10932
- export { type ConsentAuditEntry as $, type BlobObject as A, type BlobStrategy as B, type BlobPutOptions as C, DICT_COLLECTION_PREFIX as D, type BlobResponseOptions as E, BlobSet as F, type BlobStrategyOpenArgs as G, type CompactRunOptions as H, type I18nStrategy as I, type CompactionContext as J, type CompactionResult as K, DEFAULT_CHUNK_SIZE as L, EXPORT_AUDIT_COLLECTION as M, ExportBlobsAbortedError as N, type ExportBlobsAuditEntry as O, PolicyEnforcer as P, type ExportBlobsHandle as Q, type ExportBlobsOptions as R, type SessionStrategy as S, type ExportedBlob as T, type SlotInfo as U, type SlotRecord as V, type VersionRecord as W, createExportBlobsHandle as X, runCompaction as Y, type ConsentStrategy as Z, CONSENT_AUDIT_COLLECTION as _, type DictEntry as a, applyPatch as a$, type ConsentAuditFilter as a0, type ConsentContext as a1, type ConsentOp as a2, loadConsentEntries as a3, writeConsentEntry as a4, type PeriodsStrategy as a5, type CarryForwardContext as a6, type ClosePeriodOptions as a7, type OpenPeriodOptions as a8, PERIODS_COLLECTION as a9, type DerivedFromMeta as aA, type OutputSpec as aB, type MaterializedViewStrategy as aC, type MaterializedViewStrategyHandle as aD, type OverlayedViewStrategy as aE, Collection as aF, OverlayedViewRegistry as aG, type OverlayedViewStrategyHandle as aH, type SyncStrategy as aI, type Role as aJ, type UnlockedKeyring as aK, type HistoryStrategy as aL, type NoydbStore as aM, type HistoryOptions as aN, type EncryptedEnvelope as aO, type PruneOptions as aP, type AppendInput as aQ, type ChangeType as aR, CollectionInstant as aS, type DiffEntry as aT, type JsonPatch as aU, type JsonPatchOp as aV, type LedgerEntry as aW, LedgerStore as aX, type VaultEngine as aY, VaultInstant as aZ, type VerifyResult as a_, type PeriodRecord as aa, type ReadOnlyCollection as ab, appendPeriodLedgerEntry as ac, assertTsWritable as ad, chainAnchor as ae, loadPeriods as af, validatePeriodName as ag, type GuardStrategy as ah, type GuardChange as ai, type GuardContext as aj, GuardRegistry as ak, type GuardStrategyHandle as al, ReadOnlyVaultFacade as am, type ShadowStrategy as an, CollectionFrame as ao, VaultFrame as ap, type TxStrategy as aq, type AmendmentTxOptions as ar, TxCollection as as, TxContext as at, TxVault as au, runTransaction as av, type DerivationStrategy as aw, type DerivationContext as ax, DerivationRegistry as ay, type DerivationStrategyHandle as az, type DictKeyDescriptor as b, type IssueMagicLinkGrantOptions as b$, canonicalJson as b0, computePatch as b1, diff as b2, formatDiff as b3, hashEntry as b4, paddedIndex as b5, parseIndex as b6, sha256Hex as b7, type MVQueryContext as b8, type RegisteredMV as b9, DEFAULT_PUBLIC_ENVELOPE_SCHEMA as bA, DELEGATIONS_COLLECTION as bB, type DeepPartial as bC, type DeepPartialOrNull as bD, type DelegationToken as bE, type DeleteManyResult as bF, type DirtyEntry as bG, ELEVATION_AUDIT_COLLECTION as bH, ElevatedHandle as bI, type EnrollAuthenticatorOptions as bJ, type EnrollAuthenticatorWrappingDEKsOptions as bK, type EnrollAuthenticatorWrappingKEKOptions as bL, type ExportCapability as bM, type ExportChunk as bN, type ExportFormat as bO, type ExportStreamOptions as bP, type FactorKind as bQ, type FactorProofBundle as bR, type FactorRequirement as bS, type GhostRecord as bT, type GrantOptions as bU, type HistoryConfig as bV, type HistoryEntry as bW, INDEXED_STORE_POLICY as bX, type ImportCapability as bY, type InferOutput as bZ, type IssueDelegationOptions as b_, MaterializedViewRegistry as ba, type MaterializedFromMeta as bb, type MaterializedViewOutput as bc, type UserEnvelope as bd, type PublicEnvelope as be, type GateName as bf, type GatePolicy as bg, type VaultPolicy as bh, type ActiveTier as bi, type FactorProof as bj, type DirectoryConfig as bk, type UserVisibility as bl, Vault as bm, type AccessibleVault as bn, BUNDLE_STORE_POLICY as bo, type BuiltInGateName as bp, type BundleRecipient as bq, type CacheOptions as br, type CacheStats as bs, type ChangeEvent as bt, type CollectionChangeEvent as bu, type CollectionConflictResolver as bv, type Conflict as bw, type ConflictPolicy as bx, type ConflictStrategy as by, type CrossTierAccessEvent as bz, DictionaryHandle as c, type SetPublicEnvelopeInput as c$, type KeyringAuthenticator as c0, type KeyringAuthenticatorWrappingDEKs as c1, type KeyringAuthenticatorWrappingKEK as c2, type KeyringFile as c3, type ListAccessibleVaultsOptions as c4, type ListPageResult as c5, type ListUsersOptions as c6, type LiveUserEnvelope as c7, type LocaleReadOptions as c8, Lru as c9, type PublicEnvelopeField as cA, type PublicEnvelopeSchema as cB, type PublicEnvelopeText as cC, type PullMode as cD, type PullOptions as cE, type PullPolicy as cF, type PullResult as cG, type PushMode as cH, type PushOptions as cI, type PushPolicy as cJ, type PushResult as cK, type PutManyItemOptions as cL, type PutManyOptions as cM, type PutManyResult as cN, type QueryAcrossOptions as cO, type QueryAcrossResult as cP, type QuickUnlockState as cQ, QuickUnlockStore as cR, type ReAuthOperation as cS, type RecoverPassphraseInput as cT, type RecoverPassphraseResult as cU, type RecoverUserOptions as cV, type RecoveryProof as cW, type ResolvedPublicEnvelopeSchema as cX, type RevokeOptions as cY, type RotatePassphraseInput as cZ, type SessionPolicy as c_, type LruOptions as ca, type LruStats as cb, MAGIC_LINK_CONTENT_INFO_PREFIX as cc, MAGIC_LINK_GRANTS_COLLECTION as cd, MAGIC_LINK_KEK_INFO_PREFIX as ce, type MagicLinkGrantPayload as cf, type MagicLinkGrantRecord as cg, NOYDB_BACKUP_VERSION as ch, NOYDB_FORMAT_VERSION as ci, NOYDB_KEYRING_VERSION as cj, NOYDB_SYNC_VERSION as ck, Noydb as cl, type NoydbBundleStore as cm, type NoydbEventMap as cn, type NoydbOptions as co, PUBLIC_ENVELOPE_FIELDS as cp, type PaperRecoveryDoc as cq, type PaperRecoveryEntry as cr, type PassphrasePolicy as cs, type PassphraseValidationResult as ct, type Permission as cu, type Permissions as cv, type PlaintextTranslatorContext as cw, type PlaintextTranslatorFn as cx, PresenceHandle as cy, type PresencePeer as cz, type DictionaryOptions as d, magicLinkGrantRecordId as d$, type SlotRewrapCeremony as d0, type SlotRewrapContext as d1, type StandardSchemaV1 as d2, type StandardSchemaV1Issue as d3, type StandardSchemaV1SyncResult as d4, type StoreAuth as d5, type StoreAuthKind as d6, type StoreCapabilities as d7, SyncEngine as d8, type SyncMetadata as d9, WeakPassphraseError as dA, type WeakPassphraseReason as dB, type WrappedDeksBlob as dC, assertStrongPassphrase as dD, buildRecipientKeyringFile as dE, burnPaperRecoveryEntry as dF, createNoydb as dG, createStore as dH, deriveMagicLinkContentKey as dI, enrollAuthenticator as dJ, estimateEntropy as dK, evaluateExportCapability as dL, evaluateImportCapability as dM, findAuthenticator as dN, hasExportCapability as dO, hasImportCapability as dP, hasRecoveryEnrolled as dQ, isMagicLinkGrantExpired as dR, isPublicEnvelope as dS, issueDelegation as dT, recoverPassphrase as dU, rotatePassphrase as dV, listMagicLinkGrants as dW, listUsers as dX, listUsersWithEnvelopes as dY, loadActiveDelegations as dZ, loadPaperRecoveryEntries as d_, type SyncPolicy as da, SyncScheduler as db, type SyncSchedulerStatus as dc, type SyncStatus as dd, type SyncTarget as de, type SyncTargetRole as df, SyncTransaction as dg, type SyncTransactionResult as dh, type TierMode as di, type TranslatorAuditEntry as dj, type TxOp as dk, USER_ENVELOPE_COLLECTION as dl, USER_ENVELOPE_MAX_BYTES as dm, type Unsubscribe as dn, type UpdateAuthenticatorOptions as dp, type UpdateUserOptions as dq, UserApi as dr, type UserEnvelopeCheckGate as ds, UserEnvelopeOversizedError as dt, type UserEnvelopePresented as du, type UserInfo as dv, type VaultBackup as dw, type VaultPolicyOnDisk as dx, type VaultSnapshot as dy, type WarningRules as dz, type I18nTextDescriptor as e, mintPaperRecoveryEntry as e0, mintWrappedDeksBlob as e1, readMagicLinkGrantRecord as e2, recoverUser as e3, removeAuthenticator as e4, resolveSchema as e5, revokeDelegation as e6, revokeMagicLinkGrant as e7, savePaperRecoveryEntries as e8, unwrapDeksFromBlob as e9, unwrapDeksFromPaperEntry as ea, unwrapMagicLinkGrant as eb, validatePassphrase as ec, validatePublicEnvelopeInput as ed, validateSchemaInput as ee, validateSchemaOutput as ef, writeMagicLinkGrant as eg, changeSecret as eh, createOwnerKeyring as ei, ensureCollectionDEK as ej, grant as ek, loadKeyring as el, persistKeyring as em, revoke as en, updateAuthenticator as eo, updateKeyringIdentity as ep, type I18nTextOptions as f, applyI18nLocale as g, dictCollectionName as h, dictKey as i, i18nText as j, isDictCollectionName as k, isDictKeyDescriptor as l, isI18nTextDescriptor as m, createEnforcer as n, validateSessionPolicy as o, BLOB_CHUNKS_COLLECTION as p, BLOB_COLLECTION as q, resolveI18nText as r, BLOB_EVICTION_AUDIT_COLLECTION as s, BLOB_INDEX_COLLECTION as t, BLOB_SLOTS_PREFIX as u, validateI18nTextValue as v, BLOB_VERSIONS_PREFIX as w, type BlobEvictionEntry as x, type BlobFieldPolicy as y, type BlobFieldsConfig as z };
11834
+ export { type ConsentAuditEntry as $, type BlobObject as A, type BlobStrategy as B, type BlobPutOptions as C, DICT_COLLECTION_PREFIX as D, type BlobResponseOptions as E, BlobSet as F, type BlobStrategyOpenArgs as G, type CompactRunOptions as H, type I18nStrategy as I, type CompactionContext as J, type CompactionResult as K, DEFAULT_CHUNK_SIZE as L, EXPORT_AUDIT_COLLECTION as M, ExportBlobsAbortedError as N, type ExportBlobsAuditEntry as O, PolicyEnforcer as P, type ExportBlobsHandle as Q, type ExportBlobsOptions as R, type SessionStrategy as S, type ExportedBlob as T, type SlotInfo as U, type SlotRecord as V, type VersionRecord as W, createExportBlobsHandle as X, runCompaction as Y, type ConsentStrategy as Z, CONSENT_AUDIT_COLLECTION as _, type DictEntry as a, VaultInstant as a$, type ConsentAuditFilter as a0, type ConsentContext as a1, type ConsentOp as a2, loadConsentEntries as a3, writeConsentEntry as a4, type PeriodsStrategy as a5, type CarryForwardContext as a6, type ClosePeriodOptions as a7, type OpenPeriodOptions as a8, PERIODS_COLLECTION as a9, type DerivationStrategyHandle as aA, type DerivedFromMeta as aB, type OutputSpec as aC, type RecordOutputSpec as aD, type MaterializedViewStrategy as aE, type MaterializedViewStrategyHandle as aF, type OverlayedViewStrategy as aG, Collection as aH, OverlayedViewRegistry as aI, type OverlayedViewStrategyHandle as aJ, type SyncStrategy as aK, type Role as aL, type UnlockedKeyring as aM, type HistoryStrategy as aN, type NoydbStore as aO, type HistoryOptions as aP, type EncryptedEnvelope as aQ, type PruneOptions as aR, type AppendInput as aS, type ChangeType as aT, CollectionInstant as aU, type DiffEntry as aV, type JsonPatch as aW, type JsonPatchOp as aX, type LedgerEntry as aY, LedgerStore as aZ, type VaultEngine as a_, type PeriodRecord as aa, type ReadOnlyCollection as ab, appendPeriodLedgerEntry as ac, assertTsWritable as ad, chainAnchor as ae, loadPeriods as af, validatePeriodName as ag, type GuardStrategy as ah, type GuardChange as ai, type GuardContext as aj, GuardRegistry as ak, type GuardStrategyHandle as al, ReadOnlyVaultFacade as am, type ShadowStrategy as an, CollectionFrame as ao, VaultFrame as ap, type TxStrategy as aq, type AmendmentTxOptions as ar, TxCollection as as, TxContext as at, TxVault as au, runTransaction as av, type DerivationStrategy as aw, type DerivationContext as ax, type ArrayOutputSpec as ay, DerivationRegistry as az, type DictKeyDescriptor as b, type FactorRequirement as b$, type VerifyResult as b0, applyPatch as b1, canonicalJson as b2, computePatch as b3, diff as b4, formatDiff as b5, hashEntry as b6, paddedIndex as b7, parseIndex as b8, sha256Hex as b9, type CollectionDescriptor as bA, type CollectionStats as bB, type Conflict as bC, type ConflictPolicy as bD, type ConflictStrategy as bE, type CrossTierAccessEvent as bF, DEFAULT_PUBLIC_ENVELOPE_SCHEMA as bG, DELEGATIONS_COLLECTION as bH, type DeepPartial as bI, type DeepPartialOrNull as bJ, type DelegationToken as bK, type DeleteManyResult as bL, type DerivationDescriptor as bM, type DirtyEntry as bN, type DumpSchemaOptions as bO, ELEVATION_AUDIT_COLLECTION as bP, ElevatedHandle as bQ, type EnrollAuthenticatorOptions as bR, type EnrollAuthenticatorWrappingDEKsOptions as bS, type EnrollAuthenticatorWrappingKEKOptions as bT, type EnrollRecoveryResult as bU, type ExportCapability as bV, type ExportChunk as bW, type ExportFormat as bX, type ExportStreamOptions as bY, type FactorKind as bZ, type FactorProofBundle as b_, type MVQueryContext as ba, type RegisteredMV as bb, MaterializedViewRegistry as bc, type MaterializedFromMeta as bd, type MaterializedViewOutput as be, type UnionSource as bf, type UserEnvelope as bg, type PublicEnvelope as bh, type GateName as bi, type GatePolicy as bj, type VaultPolicy as bk, type ActiveTier as bl, type FactorProof as bm, type PersistedSchemaEnvelope as bn, type DirectoryConfig as bo, type UserVisibility as bp, Vault as bq, type AccessibleVault as br, BUNDLE_STORE_POLICY as bs, type BuiltInGateName as bt, type BundleRecipient as bu, type CacheOptions as bv, type CacheStats as bw, type ChangeEvent as bx, type CollectionChangeEvent as by, type CollectionConflictResolver as bz, DictionaryHandle as c, type PutManyItemOptions as c$, type FieldDescriptor as c0, type FieldSource as c1, type GhostRecord as c2, type GrantOptions as c3, type HistoryConfig as c4, type HistoryEntry as c5, INDEXED_STORE_POLICY as c6, type ImportCapability as c7, type InferOutput as c8, type InternalCollectionStats as c9, type NoydbBundleStore as cA, type NoydbEventMap as cB, type NoydbOptions as cC, type OverlayViewDescriptor as cD, PUBLIC_ENVELOPE_FIELDS as cE, type PaperRecoveryDoc as cF, type PaperRecoveryEntry as cG, type PassphrasePolicy as cH, type PassphraseValidationResult as cI, type Permission as cJ, type Permissions as cK, type PersistedSchemaKind as cL, type PlaintextTranslatorContext as cM, type PlaintextTranslatorFn as cN, PresenceHandle as cO, type PresencePeer as cP, type PublicEnvelopeField as cQ, type PublicEnvelopeSchema as cR, type PublicEnvelopeText as cS, type PullMode as cT, type PullOptions as cU, type PullPolicy as cV, type PullResult as cW, type PushMode as cX, type PushOptions as cY, type PushPolicy as cZ, type PushResult as c_, type IssueDelegationOptions as ca, type IssueMagicLinkGrantOptions as cb, type KeyringAuthenticator as cc, type KeyringAuthenticatorWrappingDEKs as cd, type KeyringAuthenticatorWrappingKEK as ce, type KeyringFile as cf, type ListAccessibleVaultsOptions as cg, type ListPageResult as ch, type ListUsersOptions as ci, type LiveUserEnvelope as cj, type LocaleReadOptions as ck, Lru as cl, type LruOptions as cm, type LruStats as cn, MAGIC_LINK_CONTENT_INFO_PREFIX as co, MAGIC_LINK_GRANTS_COLLECTION as cp, MAGIC_LINK_KEK_INFO_PREFIX as cq, type MagicLinkGrantPayload as cr, type MagicLinkGrantRecord as cs, type MaterializedViewDescriptor as ct, MemorySealingKeyProvider as cu, NOYDB_BACKUP_VERSION as cv, NOYDB_FORMAT_VERSION as cw, NOYDB_KEYRING_VERSION as cx, NOYDB_SYNC_VERSION as cy, Noydb as cz, type DictionaryOptions as d, type WrappedDeksBlob as d$, type PutManyOptions as d0, type PutManyResult as d1, type QueryAcrossOptions as d2, type QueryAcrossResult as d3, type QuickUnlockState as d4, QuickUnlockStore as d5, type ReAuthOperation as d6, type RecoverPassphraseInput as d7, type RecoverPassphraseResult as d8, type RecoverUserOptions as d9, SyncScheduler as dA, type SyncSchedulerStatus as dB, type SyncStatus as dC, type SyncTarget as dD, type SyncTargetRole as dE, SyncTransaction as dF, type SyncTransactionResult as dG, type TierMode as dH, type TranslatorAuditEntry as dI, type TxOp as dJ, USER_ENVELOPE_COLLECTION as dK, USER_ENVELOPE_MAX_BYTES as dL, type Unsubscribe as dM, type UpdateAuthenticatorOptions as dN, type UpdateUserOptions as dO, UserApi as dP, type UserEnvelopeCheckGate as dQ, UserEnvelopeOversizedError as dR, type UserEnvelopePresented as dS, type UserInfo as dT, type VaultBackup as dU, type VaultPolicyOnDisk as dV, type VaultSchemaSnapshot as dW, type VaultSnapshot as dX, type WarningRules as dY, WeakPassphraseError as dZ, type WeakPassphraseReason as d_, type RecoveryProof as da, type ResolvedPublicEnvelopeSchema as db, type RevokeOptions as dc, type RotatePassphraseInput as dd, type RotateRecoveryOptions as de, type RotateRecoveryResult as df, SEALED_PASSPHRASE_RECORD_ID as dg, type SealedEnvelope as dh, type SealedPassphrase as di, type SealingKeyProvider as dj, type SessionPolicy as dk, type SetPublicEnvelopeInput as dl, type ShamirRecoveryDoc as dm, type ShamirRecoveryEntry as dn, type SlotRewrapCeremony as dp, type SlotRewrapContext as dq, type StandardSchemaV1 as dr, type StandardSchemaV1Issue as ds, type StandardSchemaV1SyncResult as dt, type StoreAuth as du, type StoreAuthKind as dv, type StoreCapabilities as dw, SyncEngine as dx, type SyncMetadata as dy, type SyncPolicy as dz, type I18nTextDescriptor as e, assertStrongPassphrase as e0, buildRecipientKeyringFile as e1, burnPaperRecoveryEntry as e2, createNoydb as e3, createStore as e4, deriveMagicLinkContentKey as e5, enrollAuthenticator as e6, estimateEntropy as e7, evaluateExportCapability as e8, evaluateImportCapability as e9, revokeMagicLinkGrant as eA, savePaperRecoveryEntries as eB, saveSealedPassphrase as eC, saveShamirRecoveryEntries as eD, unwrapDeksFromBlob as eE, unwrapDeksFromPaperEntry as eF, unwrapDeksFromShamirEntry as eG, unwrapMagicLinkGrant as eH, validatePassphrase as eI, validatePublicEnvelopeInput as eJ, validateSchemaInput as eK, validateSchemaOutput as eL, writeMagicLinkGrant as eM, changeSecret as eN, createOwnerKeyring as eO, ensureCollectionDEK as eP, grant as eQ, loadKeyring as eR, persistKeyring as eS, revoke as eT, updateAuthenticator as eU, updateKeyringIdentity as eV, findAuthenticator as ea, hasExportCapability as eb, hasImportCapability as ec, hasRecoveryEnrolled as ed, isMagicLinkGrantExpired as ee, isPublicEnvelope as ef, issueDelegation as eg, recoverPassphrase as eh, rotatePassphrase as ei, listMagicLinkGrants as ej, listUsers as ek, listUsersWithEnvelopes as el, loadActiveDelegations as em, loadPaperRecoveryEntries as en, loadSealedPassphrase as eo, loadShamirRecoveryEntries as ep, magicLinkGrantRecordId as eq, mintPaperRecoveryEntry as er, mintShamirRecoveryEntry as es, mintWrappedDeksBlob as et, parseSealedEnvelope as eu, readMagicLinkGrantRecord as ev, recoverUser as ew, removeAuthenticator as ex, resolveSchema as ey, revokeDelegation as ez, type I18nTextOptions as f, applyI18nLocale as g, dictCollectionName as h, dictKey as i, i18nText as j, isDictCollectionName as k, isDictKeyDescriptor as l, isI18nTextDescriptor as m, createEnforcer as n, validateSessionPolicy as o, BLOB_CHUNKS_COLLECTION as p, BLOB_COLLECTION as q, resolveI18nText as r, BLOB_EVICTION_AUDIT_COLLECTION as s, BLOB_INDEX_COLLECTION as t, BLOB_SLOTS_PREFIX as u, validateI18nTextValue as v, BLOB_VERSIONS_PREFIX as w, type BlobEvictionEntry as x, type BlobFieldPolicy as y, type BlobFieldsConfig as z };