@noy-db/hub 0.7.0-pre.13 → 0.7.0-pre.15

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 (233) hide show
  1. package/CHANGELOG.md +169 -0
  2. package/README.md +4 -3
  3. package/dist/{api-UE2XQKDN.js → api-JPZT6BDD.js} +1 -2
  4. package/dist/as/index.js +3 -4
  5. package/dist/as/index.js.map +1 -1
  6. package/dist/at/index.js +0 -1
  7. package/dist/attestation/index.js +6 -7
  8. package/dist/attestation/index.js.map +1 -1
  9. package/dist/{backup-R6A4VZRX.js → backup-AKWIMYWU.js} +1 -2
  10. package/dist/{backup-R6A4VZRX.js.map → backup-AKWIMYWU.js.map} +1 -1
  11. package/dist/blobs/index.js +0 -1
  12. package/dist/blobs/index.js.map +1 -1
  13. package/dist/broker/index.js +3 -4
  14. package/dist/broker/index.js.map +1 -1
  15. package/dist/by/index.js +0 -1
  16. package/dist/cargo/index.js +10 -11
  17. package/dist/{chunk-5FUULQ6F.js → chunk-2HVILGVR.js} +6 -6
  18. package/dist/{chunk-DJ3LNNZQ.js → chunk-3LK2XZCW.js} +2 -2
  19. package/dist/{chunk-PQKG5AHH.js → chunk-6X6SEMEE.js} +4 -2
  20. package/dist/{chunk-PQKG5AHH.js.map → chunk-6X6SEMEE.js.map} +1 -1
  21. package/dist/{chunk-E2MYJGSG.js → chunk-AMODH7AU.js} +2 -2
  22. package/dist/{chunk-B2ZSGYSF.js → chunk-B2KCRDJM.js} +3 -3
  23. package/dist/{chunk-TO3EQ7HF.js → chunk-BLN54W3L.js} +2 -2
  24. package/dist/{chunk-IHFXR2VQ.js → chunk-BOBFL5TW.js} +2 -2
  25. package/dist/{chunk-ZR2YMODS.js → chunk-EKZ6VZYF.js} +2 -2
  26. package/dist/{chunk-RTYJRFQO.js → chunk-EWPRPP3M.js} +1 -1
  27. package/dist/{chunk-VGICZPVE.js → chunk-FUJSIP4K.js} +2 -2
  28. package/dist/{chunk-BWXGSSFX.js → chunk-NJXX55QX.js} +79 -16
  29. package/dist/chunk-NJXX55QX.js.map +1 -0
  30. package/dist/{chunk-PKJ43CMD.js → chunk-NLOLF6LD.js} +2 -2
  31. package/dist/{chunk-COTPDJR5.js → chunk-OMB2ZKIT.js} +8 -8
  32. package/dist/{chunk-BMELYFHU.js → chunk-P3KOF624.js} +3 -3
  33. package/dist/{chunk-Y4MJQ2UG.js → chunk-QQ2YMQZU.js} +2 -2
  34. package/dist/{chunk-URHRIAV6.js → chunk-REDOOY2Q.js} +2 -2
  35. package/dist/chunk-REDOOY2Q.js.map +1 -0
  36. package/dist/{chunk-YCXICMOY.js → chunk-RJ7NN2UR.js} +2 -2
  37. package/dist/{chunk-PDD75NUZ.js → chunk-SMH6SRX7.js} +2 -2
  38. package/dist/{chunk-QHW35ZON.js → chunk-TGPST6FK.js} +2 -2
  39. package/dist/{chunk-BERJ4AWW.js → chunk-VGXZT72I.js} +2 -2
  40. package/dist/{chunk-ZVCC36XQ.js → chunk-WL36WA3O.js} +55 -55
  41. package/dist/{chunk-2Z47NMFK.js → chunk-WZW37ZHL.js} +2 -2
  42. package/dist/{chunk-SXCYYMVS.js → chunk-YINEDONN.js} +5 -5
  43. package/dist/{chunk-AJQY44CE.js → chunk-ZXLZ62R5.js} +3 -20
  44. package/dist/chunk-ZXLZ62R5.js.map +1 -0
  45. package/dist/classified/index.js +1 -2
  46. package/dist/{classified-marker-67HBIJMZ.js → classified-marker-OO65UYXN.js} +3 -5
  47. package/dist/{classified-marker-67HBIJMZ.js.map → classified-marker-OO65UYXN.js.map} +1 -1
  48. package/dist/{collection-facade-4ACKIDDY.js → collection-facade-VOWW732S.js} +3 -4
  49. package/dist/{computed-LSEZYRJS.js → computed-VPOKQK4N.js} +1 -2
  50. package/dist/consent/index.js +0 -1
  51. package/dist/consent/index.js.map +1 -1
  52. package/dist/cover/index.js +0 -1
  53. package/dist/crdt/index.js +0 -1
  54. package/dist/crdt/index.js.map +1 -1
  55. package/dist/custody/index.js +1 -2
  56. package/dist/{dead-filter-M3NZUWBZ.js → dead-filter-OMMVBAKZ.js} +2 -3
  57. package/dist/{dead-filter-M3NZUWBZ.js.map → dead-filter-OMMVBAKZ.js.map} +1 -1
  58. package/dist/debug/index.js +0 -1
  59. package/dist/debug/index.js.map +1 -1
  60. package/dist/{delegation-OLJUWTVA.js → delegation-VRYMQRCF.js} +1 -2
  61. package/dist/{delegation-OLJUWTVA.js.map → delegation-VRYMQRCF.js.map} +1 -1
  62. package/dist/derivations/index.js +3 -4
  63. package/dist/{derive-LY47HG7Q.js → derive-HHBRFS2U.js} +2 -3
  64. package/dist/directory/index.js +0 -1
  65. package/dist/{dispatch-MSTR5VBI.js → dispatch-DC3LQJ3S.js} +6 -7
  66. package/dist/{dispatch-MSTR5VBI.js.map → dispatch-DC3LQJ3S.js.map} +1 -1
  67. package/dist/{dispatch-PDP5TA73.js → dispatch-GGFBWQU7.js} +5 -7
  68. package/dist/{dispatch-PDP5TA73.js.map → dispatch-GGFBWQU7.js.map} +1 -1
  69. package/dist/{enclave-4YBQK77G.js → enclave-VM2YQONI.js} +1 -2
  70. package/dist/{executor-TOLPBHOC.js → executor-5NIMLSKZ.js} +1 -2
  71. package/dist/{executor-YDDC5XGY.js → executor-5XJNV5H5.js} +1 -2
  72. package/dist/{executor-MZJXBWNM.js → executor-AQVXV5GZ.js} +1 -2
  73. package/dist/{export-accessible-LGV6IAFX.js → export-accessible-2LOBM67T.js} +3 -4
  74. package/dist/{extract-partition-RYJZIKQZ.js → extract-partition-AZGSEMIF.js} +3 -4
  75. package/dist/{fanout-sidecar-TSFPXY5C.js → fanout-sidecar-HJIVUPTD.js} +1 -2
  76. package/dist/{fanout-sidecar-TSFPXY5C.js.map → fanout-sidecar-HJIVUPTD.js.map} +1 -1
  77. package/dist/{fence-watcher-ZVSMLKQD.js → fence-watcher-U2PW6JU5.js} +1 -3
  78. package/dist/{fence-watcher-ZVSMLKQD.js.map → fence-watcher-U2PW6JU5.js.map} +1 -1
  79. package/dist/{find-LC7P63MX.js → find-2ABFCZX4.js} +1 -2
  80. package/dist/forget/index.js +0 -1
  81. package/dist/forget/index.js.map +1 -1
  82. package/dist/guards/index.js +0 -1
  83. package/dist/history/index.js +0 -1
  84. package/dist/history/index.js.map +1 -1
  85. package/dist/i18n/index.js +0 -1
  86. package/dist/i18n/index.js.map +1 -1
  87. package/dist/index.js +26 -27
  88. package/dist/index.js.map +1 -1
  89. package/dist/indexing/index.js +0 -1
  90. package/dist/indexing/index.js.map +1 -1
  91. package/dist/introspection/index.js +2 -3
  92. package/dist/{issue-IOXM3RSJ.js → issue-RH4H733Z.js} +1 -2
  93. package/dist/{json-schema-I22JCYCG.js → json-schema-V3M5MUCM.js} +1 -3
  94. package/dist/{json-schema-I22JCYCG.js.map → json-schema-V3M5MUCM.js.map} +1 -1
  95. package/dist/kernel/collection-config.d.ts +34 -0
  96. package/dist/lazy/index.js +0 -1
  97. package/dist/{ledger-HXHIOD36.js → ledger-454SU3VE.js} +1 -2
  98. package/dist/{liberate-4APIV7FR.js → liberate-AAZSP2BT.js} +5 -6
  99. package/dist/{link-set-6RAHEKZN.js → link-set-67CAMUAL.js} +1 -2
  100. package/dist/{managed-secret-P3JQ7ZG4.js → managed-secret-NHRYG7CD.js} +1 -2
  101. package/dist/materialized-views/index.js +2 -3
  102. package/dist/{migrate-cek-25KNTS5U.js → migrate-cek-5ENH64AA.js} +1 -2
  103. package/dist/{migrate-cek-25KNTS5U.js.map → migrate-cek-5ENH64AA.js.map} +1 -1
  104. package/dist/money/index.js +0 -1
  105. package/dist/{noydb-2C6QQJ5S.js → noydb-F5CABCU4.js} +11 -12
  106. package/dist/overlay-views/index.js +0 -1
  107. package/dist/periods/index.js +1 -2
  108. package/dist/pod/index.js +12 -13
  109. package/dist/{pod-handle-6II7KFBR.js → pod-handle-S7AMCJIF.js} +2 -3
  110. package/dist/{pod-handle-6II7KFBR.js.map → pod-handle-S7AMCJIF.js.map} +1 -1
  111. package/dist/policy/index.js +0 -1
  112. package/dist/portability/index.js +6 -7
  113. package/dist/portability/index.js.map +1 -1
  114. package/dist/{post-register-BPJ36A6D.js → post-register-HIYY4Y5S.js} +4 -5
  115. package/dist/{post-register-BPJ36A6D.js.map → post-register-HIYY4Y5S.js.map} +1 -1
  116. package/dist/{purge-scope-FGNNLWHD.js → purge-scope-4HEFPDOW.js} +1 -3
  117. package/dist/{purge-scope-FGNNLWHD.js.map → purge-scope-4HEFPDOW.js.map} +1 -1
  118. package/dist/query/index.js +0 -1
  119. package/dist/{read-only-facade-CHCKSEPD.js → read-only-facade-BIGO37U3.js} +1 -2
  120. package/dist/reduce/index.js +0 -1
  121. package/dist/reduce/index.js.map +1 -1
  122. package/dist/{register-AEOOGKNO.js → register-7OJWAWT5.js} +3 -4
  123. package/dist/{registry-SKSOUXNF.js → registry-23WF3TGH.js} +1 -2
  124. package/dist/{registry-SHMGHEK4.js → registry-4AYVUNXK.js} +1 -2
  125. package/dist/{registry-I6FP4677.js → registry-5MVJGPXW.js} +1 -2
  126. package/dist/{registry-D2YXKRYC.js → registry-BOHIKFWG.js} +4 -5
  127. package/dist/{request-withdrawal-6HJT4ODF.js → request-withdrawal-P5EQATZ7.js} +5 -6
  128. package/dist/{reveal-DDQBDWPM.js → reveal-IMGQ2EE5.js} +1 -2
  129. package/dist/{reveal-DDQBDWPM.js.map → reveal-IMGQ2EE5.js.map} +1 -1
  130. package/dist/{revoke-U7U3FMXU.js → revoke-POHP4GPB.js} +1 -2
  131. package/dist/satellites/index.js +0 -1
  132. package/dist/schema-update/index.js +0 -1
  133. package/dist/sealed-record/index.js +3 -4
  134. package/dist/sealed-record/index.js.map +1 -1
  135. package/dist/search/index.js +1 -2
  136. package/dist/{seed-3RXT4VGN.js → seed-UYR6YG3V.js} +1 -2
  137. package/dist/{seed-3RXT4VGN.js.map → seed-UYR6YG3V.js.map} +1 -1
  138. package/dist/sequence/index.js +0 -1
  139. package/dist/session/index.js +0 -1
  140. package/dist/session/index.js.map +1 -1
  141. package/dist/shadow/index.js +0 -1
  142. package/dist/shadow/index.js.map +1 -1
  143. package/dist/share-link/index.js +0 -1
  144. package/dist/share-link/index.js.map +1 -1
  145. package/dist/{signer-7NK7TMWU.js → signer-5K3WDHOC.js} +1 -2
  146. package/dist/snapshots/index.js +1 -2
  147. package/dist/snapshots/index.js.map +1 -1
  148. package/dist/{stale-2CHKZTMK.js → stale-AU74OPUW.js} +2 -3
  149. package/dist/{storage-WP4M7IFO.js → storage-AVJA5BTW.js} +1 -2
  150. package/dist/store/index.js +0 -1
  151. package/dist/{store-coordination-provider-O2VUEMJH.js → store-coordination-provider-RX2CGWXV.js} +1 -2
  152. package/dist/{store-coordination-provider-O2VUEMJH.js.map → store-coordination-provider-RX2CGWXV.js.map} +1 -1
  153. package/dist/sync/index.js +0 -1
  154. package/dist/sync/index.js.map +1 -1
  155. package/dist/team/index.js +0 -1
  156. package/dist/tiers/index.js +1 -2
  157. package/dist/to/index.js +0 -1
  158. package/dist/to/index.js.map +1 -1
  159. package/dist/transactions/index.js +2 -3
  160. package/dist/transactions/index.js.map +1 -1
  161. package/dist/{ulid-VQW6ERQN.js → ulid-4MKXG325.js} +1 -2
  162. package/dist/util/index.js +0 -1
  163. package/dist/util/index.js.map +1 -1
  164. package/dist/{vault-diff-FE5J35NJ.js → vault-diff-C6WUJFNP.js} +1 -2
  165. package/dist/vault-head/index.js +0 -1
  166. package/dist/vault-head/index.js.map +1 -1
  167. package/dist/{verify-JBXCAGDL.js → verify-KOV2G5D6.js} +1 -2
  168. package/dist/{verify-JBXCAGDL.js.map → verify-KOV2G5D6.js.map} +1 -1
  169. package/dist/{walk-P3E6QSNC.js → walk-ERAJ5RHO.js} +3 -4
  170. package/dist/with-formula/derivations/registry.d.ts +6 -0
  171. package/dist/with-shape/introspection/describe.d.ts +2 -53
  172. package/dist/with-shape/introspection/field-meta.d.ts +86 -0
  173. package/dist/{withdraw-accessible-6Q7NE4EY.js → withdraw-accessible-42D32Z7G.js} +4 -5
  174. package/package.json +7 -3
  175. package/dist/chunk-AJQY44CE.js.map +0 -1
  176. package/dist/chunk-BWXGSSFX.js.map +0 -1
  177. package/dist/chunk-PZ5AY32C.js +0 -10
  178. package/dist/chunk-URHRIAV6.js.map +0 -1
  179. package/dist/withdraw-accessible-6Q7NE4EY.js.map +0 -1
  180. package/dist/zod-HAJM7LMS.js +0 -14763
  181. package/dist/zod-HAJM7LMS.js.map +0 -1
  182. /package/dist/{api-UE2XQKDN.js.map → api-JPZT6BDD.js.map} +0 -0
  183. /package/dist/{chunk-5FUULQ6F.js.map → chunk-2HVILGVR.js.map} +0 -0
  184. /package/dist/{chunk-DJ3LNNZQ.js.map → chunk-3LK2XZCW.js.map} +0 -0
  185. /package/dist/{chunk-E2MYJGSG.js.map → chunk-AMODH7AU.js.map} +0 -0
  186. /package/dist/{chunk-B2ZSGYSF.js.map → chunk-B2KCRDJM.js.map} +0 -0
  187. /package/dist/{chunk-TO3EQ7HF.js.map → chunk-BLN54W3L.js.map} +0 -0
  188. /package/dist/{chunk-IHFXR2VQ.js.map → chunk-BOBFL5TW.js.map} +0 -0
  189. /package/dist/{chunk-ZR2YMODS.js.map → chunk-EKZ6VZYF.js.map} +0 -0
  190. /package/dist/{chunk-RTYJRFQO.js.map → chunk-EWPRPP3M.js.map} +0 -0
  191. /package/dist/{chunk-VGICZPVE.js.map → chunk-FUJSIP4K.js.map} +0 -0
  192. /package/dist/{chunk-PKJ43CMD.js.map → chunk-NLOLF6LD.js.map} +0 -0
  193. /package/dist/{chunk-COTPDJR5.js.map → chunk-OMB2ZKIT.js.map} +0 -0
  194. /package/dist/{chunk-BMELYFHU.js.map → chunk-P3KOF624.js.map} +0 -0
  195. /package/dist/{chunk-Y4MJQ2UG.js.map → chunk-QQ2YMQZU.js.map} +0 -0
  196. /package/dist/{chunk-YCXICMOY.js.map → chunk-RJ7NN2UR.js.map} +0 -0
  197. /package/dist/{chunk-PDD75NUZ.js.map → chunk-SMH6SRX7.js.map} +0 -0
  198. /package/dist/{chunk-QHW35ZON.js.map → chunk-TGPST6FK.js.map} +0 -0
  199. /package/dist/{chunk-BERJ4AWW.js.map → chunk-VGXZT72I.js.map} +0 -0
  200. /package/dist/{chunk-ZVCC36XQ.js.map → chunk-WL36WA3O.js.map} +0 -0
  201. /package/dist/{chunk-2Z47NMFK.js.map → chunk-WZW37ZHL.js.map} +0 -0
  202. /package/dist/{chunk-SXCYYMVS.js.map → chunk-YINEDONN.js.map} +0 -0
  203. /package/dist/{chunk-PZ5AY32C.js.map → collection-facade-VOWW732S.js.map} +0 -0
  204. /package/dist/{collection-facade-4ACKIDDY.js.map → computed-VPOKQK4N.js.map} +0 -0
  205. /package/dist/{computed-LSEZYRJS.js.map → derive-HHBRFS2U.js.map} +0 -0
  206. /package/dist/{derive-LY47HG7Q.js.map → enclave-VM2YQONI.js.map} +0 -0
  207. /package/dist/{enclave-4YBQK77G.js.map → executor-5NIMLSKZ.js.map} +0 -0
  208. /package/dist/{executor-MZJXBWNM.js.map → executor-5XJNV5H5.js.map} +0 -0
  209. /package/dist/{executor-TOLPBHOC.js.map → executor-AQVXV5GZ.js.map} +0 -0
  210. /package/dist/{executor-YDDC5XGY.js.map → export-accessible-2LOBM67T.js.map} +0 -0
  211. /package/dist/{export-accessible-LGV6IAFX.js.map → extract-partition-AZGSEMIF.js.map} +0 -0
  212. /package/dist/{extract-partition-RYJZIKQZ.js.map → find-2ABFCZX4.js.map} +0 -0
  213. /package/dist/{find-LC7P63MX.js.map → issue-RH4H733Z.js.map} +0 -0
  214. /package/dist/{issue-IOXM3RSJ.js.map → ledger-454SU3VE.js.map} +0 -0
  215. /package/dist/{ledger-HXHIOD36.js.map → liberate-AAZSP2BT.js.map} +0 -0
  216. /package/dist/{liberate-4APIV7FR.js.map → link-set-67CAMUAL.js.map} +0 -0
  217. /package/dist/{link-set-6RAHEKZN.js.map → managed-secret-NHRYG7CD.js.map} +0 -0
  218. /package/dist/{managed-secret-P3JQ7ZG4.js.map → noydb-F5CABCU4.js.map} +0 -0
  219. /package/dist/{noydb-2C6QQJ5S.js.map → read-only-facade-BIGO37U3.js.map} +0 -0
  220. /package/dist/{read-only-facade-CHCKSEPD.js.map → register-7OJWAWT5.js.map} +0 -0
  221. /package/dist/{register-AEOOGKNO.js.map → registry-23WF3TGH.js.map} +0 -0
  222. /package/dist/{registry-D2YXKRYC.js.map → registry-4AYVUNXK.js.map} +0 -0
  223. /package/dist/{registry-I6FP4677.js.map → registry-5MVJGPXW.js.map} +0 -0
  224. /package/dist/{registry-SHMGHEK4.js.map → registry-BOHIKFWG.js.map} +0 -0
  225. /package/dist/{registry-SKSOUXNF.js.map → request-withdrawal-P5EQATZ7.js.map} +0 -0
  226. /package/dist/{request-withdrawal-6HJT4ODF.js.map → revoke-POHP4GPB.js.map} +0 -0
  227. /package/dist/{revoke-U7U3FMXU.js.map → signer-5K3WDHOC.js.map} +0 -0
  228. /package/dist/{signer-7NK7TMWU.js.map → stale-AU74OPUW.js.map} +0 -0
  229. /package/dist/{stale-2CHKZTMK.js.map → storage-AVJA5BTW.js.map} +0 -0
  230. /package/dist/{storage-WP4M7IFO.js.map → ulid-4MKXG325.js.map} +0 -0
  231. /package/dist/{ulid-VQW6ERQN.js.map → vault-diff-C6WUJFNP.js.map} +0 -0
  232. /package/dist/{vault-diff-FE5J35NJ.js.map → walk-ERAJ5RHO.js.map} +0 -0
  233. /package/dist/{walk-P3E6QSNC.js.map → withdraw-accessible-42D32Z7G.js.map} +0 -0
@@ -11,9 +11,8 @@ import "./chunk-LP7BEXCT.js";
11
11
  import "./chunk-SL3JAAKN.js";
12
12
  import "./chunk-N4CHCXDL.js";
13
13
  import "./chunk-7O3Y4PPD.js";
14
- import "./chunk-PZ5AY32C.js";
15
14
  export {
16
15
  MaterializedViewRegistry,
17
16
  wrapDbWithPredicates
18
17
  };
19
- //# sourceMappingURL=registry-I6FP4677.js.map
18
+ //# sourceMappingURL=registry-5MVJGPXW.js.map
@@ -1,13 +1,13 @@
1
1
  import {
2
2
  DerivationRegistry
3
- } from "./chunk-AJQY44CE.js";
3
+ } from "./chunk-ZXLZ62R5.js";
4
4
  import "./chunk-TPHAGLUH.js";
5
- import "./chunk-BWXGSSFX.js";
5
+ import "./chunk-NJXX55QX.js";
6
6
  import "./chunk-TFMM5DBB.js";
7
7
  import "./chunk-RTBJ5IOO.js";
8
8
  import "./chunk-KCUIMITG.js";
9
9
  import "./chunk-VYYM3MEN.js";
10
- import "./chunk-URHRIAV6.js";
10
+ import "./chunk-REDOOY2Q.js";
11
11
  import "./chunk-Y2NBBT6K.js";
12
12
  import "./chunk-WWNYSV7S.js";
13
13
  import "./chunk-YPXNAP72.js";
@@ -26,8 +26,7 @@ import "./chunk-LP7BEXCT.js";
26
26
  import "./chunk-SL3JAAKN.js";
27
27
  import "./chunk-N4CHCXDL.js";
28
28
  import "./chunk-7O3Y4PPD.js";
29
- import "./chunk-PZ5AY32C.js";
30
29
  export {
31
30
  DerivationRegistry
32
31
  };
33
- //# sourceMappingURL=registry-D2YXKRYC.js.map
32
+ //# sourceMappingURL=registry-BOHIKFWG.js.map
@@ -5,10 +5,10 @@ import {
5
5
  listWithdrawalRequests,
6
6
  rejectWithdrawal,
7
7
  requestWithdrawal
8
- } from "./chunk-B2ZSGYSF.js";
9
- import "./chunk-QHW35ZON.js";
10
- import "./chunk-ZR2YMODS.js";
11
- import "./chunk-PKJ43CMD.js";
8
+ } from "./chunk-B2KCRDJM.js";
9
+ import "./chunk-TGPST6FK.js";
10
+ import "./chunk-EKZ6VZYF.js";
11
+ import "./chunk-NLOLF6LD.js";
12
12
  import "./chunk-MLO75SFJ.js";
13
13
  import "./chunk-CIGAQBJR.js";
14
14
  import "./chunk-4RUFYGWG.js";
@@ -19,7 +19,6 @@ import "./chunk-LP7BEXCT.js";
19
19
  import "./chunk-SL3JAAKN.js";
20
20
  import "./chunk-N4CHCXDL.js";
21
21
  import "./chunk-7O3Y4PPD.js";
22
- import "./chunk-PZ5AY32C.js";
23
22
  export {
24
23
  WITHDRAWAL_REQUESTS_COLLECTION,
25
24
  WithdrawalRequestError,
@@ -28,4 +27,4 @@ export {
28
27
  rejectWithdrawal,
29
28
  requestWithdrawal
30
29
  };
31
- //# sourceMappingURL=request-withdrawal-6HJT4ODF.js.map
30
+ //# sourceMappingURL=request-withdrawal-P5EQATZ7.js.map
@@ -9,7 +9,6 @@ import "./chunk-N4CHCXDL.js";
9
9
  import {
10
10
  ClassifiedRevealError
11
11
  } from "./chunk-7O3Y4PPD.js";
12
- import "./chunk-PZ5AY32C.js";
13
12
 
14
13
  // src/kernel/enclave/classify/reveal.ts
15
14
  async function revealSealedField(ctx, id, field) {
@@ -35,4 +34,4 @@ async function revealSealedField(ctx, id, field) {
35
34
  export {
36
35
  revealSealedField
37
36
  };
38
- //# sourceMappingURL=reveal-DDQBDWPM.js.map
37
+ //# sourceMappingURL=reveal-IMGQ2EE5.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/kernel/enclave/classify/reveal.ts"],"sourcesContent":["/**\n * Single-point audited reveal, stage-2 rework (I6): decrypt ONE sealed slot\n * from the raw envelope — no full record view, no collection.get(), so no\n * spurious 'get' consent entry. Fail-closed gates (b)/(c) live here; gate\n * (a) (storage:'never') fires in collection.reveal before any strategy call.\n * @module\n */\nimport { dualReadSealedSlot } from '../record-keys/sealed-slot.js'\nimport { isTombstone } from '../record-keys/tombstone.js'\nimport { ClassifiedRevealError } from '../../errors.js'\nimport type { EncryptedEnvelope } from '../../types.js'\nimport type { EnclaveKey } from '../crypto.js'\n\nexport interface RevealEngineCtx {\n readonly collection: string\n readonly encrypted: boolean\n getEnvelope(id: string): Promise<EncryptedEnvelope | null>\n resolveCek(env: EncryptedEnvelope): Promise<EnclaveKey | undefined>\n getDEK(): Promise<EnclaveKey>\n}\n\nexport async function revealSealedField(ctx: RevealEngineCtx, id: string, field: string): Promise<unknown> {\n const env = await ctx.getEnvelope(id)\n if (env === null || isTombstone(env, ctx.encrypted) || (env._tier ?? 0) > 0) {\n throw new ClassifiedRevealError(ctx.collection, field, `record \"${id}\" not found in \"${ctx.collection}\"`)\n }\n if (!ctx.encrypted) {\n // Plaintext collection: nothing is sealed; the value sits in the open body.\n const record = JSON.parse(env._data || '{}') as Record<string, unknown>\n if (!(field in record)) {\n throw new ClassifiedRevealError(ctx.collection, field, `no stored value for \"${field}\"`)\n }\n return record[field]\n }\n const blob = env._sealed?.[field]\n if (blob === undefined) {\n // Gate (c): fail-closed error, never parseSealedSlot(undefined)'s TypeError.\n throw new ClassifiedRevealError(ctx.collection, field, `no sealed value stored for \"${field}\"`)\n }\n const cek = await ctx.resolveCek(env)\n const dek = await ctx.getDEK()\n return JSON.parse(await dualReadSealedSlot(blob, field, ctx.collection, cek, dek))\n}\n"],"mappings":";;;;;;;;;;;;;;AAqBA,eAAsB,kBAAkB,KAAsB,IAAY,OAAiC;AACzG,QAAM,MAAM,MAAM,IAAI,YAAY,EAAE;AACpC,MAAI,QAAQ,QAAQ,YAAY,KAAK,IAAI,SAAS,MAAM,IAAI,SAAS,KAAK,GAAG;AAC3E,UAAM,IAAI,sBAAsB,IAAI,YAAY,OAAO,WAAW,EAAE,mBAAmB,IAAI,UAAU,GAAG;AAAA,EAC1G;AACA,MAAI,CAAC,IAAI,WAAW;AAElB,UAAM,SAAS,KAAK,MAAM,IAAI,SAAS,IAAI;AAC3C,QAAI,EAAE,SAAS,SAAS;AACtB,YAAM,IAAI,sBAAsB,IAAI,YAAY,OAAO,wBAAwB,KAAK,GAAG;AAAA,IACzF;AACA,WAAO,OAAO,KAAK;AAAA,EACrB;AACA,QAAM,OAAO,IAAI,UAAU,KAAK;AAChC,MAAI,SAAS,QAAW;AAEtB,UAAM,IAAI,sBAAsB,IAAI,YAAY,OAAO,+BAA+B,KAAK,GAAG;AAAA,EAChG;AACA,QAAM,MAAM,MAAM,IAAI,WAAW,GAAG;AACpC,QAAM,MAAM,MAAM,IAAI,OAAO;AAC7B,SAAO,KAAK,MAAM,MAAM,mBAAmB,MAAM,OAAO,IAAI,YAAY,KAAK,GAAG,CAAC;AACnF;","names":[]}
1
+ {"version":3,"sources":["../src/kernel/enclave/classify/reveal.ts"],"sourcesContent":["/**\n * Single-point audited reveal, stage-2 rework (I6): decrypt ONE sealed slot\n * from the raw envelope — no full record view, no collection.get(), so no\n * spurious 'get' consent entry. Fail-closed gates (b)/(c) live here; gate\n * (a) (storage:'never') fires in collection.reveal before any strategy call.\n * @module\n */\nimport { dualReadSealedSlot } from '../record-keys/sealed-slot.js'\nimport { isTombstone } from '../record-keys/tombstone.js'\nimport { ClassifiedRevealError } from '../../errors.js'\nimport type { EncryptedEnvelope } from '../../types.js'\nimport type { EnclaveKey } from '../crypto.js'\n\nexport interface RevealEngineCtx {\n readonly collection: string\n readonly encrypted: boolean\n getEnvelope(id: string): Promise<EncryptedEnvelope | null>\n resolveCek(env: EncryptedEnvelope): Promise<EnclaveKey | undefined>\n getDEK(): Promise<EnclaveKey>\n}\n\nexport async function revealSealedField(ctx: RevealEngineCtx, id: string, field: string): Promise<unknown> {\n const env = await ctx.getEnvelope(id)\n if (env === null || isTombstone(env, ctx.encrypted) || (env._tier ?? 0) > 0) {\n throw new ClassifiedRevealError(ctx.collection, field, `record \"${id}\" not found in \"${ctx.collection}\"`)\n }\n if (!ctx.encrypted) {\n // Plaintext collection: nothing is sealed; the value sits in the open body.\n const record = JSON.parse(env._data || '{}') as Record<string, unknown>\n if (!(field in record)) {\n throw new ClassifiedRevealError(ctx.collection, field, `no stored value for \"${field}\"`)\n }\n return record[field]\n }\n const blob = env._sealed?.[field]\n if (blob === undefined) {\n // Gate (c): fail-closed error, never parseSealedSlot(undefined)'s TypeError.\n throw new ClassifiedRevealError(ctx.collection, field, `no sealed value stored for \"${field}\"`)\n }\n const cek = await ctx.resolveCek(env)\n const dek = await ctx.getDEK()\n return JSON.parse(await dualReadSealedSlot(blob, field, ctx.collection, cek, dek))\n}\n"],"mappings":";;;;;;;;;;;;;AAqBA,eAAsB,kBAAkB,KAAsB,IAAY,OAAiC;AACzG,QAAM,MAAM,MAAM,IAAI,YAAY,EAAE;AACpC,MAAI,QAAQ,QAAQ,YAAY,KAAK,IAAI,SAAS,MAAM,IAAI,SAAS,KAAK,GAAG;AAC3E,UAAM,IAAI,sBAAsB,IAAI,YAAY,OAAO,WAAW,EAAE,mBAAmB,IAAI,UAAU,GAAG;AAAA,EAC1G;AACA,MAAI,CAAC,IAAI,WAAW;AAElB,UAAM,SAAS,KAAK,MAAM,IAAI,SAAS,IAAI;AAC3C,QAAI,EAAE,SAAS,SAAS;AACtB,YAAM,IAAI,sBAAsB,IAAI,YAAY,OAAO,wBAAwB,KAAK,GAAG;AAAA,IACzF;AACA,WAAO,OAAO,KAAK;AAAA,EACrB;AACA,QAAM,OAAO,IAAI,UAAU,KAAK;AAChC,MAAI,SAAS,QAAW;AAEtB,UAAM,IAAI,sBAAsB,IAAI,YAAY,OAAO,+BAA+B,KAAK,GAAG;AAAA,EAChG;AACA,QAAM,MAAM,MAAM,IAAI,WAAW,GAAG;AACpC,QAAM,MAAM,MAAM,IAAI,OAAO;AAC7B,SAAO,KAAK,MAAM,MAAM,mBAAmB,MAAM,OAAO,IAAI,YAAY,KAAK,GAAG,CAAC;AACnF;","names":[]}
@@ -14,11 +14,10 @@ import "./chunk-LP7BEXCT.js";
14
14
  import "./chunk-SL3JAAKN.js";
15
15
  import "./chunk-N4CHCXDL.js";
16
16
  import "./chunk-7O3Y4PPD.js";
17
- import "./chunk-PZ5AY32C.js";
18
17
  export {
19
18
  getRevokedDocIdsCore,
20
19
  publishRevocationListCore,
21
20
  revokeDocCore,
22
21
  unrevokeDocCore
23
22
  };
24
- //# sourceMappingURL=revoke-U7U3FMXU.js.map
23
+ //# sourceMappingURL=revoke-POHP4GPB.js.map
@@ -1,7 +1,6 @@
1
1
  import {
2
2
  SatelliteConfigError
3
3
  } from "../chunk-7O3Y4PPD.js";
4
- import "../chunk-PZ5AY32C.js";
5
4
  export {
6
5
  SatelliteConfigError
7
6
  };
@@ -13,7 +13,6 @@ import {
13
13
  } from "../chunk-6LJY7X5A.js";
14
14
  import "../chunk-Y2NBBT6K.js";
15
15
  import "../chunk-7O3Y4PPD.js";
16
- import "../chunk-PZ5AY32C.js";
17
16
  export {
18
17
  SchemaUpdateGate,
19
18
  additiveOnly,
@@ -20,21 +20,20 @@ import {
20
20
  SealedRecordMismatchError,
21
21
  SealedRecordNotEnabledError
22
22
  } from "../chunk-7O3Y4PPD.js";
23
- import "../chunk-PZ5AY32C.js";
24
23
 
25
24
  // src/with-audit/sealed-record/active.ts
26
25
  function withSealedRecord() {
27
26
  return {
28
27
  async sealRecordToHost(ctx, collection, id, hostSealer, opts) {
29
- const { sealRecordToHost } = await import("../enclave-4YBQK77G.js");
28
+ const { sealRecordToHost } = await import("../enclave-VM2YQONI.js");
30
29
  return sealRecordToHost(ctx, collection, id, hostSealer, opts);
31
30
  },
32
31
  async revokeSealedRecord(ctx, collection, id, pid, opts) {
33
- const { revokeSealedRecord } = await import("../enclave-4YBQK77G.js");
32
+ const { revokeSealedRecord } = await import("../enclave-VM2YQONI.js");
34
33
  return revokeSealedRecord(ctx, collection, id, pid, opts);
35
34
  },
36
35
  async rotateRecordCek(ctx, collection, id) {
37
- const { rotateRecordCek } = await import("../enclave-4YBQK77G.js");
36
+ const { rotateRecordCek } = await import("../enclave-VM2YQONI.js");
38
37
  return rotateRecordCek(ctx, collection, id);
39
38
  }
40
39
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/with-audit/sealed-record/active.ts","../../src/with-audit/sealed-record/index.ts"],"sourcesContent":["/**\n * Enable the sealed-record (grantor-side) capability.\n * Pass to `createNoydb({ sealedRecordStrategy: withSealedRecord() })` to make a\n * vault's `sealRecordToHost` / `revokeSealedRecord` / `rotateRecordCek` methods\n * live. The `record-keys` grantor engine is dynamically imported here, so it is\n * reached only via opt-in.\n */\nimport type { SealedRecordStrategy } from './strategy.js'\n\nexport function withSealedRecord(): SealedRecordStrategy {\n return {\n async sealRecordToHost(ctx, collection, id, hostSealer, opts) {\n const { sealRecordToHost } = await import('../../kernel/enclave/index.js')\n return sealRecordToHost(ctx, collection, id, hostSealer, opts)\n },\n async revokeSealedRecord(ctx, collection, id, pid, opts) {\n const { revokeSealedRecord } = await import('../../kernel/enclave/index.js')\n return revokeSealedRecord(ctx, collection, id, pid, opts)\n },\n async rotateRecordCek(ctx, collection, id) {\n const { rotateRecordCek } = await import('../../kernel/enclave/index.js')\n return rotateRecordCek(ctx, collection, id)\n },\n }\n}\n","/**\n * `@noy-db/hub/sealed-record` — host-side opener for record-scoped CEK\n * sealing.\n *\n * The **grantor** side (sealing a record's CEK to a host, revoking, and hard\n * rotation) lives on `Vault` (`vault.sealRecordToHost` /\n * `vault.revokeSealedRecord` / `vault.rotateRecordCek`) because it needs the\n * collection DEK. This subpath exposes the **recipient host** side:\n * {@link openSealedRecord} reconstructs a single record's plaintext from\n * (a) the sealed-CEK delivery envelope, (b) the record's encrypted envelope\n * body, and (c) the host's own unsealer — and **nothing else**. The host never\n * touches a vault DEK and can decrypt exactly the one bound record.\n *\n * @module\n */\n\nimport { recordAadFor, decrypt, base64ToBuffer, importCek } from '../../kernel/enclave/index.js'\nimport {\n SealedRecordExpiredError,\n SealedRecordMismatchError,\n} from '../../kernel/errors.js'\nimport type {\n SealedCekDeliveryEnvelope,\n SealedCekBinding,\n} from './types.js'\n\nexport type { SealedCekDeliveryEnvelope, SealedCekBinding } from './types.js'\nexport { SealedRecordExpiredError, SealedRecordMismatchError } from '../../kernel/errors.js'\nexport { withSealedRecord } from './active.js'\nexport { NO_SEALED_RECORD, type SealedRecordStrategy } from './strategy.js'\nexport { SealedRecordNotEnabledError } from '../../kernel/errors.js'\n\n/**\n * Host-side: decrypt a single record whose CEK was sealed to this host by a\n * vault grantor via `vault.sealRecordToHost()`.\n *\n * Verification order (security-critical — do NOT reorder past the unseal):\n * 1. **Fast-path expiry** — reject on the delivery envelope's clear-text\n * `expiresAt` before doing any (potentially expensive) unseal work. This is\n * a hint only.\n * 2. **Unseal** the `payload` with the host's `recipientSealer.unseal` →\n * parse the {@link SealedCekBinding}.\n * 3. **Binding match** — the binding's `{collection, id}` MUST equal the\n * expected pair, else {@link SealedRecordMismatchError}. This is the\n * host-denial boundary (a CEK sealed for record A cannot open record B).\n * 4. **Authoritative expiry** — re-check `binding.expiresAt` (the copy that\n * cannot be forged by editing the delivery envelope), else\n * {@link SealedRecordExpiredError}.\n * 5. **Decrypt** the record body under the unsealed CEK. A CEK from a\n * PRE-rotation seal applied to a POST-rotation live envelope passes (1)-(4)\n * but fails the AES-GCM auth tag → `TamperedError` from `decrypt`.\n *\n * @param sealedCekEnvelope The `SealedCekDeliveryEnvelope` written by the grantor.\n * @param recordEnvelope The record's `{ _iv, _data }` (the encrypted body).\n * @param recipientSealer The host's unsealer (`{ unseal(bytes) }`) — e.g. a\n * KMS-backed sealer or `MemoryRecipientSealer`.\n * @param expectedCollection Collection the caller believes `recordEnvelope` is in.\n * @param expectedId Record id the caller believes `recordEnvelope` is.\n * @returns The decrypted record body as a JSON string.\n *\n * ⚠️ **`recordEnvelope` must carry `_tier` and `_by`, not just the body**\n * (#1041). They are plaintext metadata rather than secrets, but they are bound\n * into the record's AAD, so a host handed only `{_iv, _data}` cannot\n * reconstruct the identity the body was sealed against.\n *\n * That is a feature, not a tax: it means a host cannot be fed a body with its\n * tags stripped or rewritten — a re-tiered or re-authored envelope no longer\n * opens, even with a valid sealed CEK.\n */\nexport async function openSealedRecord(\n sealedCekEnvelope: SealedCekDeliveryEnvelope,\n recordEnvelope: {\n readonly _iv: string\n readonly _data: string\n /** Bound into the AAD since #1093 — a rewound body no longer opens. */\n readonly _v: number\n readonly _tier?: number | undefined\n readonly _by?: string | undefined\n },\n recipientSealer: { unseal(bytes: Uint8Array): Promise<Uint8Array> },\n expectedCollection: string,\n expectedId: string,\n): Promise<string> {\n // (1) Fast-path expiry on the (unauthenticated) delivery envelope. Fail\n // CLOSED (M-4): a malformed/empty `expiresAt` parses to NaN — treat it as\n // expired rather than letting `NaN <= now` (which is `false`) skip the check.\n const fastT = Date.parse(sealedCekEnvelope.expiresAt)\n if (!Number.isFinite(fastT) || fastT <= Date.now()) {\n throw new SealedRecordExpiredError(sealedCekEnvelope.expiresAt)\n }\n\n // (2) Unseal → parse binding.\n const payloadBytes = base64ToBuffer(sealedCekEnvelope.payload)\n const openedBytes = await recipientSealer.unseal(payloadBytes)\n const binding = JSON.parse(new TextDecoder().decode(openedBytes)) as SealedCekBinding\n\n // (3) Binding match — host-denial boundary.\n if (binding.collection !== expectedCollection || binding.id !== expectedId) {\n throw new SealedRecordMismatchError(\n { collection: expectedCollection, id: expectedId },\n { collection: binding.collection, id: binding.id },\n )\n }\n\n // (4) Authoritative expiry — the copy that cannot be forged on the envelope.\n // Fail CLOSED (M-4): a malformed/empty `expiresAt` → NaN → treat as expired.\n const t = Date.parse(binding.expiresAt)\n if (!Number.isFinite(t) || t <= Date.now()) {\n throw new SealedRecordExpiredError(binding.expiresAt)\n }\n\n // (5) Import the raw CEK + decrypt the body. Wrong-key (pre-rotation CEK vs\n // post-rotation body) surfaces as TamperedError from decrypt's GCM tag check.\n const cek = await importCek(base64ToBuffer(binding.cek))\n // AAD from the binding's OWN collection/id — which step (3) has just proven\n // match the caller's expectation. So a host cannot be handed record A's\n // sealed CEK alongside record B's body and open it (#1041): the AAD would\n // describe A while the ciphertext was sealed for B.\n return decrypt(\n recordEnvelope._iv,\n recordEnvelope._data,\n cek,\n recordAadFor({ collection: expectedCollection, id: expectedId }, recordEnvelope),\n )\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AASO,SAAS,mBAAyC;AACvD,SAAO;AAAA,IACL,MAAM,iBAAiB,KAAK,YAAY,IAAI,YAAY,MAAM;AAC5D,YAAM,EAAE,iBAAiB,IAAI,MAAM,OAAO,wBAA+B;AACzE,aAAO,iBAAiB,KAAK,YAAY,IAAI,YAAY,IAAI;AAAA,IAC/D;AAAA,IACA,MAAM,mBAAmB,KAAK,YAAY,IAAI,KAAK,MAAM;AACvD,YAAM,EAAE,mBAAmB,IAAI,MAAM,OAAO,wBAA+B;AAC3E,aAAO,mBAAmB,KAAK,YAAY,IAAI,KAAK,IAAI;AAAA,IAC1D;AAAA,IACA,MAAM,gBAAgB,KAAK,YAAY,IAAI;AACzC,YAAM,EAAE,gBAAgB,IAAI,MAAM,OAAO,wBAA+B;AACxE,aAAO,gBAAgB,KAAK,YAAY,EAAE;AAAA,IAC5C;AAAA,EACF;AACF;;;AC6CA,eAAsB,iBACpB,mBACA,gBAQA,iBACA,oBACA,YACiB;AAIjB,QAAM,QAAQ,KAAK,MAAM,kBAAkB,SAAS;AACpD,MAAI,CAAC,OAAO,SAAS,KAAK,KAAK,SAAS,KAAK,IAAI,GAAG;AAClD,UAAM,IAAI,yBAAyB,kBAAkB,SAAS;AAAA,EAChE;AAGA,QAAM,eAAe,eAAe,kBAAkB,OAAO;AAC7D,QAAM,cAAc,MAAM,gBAAgB,OAAO,YAAY;AAC7D,QAAM,UAAU,KAAK,MAAM,IAAI,YAAY,EAAE,OAAO,WAAW,CAAC;AAGhE,MAAI,QAAQ,eAAe,sBAAsB,QAAQ,OAAO,YAAY;AAC1E,UAAM,IAAI;AAAA,MACR,EAAE,YAAY,oBAAoB,IAAI,WAAW;AAAA,MACjD,EAAE,YAAY,QAAQ,YAAY,IAAI,QAAQ,GAAG;AAAA,IACnD;AAAA,EACF;AAIA,QAAM,IAAI,KAAK,MAAM,QAAQ,SAAS;AACtC,MAAI,CAAC,OAAO,SAAS,CAAC,KAAK,KAAK,KAAK,IAAI,GAAG;AAC1C,UAAM,IAAI,yBAAyB,QAAQ,SAAS;AAAA,EACtD;AAIA,QAAM,MAAM,MAAM,UAAU,eAAe,QAAQ,GAAG,CAAC;AAKvD,SAAO;AAAA,IACL,eAAe;AAAA,IACf,eAAe;AAAA,IACf;AAAA,IACA,aAAa,EAAE,YAAY,oBAAoB,IAAI,WAAW,GAAG,cAAc;AAAA,EACjF;AACF;","names":[]}
1
+ {"version":3,"sources":["../../src/with-audit/sealed-record/active.ts","../../src/with-audit/sealed-record/index.ts"],"sourcesContent":["/**\n * Enable the sealed-record (grantor-side) capability.\n * Pass to `createNoydb({ sealedRecordStrategy: withSealedRecord() })` to make a\n * vault's `sealRecordToHost` / `revokeSealedRecord` / `rotateRecordCek` methods\n * live. The `record-keys` grantor engine is dynamically imported here, so it is\n * reached only via opt-in.\n */\nimport type { SealedRecordStrategy } from './strategy.js'\n\nexport function withSealedRecord(): SealedRecordStrategy {\n return {\n async sealRecordToHost(ctx, collection, id, hostSealer, opts) {\n const { sealRecordToHost } = await import('../../kernel/enclave/index.js')\n return sealRecordToHost(ctx, collection, id, hostSealer, opts)\n },\n async revokeSealedRecord(ctx, collection, id, pid, opts) {\n const { revokeSealedRecord } = await import('../../kernel/enclave/index.js')\n return revokeSealedRecord(ctx, collection, id, pid, opts)\n },\n async rotateRecordCek(ctx, collection, id) {\n const { rotateRecordCek } = await import('../../kernel/enclave/index.js')\n return rotateRecordCek(ctx, collection, id)\n },\n }\n}\n","/**\n * `@noy-db/hub/sealed-record` — host-side opener for record-scoped CEK\n * sealing.\n *\n * The **grantor** side (sealing a record's CEK to a host, revoking, and hard\n * rotation) lives on `Vault` (`vault.sealRecordToHost` /\n * `vault.revokeSealedRecord` / `vault.rotateRecordCek`) because it needs the\n * collection DEK. This subpath exposes the **recipient host** side:\n * {@link openSealedRecord} reconstructs a single record's plaintext from\n * (a) the sealed-CEK delivery envelope, (b) the record's encrypted envelope\n * body, and (c) the host's own unsealer — and **nothing else**. The host never\n * touches a vault DEK and can decrypt exactly the one bound record.\n *\n * @module\n */\n\nimport { recordAadFor, decrypt, base64ToBuffer, importCek } from '../../kernel/enclave/index.js'\nimport {\n SealedRecordExpiredError,\n SealedRecordMismatchError,\n} from '../../kernel/errors.js'\nimport type {\n SealedCekDeliveryEnvelope,\n SealedCekBinding,\n} from './types.js'\n\nexport type { SealedCekDeliveryEnvelope, SealedCekBinding } from './types.js'\nexport { SealedRecordExpiredError, SealedRecordMismatchError } from '../../kernel/errors.js'\nexport { withSealedRecord } from './active.js'\nexport { NO_SEALED_RECORD, type SealedRecordStrategy } from './strategy.js'\nexport { SealedRecordNotEnabledError } from '../../kernel/errors.js'\n\n/**\n * Host-side: decrypt a single record whose CEK was sealed to this host by a\n * vault grantor via `vault.sealRecordToHost()`.\n *\n * Verification order (security-critical — do NOT reorder past the unseal):\n * 1. **Fast-path expiry** — reject on the delivery envelope's clear-text\n * `expiresAt` before doing any (potentially expensive) unseal work. This is\n * a hint only.\n * 2. **Unseal** the `payload` with the host's `recipientSealer.unseal` →\n * parse the {@link SealedCekBinding}.\n * 3. **Binding match** — the binding's `{collection, id}` MUST equal the\n * expected pair, else {@link SealedRecordMismatchError}. This is the\n * host-denial boundary (a CEK sealed for record A cannot open record B).\n * 4. **Authoritative expiry** — re-check `binding.expiresAt` (the copy that\n * cannot be forged by editing the delivery envelope), else\n * {@link SealedRecordExpiredError}.\n * 5. **Decrypt** the record body under the unsealed CEK. A CEK from a\n * PRE-rotation seal applied to a POST-rotation live envelope passes (1)-(4)\n * but fails the AES-GCM auth tag → `TamperedError` from `decrypt`.\n *\n * @param sealedCekEnvelope The `SealedCekDeliveryEnvelope` written by the grantor.\n * @param recordEnvelope The record's `{ _iv, _data }` (the encrypted body).\n * @param recipientSealer The host's unsealer (`{ unseal(bytes) }`) — e.g. a\n * KMS-backed sealer or `MemoryRecipientSealer`.\n * @param expectedCollection Collection the caller believes `recordEnvelope` is in.\n * @param expectedId Record id the caller believes `recordEnvelope` is.\n * @returns The decrypted record body as a JSON string.\n *\n * ⚠️ **`recordEnvelope` must carry `_tier` and `_by`, not just the body**\n * (#1041). They are plaintext metadata rather than secrets, but they are bound\n * into the record's AAD, so a host handed only `{_iv, _data}` cannot\n * reconstruct the identity the body was sealed against.\n *\n * That is a feature, not a tax: it means a host cannot be fed a body with its\n * tags stripped or rewritten — a re-tiered or re-authored envelope no longer\n * opens, even with a valid sealed CEK.\n */\nexport async function openSealedRecord(\n sealedCekEnvelope: SealedCekDeliveryEnvelope,\n recordEnvelope: {\n readonly _iv: string\n readonly _data: string\n /** Bound into the AAD since #1093 — a rewound body no longer opens. */\n readonly _v: number\n readonly _tier?: number | undefined\n readonly _by?: string | undefined\n },\n recipientSealer: { unseal(bytes: Uint8Array): Promise<Uint8Array> },\n expectedCollection: string,\n expectedId: string,\n): Promise<string> {\n // (1) Fast-path expiry on the (unauthenticated) delivery envelope. Fail\n // CLOSED (M-4): a malformed/empty `expiresAt` parses to NaN — treat it as\n // expired rather than letting `NaN <= now` (which is `false`) skip the check.\n const fastT = Date.parse(sealedCekEnvelope.expiresAt)\n if (!Number.isFinite(fastT) || fastT <= Date.now()) {\n throw new SealedRecordExpiredError(sealedCekEnvelope.expiresAt)\n }\n\n // (2) Unseal → parse binding.\n const payloadBytes = base64ToBuffer(sealedCekEnvelope.payload)\n const openedBytes = await recipientSealer.unseal(payloadBytes)\n const binding = JSON.parse(new TextDecoder().decode(openedBytes)) as SealedCekBinding\n\n // (3) Binding match — host-denial boundary.\n if (binding.collection !== expectedCollection || binding.id !== expectedId) {\n throw new SealedRecordMismatchError(\n { collection: expectedCollection, id: expectedId },\n { collection: binding.collection, id: binding.id },\n )\n }\n\n // (4) Authoritative expiry — the copy that cannot be forged on the envelope.\n // Fail CLOSED (M-4): a malformed/empty `expiresAt` → NaN → treat as expired.\n const t = Date.parse(binding.expiresAt)\n if (!Number.isFinite(t) || t <= Date.now()) {\n throw new SealedRecordExpiredError(binding.expiresAt)\n }\n\n // (5) Import the raw CEK + decrypt the body. Wrong-key (pre-rotation CEK vs\n // post-rotation body) surfaces as TamperedError from decrypt's GCM tag check.\n const cek = await importCek(base64ToBuffer(binding.cek))\n // AAD from the binding's OWN collection/id — which step (3) has just proven\n // match the caller's expectation. So a host cannot be handed record A's\n // sealed CEK alongside record B's body and open it (#1041): the AAD would\n // describe A while the ciphertext was sealed for B.\n return decrypt(\n recordEnvelope._iv,\n recordEnvelope._data,\n cek,\n recordAadFor({ collection: expectedCollection, id: expectedId }, recordEnvelope),\n )\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AASO,SAAS,mBAAyC;AACvD,SAAO;AAAA,IACL,MAAM,iBAAiB,KAAK,YAAY,IAAI,YAAY,MAAM;AAC5D,YAAM,EAAE,iBAAiB,IAAI,MAAM,OAAO,wBAA+B;AACzE,aAAO,iBAAiB,KAAK,YAAY,IAAI,YAAY,IAAI;AAAA,IAC/D;AAAA,IACA,MAAM,mBAAmB,KAAK,YAAY,IAAI,KAAK,MAAM;AACvD,YAAM,EAAE,mBAAmB,IAAI,MAAM,OAAO,wBAA+B;AAC3E,aAAO,mBAAmB,KAAK,YAAY,IAAI,KAAK,IAAI;AAAA,IAC1D;AAAA,IACA,MAAM,gBAAgB,KAAK,YAAY,IAAI;AACzC,YAAM,EAAE,gBAAgB,IAAI,MAAM,OAAO,wBAA+B;AACxE,aAAO,gBAAgB,KAAK,YAAY,EAAE;AAAA,IAC5C;AAAA,EACF;AACF;;;AC6CA,eAAsB,iBACpB,mBACA,gBAQA,iBACA,oBACA,YACiB;AAIjB,QAAM,QAAQ,KAAK,MAAM,kBAAkB,SAAS;AACpD,MAAI,CAAC,OAAO,SAAS,KAAK,KAAK,SAAS,KAAK,IAAI,GAAG;AAClD,UAAM,IAAI,yBAAyB,kBAAkB,SAAS;AAAA,EAChE;AAGA,QAAM,eAAe,eAAe,kBAAkB,OAAO;AAC7D,QAAM,cAAc,MAAM,gBAAgB,OAAO,YAAY;AAC7D,QAAM,UAAU,KAAK,MAAM,IAAI,YAAY,EAAE,OAAO,WAAW,CAAC;AAGhE,MAAI,QAAQ,eAAe,sBAAsB,QAAQ,OAAO,YAAY;AAC1E,UAAM,IAAI;AAAA,MACR,EAAE,YAAY,oBAAoB,IAAI,WAAW;AAAA,MACjD,EAAE,YAAY,QAAQ,YAAY,IAAI,QAAQ,GAAG;AAAA,IACnD;AAAA,EACF;AAIA,QAAM,IAAI,KAAK,MAAM,QAAQ,SAAS;AACtC,MAAI,CAAC,OAAO,SAAS,CAAC,KAAK,KAAK,KAAK,IAAI,GAAG;AAC1C,UAAM,IAAI,yBAAyB,QAAQ,SAAS;AAAA,EACtD;AAIA,QAAM,MAAM,MAAM,UAAU,eAAe,QAAQ,GAAG,CAAC;AAKvD,SAAO;AAAA,IACL,eAAe;AAAA,IACf,eAAe;AAAA,IACf;AAAA,IACA,aAAa,EAAE,YAAY,oBAAoB,IAAI,WAAW,GAAG,cAAc;AAAA,EACjF;AACF;","names":[]}
@@ -9,7 +9,7 @@ import {
9
9
  segmentTokens,
10
10
  tokenize,
11
11
  withSearch
12
- } from "../chunk-COTPDJR5.js";
12
+ } from "../chunk-OMB2ZKIT.js";
13
13
  import {
14
14
  fuseRetrieval
15
15
  } from "../chunk-AURFOK3D.js";
@@ -18,7 +18,6 @@ import "../chunk-UXVWEX5L.js";
18
18
  import {
19
19
  SearchNotEnabledError
20
20
  } from "../chunk-7O3Y4PPD.js";
21
- import "../chunk-PZ5AY32C.js";
22
21
  export {
23
22
  InvertedIndex,
24
23
  MemoryIndexStore,
@@ -30,7 +30,6 @@ import {
30
30
  ValidationError,
31
31
  isConflictError
32
32
  } from "./chunk-7O3Y4PPD.js";
33
- import "./chunk-PZ5AY32C.js";
34
33
 
35
34
  // src/with-party/broker/seed.ts
36
35
  function requireAdminAccess(keyring) {
@@ -226,4 +225,4 @@ export {
226
225
  mintStoreCredentials,
227
226
  rotateSeed
228
227
  };
229
- //# sourceMappingURL=seed-3RXT4VGN.js.map
228
+ //# sourceMappingURL=seed-UYR6YG3V.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/with-party/broker/seed.ts"],"sourcesContent":["/**\n * `_broker` reserved-collection seed lifecycle (#479, credential-broker\n * spec §2/§3). Modeled on `with-sync/credentials.ts`: a\n * `_`-prefixed reserved collection, record id = `brokerId`, encrypted under\n * the collection's own DEK, `requireAdminAccess` role gate (owner/admin\n * only — custodian intentionally excluded, same rationale as\n * `_sync_credentials`: this is transport-auth material, not the\n * custodian's operational scope).\n *\n * Three operations, each a free function taking a {@link BrokerSeedCtx}\n * (the {@link BrokerCtx} threaded through `with-party/broker/active.ts`,\n * augmented there with the `config` its closure was built with):\n *\n * - {@link enrollSeed} — two-phase commit (I9): create-if-absent CAS the\n * seed (I4), then register its derived proof key with the broker host,\n * then (only on a 2xx) mark the record `registered: true`. A seed\n * persisted whose `/enroll` POST failed stays `registered: false` so a\n * later `credentialSource()` fails fast instead of degrading into an\n * opaque `BrokerProofError` (I9/V23).\n * - {@link rotateSeed} — quiesce-then-swap (I5): mint a fresh seed,\n * register-new-first, THEN overwrite the local record — closing the\n * torn-proof window a naive local-overwrite-then-register order would\n * open. The single-flight cache quiesce itself (await in-flight, bump\n * epoch) is the caller's job (`with-party/broker/active.ts`) since the\n * cache lives in that closure, not here.\n * - {@link mintStoreCredentials} — the challenge/response round trip\n * (`/challenge` then `/credentials`) that produces one `StoreCredentials`\n * value; the single-flight cache wrapping it lives in `active.ts`.\n *\n * First-seed creation calls `ensureCollectionDEK`, which throws\n * (`ValidationError`) when the keyring's KEK is `null` (PIN quick-resume /\n * session-restore) — this module intercepts that and re-surfaces it as\n * {@link BrokerEnrolmentError} (R-B8/I3): *enrol* needs the KEK; *use* of an\n * already-enrolled seed needs only the DEK.\n *\n * @module\n */\nimport type { NoydbStore, EncryptedEnvelope, StoreCredentials } from '../../kernel/types.js'\nimport type { UnlockedKeyring } from '../../with-party/team/keyring.js'\nimport { ensureCollectionDEK } from '../../with-party/team/keyring.js'\nimport { BROKER_COLLECTION } from '../../with-party/team/reserved-secret-collections.js'\nimport {\n buildSealedRecordEnvelope,\n openEnvelopeJson,\n writeEnvelopeBody,\n bufferToBase64,\n base64ToBuffer,\n deriveBrokerProofBits,\n computeBrokerProof,\n} from '../../kernel/enclave/index.js'\nimport type { EnclaveKey } from '../../kernel/enclave/index.js'\nimport {\n PermissionDeniedError,\n ValidationError,\n isConflictError,\n NetworkError,\n BrokerEnrolmentError,\n BrokerProofError,\n} from '../../kernel/errors.js'\nimport type { BrokerSeedCtx, BrokerConfig } from '../../port/with/broker-strategy.js'\n\n/** The `_broker/<brokerId>` record payload (spec §3). */\nexport interface BrokerSeedRecord {\n readonly brokerId: string\n /** base64(32 random bytes). */\n readonly seed: string\n readonly endpoint: string\n readonly createdAt: string\n /** `true` only once `/enroll` has returned a 2xx for this seed (I9). */\n readonly registered: boolean\n}\n\nfunction requireAdminAccess(keyring: UnlockedKeyring): void {\n // Custodian is INTENTIONALLY excluded — the _broker seed is transport-auth\n // material (mints cloud store credentials), not the custodian's\n // operational data scope. Same rationale as _sync_credentials' FR-6 note.\n if (keyring.role !== 'owner' && keyring.role !== 'admin') {\n throw new PermissionDeniedError(\n `Credential-broker operations require owner or admin role. Current role: \"${keyring.role}\"`,\n )\n }\n}\n\n// `ensureCollectionDEK` only dedupes concurrent first-time DEK creates WITHIN one\n// closure instance — since every call site here asks for a fresh closure, two\n// concurrent `enrollSeed()` calls sharing the same (store, vault, keyring) would\n// otherwise each mint a DIFFERENT `_broker` DEK and race `persistKeyring`, leaving\n// the seed record encrypted under a DEK the keyring no longer holds (TamperedError\n// on the next read). Dedupe across calls here, keyed by store identity + vault\n// (V20 — the CAS enrol vector actually exercises this).\nconst inFlightDekByStore = new WeakMap<NoydbStore, Map<string, Promise<EnclaveKey>>>()\n\nasync function brokerDek(store: NoydbStore, vault: string, keyring: UnlockedKeyring): Promise<EnclaveKey> {\n const existing = keyring.deks.get(BROKER_COLLECTION)\n if (existing) return existing\n\n let perStore = inFlightDekByStore.get(store)\n if (!perStore) { perStore = new Map(); inFlightDekByStore.set(store, perStore) }\n const pending = perStore.get(vault)\n if (pending) return pending\n\n const promise = (async () => {\n try {\n const getDek = await ensureCollectionDEK(store, vault, keyring)\n return await getDek(BROKER_COLLECTION)\n } catch (err) {\n if (err instanceof ValidationError) {\n throw new BrokerEnrolmentError(\n 'enroll()/rotate() need the KEK to provision the _broker DEK on first use — ' +\n 're-authenticate at tier 1 (secret) before enrolling. Using an already-' +\n 'enrolled seed (credentialSource()) needs only the DEK.',\n )\n }\n throw err\n }\n })()\n perStore.set(vault, promise)\n try {\n return await promise\n } finally {\n perStore.delete(vault)\n }\n}\n\nasync function readRecord(\n store: NoydbStore,\n vault: string,\n brokerId: string,\n dek: EnclaveKey,\n): Promise<{ envelope: EncryptedEnvelope; record: BrokerSeedRecord } | null> {\n const envelope = await store.get(vault, BROKER_COLLECTION, brokerId)\n if (!envelope) return null\n const record = JSON.parse(await openEnvelopeJson({ collection: BROKER_COLLECTION, id: brokerId }, envelope, dek)) as BrokerSeedRecord\n return { envelope, record }\n}\n\nasync function persistRecord(\n store: NoydbStore,\n vault: string,\n keyring: UnlockedKeyring,\n dek: EnclaveKey,\n record: BrokerSeedRecord,\n expectedVersion: number,\n): Promise<void> {\n const envelope = await buildSealedRecordEnvelope(\n { collection: BROKER_COLLECTION, id: record.brokerId, by: keyring.userId, version: expectedVersion + 1 },\n (identity) => writeEnvelopeBody(identity, JSON.stringify(record), dek),\n {},\n )\n await store.put(vault, BROKER_COLLECTION, record.brokerId, envelope, expectedVersion)\n}\n\ninterface SeedState {\n readonly seedBytes: Uint8Array\n readonly record: BrokerSeedRecord\n readonly dek: EnclaveKey\n readonly version: number\n}\n\n/** Create-if-absent CAS (I4/V20): a concurrent enrol that loses the race\n * re-reads the winner's seed instead of persisting a second one. */\nasync function ensureSeedRecord(store: NoydbStore, vault: string, keyring: UnlockedKeyring, config: BrokerConfig): Promise<SeedState> {\n requireAdminAccess(keyring)\n const dek = await brokerDek(store, vault, keyring)\n\n const existing = await readRecord(store, vault, config.brokerId, dek)\n if (existing) {\n return { seedBytes: base64ToBuffer(existing.record.seed), record: existing.record, dek, version: existing.envelope._v }\n }\n\n const seedBytes = globalThis.crypto.getRandomValues(new Uint8Array(32))\n const record: BrokerSeedRecord = {\n brokerId: config.brokerId,\n seed: bufferToBase64(seedBytes),\n endpoint: config.endpoint,\n createdAt: new Date().toISOString(),\n registered: false,\n }\n try {\n await persistRecord(store, vault, keyring, dek, record, 0)\n return { seedBytes, record, dek, version: 1 }\n } catch (err) {\n if (!(isConflictError(err))) throw err\n const winner = await readRecord(store, vault, config.brokerId, dek)\n if (!winner) throw err\n return { seedBytes: base64ToBuffer(winner.record.seed), record: winner.record, dek, version: winner.envelope._v }\n }\n}\n\nasync function postEnroll(config: BrokerConfig, vault: string, proofBits: Uint8Array): Promise<void> {\n const fetchFn = config.fetch ?? globalThis.fetch\n const attestation = config.attestation ? await config.attestation() : undefined\n let res: Response\n try {\n res = await fetchFn(`${config.endpoint}/enroll`, {\n method: 'POST',\n headers: {\n 'content-type': 'application/json',\n ...(attestation !== undefined ? { authorization: `Bearer ${attestation}` } : {}),\n },\n body: JSON.stringify({ vaultId: vault, brokerId: config.brokerId, proofKey: bufferToBase64(proofBits) }),\n })\n } catch (err) {\n throw new BrokerEnrolmentError(`broker /enroll request failed: ${(err as Error).message}`)\n }\n if (!res.ok) {\n throw new BrokerEnrolmentError(`broker refused enrolment (status ${res.status}) — attestation likely missing or invalid`)\n }\n}\n\n/**\n * Enrol (idempotent, two-phase commit — I9): create-if-absent the `_broker`\n * seed (I4), derive+register its proof key with the broker host, and only\n * on a 2xx mark the record `registered: true`. Requires owner/admin role\n * and (for first-seed creation only) the KEK.\n */\nexport async function enrollSeed(ctx: BrokerSeedCtx): Promise<void> {\n const { store, vault, keyring, config } = ctx\n const { seedBytes, record, dek, version } = await ensureSeedRecord(store, vault, keyring, config)\n if (record.registered) {\n seedBytes.fill(0)\n return\n }\n\n const proofBits = await deriveBrokerProofBits(seedBytes, vault, config.brokerId)\n seedBytes.fill(0)\n try {\n await postEnroll(config, vault, proofBits)\n } finally {\n proofBits.fill(0)\n }\n\n try {\n await persistRecord(store, vault, keyring, dek, { ...record, registered: true }, version)\n } catch (err) {\n if (isConflictError(err)) {\n const latest = await readRecord(store, vault, config.brokerId, dek)\n if (latest?.record.registered) return // a concurrent enrol already committed the same outcome\n }\n throw err\n }\n}\n\n/**\n * Rotate (quiesce-then-swap — I5): mint a fresh seed, register its proof\n * key with the broker host FIRST, then overwrite the local `_broker`\n * record. The broker host is expected to accept both the old and new\n * registration for a short grace window, so an in-flight proof computed\n * under the old key (from before rotation started) still verifies. Cache\n * quiescing (awaiting any in-flight round-trip, bumping the refresh-cache\n * epoch) is the caller's job — `with-party/broker/active.ts` — since the\n * cache lives in that closure, not here.\n */\nexport async function rotateSeed(ctx: BrokerSeedCtx): Promise<void> {\n const { store, vault, keyring, config } = ctx\n requireAdminAccess(keyring)\n const dek = await brokerDek(store, vault, keyring)\n\n const existing = await readRecord(store, vault, config.brokerId, dek)\n if (!existing) {\n // Nothing to rotate yet — behaves like a first enrol.\n await enrollSeed(ctx)\n return\n }\n\n const newSeedBytes = globalThis.crypto.getRandomValues(new Uint8Array(32))\n const newProofBits = await deriveBrokerProofBits(newSeedBytes, vault, config.brokerId)\n try {\n await postEnroll(config, vault, newProofBits)\n } finally {\n newProofBits.fill(0)\n }\n\n const record: BrokerSeedRecord = {\n brokerId: config.brokerId,\n seed: bufferToBase64(newSeedBytes),\n endpoint: config.endpoint,\n createdAt: new Date().toISOString(),\n registered: true,\n }\n newSeedBytes.fill(0)\n await persistRecord(store, vault, keyring, dek, record, existing.envelope._v)\n}\n\n/**\n * The challenge/response round trip: `POST /challenge`, derive+sign the\n * proof from the decrypted seed, `POST /credentials`. One network round\n * trip per call — the single-flight/cache wrapping lives in `active.ts`.\n *\n * Fails fast with {@link BrokerEnrolmentError} if the seed hasn't completed\n * enrolment yet (`registered !== true` — I9/V23), never degrading into an\n * opaque {@link BrokerProofError}.\n */\nexport async function mintStoreCredentials(ctx: BrokerSeedCtx, profile?: string): Promise<StoreCredentials> {\n const { store, vault, keyring, config } = ctx\n requireAdminAccess(keyring)\n const dek = await brokerDek(store, vault, keyring)\n\n const found = await readRecord(store, vault, config.brokerId, dek)\n if (!found) throw new BrokerEnrolmentError(`No _broker seed enrolled for brokerId \"${config.brokerId}\" — call enroll() first.`)\n if (!found.record.registered) {\n throw new BrokerEnrolmentError(\n `The _broker seed for \"${config.brokerId}\" has not completed enrolment with the broker host — call enroll() again.`,\n )\n }\n\n const fetchFn = config.fetch ?? globalThis.fetch\n const endpointOrigin = new URL(config.endpoint).origin\n\n let challengeRes: Response\n try {\n challengeRes = await fetchFn(`${config.endpoint}/challenge`, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ vaultId: vault, brokerId: config.brokerId }),\n })\n } catch (err) {\n throw new NetworkError(`broker /challenge request failed: ${(err as Error).message}`)\n }\n if (!challengeRes.ok) throw new NetworkError(`broker /challenge failed with status ${challengeRes.status}`)\n const { challenge, expiresAt } = (await challengeRes.json()) as { challenge: string; expiresAt: string }\n\n const seedBytes = base64ToBuffer(found.record.seed)\n const proof = await computeBrokerProof(seedBytes, vault, config.brokerId, { endpointOrigin, profile, challenge, expiresAt })\n\n let credsRes: Response\n try {\n credsRes = await fetchFn(`${config.endpoint}/credentials`, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ vaultId: vault, brokerId: config.brokerId, challenge, proof, ...(profile !== undefined ? { profile } : {}) }),\n })\n } catch (err) {\n throw new NetworkError(`broker /credentials request failed: ${(err as Error).message}`)\n }\n if (credsRes.status === 401 || credsRes.status === 403) throw new BrokerProofError('broker rejected the challenge proof')\n if (!credsRes.ok) throw new NetworkError(`broker /credentials failed with status ${credsRes.status}`)\n return (await credsRes.json()) as StoreCredentials\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/with-party/broker/seed.ts"],"sourcesContent":["/**\n * `_broker` reserved-collection seed lifecycle (#479, credential-broker\n * spec §2/§3). Modeled on `with-sync/credentials.ts`: a\n * `_`-prefixed reserved collection, record id = `brokerId`, encrypted under\n * the collection's own DEK, `requireAdminAccess` role gate (owner/admin\n * only — custodian intentionally excluded, same rationale as\n * `_sync_credentials`: this is transport-auth material, not the\n * custodian's operational scope).\n *\n * Three operations, each a free function taking a {@link BrokerSeedCtx}\n * (the {@link BrokerCtx} threaded through `with-party/broker/active.ts`,\n * augmented there with the `config` its closure was built with):\n *\n * - {@link enrollSeed} — two-phase commit (I9): create-if-absent CAS the\n * seed (I4), then register its derived proof key with the broker host,\n * then (only on a 2xx) mark the record `registered: true`. A seed\n * persisted whose `/enroll` POST failed stays `registered: false` so a\n * later `credentialSource()` fails fast instead of degrading into an\n * opaque `BrokerProofError` (I9/V23).\n * - {@link rotateSeed} — quiesce-then-swap (I5): mint a fresh seed,\n * register-new-first, THEN overwrite the local record — closing the\n * torn-proof window a naive local-overwrite-then-register order would\n * open. The single-flight cache quiesce itself (await in-flight, bump\n * epoch) is the caller's job (`with-party/broker/active.ts`) since the\n * cache lives in that closure, not here.\n * - {@link mintStoreCredentials} — the challenge/response round trip\n * (`/challenge` then `/credentials`) that produces one `StoreCredentials`\n * value; the single-flight cache wrapping it lives in `active.ts`.\n *\n * First-seed creation calls `ensureCollectionDEK`, which throws\n * (`ValidationError`) when the keyring's KEK is `null` (PIN quick-resume /\n * session-restore) — this module intercepts that and re-surfaces it as\n * {@link BrokerEnrolmentError} (R-B8/I3): *enrol* needs the KEK; *use* of an\n * already-enrolled seed needs only the DEK.\n *\n * @module\n */\nimport type { NoydbStore, EncryptedEnvelope, StoreCredentials } from '../../kernel/types.js'\nimport type { UnlockedKeyring } from '../../with-party/team/keyring.js'\nimport { ensureCollectionDEK } from '../../with-party/team/keyring.js'\nimport { BROKER_COLLECTION } from '../../with-party/team/reserved-secret-collections.js'\nimport {\n buildSealedRecordEnvelope,\n openEnvelopeJson,\n writeEnvelopeBody,\n bufferToBase64,\n base64ToBuffer,\n deriveBrokerProofBits,\n computeBrokerProof,\n} from '../../kernel/enclave/index.js'\nimport type { EnclaveKey } from '../../kernel/enclave/index.js'\nimport {\n PermissionDeniedError,\n ValidationError,\n isConflictError,\n NetworkError,\n BrokerEnrolmentError,\n BrokerProofError,\n} from '../../kernel/errors.js'\nimport type { BrokerSeedCtx, BrokerConfig } from '../../port/with/broker-strategy.js'\n\n/** The `_broker/<brokerId>` record payload (spec §3). */\nexport interface BrokerSeedRecord {\n readonly brokerId: string\n /** base64(32 random bytes). */\n readonly seed: string\n readonly endpoint: string\n readonly createdAt: string\n /** `true` only once `/enroll` has returned a 2xx for this seed (I9). */\n readonly registered: boolean\n}\n\nfunction requireAdminAccess(keyring: UnlockedKeyring): void {\n // Custodian is INTENTIONALLY excluded — the _broker seed is transport-auth\n // material (mints cloud store credentials), not the custodian's\n // operational data scope. Same rationale as _sync_credentials' FR-6 note.\n if (keyring.role !== 'owner' && keyring.role !== 'admin') {\n throw new PermissionDeniedError(\n `Credential-broker operations require owner or admin role. Current role: \"${keyring.role}\"`,\n )\n }\n}\n\n// `ensureCollectionDEK` only dedupes concurrent first-time DEK creates WITHIN one\n// closure instance — since every call site here asks for a fresh closure, two\n// concurrent `enrollSeed()` calls sharing the same (store, vault, keyring) would\n// otherwise each mint a DIFFERENT `_broker` DEK and race `persistKeyring`, leaving\n// the seed record encrypted under a DEK the keyring no longer holds (TamperedError\n// on the next read). Dedupe across calls here, keyed by store identity + vault\n// (V20 — the CAS enrol vector actually exercises this).\nconst inFlightDekByStore = new WeakMap<NoydbStore, Map<string, Promise<EnclaveKey>>>()\n\nasync function brokerDek(store: NoydbStore, vault: string, keyring: UnlockedKeyring): Promise<EnclaveKey> {\n const existing = keyring.deks.get(BROKER_COLLECTION)\n if (existing) return existing\n\n let perStore = inFlightDekByStore.get(store)\n if (!perStore) { perStore = new Map(); inFlightDekByStore.set(store, perStore) }\n const pending = perStore.get(vault)\n if (pending) return pending\n\n const promise = (async () => {\n try {\n const getDek = await ensureCollectionDEK(store, vault, keyring)\n return await getDek(BROKER_COLLECTION)\n } catch (err) {\n if (err instanceof ValidationError) {\n throw new BrokerEnrolmentError(\n 'enroll()/rotate() need the KEK to provision the _broker DEK on first use — ' +\n 're-authenticate at tier 1 (secret) before enrolling. Using an already-' +\n 'enrolled seed (credentialSource()) needs only the DEK.',\n )\n }\n throw err\n }\n })()\n perStore.set(vault, promise)\n try {\n return await promise\n } finally {\n perStore.delete(vault)\n }\n}\n\nasync function readRecord(\n store: NoydbStore,\n vault: string,\n brokerId: string,\n dek: EnclaveKey,\n): Promise<{ envelope: EncryptedEnvelope; record: BrokerSeedRecord } | null> {\n const envelope = await store.get(vault, BROKER_COLLECTION, brokerId)\n if (!envelope) return null\n const record = JSON.parse(await openEnvelopeJson({ collection: BROKER_COLLECTION, id: brokerId }, envelope, dek)) as BrokerSeedRecord\n return { envelope, record }\n}\n\nasync function persistRecord(\n store: NoydbStore,\n vault: string,\n keyring: UnlockedKeyring,\n dek: EnclaveKey,\n record: BrokerSeedRecord,\n expectedVersion: number,\n): Promise<void> {\n const envelope = await buildSealedRecordEnvelope(\n { collection: BROKER_COLLECTION, id: record.brokerId, by: keyring.userId, version: expectedVersion + 1 },\n (identity) => writeEnvelopeBody(identity, JSON.stringify(record), dek),\n {},\n )\n await store.put(vault, BROKER_COLLECTION, record.brokerId, envelope, expectedVersion)\n}\n\ninterface SeedState {\n readonly seedBytes: Uint8Array\n readonly record: BrokerSeedRecord\n readonly dek: EnclaveKey\n readonly version: number\n}\n\n/** Create-if-absent CAS (I4/V20): a concurrent enrol that loses the race\n * re-reads the winner's seed instead of persisting a second one. */\nasync function ensureSeedRecord(store: NoydbStore, vault: string, keyring: UnlockedKeyring, config: BrokerConfig): Promise<SeedState> {\n requireAdminAccess(keyring)\n const dek = await brokerDek(store, vault, keyring)\n\n const existing = await readRecord(store, vault, config.brokerId, dek)\n if (existing) {\n return { seedBytes: base64ToBuffer(existing.record.seed), record: existing.record, dek, version: existing.envelope._v }\n }\n\n const seedBytes = globalThis.crypto.getRandomValues(new Uint8Array(32))\n const record: BrokerSeedRecord = {\n brokerId: config.brokerId,\n seed: bufferToBase64(seedBytes),\n endpoint: config.endpoint,\n createdAt: new Date().toISOString(),\n registered: false,\n }\n try {\n await persistRecord(store, vault, keyring, dek, record, 0)\n return { seedBytes, record, dek, version: 1 }\n } catch (err) {\n if (!(isConflictError(err))) throw err\n const winner = await readRecord(store, vault, config.brokerId, dek)\n if (!winner) throw err\n return { seedBytes: base64ToBuffer(winner.record.seed), record: winner.record, dek, version: winner.envelope._v }\n }\n}\n\nasync function postEnroll(config: BrokerConfig, vault: string, proofBits: Uint8Array): Promise<void> {\n const fetchFn = config.fetch ?? globalThis.fetch\n const attestation = config.attestation ? await config.attestation() : undefined\n let res: Response\n try {\n res = await fetchFn(`${config.endpoint}/enroll`, {\n method: 'POST',\n headers: {\n 'content-type': 'application/json',\n ...(attestation !== undefined ? { authorization: `Bearer ${attestation}` } : {}),\n },\n body: JSON.stringify({ vaultId: vault, brokerId: config.brokerId, proofKey: bufferToBase64(proofBits) }),\n })\n } catch (err) {\n throw new BrokerEnrolmentError(`broker /enroll request failed: ${(err as Error).message}`)\n }\n if (!res.ok) {\n throw new BrokerEnrolmentError(`broker refused enrolment (status ${res.status}) — attestation likely missing or invalid`)\n }\n}\n\n/**\n * Enrol (idempotent, two-phase commit — I9): create-if-absent the `_broker`\n * seed (I4), derive+register its proof key with the broker host, and only\n * on a 2xx mark the record `registered: true`. Requires owner/admin role\n * and (for first-seed creation only) the KEK.\n */\nexport async function enrollSeed(ctx: BrokerSeedCtx): Promise<void> {\n const { store, vault, keyring, config } = ctx\n const { seedBytes, record, dek, version } = await ensureSeedRecord(store, vault, keyring, config)\n if (record.registered) {\n seedBytes.fill(0)\n return\n }\n\n const proofBits = await deriveBrokerProofBits(seedBytes, vault, config.brokerId)\n seedBytes.fill(0)\n try {\n await postEnroll(config, vault, proofBits)\n } finally {\n proofBits.fill(0)\n }\n\n try {\n await persistRecord(store, vault, keyring, dek, { ...record, registered: true }, version)\n } catch (err) {\n if (isConflictError(err)) {\n const latest = await readRecord(store, vault, config.brokerId, dek)\n if (latest?.record.registered) return // a concurrent enrol already committed the same outcome\n }\n throw err\n }\n}\n\n/**\n * Rotate (quiesce-then-swap — I5): mint a fresh seed, register its proof\n * key with the broker host FIRST, then overwrite the local `_broker`\n * record. The broker host is expected to accept both the old and new\n * registration for a short grace window, so an in-flight proof computed\n * under the old key (from before rotation started) still verifies. Cache\n * quiescing (awaiting any in-flight round-trip, bumping the refresh-cache\n * epoch) is the caller's job — `with-party/broker/active.ts` — since the\n * cache lives in that closure, not here.\n */\nexport async function rotateSeed(ctx: BrokerSeedCtx): Promise<void> {\n const { store, vault, keyring, config } = ctx\n requireAdminAccess(keyring)\n const dek = await brokerDek(store, vault, keyring)\n\n const existing = await readRecord(store, vault, config.brokerId, dek)\n if (!existing) {\n // Nothing to rotate yet — behaves like a first enrol.\n await enrollSeed(ctx)\n return\n }\n\n const newSeedBytes = globalThis.crypto.getRandomValues(new Uint8Array(32))\n const newProofBits = await deriveBrokerProofBits(newSeedBytes, vault, config.brokerId)\n try {\n await postEnroll(config, vault, newProofBits)\n } finally {\n newProofBits.fill(0)\n }\n\n const record: BrokerSeedRecord = {\n brokerId: config.brokerId,\n seed: bufferToBase64(newSeedBytes),\n endpoint: config.endpoint,\n createdAt: new Date().toISOString(),\n registered: true,\n }\n newSeedBytes.fill(0)\n await persistRecord(store, vault, keyring, dek, record, existing.envelope._v)\n}\n\n/**\n * The challenge/response round trip: `POST /challenge`, derive+sign the\n * proof from the decrypted seed, `POST /credentials`. One network round\n * trip per call — the single-flight/cache wrapping lives in `active.ts`.\n *\n * Fails fast with {@link BrokerEnrolmentError} if the seed hasn't completed\n * enrolment yet (`registered !== true` — I9/V23), never degrading into an\n * opaque {@link BrokerProofError}.\n */\nexport async function mintStoreCredentials(ctx: BrokerSeedCtx, profile?: string): Promise<StoreCredentials> {\n const { store, vault, keyring, config } = ctx\n requireAdminAccess(keyring)\n const dek = await brokerDek(store, vault, keyring)\n\n const found = await readRecord(store, vault, config.brokerId, dek)\n if (!found) throw new BrokerEnrolmentError(`No _broker seed enrolled for brokerId \"${config.brokerId}\" — call enroll() first.`)\n if (!found.record.registered) {\n throw new BrokerEnrolmentError(\n `The _broker seed for \"${config.brokerId}\" has not completed enrolment with the broker host — call enroll() again.`,\n )\n }\n\n const fetchFn = config.fetch ?? globalThis.fetch\n const endpointOrigin = new URL(config.endpoint).origin\n\n let challengeRes: Response\n try {\n challengeRes = await fetchFn(`${config.endpoint}/challenge`, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ vaultId: vault, brokerId: config.brokerId }),\n })\n } catch (err) {\n throw new NetworkError(`broker /challenge request failed: ${(err as Error).message}`)\n }\n if (!challengeRes.ok) throw new NetworkError(`broker /challenge failed with status ${challengeRes.status}`)\n const { challenge, expiresAt } = (await challengeRes.json()) as { challenge: string; expiresAt: string }\n\n const seedBytes = base64ToBuffer(found.record.seed)\n const proof = await computeBrokerProof(seedBytes, vault, config.brokerId, { endpointOrigin, profile, challenge, expiresAt })\n\n let credsRes: Response\n try {\n credsRes = await fetchFn(`${config.endpoint}/credentials`, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ vaultId: vault, brokerId: config.brokerId, challenge, proof, ...(profile !== undefined ? { profile } : {}) }),\n })\n } catch (err) {\n throw new NetworkError(`broker /credentials request failed: ${(err as Error).message}`)\n }\n if (credsRes.status === 401 || credsRes.status === 403) throw new BrokerProofError('broker rejected the challenge proof')\n if (!credsRes.ok) throw new NetworkError(`broker /credentials failed with status ${credsRes.status}`)\n return (await credsRes.json()) as StoreCredentials\n}\n"],"mappings":"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","names":[]}
@@ -17,7 +17,6 @@ import "../chunk-N4CHCXDL.js";
17
17
  import {
18
18
  SequenceNotEnabledError
19
19
  } from "../chunk-7O3Y4PPD.js";
20
- import "../chunk-PZ5AY32C.js";
21
20
  export {
22
21
  NO_SEQUENCE,
23
22
  SEQUENCE_COLLECTION,
@@ -30,7 +30,6 @@ import {
30
30
  SessionNotFoundError,
31
31
  SessionPolicyError
32
32
  } from "../chunk-7O3Y4PPD.js";
33
- import "../chunk-PZ5AY32C.js";
34
33
 
35
34
  // src/with-party/session/active.ts
36
35
  function withSession() {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/with-party/session/active.ts"],"sourcesContent":["/**\n * Active session strategy — `withSession()` returns the real\n * implementation that wires `SessionPolicy` validation, the\n * `PolicyEnforcer`, and the global session-token registry into the\n * Noydb lifecycle.\n *\n * Consumers opt in by:\n *\n * ```ts\n * import { createNoydb } from '@noy-db/hub'\n * import { withSession } from '@noy-db/hub/session'\n *\n * const db = await createNoydb({\n * store: ...,\n * user: ...,\n * sessionPolicy: { idleTimeoutMs: 15 * 60_000 },\n * sessionStrategy: withSession(),\n * })\n * ```\n *\n * The factory delegates to the existing `session-policy.ts` and\n * `session.ts` modules. Splitting the import chain through this file\n * is what lets tsup tree-shake the ~495 LOC of policy + token code\n * out of the default bundle when no `withSession()` import is\n * present.\n *\n * Note: dev-unlock (`devUnlock`, `cancelDevUnlock`) is a separate\n * named import from `@noy-db/hub` and tree-shakes independently —\n * apps that don't import it never include it.\n *\n * @public\n */\n\nimport type { SessionStrategy } from './strategy.js'\nimport { createEnforcer, validateSessionPolicy } from './session-policy.js'\nimport { revokeAllSessions } from './session.js'\n\nexport function withSession(): SessionStrategy {\n return {\n validateSessionPolicy,\n createEnforcer,\n revokeAllSessions,\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqCO,SAAS,cAA+B;AAC7C,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;","names":[]}
1
+ {"version":3,"sources":["../../src/with-party/session/active.ts"],"sourcesContent":["/**\n * Active session strategy — `withSession()` returns the real\n * implementation that wires `SessionPolicy` validation, the\n * `PolicyEnforcer`, and the global session-token registry into the\n * Noydb lifecycle.\n *\n * Consumers opt in by:\n *\n * ```ts\n * import { createNoydb } from '@noy-db/hub'\n * import { withSession } from '@noy-db/hub/session'\n *\n * const db = await createNoydb({\n * store: ...,\n * user: ...,\n * sessionPolicy: { idleTimeoutMs: 15 * 60_000 },\n * sessionStrategy: withSession(),\n * })\n * ```\n *\n * The factory delegates to the existing `session-policy.ts` and\n * `session.ts` modules. Splitting the import chain through this file\n * is what lets tsup tree-shake the ~495 LOC of policy + token code\n * out of the default bundle when no `withSession()` import is\n * present.\n *\n * Note: dev-unlock (`devUnlock`, `cancelDevUnlock`) is a separate\n * named import from `@noy-db/hub` and tree-shakes independently —\n * apps that don't import it never include it.\n *\n * @public\n */\n\nimport type { SessionStrategy } from './strategy.js'\nimport { createEnforcer, validateSessionPolicy } from './session-policy.js'\nimport { revokeAllSessions } from './session.js'\n\nexport function withSession(): SessionStrategy {\n return {\n validateSessionPolicy,\n createEnforcer,\n revokeAllSessions,\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqCO,SAAS,cAA+B;AAC7C,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;","names":[]}
@@ -6,7 +6,6 @@ import {
6
6
  NO_SHADOW
7
7
  } from "../chunk-SMY5X46Z.js";
8
8
  import "../chunk-7O3Y4PPD.js";
9
- import "../chunk-PZ5AY32C.js";
10
9
 
11
10
  // src/with-fork/shadow/active.ts
12
11
  function withShadow() {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/with-fork/shadow/active.ts"],"sourcesContent":["/**\n * Active shadow strategy — the only place `VaultFrame` is constructed.\n * Only reachable through `@noy-db/hub/shadow`.\n */\n\nimport { VaultFrame } from './vault-frame.js'\nimport type { ShadowStrategy } from './strategy.js'\nimport type { Vault } from '../../kernel/vault.js'\n\nexport function withShadow(): ShadowStrategy {\n return {\n buildFrame(vault) { return new VaultFrame(vault as Vault) },\n }\n}\n"],"mappings":";;;;;;;;;;;AASO,SAAS,aAA6B;AAC3C,SAAO;AAAA,IACL,WAAW,OAAO;AAAE,aAAO,IAAI,WAAW,KAAc;AAAA,IAAE;AAAA,EAC5D;AACF;","names":[]}
1
+ {"version":3,"sources":["../../src/with-fork/shadow/active.ts"],"sourcesContent":["/**\n * Active shadow strategy — the only place `VaultFrame` is constructed.\n * Only reachable through `@noy-db/hub/shadow`.\n */\n\nimport { VaultFrame } from './vault-frame.js'\nimport type { ShadowStrategy } from './strategy.js'\nimport type { Vault } from '../../kernel/vault.js'\n\nexport function withShadow(): ShadowStrategy {\n return {\n buildFrame(vault) { return new VaultFrame(vault as Vault) },\n }\n}\n"],"mappings":";;;;;;;;;;AASO,SAAS,aAA6B;AAC3C,SAAO;AAAA,IACL,WAAW,OAAO;AAAE,aAAO,IAAI,WAAW,KAAc;AAAA,IAAE;AAAA,EAC5D;AACF;","names":[]}
@@ -1,7 +1,6 @@
1
1
  import {
2
2
  isULID
3
3
  } from "../chunk-4AOERLTV.js";
4
- import "../chunk-PZ5AY32C.js";
5
4
 
6
5
  // src/share-link/share-link.ts
7
6
  var ShareLinkParseError = class extends Error {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/share-link/share-link.ts"],"sourcesContent":["/**\n * Portal share-link grammar + parse/build helper (#806).\n *\n * ONE canonical link grammar addresses `vault / period / collection /\n * record` across all three portal surfaces — the LIFF permalink path\n * (`https://liff.line.me/{liffId}/r/…`), the installed-PWA same-origin\n * path (`https://{portal-origin}/r/…`), and the vendor backend console.\n * The link SHAPE is identical everywhere; only the origin prefix\n * differs, and callers own the prefix.\n *\n * ## Grammar (frozen — see `__tests__/share-link-surface.golden.json`)\n *\n * ```\n * share-link = [ base ] \"/r/\" vault \"/\" collection \"/\" record\n * [ \"?\" query ] [ \"#\" fragment ]\n * vault = ULID ; 26 uppercase Crockford base32 chars\n * collection = segment ; opaque, non-empty after decode\n * record = segment ; opaque, non-empty after decode\n * query = pair *( \"&\" pair ) ; keys unique; ONLY these two keys\n * pair = \"period=\" component ; opaque period id\n * / \"v=\" version ; record version\n * version = \"0\" / %x31-39 *DIGIT ; base-10 integer, ≤ 2^53 − 1\n * fragment = \"g=\" component ; single-use grant token (opaque)\n * ```\n *\n * `segment` / `component` are strict-canonical `encodeURIComponent`\n * output: decoding then re-encoding MUST reproduce the raw text exactly\n * (uppercase-hex escapes, `+` is a literal plus never a space, `%2F`\n * inside a segment never creates an extra segment). Path segments\n * additionally may not decode to `.` or `..`. Links produced by\n * {@link buildShareLink} always satisfy this; hand-written links using\n * only unreserved characters (`inv-001`, ULIDs, …) do too.\n *\n * ## Transport rule for the grant token\n *\n * The optional single-use grant token travels ONLY in the URL fragment\n * (`#g={token}`) — never in the path or query — because fragments are\n * not sent to servers, so the token never reaches server logs, CDNs, or\n * LINE's infrastructure. Same rule `on-magic-link` follows. The token is\n * fully OPAQUE here: no validation beyond non-empty, no decoding of its\n * semantics — grant semantics live in `on-magic-link` (not imported).\n *\n * ## Fail closed\n *\n * {@link parseShareLink} throws a typed {@link ShareLinkParseError} for\n * ANYTHING that is not exactly the grammar above — unknown prefix,\n * missing/extra/empty/malformed segments, a non-ULID vault handle,\n * duplicate or unknown query keys, malformed fragments. There is never a\n * default-vault (or any other) fallback; the app maps the error to a 404.\n *\n * Zero dependencies on the hub floor: pure string/`URL` code, no crypto,\n * no store — LIFF shells, PWAs, and klum-db can import this subpath\n * without dragging anything else. (The one internal import is the pure,\n * leaf ULID shape check shared with the pod format.)\n */\n\nimport { isULID } from '../with-pod/ulid.js'\n\n/** Machine-readable {@link ShareLinkParseError} discriminant. */\nexport type ShareLinkErrorCode =\n | 'MALFORMED_URL'\n | 'UNKNOWN_PREFIX'\n | 'MALFORMED_PATH'\n | 'MALFORMED_SEGMENT'\n | 'INVALID_VAULT_HANDLE'\n | 'MALFORMED_QUERY'\n | 'DUPLICATE_QUERY_KEY'\n | 'UNKNOWN_QUERY_KEY'\n | 'INVALID_VERSION'\n | 'MALFORMED_FRAGMENT'\n | 'INVALID_PARTS'\n\n/**\n * Typed failure for the share-link grammar. Thrown by\n * {@link parseShareLink} for any input outside the grammar (fail closed —\n * map to an app-level 404, never a default-vault fallback), and by\n * {@link buildShareLink} with code `'INVALID_PARTS'` when the caller\n * hands it parts that could not round-trip.\n *\n * Standalone (`extends Error`, not `NoydbError`) so this subpath stays\n * import-light; the `code` field follows the same machine-readable\n * convention as the kernel error classes.\n */\nexport class ShareLinkParseError extends Error {\n /** Machine-readable error code. Stable across library versions. */\n readonly code: ShareLinkErrorCode\n\n constructor(code: ShareLinkErrorCode, message: string) {\n super(message)\n this.name = 'ShareLinkParseError'\n this.code = code\n }\n}\n\n/**\n * The logical address a share link carries. Input to\n * {@link buildShareLink}; {@link parseShareLink} returns the same shape\n * (as the readonly {@link ShareLink} alias) with optional fields OMITTED\n * (not `undefined`) when absent from the link.\n */\nexport interface ShareLinkParts {\n /** Vault handle — a 26-character uppercase Crockford-base32 ULID. */\n vaultHandle: string\n /** Collection name — opaque, non-empty. */\n collection: string\n /** Record id — opaque, non-empty. */\n recordId: string\n /** Optional period id (`?period=`) — opaque, non-empty. */\n period?: string\n /** Optional record version (`?v=`) — non-negative safe integer. */\n version?: number\n /**\n * Optional single-use grant token (`#g=`) — opaque, non-empty. Carried\n * ONLY in the URL fragment; semantics belong to `on-magic-link`.\n */\n grantToken?: string\n}\n\n/** A successfully parsed share link. See {@link ShareLinkParts}. */\nexport type ShareLink = Readonly<ShareLinkParts>\n\n// Dummy base for parsing bare `/r/…` paths with the WHATWG URL parser.\n// `.invalid` is reserved (RFC 2606) — it can never collide with a real\n// origin, and the host is discarded anyway (only path/query/fragment\n// matter to the grammar).\nconst DUMMY_BASE = 'https://share-link.invalid'\n\n/**\n * Canonical encoding of a QUERY value. `encodeURIComponent`, plus `'`\n * → `%27`: the WHATWG URL parser percent-encodes the apostrophe inside\n * the query component of special (http/https) URLs — if we emitted a\n * raw `'`, any URL-object round-trip (browser address bar, `new URL`)\n * would rewrite the link into a form the strict parser then rejects.\n * Path and fragment components don't need this — every character the\n * URL parser escapes there is already escaped by `encodeURIComponent`.\n */\nfunction encodeQueryValue(value: string): string {\n return encodeURIComponent(value).replace(/'/g, '%27')\n}\n\n/**\n * Strict-canonical decode: `raw` must be exactly what the given\n * canonical `encode` produces for the decoded text — no `+`-for-space,\n * no lowercase hex escapes, no unencoded reserved characters,\n * non-empty. `redact` keeps the raw text out of the error message\n * (grant tokens).\n */\nfunction decodeCanonical(\n raw: string,\n code: ShareLinkErrorCode,\n what: string,\n { redact = false, encode = encodeURIComponent }: {\n redact?: boolean\n encode?: (value: string) => string\n } = {},\n): string {\n const shown = redact ? '(redacted)' : `\"${raw}\"`\n let decoded: string\n try {\n decoded = decodeURIComponent(raw)\n } catch {\n throw new ShareLinkParseError(code, `${what}: malformed percent-encoding in ${shown}`)\n }\n if (decoded === '') {\n throw new ShareLinkParseError(code, `${what}: empty`)\n }\n if (encode(decoded) !== raw) {\n throw new ShareLinkParseError(\n code,\n `${what}: ${shown} is not canonical encodeURIComponent output`,\n )\n }\n return decoded\n}\n\n/** Path-segment decode — canonical, plus the dot-segment refusal. */\nfunction decodePathSegment(raw: string, what: string): string {\n const decoded = decodeCanonical(raw, 'MALFORMED_SEGMENT', what)\n if (decoded === '.' || decoded === '..') {\n throw new ShareLinkParseError('MALFORMED_SEGMENT', `${what}: \"${decoded}\" is a dot segment`)\n }\n return decoded\n}\n\n/** `v=` value → non-negative safe integer. Canonical base-10 only. */\nfunction parseVersion(raw: string): number {\n if (!/^(?:0|[1-9][0-9]*)$/.test(raw)) {\n throw new ShareLinkParseError(\n 'INVALID_VERSION',\n `version \"${raw}\" is not a canonical base-10 integer`,\n )\n }\n const version = Number(raw)\n if (!Number.isSafeInteger(version)) {\n throw new ShareLinkParseError('INVALID_VERSION', `version \"${raw}\" exceeds 2^53 - 1`)\n }\n return version\n}\n\n/** Resolve string | URL input to a URL object, fail closed. */\nfunction toUrl(input: string | URL): URL {\n if (input instanceof URL) {\n if (input.protocol !== 'http:' && input.protocol !== 'https:') {\n throw new ShareLinkParseError(\n 'UNKNOWN_PREFIX',\n `unsupported protocol \"${input.protocol}\" — share links are http(s) URLs or bare /r/ paths`,\n )\n }\n return input\n }\n if (input.startsWith('/')) {\n try {\n return new URL(input, DUMMY_BASE)\n } catch {\n throw new ShareLinkParseError('MALFORMED_URL', 'unparseable share path')\n }\n }\n if (/^https?:\\/\\//i.test(input)) {\n try {\n return new URL(input)\n } catch {\n throw new ShareLinkParseError('MALFORMED_URL', 'unparseable share URL')\n }\n }\n throw new ShareLinkParseError(\n 'UNKNOWN_PREFIX',\n 'input is neither a \"/\"-rooted share path nor an http(s) URL',\n )\n}\n\n/**\n * Parse a share link — full URL, `URL` instance, or bare `/r/…` path.\n *\n * A LIFF permalink prefix is tolerated: when the path does not start\n * with `/r/` directly, the share path is taken from the FIRST `/r/`\n * segment boundary (`https://liff.line.me/{liffId}/r/…` → `/r/…`). No\n * fuzzy matching beyond that single rule.\n *\n * Fail closed: anything outside the grammar throws\n * {@link ShareLinkParseError}. Never a default-vault fallback.\n */\nexport function parseShareLink(input: string | URL): ShareLink {\n const url = toUrl(input)\n\n let pathname = url.pathname\n if (!pathname.startsWith('/r/')) {\n // LIFF permalink tolerance — only after the direct parse failed. An\n // indexOf hit is always a whole `r` segment (the match is slash-\n // delimited on both sides), so this cannot fire mid-segment.\n const idx = pathname.indexOf('/r/')\n if (idx === -1) {\n throw new ShareLinkParseError(\n 'UNKNOWN_PREFIX',\n `path \"${pathname}\" has no /r/ share prefix`,\n )\n }\n pathname = pathname.slice(idx)\n }\n\n // \"/r/{vault}/{collection}/{record}\" splits into exactly 5 parts:\n // ['', 'r', vault, collection, record].\n const rawSegments = pathname.split('/')\n if (rawSegments.length !== 5) {\n throw new ShareLinkParseError(\n 'MALFORMED_PATH',\n `expected /r/{vaultHandle}/{collection}/{recordId} — got ${rawSegments.length - 2} segment(s) after /r/`,\n )\n }\n\n const vaultHandle = decodePathSegment(rawSegments[2]!, 'vault handle')\n if (!isULID(vaultHandle)) {\n throw new ShareLinkParseError(\n 'INVALID_VAULT_HANDLE',\n `vault handle \"${vaultHandle}\" is not a ULID`,\n )\n }\n const collection = decodePathSegment(rawSegments[3]!, 'collection')\n const recordId = decodePathSegment(rawSegments[4]!, 'record id')\n\n // Query — manual parse of url.search (NOT URLSearchParams, whose\n // form-urlencoded rules decode \"+\" as a space and hide duplicates).\n let period: string | undefined\n let version: number | undefined\n if (url.search !== '') {\n const seen = new Set<string>()\n for (const pair of url.search.slice(1).split('&')) {\n const eq = pair.indexOf('=')\n if (eq <= 0) {\n throw new ShareLinkParseError('MALFORMED_QUERY', `query pair \"${pair}\" is not key=value`)\n }\n const key = pair.slice(0, eq)\n const rawValue = pair.slice(eq + 1)\n if (key !== 'period' && key !== 'v') {\n throw new ShareLinkParseError('UNKNOWN_QUERY_KEY', `unknown query key \"${key}\"`)\n }\n if (seen.has(key)) {\n throw new ShareLinkParseError('DUPLICATE_QUERY_KEY', `duplicate query key \"${key}\"`)\n }\n seen.add(key)\n if (key === 'period') {\n period = decodeCanonical(rawValue, 'MALFORMED_QUERY', 'period', {\n encode: encodeQueryValue,\n })\n } else {\n version = parseVersion(rawValue)\n }\n }\n }\n\n // Fragment — the ONLY place a grant token may appear.\n let grantToken: string | undefined\n if (url.hash !== '' && url.hash !== '#') {\n const fragment = url.hash.slice(1)\n if (!fragment.startsWith('g=')) {\n throw new ShareLinkParseError(\n 'MALFORMED_FRAGMENT',\n 'fragment is not a g={token} grant carrier',\n )\n }\n grantToken = decodeCanonical(fragment.slice(2), 'MALFORMED_FRAGMENT', 'grant token', {\n redact: true,\n })\n }\n\n return {\n vaultHandle,\n collection,\n recordId,\n ...(period !== undefined ? { period } : {}),\n ...(version !== undefined ? { version } : {}),\n ...(grantToken !== undefined ? { grantToken } : {}),\n }\n}\n\n/** Build-side part validation. Returns the (narrowed) string value. */\nfunction requirePart(value: unknown, what: string, dotSegmentRefused = false): string {\n if (typeof value !== 'string' || value === '') {\n throw new ShareLinkParseError('INVALID_PARTS', `${what} must be a non-empty string`)\n }\n if (dotSegmentRefused && (value === '.' || value === '..')) {\n throw new ShareLinkParseError('INVALID_PARTS', `${what}: \"${value}\" is a dot segment`)\n }\n return value\n}\n\n/**\n * Build a RELATIVE share link (`/r/…`) from typed parts. Callers prefix\n * their surface origin — the LIFF permalink base, the PWA origin, or the\n * vendor console — either themselves or via `base`, which is prepended\n * VERBATIM (no validation, no joining logic; pass it without a trailing\n * slash, e.g. `https://liff.line.me/{liffId}`).\n *\n * The grant token, when present, is emitted ONLY as the `#g=` fragment.\n * Invalid parts throw {@link ShareLinkParseError} with code\n * `'INVALID_PARTS'` — every built link is guaranteed to round-trip\n * through {@link parseShareLink}.\n */\nexport function buildShareLink(parts: ShareLinkParts, base?: string): string {\n const { vaultHandle, collection, recordId, period, version, grantToken } = parts\n\n if (typeof vaultHandle !== 'string' || !isULID(vaultHandle)) {\n throw new ShareLinkParseError('INVALID_PARTS', 'vaultHandle is not a ULID')\n }\n const path = `/r/${vaultHandle}/${encodeURIComponent(\n requirePart(collection, 'collection', true),\n )}/${encodeURIComponent(requirePart(recordId, 'recordId', true))}`\n\n const query: string[] = []\n if (period !== undefined) {\n query.push(`period=${encodeQueryValue(requirePart(period, 'period'))}`)\n }\n if (version !== undefined) {\n if (typeof version !== 'number' || !Number.isSafeInteger(version) || version < 0) {\n throw new ShareLinkParseError('INVALID_PARTS', 'version must be a non-negative safe integer')\n }\n query.push(`v=${version}`)\n }\n\n const fragment =\n grantToken !== undefined\n ? `#g=${encodeURIComponent(requirePart(grantToken, 'grantToken'))}`\n : ''\n\n return (base ?? '') + path + (query.length > 0 ? `?${query.join('&')}` : '') + fragment\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/share-link/share-link.ts"],"sourcesContent":["/**\n * Portal share-link grammar + parse/build helper (#806).\n *\n * ONE canonical link grammar addresses `vault / period / collection /\n * record` across all three portal surfaces — the LIFF permalink path\n * (`https://liff.line.me/{liffId}/r/…`), the installed-PWA same-origin\n * path (`https://{portal-origin}/r/…`), and the vendor backend console.\n * The link SHAPE is identical everywhere; only the origin prefix\n * differs, and callers own the prefix.\n *\n * ## Grammar (frozen — see `__tests__/share-link-surface.golden.json`)\n *\n * ```\n * share-link = [ base ] \"/r/\" vault \"/\" collection \"/\" record\n * [ \"?\" query ] [ \"#\" fragment ]\n * vault = ULID ; 26 uppercase Crockford base32 chars\n * collection = segment ; opaque, non-empty after decode\n * record = segment ; opaque, non-empty after decode\n * query = pair *( \"&\" pair ) ; keys unique; ONLY these two keys\n * pair = \"period=\" component ; opaque period id\n * / \"v=\" version ; record version\n * version = \"0\" / %x31-39 *DIGIT ; base-10 integer, ≤ 2^53 − 1\n * fragment = \"g=\" component ; single-use grant token (opaque)\n * ```\n *\n * `segment` / `component` are strict-canonical `encodeURIComponent`\n * output: decoding then re-encoding MUST reproduce the raw text exactly\n * (uppercase-hex escapes, `+` is a literal plus never a space, `%2F`\n * inside a segment never creates an extra segment). Path segments\n * additionally may not decode to `.` or `..`. Links produced by\n * {@link buildShareLink} always satisfy this; hand-written links using\n * only unreserved characters (`inv-001`, ULIDs, …) do too.\n *\n * ## Transport rule for the grant token\n *\n * The optional single-use grant token travels ONLY in the URL fragment\n * (`#g={token}`) — never in the path or query — because fragments are\n * not sent to servers, so the token never reaches server logs, CDNs, or\n * LINE's infrastructure. Same rule `on-magic-link` follows. The token is\n * fully OPAQUE here: no validation beyond non-empty, no decoding of its\n * semantics — grant semantics live in `on-magic-link` (not imported).\n *\n * ## Fail closed\n *\n * {@link parseShareLink} throws a typed {@link ShareLinkParseError} for\n * ANYTHING that is not exactly the grammar above — unknown prefix,\n * missing/extra/empty/malformed segments, a non-ULID vault handle,\n * duplicate or unknown query keys, malformed fragments. There is never a\n * default-vault (or any other) fallback; the app maps the error to a 404.\n *\n * Zero dependencies on the hub floor: pure string/`URL` code, no crypto,\n * no store — LIFF shells, PWAs, and klum-db can import this subpath\n * without dragging anything else. (The one internal import is the pure,\n * leaf ULID shape check shared with the pod format.)\n */\n\nimport { isULID } from '../with-pod/ulid.js'\n\n/** Machine-readable {@link ShareLinkParseError} discriminant. */\nexport type ShareLinkErrorCode =\n | 'MALFORMED_URL'\n | 'UNKNOWN_PREFIX'\n | 'MALFORMED_PATH'\n | 'MALFORMED_SEGMENT'\n | 'INVALID_VAULT_HANDLE'\n | 'MALFORMED_QUERY'\n | 'DUPLICATE_QUERY_KEY'\n | 'UNKNOWN_QUERY_KEY'\n | 'INVALID_VERSION'\n | 'MALFORMED_FRAGMENT'\n | 'INVALID_PARTS'\n\n/**\n * Typed failure for the share-link grammar. Thrown by\n * {@link parseShareLink} for any input outside the grammar (fail closed —\n * map to an app-level 404, never a default-vault fallback), and by\n * {@link buildShareLink} with code `'INVALID_PARTS'` when the caller\n * hands it parts that could not round-trip.\n *\n * Standalone (`extends Error`, not `NoydbError`) so this subpath stays\n * import-light; the `code` field follows the same machine-readable\n * convention as the kernel error classes.\n */\nexport class ShareLinkParseError extends Error {\n /** Machine-readable error code. Stable across library versions. */\n readonly code: ShareLinkErrorCode\n\n constructor(code: ShareLinkErrorCode, message: string) {\n super(message)\n this.name = 'ShareLinkParseError'\n this.code = code\n }\n}\n\n/**\n * The logical address a share link carries. Input to\n * {@link buildShareLink}; {@link parseShareLink} returns the same shape\n * (as the readonly {@link ShareLink} alias) with optional fields OMITTED\n * (not `undefined`) when absent from the link.\n */\nexport interface ShareLinkParts {\n /** Vault handle — a 26-character uppercase Crockford-base32 ULID. */\n vaultHandle: string\n /** Collection name — opaque, non-empty. */\n collection: string\n /** Record id — opaque, non-empty. */\n recordId: string\n /** Optional period id (`?period=`) — opaque, non-empty. */\n period?: string\n /** Optional record version (`?v=`) — non-negative safe integer. */\n version?: number\n /**\n * Optional single-use grant token (`#g=`) — opaque, non-empty. Carried\n * ONLY in the URL fragment; semantics belong to `on-magic-link`.\n */\n grantToken?: string\n}\n\n/** A successfully parsed share link. See {@link ShareLinkParts}. */\nexport type ShareLink = Readonly<ShareLinkParts>\n\n// Dummy base for parsing bare `/r/…` paths with the WHATWG URL parser.\n// `.invalid` is reserved (RFC 2606) — it can never collide with a real\n// origin, and the host is discarded anyway (only path/query/fragment\n// matter to the grammar).\nconst DUMMY_BASE = 'https://share-link.invalid'\n\n/**\n * Canonical encoding of a QUERY value. `encodeURIComponent`, plus `'`\n * → `%27`: the WHATWG URL parser percent-encodes the apostrophe inside\n * the query component of special (http/https) URLs — if we emitted a\n * raw `'`, any URL-object round-trip (browser address bar, `new URL`)\n * would rewrite the link into a form the strict parser then rejects.\n * Path and fragment components don't need this — every character the\n * URL parser escapes there is already escaped by `encodeURIComponent`.\n */\nfunction encodeQueryValue(value: string): string {\n return encodeURIComponent(value).replace(/'/g, '%27')\n}\n\n/**\n * Strict-canonical decode: `raw` must be exactly what the given\n * canonical `encode` produces for the decoded text — no `+`-for-space,\n * no lowercase hex escapes, no unencoded reserved characters,\n * non-empty. `redact` keeps the raw text out of the error message\n * (grant tokens).\n */\nfunction decodeCanonical(\n raw: string,\n code: ShareLinkErrorCode,\n what: string,\n { redact = false, encode = encodeURIComponent }: {\n redact?: boolean\n encode?: (value: string) => string\n } = {},\n): string {\n const shown = redact ? '(redacted)' : `\"${raw}\"`\n let decoded: string\n try {\n decoded = decodeURIComponent(raw)\n } catch {\n throw new ShareLinkParseError(code, `${what}: malformed percent-encoding in ${shown}`)\n }\n if (decoded === '') {\n throw new ShareLinkParseError(code, `${what}: empty`)\n }\n if (encode(decoded) !== raw) {\n throw new ShareLinkParseError(\n code,\n `${what}: ${shown} is not canonical encodeURIComponent output`,\n )\n }\n return decoded\n}\n\n/** Path-segment decode — canonical, plus the dot-segment refusal. */\nfunction decodePathSegment(raw: string, what: string): string {\n const decoded = decodeCanonical(raw, 'MALFORMED_SEGMENT', what)\n if (decoded === '.' || decoded === '..') {\n throw new ShareLinkParseError('MALFORMED_SEGMENT', `${what}: \"${decoded}\" is a dot segment`)\n }\n return decoded\n}\n\n/** `v=` value → non-negative safe integer. Canonical base-10 only. */\nfunction parseVersion(raw: string): number {\n if (!/^(?:0|[1-9][0-9]*)$/.test(raw)) {\n throw new ShareLinkParseError(\n 'INVALID_VERSION',\n `version \"${raw}\" is not a canonical base-10 integer`,\n )\n }\n const version = Number(raw)\n if (!Number.isSafeInteger(version)) {\n throw new ShareLinkParseError('INVALID_VERSION', `version \"${raw}\" exceeds 2^53 - 1`)\n }\n return version\n}\n\n/** Resolve string | URL input to a URL object, fail closed. */\nfunction toUrl(input: string | URL): URL {\n if (input instanceof URL) {\n if (input.protocol !== 'http:' && input.protocol !== 'https:') {\n throw new ShareLinkParseError(\n 'UNKNOWN_PREFIX',\n `unsupported protocol \"${input.protocol}\" — share links are http(s) URLs or bare /r/ paths`,\n )\n }\n return input\n }\n if (input.startsWith('/')) {\n try {\n return new URL(input, DUMMY_BASE)\n } catch {\n throw new ShareLinkParseError('MALFORMED_URL', 'unparseable share path')\n }\n }\n if (/^https?:\\/\\//i.test(input)) {\n try {\n return new URL(input)\n } catch {\n throw new ShareLinkParseError('MALFORMED_URL', 'unparseable share URL')\n }\n }\n throw new ShareLinkParseError(\n 'UNKNOWN_PREFIX',\n 'input is neither a \"/\"-rooted share path nor an http(s) URL',\n )\n}\n\n/**\n * Parse a share link — full URL, `URL` instance, or bare `/r/…` path.\n *\n * A LIFF permalink prefix is tolerated: when the path does not start\n * with `/r/` directly, the share path is taken from the FIRST `/r/`\n * segment boundary (`https://liff.line.me/{liffId}/r/…` → `/r/…`). No\n * fuzzy matching beyond that single rule.\n *\n * Fail closed: anything outside the grammar throws\n * {@link ShareLinkParseError}. Never a default-vault fallback.\n */\nexport function parseShareLink(input: string | URL): ShareLink {\n const url = toUrl(input)\n\n let pathname = url.pathname\n if (!pathname.startsWith('/r/')) {\n // LIFF permalink tolerance — only after the direct parse failed. An\n // indexOf hit is always a whole `r` segment (the match is slash-\n // delimited on both sides), so this cannot fire mid-segment.\n const idx = pathname.indexOf('/r/')\n if (idx === -1) {\n throw new ShareLinkParseError(\n 'UNKNOWN_PREFIX',\n `path \"${pathname}\" has no /r/ share prefix`,\n )\n }\n pathname = pathname.slice(idx)\n }\n\n // \"/r/{vault}/{collection}/{record}\" splits into exactly 5 parts:\n // ['', 'r', vault, collection, record].\n const rawSegments = pathname.split('/')\n if (rawSegments.length !== 5) {\n throw new ShareLinkParseError(\n 'MALFORMED_PATH',\n `expected /r/{vaultHandle}/{collection}/{recordId} — got ${rawSegments.length - 2} segment(s) after /r/`,\n )\n }\n\n const vaultHandle = decodePathSegment(rawSegments[2]!, 'vault handle')\n if (!isULID(vaultHandle)) {\n throw new ShareLinkParseError(\n 'INVALID_VAULT_HANDLE',\n `vault handle \"${vaultHandle}\" is not a ULID`,\n )\n }\n const collection = decodePathSegment(rawSegments[3]!, 'collection')\n const recordId = decodePathSegment(rawSegments[4]!, 'record id')\n\n // Query — manual parse of url.search (NOT URLSearchParams, whose\n // form-urlencoded rules decode \"+\" as a space and hide duplicates).\n let period: string | undefined\n let version: number | undefined\n if (url.search !== '') {\n const seen = new Set<string>()\n for (const pair of url.search.slice(1).split('&')) {\n const eq = pair.indexOf('=')\n if (eq <= 0) {\n throw new ShareLinkParseError('MALFORMED_QUERY', `query pair \"${pair}\" is not key=value`)\n }\n const key = pair.slice(0, eq)\n const rawValue = pair.slice(eq + 1)\n if (key !== 'period' && key !== 'v') {\n throw new ShareLinkParseError('UNKNOWN_QUERY_KEY', `unknown query key \"${key}\"`)\n }\n if (seen.has(key)) {\n throw new ShareLinkParseError('DUPLICATE_QUERY_KEY', `duplicate query key \"${key}\"`)\n }\n seen.add(key)\n if (key === 'period') {\n period = decodeCanonical(rawValue, 'MALFORMED_QUERY', 'period', {\n encode: encodeQueryValue,\n })\n } else {\n version = parseVersion(rawValue)\n }\n }\n }\n\n // Fragment — the ONLY place a grant token may appear.\n let grantToken: string | undefined\n if (url.hash !== '' && url.hash !== '#') {\n const fragment = url.hash.slice(1)\n if (!fragment.startsWith('g=')) {\n throw new ShareLinkParseError(\n 'MALFORMED_FRAGMENT',\n 'fragment is not a g={token} grant carrier',\n )\n }\n grantToken = decodeCanonical(fragment.slice(2), 'MALFORMED_FRAGMENT', 'grant token', {\n redact: true,\n })\n }\n\n return {\n vaultHandle,\n collection,\n recordId,\n ...(period !== undefined ? { period } : {}),\n ...(version !== undefined ? { version } : {}),\n ...(grantToken !== undefined ? { grantToken } : {}),\n }\n}\n\n/** Build-side part validation. Returns the (narrowed) string value. */\nfunction requirePart(value: unknown, what: string, dotSegmentRefused = false): string {\n if (typeof value !== 'string' || value === '') {\n throw new ShareLinkParseError('INVALID_PARTS', `${what} must be a non-empty string`)\n }\n if (dotSegmentRefused && (value === '.' || value === '..')) {\n throw new ShareLinkParseError('INVALID_PARTS', `${what}: \"${value}\" is a dot segment`)\n }\n return value\n}\n\n/**\n * Build a RELATIVE share link (`/r/…`) from typed parts. Callers prefix\n * their surface origin — the LIFF permalink base, the PWA origin, or the\n * vendor console — either themselves or via `base`, which is prepended\n * VERBATIM (no validation, no joining logic; pass it without a trailing\n * slash, e.g. `https://liff.line.me/{liffId}`).\n *\n * The grant token, when present, is emitted ONLY as the `#g=` fragment.\n * Invalid parts throw {@link ShareLinkParseError} with code\n * `'INVALID_PARTS'` — every built link is guaranteed to round-trip\n * through {@link parseShareLink}.\n */\nexport function buildShareLink(parts: ShareLinkParts, base?: string): string {\n const { vaultHandle, collection, recordId, period, version, grantToken } = parts\n\n if (typeof vaultHandle !== 'string' || !isULID(vaultHandle)) {\n throw new ShareLinkParseError('INVALID_PARTS', 'vaultHandle is not a ULID')\n }\n const path = `/r/${vaultHandle}/${encodeURIComponent(\n requirePart(collection, 'collection', true),\n )}/${encodeURIComponent(requirePart(recordId, 'recordId', true))}`\n\n const query: string[] = []\n if (period !== undefined) {\n query.push(`period=${encodeQueryValue(requirePart(period, 'period'))}`)\n }\n if (version !== undefined) {\n if (typeof version !== 'number' || !Number.isSafeInteger(version) || version < 0) {\n throw new ShareLinkParseError('INVALID_PARTS', 'version must be a non-negative safe integer')\n }\n query.push(`v=${version}`)\n }\n\n const fragment =\n grantToken !== undefined\n ? `#g=${encodeURIComponent(requirePart(grantToken, 'grantToken'))}`\n : ''\n\n return (base ?? '') + path + (query.length > 0 ? `?${query.join('&')}` : '') + fragment\n}\n"],"mappings":"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","names":[]}
@@ -14,7 +14,6 @@ import "./chunk-LP7BEXCT.js";
14
14
  import "./chunk-SL3JAAKN.js";
15
15
  import "./chunk-N4CHCXDL.js";
16
16
  import "./chunk-7O3Y4PPD.js";
17
- import "./chunk-PZ5AY32C.js";
18
17
  export {
19
18
  ATTESTATIONS_COLLECTION,
20
19
  REVOKED_RECORD_ID,
@@ -22,4 +21,4 @@ export {
22
21
  loadOrCreateSigner,
23
22
  loadSigner
24
23
  };
25
- //# sourceMappingURL=signer-7NK7TMWU.js.map
24
+ //# sourceMappingURL=signer-5K3WDHOC.js.map
@@ -4,7 +4,7 @@ import {
4
4
  import {
5
5
  readPod,
6
6
  writePod
7
- } from "../chunk-PKJ43CMD.js";
7
+ } from "../chunk-NLOLF6LD.js";
8
8
  import "../chunk-MLO75SFJ.js";
9
9
  import "../chunk-CIGAQBJR.js";
10
10
  import "../chunk-4RUFYGWG.js";
@@ -17,7 +17,6 @@ import "../chunk-N4CHCXDL.js";
17
17
  import {
18
18
  SnapshotNotFoundError
19
19
  } from "../chunk-7O3Y4PPD.js";
20
- import "../chunk-PZ5AY32C.js";
21
20
 
22
21
  // src/with-fork/snapshots/engine.ts
23
22
  var SnapshotEngine = class {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/with-fork/snapshots/engine.ts","../../src/with-fork/snapshots/active.ts"],"sourcesContent":["import { writePod, readPod } from '../../with-pod/pod.js'\nimport { SnapshotNotFoundError } from '../../kernel/errors.js'\nimport type { NoydbPodStore } from '../../kernel/types.js'\nimport type { Vault } from '../../kernel/vault.js'\nimport type { SnapshotMeta, RetentionPolicy, SnapshotIndex } from './strategy.js'\n\nexport class SnapshotEngine {\n constructor(\n private readonly store: NoydbPodStore,\n private readonly retention: RetentionPolicy,\n ) {}\n\n private indexKey(vaultName: string): string {\n return `${vaultName}__index`\n }\n\n private snapKey(vaultName: string, n: number): string {\n return `${vaultName}__snap_${n.toString().padStart(6, '0')}`\n }\n\n private autoKey(vaultName: string): string {\n return `${vaultName}__auto`\n }\n\n private async readIndex(\n vaultName: string,\n ): Promise<{ index: SnapshotIndex; indexVersion: string | null }> {\n const result = await this.store.readBundle(this.indexKey(vaultName))\n if (!result) return { index: { snapshots: [], nextCounter: 1 }, indexVersion: null }\n const text = new TextDecoder().decode(result.bytes)\n return { index: JSON.parse(text) as SnapshotIndex, indexVersion: result.version }\n }\n\n private async writeIndex(\n vaultName: string,\n index: SnapshotIndex,\n expectedVersion: string | null,\n ): Promise<void> {\n const bytes = new TextEncoder().encode(JSON.stringify(index))\n await this.store.writeBundle(this.indexKey(vaultName), bytes, expectedVersion)\n }\n\n async snapshot(\n vault: Vault,\n by: string,\n opts?: { label?: string; note?: string },\n ): Promise<SnapshotMeta> {\n const bytes = await writePod(vault, {})\n const { index, indexVersion } = await this.readIndex(vault.name)\n const key = this.snapKey(vault.name, index.nextCounter)\n\n // Write blob first. If the subsequent index write fails, the next snapshot()\n // call will re-derive the same key (counter not persisted) and overwrite this blob.\n // This is an accepted v1 trade-off: failure window is narrow and requires a store\n // error between the two writes. A retry produces a fresh snapshot at the same key.\n await this.store.writeBundle(key, bytes, null)\n\n const meta: SnapshotMeta = {\n version: key,\n ...(opts?.label !== undefined ? { label: opts.label } : {}),\n ...(opts?.note !== undefined ? { note: opts.note } : {}),\n exportedAt: new Date().toISOString(),\n exportedBy: by,\n size: bytes.length,\n integrity: 'verified',\n }\n\n const newIndex: SnapshotIndex = {\n snapshots: [...index.snapshots, meta],\n nextCounter: index.nextCounter + 1,\n // Preserve the rolling auto slot — on-demand snapshots never touch it.\n ...(index.auto ? { auto: index.auto } : {}),\n }\n const toDelete = this.applyRetention(newIndex)\n await this.writeIndex(vault.name, newIndex, indexVersion)\n\n for (const k of toDelete) {\n await this.store.deleteBundle(k)\n }\n\n return meta\n }\n\n /**\n * Rolling auto-snapshot. Overwrites the single fixed `<vault>__auto` key and\n * stores its meta in `index.auto`, separate from the immutable `snapshots`\n * pool — retention never prunes it. Used by the cadence scheduler.\n */\n async autoSnapshot(\n vault: Vault,\n by: string,\n opts?: { label?: string; note?: string },\n ): Promise<SnapshotMeta> {\n const bytes = await writePod(vault, {})\n const { index, indexVersion } = await this.readIndex(vault.name)\n const key = this.autoKey(vault.name)\n\n // Unconditional overwrite of the rolling slot.\n await this.store.writeBundle(key, bytes, null)\n\n const meta: SnapshotMeta = {\n version: key,\n label: opts?.label ?? 'auto',\n ...(opts?.note !== undefined ? { note: opts.note } : {}),\n exportedAt: new Date().toISOString(),\n exportedBy: by,\n size: bytes.length,\n integrity: 'verified',\n auto: true,\n }\n\n index.auto = meta\n await this.writeIndex(vault.name, index, indexVersion)\n return meta\n }\n\n async listSnapshots(vaultId: string): Promise<SnapshotMeta[]> {\n const { index } = await this.readIndex(vaultId)\n const immutable = [...index.snapshots].reverse()\n return index.auto ? [index.auto, ...immutable] : immutable\n }\n\n async restoreSnapshot(vault: Vault, version: string): Promise<void> {\n if (!version.startsWith(`${vault.name}__`)) throw new SnapshotNotFoundError(version)\n const result = await this.store.readBundle(version)\n if (!result) throw new SnapshotNotFoundError(version)\n const { dumpJson } = await readPod(result.bytes)\n await vault.load(dumpJson)\n }\n\n /**\n * Applies the configured retention policy to `index`, mutating `index.snapshots`\n * in place and returning the blob keys that should be deleted from the store.\n * Called by `snapshot()` before the index is written.\n *\n * @internal — public for direct testing only\n */\n applyRetention(index: SnapshotIndex): string[] {\n const prune = this.retention.prune ?? true\n if (!prune) return []\n\n const toDelete: string[] = []\n let remaining = index.snapshots.slice()\n\n if (this.retention.keepLast !== undefined && remaining.length > this.retention.keepLast) {\n const excess = remaining.splice(0, remaining.length - this.retention.keepLast)\n toDelete.push(...excess.map(m => m.version))\n }\n\n if (this.retention.maxAgeDays !== undefined) {\n const cutoffMs = this.retention.maxAgeDays * 86_400_000\n const now = Date.now()\n const fresh = remaining.filter(m => now - new Date(m.exportedAt).getTime() <= cutoffMs)\n toDelete.push(...remaining.filter(m => !fresh.includes(m)).map(m => m.version))\n remaining = fresh\n }\n\n index.snapshots = remaining\n return toDelete\n }\n}\n","import { SnapshotEngine } from './engine.js'\nimport type { SnapshotsStrategy, RetentionPolicy } from './strategy.js'\nimport type { SnapshotPolicy } from './policy.js'\nimport type { NoydbPodStore } from '../../kernel/types.js'\nimport type { Vault } from '../../kernel/vault.js'\n\nexport interface WithSnapshotsOptions {\n /** Bundle store where snapshot blobs and the sidecar index are written. */\n store: NoydbPodStore\n /**\n * Declarative retention policy. Enforced eagerly after each on-demand `snapshot()`.\n * Defaults to no retention (all on-demand snapshots kept forever). Never affects\n * the rolling auto-snapshot.\n */\n retention?: RetentionPolicy\n /**\n * Automatic-snapshot cadence. Default `mode:'manual'` ⇒ no timers; snapshots\n * stay on-demand. Set `mode:'debounce'`/`'interval'` to enable auto-snapshots\n * to the rolling `<vault>__auto` key.\n */\n snapshotPolicy?: SnapshotPolicy\n}\n\nexport function withSnapshots(opts: WithSnapshotsOptions): SnapshotsStrategy {\n const engine = new SnapshotEngine(opts.store, opts.retention ?? {})\n return {\n snapshot(vault, by, snapOpts) {\n return engine.snapshot(vault as Vault, by, snapOpts)\n },\n listSnapshots(vaultId) {\n return engine.listSnapshots(vaultId)\n },\n restoreSnapshot(vault, version) {\n return engine.restoreSnapshot(vault as Vault, version)\n },\n autoSnapshot(vault, by, snapOpts) {\n return engine.autoSnapshot(vault as Vault, by, snapOpts)\n },\n ...(opts.snapshotPolicy ? { policy: opts.snapshotPolicy } : {}),\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/with-fork/snapshots/engine.ts","../../src/with-fork/snapshots/active.ts"],"sourcesContent":["import { writePod, readPod } from '../../with-pod/pod.js'\nimport { SnapshotNotFoundError } from '../../kernel/errors.js'\nimport type { NoydbPodStore } from '../../kernel/types.js'\nimport type { Vault } from '../../kernel/vault.js'\nimport type { SnapshotMeta, RetentionPolicy, SnapshotIndex } from './strategy.js'\n\nexport class SnapshotEngine {\n constructor(\n private readonly store: NoydbPodStore,\n private readonly retention: RetentionPolicy,\n ) {}\n\n private indexKey(vaultName: string): string {\n return `${vaultName}__index`\n }\n\n private snapKey(vaultName: string, n: number): string {\n return `${vaultName}__snap_${n.toString().padStart(6, '0')}`\n }\n\n private autoKey(vaultName: string): string {\n return `${vaultName}__auto`\n }\n\n private async readIndex(\n vaultName: string,\n ): Promise<{ index: SnapshotIndex; indexVersion: string | null }> {\n const result = await this.store.readBundle(this.indexKey(vaultName))\n if (!result) return { index: { snapshots: [], nextCounter: 1 }, indexVersion: null }\n const text = new TextDecoder().decode(result.bytes)\n return { index: JSON.parse(text) as SnapshotIndex, indexVersion: result.version }\n }\n\n private async writeIndex(\n vaultName: string,\n index: SnapshotIndex,\n expectedVersion: string | null,\n ): Promise<void> {\n const bytes = new TextEncoder().encode(JSON.stringify(index))\n await this.store.writeBundle(this.indexKey(vaultName), bytes, expectedVersion)\n }\n\n async snapshot(\n vault: Vault,\n by: string,\n opts?: { label?: string; note?: string },\n ): Promise<SnapshotMeta> {\n const bytes = await writePod(vault, {})\n const { index, indexVersion } = await this.readIndex(vault.name)\n const key = this.snapKey(vault.name, index.nextCounter)\n\n // Write blob first. If the subsequent index write fails, the next snapshot()\n // call will re-derive the same key (counter not persisted) and overwrite this blob.\n // This is an accepted v1 trade-off: failure window is narrow and requires a store\n // error between the two writes. A retry produces a fresh snapshot at the same key.\n await this.store.writeBundle(key, bytes, null)\n\n const meta: SnapshotMeta = {\n version: key,\n ...(opts?.label !== undefined ? { label: opts.label } : {}),\n ...(opts?.note !== undefined ? { note: opts.note } : {}),\n exportedAt: new Date().toISOString(),\n exportedBy: by,\n size: bytes.length,\n integrity: 'verified',\n }\n\n const newIndex: SnapshotIndex = {\n snapshots: [...index.snapshots, meta],\n nextCounter: index.nextCounter + 1,\n // Preserve the rolling auto slot — on-demand snapshots never touch it.\n ...(index.auto ? { auto: index.auto } : {}),\n }\n const toDelete = this.applyRetention(newIndex)\n await this.writeIndex(vault.name, newIndex, indexVersion)\n\n for (const k of toDelete) {\n await this.store.deleteBundle(k)\n }\n\n return meta\n }\n\n /**\n * Rolling auto-snapshot. Overwrites the single fixed `<vault>__auto` key and\n * stores its meta in `index.auto`, separate from the immutable `snapshots`\n * pool — retention never prunes it. Used by the cadence scheduler.\n */\n async autoSnapshot(\n vault: Vault,\n by: string,\n opts?: { label?: string; note?: string },\n ): Promise<SnapshotMeta> {\n const bytes = await writePod(vault, {})\n const { index, indexVersion } = await this.readIndex(vault.name)\n const key = this.autoKey(vault.name)\n\n // Unconditional overwrite of the rolling slot.\n await this.store.writeBundle(key, bytes, null)\n\n const meta: SnapshotMeta = {\n version: key,\n label: opts?.label ?? 'auto',\n ...(opts?.note !== undefined ? { note: opts.note } : {}),\n exportedAt: new Date().toISOString(),\n exportedBy: by,\n size: bytes.length,\n integrity: 'verified',\n auto: true,\n }\n\n index.auto = meta\n await this.writeIndex(vault.name, index, indexVersion)\n return meta\n }\n\n async listSnapshots(vaultId: string): Promise<SnapshotMeta[]> {\n const { index } = await this.readIndex(vaultId)\n const immutable = [...index.snapshots].reverse()\n return index.auto ? [index.auto, ...immutable] : immutable\n }\n\n async restoreSnapshot(vault: Vault, version: string): Promise<void> {\n if (!version.startsWith(`${vault.name}__`)) throw new SnapshotNotFoundError(version)\n const result = await this.store.readBundle(version)\n if (!result) throw new SnapshotNotFoundError(version)\n const { dumpJson } = await readPod(result.bytes)\n await vault.load(dumpJson)\n }\n\n /**\n * Applies the configured retention policy to `index`, mutating `index.snapshots`\n * in place and returning the blob keys that should be deleted from the store.\n * Called by `snapshot()` before the index is written.\n *\n * @internal — public for direct testing only\n */\n applyRetention(index: SnapshotIndex): string[] {\n const prune = this.retention.prune ?? true\n if (!prune) return []\n\n const toDelete: string[] = []\n let remaining = index.snapshots.slice()\n\n if (this.retention.keepLast !== undefined && remaining.length > this.retention.keepLast) {\n const excess = remaining.splice(0, remaining.length - this.retention.keepLast)\n toDelete.push(...excess.map(m => m.version))\n }\n\n if (this.retention.maxAgeDays !== undefined) {\n const cutoffMs = this.retention.maxAgeDays * 86_400_000\n const now = Date.now()\n const fresh = remaining.filter(m => now - new Date(m.exportedAt).getTime() <= cutoffMs)\n toDelete.push(...remaining.filter(m => !fresh.includes(m)).map(m => m.version))\n remaining = fresh\n }\n\n index.snapshots = remaining\n return toDelete\n }\n}\n","import { SnapshotEngine } from './engine.js'\nimport type { SnapshotsStrategy, RetentionPolicy } from './strategy.js'\nimport type { SnapshotPolicy } from './policy.js'\nimport type { NoydbPodStore } from '../../kernel/types.js'\nimport type { Vault } from '../../kernel/vault.js'\n\nexport interface WithSnapshotsOptions {\n /** Bundle store where snapshot blobs and the sidecar index are written. */\n store: NoydbPodStore\n /**\n * Declarative retention policy. Enforced eagerly after each on-demand `snapshot()`.\n * Defaults to no retention (all on-demand snapshots kept forever). Never affects\n * the rolling auto-snapshot.\n */\n retention?: RetentionPolicy\n /**\n * Automatic-snapshot cadence. Default `mode:'manual'` ⇒ no timers; snapshots\n * stay on-demand. Set `mode:'debounce'`/`'interval'` to enable auto-snapshots\n * to the rolling `<vault>__auto` key.\n */\n snapshotPolicy?: SnapshotPolicy\n}\n\nexport function withSnapshots(opts: WithSnapshotsOptions): SnapshotsStrategy {\n const engine = new SnapshotEngine(opts.store, opts.retention ?? {})\n return {\n snapshot(vault, by, snapOpts) {\n return engine.snapshot(vault as Vault, by, snapOpts)\n },\n listSnapshots(vaultId) {\n return engine.listSnapshots(vaultId)\n },\n restoreSnapshot(vault, version) {\n return engine.restoreSnapshot(vault as Vault, version)\n },\n autoSnapshot(vault, by, snapOpts) {\n return engine.autoSnapshot(vault as Vault, by, snapOpts)\n },\n ...(opts.snapshotPolicy ? { policy: opts.snapshotPolicy } : {}),\n }\n}\n"],"mappings":"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","names":[]}
@@ -6,7 +6,7 @@ import {
6
6
  isMVStale,
7
7
  markMVStale,
8
8
  resolveStaleMVOnRead
9
- } from "./chunk-E2MYJGSG.js";
9
+ } from "./chunk-AMODH7AU.js";
10
10
  import "./chunk-5D2ALSM5.js";
11
11
  import "./chunk-CIGAQBJR.js";
12
12
  import "./chunk-4RUFYGWG.js";
@@ -17,7 +17,6 @@ import "./chunk-LP7BEXCT.js";
17
17
  import "./chunk-SL3JAAKN.js";
18
18
  import "./chunk-N4CHCXDL.js";
19
19
  import "./chunk-7O3Y4PPD.js";
20
- import "./chunk-PZ5AY32C.js";
21
20
  export {
22
21
  MV_STALE_COLLECTION,
23
22
  clearMVStale,
@@ -27,4 +26,4 @@ export {
27
26
  markMVStale,
28
27
  resolveStaleMVOnRead
29
28
  };
30
- //# sourceMappingURL=stale-2CHKZTMK.js.map
29
+ //# sourceMappingURL=stale-AU74OPUW.js.map