@noy-db/hub 0.7.0-pre.12 → 0.7.0-pre.14
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.
- package/CHANGELOG.md +210 -0
- package/README.md +4 -3
- package/dist/{api-UE2XQKDN.js → api-JPZT6BDD.js} +1 -2
- package/dist/as/index.js +3 -4
- package/dist/as/index.js.map +1 -1
- package/dist/at/index.js +0 -1
- package/dist/attestation/index.js +6 -7
- package/dist/attestation/index.js.map +1 -1
- package/dist/{backup-R6A4VZRX.js → backup-AKWIMYWU.js} +1 -2
- package/dist/{backup-R6A4VZRX.js.map → backup-AKWIMYWU.js.map} +1 -1
- package/dist/blobs/index.js +0 -1
- package/dist/blobs/index.js.map +1 -1
- package/dist/broker/index.js +3 -4
- package/dist/broker/index.js.map +1 -1
- package/dist/by/index.js +0 -1
- package/dist/cargo/index.js +10 -11
- package/dist/{chunk-5FUULQ6F.js → chunk-2HVILGVR.js} +6 -6
- package/dist/{chunk-DJ3LNNZQ.js → chunk-3LK2XZCW.js} +2 -2
- package/dist/chunk-4SZMZAYN.js +1652 -0
- package/dist/chunk-4SZMZAYN.js.map +1 -0
- package/dist/chunk-66TQOPFM.js +37 -0
- package/dist/chunk-66TQOPFM.js.map +1 -0
- package/dist/{chunk-PQKG5AHH.js → chunk-6X6SEMEE.js} +4 -2
- package/dist/{chunk-PQKG5AHH.js.map → chunk-6X6SEMEE.js.map} +1 -1
- package/dist/{chunk-E3EBMC4G.js → chunk-AMODH7AU.js} +2 -2
- package/dist/{chunk-B2ZSGYSF.js → chunk-B2KCRDJM.js} +3 -3
- package/dist/{chunk-TO3EQ7HF.js → chunk-BLN54W3L.js} +2 -2
- package/dist/{chunk-WQF3QABE.js → chunk-BOBFL5TW.js} +9 -79
- package/dist/chunk-BOBFL5TW.js.map +1 -0
- package/dist/{chunk-XMF3GKUR.js → chunk-DRB6IHPS.js} +90 -910
- package/dist/chunk-DRB6IHPS.js.map +1 -0
- package/dist/{chunk-H7XCIM3Z.js → chunk-EKVNVJVV.js} +26 -16
- package/dist/chunk-EKVNVJVV.js.map +1 -0
- package/dist/{chunk-ZR2YMODS.js → chunk-EKZ6VZYF.js} +2 -2
- package/dist/{chunk-RTYJRFQO.js → chunk-EWPRPP3M.js} +1 -1
- package/dist/{chunk-JJR2JIN4.js → chunk-FFBYRWDJ.js} +2 -2
- package/dist/{chunk-VGICZPVE.js → chunk-FUJSIP4K.js} +2 -2
- package/dist/chunk-MGOXJIBV.js +176 -0
- package/dist/chunk-MGOXJIBV.js.map +1 -0
- package/dist/{chunk-PKJ43CMD.js → chunk-NLOLF6LD.js} +2 -2
- package/dist/{chunk-7NTBVFGD.js → chunk-NOSRK3ZZ.js} +5 -5
- package/dist/{chunk-HN4FNA4X.js → chunk-OMB2ZKIT.js} +8 -8
- package/dist/{chunk-Y4MJQ2UG.js → chunk-QQ2YMQZU.js} +2 -2
- package/dist/{chunk-URHRIAV6.js → chunk-REDOOY2Q.js} +2 -2
- package/dist/chunk-REDOOY2Q.js.map +1 -0
- package/dist/{chunk-YCXICMOY.js → chunk-RJ7NN2UR.js} +2 -2
- package/dist/chunk-RTBJ5IOO.js +87 -0
- package/dist/chunk-RTBJ5IOO.js.map +1 -0
- package/dist/{chunk-KL7AS3HV.js → chunk-SMH6SRX7.js} +6 -3
- package/dist/chunk-SMH6SRX7.js.map +1 -0
- package/dist/{chunk-QHW35ZON.js → chunk-TGPST6FK.js} +2 -2
- package/dist/{chunk-THNJ3IKU.js → chunk-ULUMAZ5S.js} +2 -2
- package/dist/{chunk-E5OHKSBD.js → chunk-V2PNPF6J.js} +3 -3
- package/dist/{chunk-NO272T7Q.js → chunk-V4PXLYWM.js} +2 -31
- package/dist/chunk-V4PXLYWM.js.map +1 -0
- package/dist/{chunk-BERJ4AWW.js → chunk-VGXZT72I.js} +2 -2
- package/dist/{chunk-2Z47NMFK.js → chunk-WZW37ZHL.js} +2 -2
- package/dist/{chunk-SWVNZQVR.js → chunk-YPXNAP72.js} +2 -169
- package/dist/chunk-YPXNAP72.js.map +1 -0
- package/dist/classified/index.js +1 -2
- package/dist/{classified-marker-67HBIJMZ.js → classified-marker-OO65UYXN.js} +3 -5
- package/dist/{classified-marker-67HBIJMZ.js.map → classified-marker-OO65UYXN.js.map} +1 -1
- package/dist/{collection-facade-MGGHQ3HU.js → collection-facade-VOWW732S.js} +4 -4
- package/dist/{computed-LSEZYRJS.js → computed-VPOKQK4N.js} +1 -2
- package/dist/consent/index.js +0 -1
- package/dist/consent/index.js.map +1 -1
- package/dist/cover/index.js +0 -1
- package/dist/crdt/index.js +0 -1
- package/dist/crdt/index.js.map +1 -1
- package/dist/custody/index.js +1 -2
- package/dist/{dead-filter-M3NZUWBZ.js → dead-filter-OMMVBAKZ.js} +2 -3
- package/dist/{dead-filter-M3NZUWBZ.js.map → dead-filter-OMMVBAKZ.js.map} +1 -1
- package/dist/debug/index.js +0 -1
- package/dist/debug/index.js.map +1 -1
- package/dist/{delegation-OLJUWTVA.js → delegation-VRYMQRCF.js} +1 -2
- package/dist/{delegation-OLJUWTVA.js.map → delegation-VRYMQRCF.js.map} +1 -1
- package/dist/derivations/index.js +7 -4
- package/dist/{derive-LY47HG7Q.js → derive-HHBRFS2U.js} +2 -3
- package/dist/directory/index.js +0 -1
- package/dist/{dispatch-XEBHUGBV.js → dispatch-DC3LQJ3S.js} +14 -10
- package/dist/dispatch-DC3LQJ3S.js.map +1 -0
- package/dist/{dispatch-44J4TL72.js → dispatch-GGFBWQU7.js} +5 -7
- package/dist/{dispatch-44J4TL72.js.map → dispatch-GGFBWQU7.js.map} +1 -1
- package/dist/{enclave-4YBQK77G.js → enclave-VM2YQONI.js} +1 -2
- package/dist/{executor-TOLPBHOC.js → executor-5NIMLSKZ.js} +1 -2
- package/dist/{executor-KSPGNVDO.js → executor-5XJNV5H5.js} +4 -4
- package/dist/{executor-MZJXBWNM.js → executor-AQVXV5GZ.js} +1 -2
- package/dist/{export-accessible-LGV6IAFX.js → export-accessible-2LOBM67T.js} +3 -4
- package/dist/{extract-partition-RYJZIKQZ.js → extract-partition-AZGSEMIF.js} +3 -4
- package/dist/{fanout-sidecar-TSFPXY5C.js → fanout-sidecar-HJIVUPTD.js} +1 -2
- package/dist/{fanout-sidecar-TSFPXY5C.js.map → fanout-sidecar-HJIVUPTD.js.map} +1 -1
- package/dist/{fence-watcher-ZVSMLKQD.js → fence-watcher-U2PW6JU5.js} +1 -3
- package/dist/{fence-watcher-ZVSMLKQD.js.map → fence-watcher-U2PW6JU5.js.map} +1 -1
- package/dist/{find-LC7P63MX.js → find-2ABFCZX4.js} +1 -2
- package/dist/forget/index.js +0 -1
- package/dist/forget/index.js.map +1 -1
- package/dist/guards/index.js +0 -1
- package/dist/history/index.js +0 -1
- package/dist/history/index.js.map +1 -1
- package/dist/i18n/index.js +0 -1
- package/dist/i18n/index.js.map +1 -1
- package/dist/index.js +43 -40
- package/dist/index.js.map +1 -1
- package/dist/indexing/index.js +0 -1
- package/dist/indexing/index.js.map +1 -1
- package/dist/introspection/index.js +2 -3
- package/dist/{issue-IOXM3RSJ.js → issue-RH4H733Z.js} +1 -2
- package/dist/{json-schema-I22JCYCG.js → json-schema-V3M5MUCM.js} +1 -3
- package/dist/{json-schema-I22JCYCG.js.map → json-schema-V3M5MUCM.js.map} +1 -1
- package/dist/kernel/collection-config.d.ts +34 -0
- package/dist/lazy/index.js +3 -3
- package/dist/{ledger-HXHIOD36.js → ledger-454SU3VE.js} +1 -2
- package/dist/{liberate-4APIV7FR.js → liberate-AAZSP2BT.js} +5 -6
- package/dist/{link-set-6RAHEKZN.js → link-set-67CAMUAL.js} +1 -2
- package/dist/{managed-secret-P3JQ7ZG4.js → managed-secret-NHRYG7CD.js} +1 -2
- package/dist/materialized-views/index.js +5 -5
- package/dist/{migrate-cek-25KNTS5U.js → migrate-cek-5ENH64AA.js} +1 -2
- package/dist/{migrate-cek-25KNTS5U.js.map → migrate-cek-5ENH64AA.js.map} +1 -1
- package/dist/money/index.js +0 -1
- package/dist/{noydb-JJXPICIC.js → noydb-Y53CQHZK.js} +17 -15
- package/dist/overlay-views/index.js +0 -1
- package/dist/periods/index.js +1 -2
- package/dist/pod/index.js +18 -16
- package/dist/{pod-handle-6II7KFBR.js → pod-handle-S7AMCJIF.js} +2 -3
- package/dist/{pod-handle-6II7KFBR.js.map → pod-handle-S7AMCJIF.js.map} +1 -1
- package/dist/policy/index.js +0 -1
- package/dist/portability/index.js +6 -7
- package/dist/portability/index.js.map +1 -1
- package/dist/{post-register-BPJ36A6D.js → post-register-HIYY4Y5S.js} +4 -5
- package/dist/{post-register-BPJ36A6D.js.map → post-register-HIYY4Y5S.js.map} +1 -1
- package/dist/{purge-scope-FGNNLWHD.js → purge-scope-4HEFPDOW.js} +1 -3
- package/dist/{purge-scope-FGNNLWHD.js.map → purge-scope-4HEFPDOW.js.map} +1 -1
- package/dist/query/index.js +0 -1
- package/dist/{read-only-facade-CHCKSEPD.js → read-only-facade-BIGO37U3.js} +1 -2
- package/dist/reduce/index.js +0 -1
- package/dist/reduce/index.js.map +1 -1
- package/dist/{register-AEOOGKNO.js → register-7OJWAWT5.js} +3 -4
- package/dist/{registry-SKSOUXNF.js → registry-23WF3TGH.js} +1 -2
- package/dist/{registry-SHMGHEK4.js → registry-4AYVUNXK.js} +1 -2
- package/dist/{registry-I6FP4677.js → registry-5MVJGPXW.js} +1 -2
- package/dist/{registry-BZQIVNNO.js → registry-HDINK5QW.js} +8 -5
- package/dist/{request-withdrawal-6HJT4ODF.js → request-withdrawal-P5EQATZ7.js} +5 -6
- package/dist/{reveal-DDQBDWPM.js → reveal-IMGQ2EE5.js} +1 -2
- package/dist/{reveal-DDQBDWPM.js.map → reveal-IMGQ2EE5.js.map} +1 -1
- package/dist/{revoke-U7U3FMXU.js → revoke-POHP4GPB.js} +1 -2
- package/dist/satellites/index.js +0 -1
- package/dist/schema-update/index.js +0 -1
- package/dist/sealed-record/index.js +3 -4
- package/dist/sealed-record/index.js.map +1 -1
- package/dist/search/index.js +1 -2
- package/dist/{seed-3RXT4VGN.js → seed-UYR6YG3V.js} +1 -2
- package/dist/{seed-3RXT4VGN.js.map → seed-UYR6YG3V.js.map} +1 -1
- package/dist/sequence/index.js +0 -1
- package/dist/session/index.js +0 -1
- package/dist/session/index.js.map +1 -1
- package/dist/shadow/index.js +0 -1
- package/dist/shadow/index.js.map +1 -1
- package/dist/share-link/index.js +0 -1
- package/dist/share-link/index.js.map +1 -1
- package/dist/{signer-7NK7TMWU.js → signer-5K3WDHOC.js} +1 -2
- package/dist/snapshots/index.js +1 -2
- package/dist/snapshots/index.js.map +1 -1
- package/dist/{stale-KSPG6LFX.js → stale-AU74OPUW.js} +2 -3
- package/dist/{storage-WP4M7IFO.js → storage-AVJA5BTW.js} +1 -2
- package/dist/store/index.js +0 -1
- package/dist/{store-coordination-provider-O2VUEMJH.js → store-coordination-provider-RX2CGWXV.js} +1 -2
- package/dist/{store-coordination-provider-O2VUEMJH.js.map → store-coordination-provider-RX2CGWXV.js.map} +1 -1
- package/dist/sync/index.js +0 -1
- package/dist/sync/index.js.map +1 -1
- package/dist/team/index.js +0 -1
- package/dist/tiers/index.js +1 -2
- package/dist/to/index.js +0 -1
- package/dist/to/index.js.map +1 -1
- package/dist/transactions/index.js +2 -3
- package/dist/transactions/index.js.map +1 -1
- package/dist/{ulid-VQW6ERQN.js → ulid-4MKXG325.js} +1 -2
- package/dist/util/index.js +0 -1
- package/dist/util/index.js.map +1 -1
- package/dist/{vault-diff-FE5J35NJ.js → vault-diff-C6WUJFNP.js} +1 -2
- package/dist/vault-head/index.js +0 -1
- package/dist/vault-head/index.js.map +1 -1
- package/dist/{verify-JBXCAGDL.js → verify-KOV2G5D6.js} +1 -2
- package/dist/{verify-JBXCAGDL.js.map → verify-KOV2G5D6.js.map} +1 -1
- package/dist/{walk-P3E6QSNC.js → walk-ERAJ5RHO.js} +3 -4
- package/dist/with-formula/derivations/registry.d.ts +6 -1
- package/dist/with-shape/introspection/describe.d.ts +2 -33
- package/dist/with-shape/introspection/field-meta.d.ts +86 -0
- package/dist/{withdraw-accessible-6Q7NE4EY.js → withdraw-accessible-42D32Z7G.js} +4 -5
- package/package.json +7 -3
- package/dist/chunk-7BE7ZDVQ.js +0 -722
- package/dist/chunk-7BE7ZDVQ.js.map +0 -1
- package/dist/chunk-H7XCIM3Z.js.map +0 -1
- package/dist/chunk-KL7AS3HV.js.map +0 -1
- package/dist/chunk-NO272T7Q.js.map +0 -1
- package/dist/chunk-PZ5AY32C.js +0 -10
- package/dist/chunk-SWVNZQVR.js.map +0 -1
- package/dist/chunk-URHRIAV6.js.map +0 -1
- package/dist/chunk-WQF3QABE.js.map +0 -1
- package/dist/chunk-XMF3GKUR.js.map +0 -1
- package/dist/dispatch-XEBHUGBV.js.map +0 -1
- package/dist/withdraw-accessible-6Q7NE4EY.js.map +0 -1
- package/dist/zod-HAJM7LMS.js +0 -14763
- package/dist/zod-HAJM7LMS.js.map +0 -1
- /package/dist/{api-UE2XQKDN.js.map → api-JPZT6BDD.js.map} +0 -0
- /package/dist/{chunk-5FUULQ6F.js.map → chunk-2HVILGVR.js.map} +0 -0
- /package/dist/{chunk-DJ3LNNZQ.js.map → chunk-3LK2XZCW.js.map} +0 -0
- /package/dist/{chunk-E3EBMC4G.js.map → chunk-AMODH7AU.js.map} +0 -0
- /package/dist/{chunk-B2ZSGYSF.js.map → chunk-B2KCRDJM.js.map} +0 -0
- /package/dist/{chunk-TO3EQ7HF.js.map → chunk-BLN54W3L.js.map} +0 -0
- /package/dist/{chunk-ZR2YMODS.js.map → chunk-EKZ6VZYF.js.map} +0 -0
- /package/dist/{chunk-RTYJRFQO.js.map → chunk-EWPRPP3M.js.map} +0 -0
- /package/dist/{chunk-JJR2JIN4.js.map → chunk-FFBYRWDJ.js.map} +0 -0
- /package/dist/{chunk-VGICZPVE.js.map → chunk-FUJSIP4K.js.map} +0 -0
- /package/dist/{chunk-PKJ43CMD.js.map → chunk-NLOLF6LD.js.map} +0 -0
- /package/dist/{chunk-7NTBVFGD.js.map → chunk-NOSRK3ZZ.js.map} +0 -0
- /package/dist/{chunk-HN4FNA4X.js.map → chunk-OMB2ZKIT.js.map} +0 -0
- /package/dist/{chunk-Y4MJQ2UG.js.map → chunk-QQ2YMQZU.js.map} +0 -0
- /package/dist/{chunk-YCXICMOY.js.map → chunk-RJ7NN2UR.js.map} +0 -0
- /package/dist/{chunk-QHW35ZON.js.map → chunk-TGPST6FK.js.map} +0 -0
- /package/dist/{chunk-THNJ3IKU.js.map → chunk-ULUMAZ5S.js.map} +0 -0
- /package/dist/{chunk-E5OHKSBD.js.map → chunk-V2PNPF6J.js.map} +0 -0
- /package/dist/{chunk-BERJ4AWW.js.map → chunk-VGXZT72I.js.map} +0 -0
- /package/dist/{chunk-2Z47NMFK.js.map → chunk-WZW37ZHL.js.map} +0 -0
- /package/dist/{chunk-PZ5AY32C.js.map → collection-facade-VOWW732S.js.map} +0 -0
- /package/dist/{collection-facade-MGGHQ3HU.js.map → computed-VPOKQK4N.js.map} +0 -0
- /package/dist/{computed-LSEZYRJS.js.map → derive-HHBRFS2U.js.map} +0 -0
- /package/dist/{derive-LY47HG7Q.js.map → enclave-VM2YQONI.js.map} +0 -0
- /package/dist/{enclave-4YBQK77G.js.map → executor-5NIMLSKZ.js.map} +0 -0
- /package/dist/{executor-KSPGNVDO.js.map → executor-5XJNV5H5.js.map} +0 -0
- /package/dist/{executor-MZJXBWNM.js.map → executor-AQVXV5GZ.js.map} +0 -0
- /package/dist/{executor-TOLPBHOC.js.map → export-accessible-2LOBM67T.js.map} +0 -0
- /package/dist/{export-accessible-LGV6IAFX.js.map → extract-partition-AZGSEMIF.js.map} +0 -0
- /package/dist/{extract-partition-RYJZIKQZ.js.map → find-2ABFCZX4.js.map} +0 -0
- /package/dist/{find-LC7P63MX.js.map → issue-RH4H733Z.js.map} +0 -0
- /package/dist/{issue-IOXM3RSJ.js.map → ledger-454SU3VE.js.map} +0 -0
- /package/dist/{ledger-HXHIOD36.js.map → liberate-AAZSP2BT.js.map} +0 -0
- /package/dist/{liberate-4APIV7FR.js.map → link-set-67CAMUAL.js.map} +0 -0
- /package/dist/{link-set-6RAHEKZN.js.map → managed-secret-NHRYG7CD.js.map} +0 -0
- /package/dist/{managed-secret-P3JQ7ZG4.js.map → noydb-Y53CQHZK.js.map} +0 -0
- /package/dist/{noydb-JJXPICIC.js.map → read-only-facade-BIGO37U3.js.map} +0 -0
- /package/dist/{read-only-facade-CHCKSEPD.js.map → register-7OJWAWT5.js.map} +0 -0
- /package/dist/{register-AEOOGKNO.js.map → registry-23WF3TGH.js.map} +0 -0
- /package/dist/{registry-BZQIVNNO.js.map → registry-4AYVUNXK.js.map} +0 -0
- /package/dist/{registry-I6FP4677.js.map → registry-5MVJGPXW.js.map} +0 -0
- /package/dist/{registry-SHMGHEK4.js.map → registry-HDINK5QW.js.map} +0 -0
- /package/dist/{registry-SKSOUXNF.js.map → request-withdrawal-P5EQATZ7.js.map} +0 -0
- /package/dist/{request-withdrawal-6HJT4ODF.js.map → revoke-POHP4GPB.js.map} +0 -0
- /package/dist/{revoke-U7U3FMXU.js.map → signer-5K3WDHOC.js.map} +0 -0
- /package/dist/{signer-7NK7TMWU.js.map → stale-AU74OPUW.js.map} +0 -0
- /package/dist/{stale-KSPG6LFX.js.map → storage-AVJA5BTW.js.map} +0 -0
- /package/dist/{storage-WP4M7IFO.js.map → ulid-4MKXG325.js.map} +0 -0
- /package/dist/{ulid-VQW6ERQN.js.map → vault-diff-C6WUJFNP.js.map} +0 -0
- /package/dist/{vault-diff-FE5J35NJ.js.map → walk-ERAJ5RHO.js.map} +0 -0
- /package/dist/{walk-P3E6QSNC.js.map → withdraw-accessible-42D32Z7G.js.map} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,215 @@
|
|
|
1
1
|
# Changelog — hub
|
|
2
2
|
|
|
3
|
+
## 0.7.0-pre.14
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- The bundle-size gate now needs a growth to exceed BOTH its percentage tolerance
|
|
8
|
+
and an absolute byte allowance before failing (#1268). No runtime change — this
|
|
9
|
+
is the CI gate only.
|
|
10
|
+
|
|
11
|
+
A percentage on a small baseline measures the wrong thing. The `floor` scenario
|
|
12
|
+
is ~500 gzipped bytes, so 5% is ~25 bytes — narrower than a single
|
|
13
|
+
registration-time guard. The gate had fired twice on necessary validation
|
|
14
|
+
(#1249, #1266) and never once on the thing it exists to catch.
|
|
15
|
+
|
|
16
|
+
The two are separated by orders of magnitude: a subsystem leaking into a bundle
|
|
17
|
+
is kilobytes (measured elsewhere in the family: forcing an SDK inline moved a
|
|
18
|
+
package from 14,069 to 1,081,539 bytes), while a guard is tens of bytes. A
|
|
19
|
+
192-byte allowance sits far above the latter and far below the former.
|
|
20
|
+
|
|
21
|
+
Verified in both directions rather than assumed: a simulated leak (+406 bytes)
|
|
22
|
+
still FAILS, and a 26-byte guard at +5.2% now PASSES. The numbers still ratchet
|
|
23
|
+
and nothing was re-baselined.
|
|
24
|
+
|
|
25
|
+
- A materialized view whose `groupBy` names a VIRTUAL computed field is now
|
|
26
|
+
refused at collection registration (#1269).
|
|
27
|
+
|
|
28
|
+
A virtual field is evaluated on read and never stored, so the MV pipeline read
|
|
29
|
+
the stored row, found nothing, and bucketed every row under an `undefined` key —
|
|
30
|
+
a well-formed aggregate carrying a wrong NUMBER rather than an error, on the one
|
|
31
|
+
path where nobody re-checks the arithmetic. `query().where()` already refuses the
|
|
32
|
+
same field with `FieldNotQueryableError`, so the two halves of what reads as one
|
|
33
|
+
query layer disagreed.
|
|
34
|
+
|
|
35
|
+
Same principle as the executor's existing object-valued-group-key refusal
|
|
36
|
+
("refuse, don't bucket wrong") and the `triggerBy` virtual-target refusal
|
|
37
|
+
(#1266), applied at the earliest point that can see both the MV and the
|
|
38
|
+
collection's field modes.
|
|
39
|
+
|
|
40
|
+
**Known residue, stated rather than left to be discovered:** a single-query MV of
|
|
41
|
+
the shape `query: (db) => db.collection(name)…` constructs its own source from
|
|
42
|
+
inside `MaterializedViewRegistry.register()`, before the dependency is recorded,
|
|
43
|
+
so that shape is not caught by this guard. `unionSources`, an aggregate with
|
|
44
|
+
explicit `sources`, and any source built by an earlier `vault.collection()` call
|
|
45
|
+
are caught. The same ordering is already documented for the neighbouring
|
|
46
|
+
tiers+crdt guard.
|
|
47
|
+
|
|
48
|
+
- Two guards now refuse where the information to refuse already exists (design
|
|
49
|
+
pass: _where a check refuses vs where it quietly answers_).
|
|
50
|
+
|
|
51
|
+
**`fieldMeta` typo keys are refused at `vault.collection()`, not only at
|
|
52
|
+
`describe()`.** A collection nobody describes was never checked at all — and
|
|
53
|
+
`fieldMeta` is what carries `sensitivity`, so the unchecked case was the one
|
|
54
|
+
with a data-classification inventory hanging off it.
|
|
55
|
+
|
|
56
|
+
⚠️ **BREAKING for code that catches around `describe()`.** For a validator whose
|
|
57
|
+
fields read synchronously, `FieldMetaUnknownFieldError` now arrives from
|
|
58
|
+
`vault.collection()` instead of from `describe()`. This is not only "the throw
|
|
59
|
+
comes earlier" — **a `try` that wraps only the `describe()` call no longer
|
|
60
|
+
catches it at all**, so the error escapes the handler written for it. A pilot
|
|
61
|
+
consumer's guard suite had exactly this shape: it constructed the collection
|
|
62
|
+
outside the `try`, and on upgrade those tests would have started ERRORING rather
|
|
63
|
+
than failing with a clear message, reading as "the new release broke our tests"
|
|
64
|
+
instead of "the guard moved". If you assert on this error, catch around the
|
|
65
|
+
`vault.collection()` call — or around both, and report which surface rejected,
|
|
66
|
+
which turns the relocation into a changed label rather than a break.
|
|
67
|
+
|
|
68
|
+
The describe-time check is KEPT, not moved, because there are three tiers of
|
|
69
|
+
knowability rather than two: a name declared in the config itself needs no
|
|
70
|
+
schema; a validator whose fields read synchronously is checkable at
|
|
71
|
+
registration; fields that exist only after async derivation are checkable only
|
|
72
|
+
at `describe()`. Hoisting alone would move the check earlier for some
|
|
73
|
+
collections and remove it for others. Unchanged: no readable validator means no
|
|
74
|
+
check, because a TS-generic collection names real fields that appear in no
|
|
75
|
+
runtime config.
|
|
76
|
+
|
|
77
|
+
**`withMaterializedView` distinguishes an absent `rowKey` from a wrong-typed
|
|
78
|
+
one.** Passing a field NAME — the shape every neighbouring option takes —
|
|
79
|
+
reported `rowKey is required`, which names absence. It now says it must be a
|
|
80
|
+
function, names the type it got, and shows the fix. Two states that warrant
|
|
81
|
+
different responses no longer render identically.
|
|
82
|
+
|
|
83
|
+
- `zod` is now an OPTIONAL peer dependency instead of being vendored into the
|
|
84
|
+
bundle (#1227).
|
|
85
|
+
|
|
86
|
+
Hub shipped `zod@4.4.3` inside `dist/` — 548 KB, at a build-frozen version,
|
|
87
|
+
declared in no manifest field. `npm ls zod` in a consumer tree showed nothing
|
|
88
|
+
while that copy was reachable, so SBOM and audit tooling missed it and a zod
|
|
89
|
+
advisory could not be remediated by a consumer bumping zod.
|
|
90
|
+
|
|
91
|
+
**This was never a deliberate vendoring.** The loader's own comment said "this
|
|
92
|
+
is a dynamic import so it does not add a static zod dependency to hub" — but
|
|
93
|
+
tsup externalises DECLARED dependencies and bundles everything else, static or
|
|
94
|
+
dynamic alike, and zod was a devDependency only. The comment described an
|
|
95
|
+
intent the build silently defeated; it now records the actual mechanism.
|
|
96
|
+
|
|
97
|
+
**Declaring it is also the correctness fix, not only a size one.** Zod's
|
|
98
|
+
`toJSONSchema` reads a schema's internals, and the schema is built by the
|
|
99
|
+
CONSUMER's zod. A vendored copy meant hub inspected one zod's objects with a
|
|
100
|
+
different zod's reader, with version skew that nothing detected. Now there is
|
|
101
|
+
one zod — theirs.
|
|
102
|
+
|
|
103
|
+
Nothing to install: the peer is optional, exactly like `zod-to-json-schema`,
|
|
104
|
+
and the converter is still loaded lazily. A caller with a Zod v4 schema
|
|
105
|
+
necessarily already has zod; a caller without one never reaches the path.
|
|
106
|
+
|
|
107
|
+
Measured, both sides rebuilt: tarball 3.3 MB → 3.1 MB packed, 12.0 MB → 10.6 MB
|
|
108
|
+
unpacked. The README claim is updated in the same change to say what is now
|
|
109
|
+
true.
|
|
110
|
+
|
|
111
|
+
## 0.7.0-pre.13
|
|
112
|
+
|
|
113
|
+
### Patch Changes
|
|
114
|
+
|
|
115
|
+
- A FAILED non-strict lazy re-derive no longer clears the stale flag (#1258).
|
|
116
|
+
|
|
117
|
+
`resolveStaleOnRead` consumes the pending flag before reading the source (a
|
|
118
|
+
recursion guard for self-write outputs). A STRICT failure throws and the catch
|
|
119
|
+
restores it; a NON-STRICT failure warned and continued, leaving the flag
|
|
120
|
+
consumed — so the record was served as fresh and never retried, permanently,
|
|
121
|
+
because nothing would mark it stale again.
|
|
122
|
+
|
|
123
|
+
The decision this needed was what `strict: false` means. It means "a failed
|
|
124
|
+
derivation must not break the read", NOT "report this output as current". The
|
|
125
|
+
record is still served, the warning still fires, and the flag now survives so
|
|
126
|
+
the next read retries — strict-mode behaviour minus the throw.
|
|
127
|
+
|
|
128
|
+
Deliberate cost, stated so it is not later mistaken for a bug: a
|
|
129
|
+
permanently-failing non-strict derivation now re-runs on every read of that id
|
|
130
|
+
rather than once. That is louder and more expensive than silently serving a
|
|
131
|
+
stale value forever, and it is the trade made everywhere else here — a degraded
|
|
132
|
+
state must not render as a healthy one.
|
|
133
|
+
|
|
134
|
+
- A rollup child that RE-PARENTS now recomputes the old parent as well as the new
|
|
135
|
+
one (#1257).
|
|
136
|
+
|
|
137
|
+
The dispatcher read the rollup key from the incoming record only, so moving a
|
|
138
|
+
child from parent A to parent B recomputed B and left A's aggregate holding a
|
|
139
|
+
number that was correct before the move. That is the dangerous shape: a stale
|
|
140
|
+
total reads as data, not as an error, so nothing prompts anyone to re-check it.
|
|
141
|
+
|
|
142
|
+
Same old-value class as the `triggerBy` update fan-out fixed in #1249 — and it
|
|
143
|
+
is a small change rather than a new mechanism only because that fix added
|
|
144
|
+
prior-record capture to the write path, which this reuses.
|
|
145
|
+
|
|
146
|
+
Consistent with the rest of the derivation surface: a write that does not thread
|
|
147
|
+
a prior record (a sync-applied wave write, a tiers restore) degrades to
|
|
148
|
+
new-parent-only, the previous behaviour, rather than guessing at the old key.
|
|
149
|
+
|
|
150
|
+
- `schemaFieldKeys` now unwraps `z.preprocess()` on both Zod majors, so the two
|
|
151
|
+
field-typo guards (#1253's `fieldMeta`, #1249's `triggerBy` match) see through
|
|
152
|
+
it (#1262, reported by the pilot).
|
|
153
|
+
|
|
154
|
+
The `0.7.0-pre.12` fix followed refinement effects only and put `preprocess` on
|
|
155
|
+
the carve-out side with `transform`. That was wrong: `z.preprocess(fn, inner)`
|
|
156
|
+
rewrites the INPUT and then parses with `inner`, so the parsed record's keys ARE
|
|
157
|
+
`inner`'s keys — measured, `preprocess({a,b})` parses to `['a','b']` while
|
|
158
|
+
`transform` parses to `['c']`.
|
|
159
|
+
|
|
160
|
+
Scope, measured rather than assumed: this left the **sync** `describe()` path
|
|
161
|
+
unguarded for wrapped schemas. The async path was never affected —
|
|
162
|
+
`derivePersistedSchema` sees through `preprocess` on its own, so
|
|
163
|
+
`buildDescription` gets a populated field map and never reaches the
|
|
164
|
+
`schemaFieldKeys` fallback. The sync path is public, is what tooling reaches
|
|
165
|
+
for first, and is the only path some Zod 3 consumers can reach at all (the
|
|
166
|
+
async path needs the `zod-to-json-schema` peer), so the gap was real — but it
|
|
167
|
+
was one path, not both.
|
|
168
|
+
|
|
169
|
+
Replaced the effect-kind allowlist with one rule that covers every wrapper on
|
|
170
|
+
both majors: **follow the output side.** These keys describe the parsed record,
|
|
171
|
+
so the only question a wrapper raises is whether it changes the parsed shape.
|
|
172
|
+
Zod 3 `ZodEffects` follows `_def.schema` for `refinement` and `preprocess`;
|
|
173
|
+
Zod 4 wraps both `z.preprocess()` and `.transform()` as a `ZodPipe` and
|
|
174
|
+
following `_def.out` resolves them with no effect-kind test at all — preprocess
|
|
175
|
+
reaches the object, `.transform()` reaches a shapeless `ZodTransform` and stays
|
|
176
|
+
silent on its own. **This also fixes Zod 4 `z.preprocess()`, which the narrower
|
|
177
|
+
one-line widening would have missed.**
|
|
178
|
+
|
|
179
|
+
`.transform()` remains deliberately silent on both majors: it replaces the
|
|
180
|
+
output, so the inner keys would describe a record that is never stored. The two
|
|
181
|
+
are one string apart and mean opposite things, so tests pin them against each
|
|
182
|
+
other on both majors.
|
|
183
|
+
|
|
184
|
+
- `triggerBy` match targets are now checked against where the field actually
|
|
185
|
+
LIVES, not just whether it is declared (#1266, reported by the pilot).
|
|
186
|
+
|
|
187
|
+
A derivation matcher reads STORED records. A `mode: 'virtual'` computed field is
|
|
188
|
+
evaluated on the read path and never persisted — but it appears in `computed:`
|
|
189
|
+
(and in `via(computed(...))`) exactly like a materialized one, so registration
|
|
190
|
+
accepted it and the fan-out then matched nothing, forever. That is the precise
|
|
191
|
+
failure the #1249 match guard exists to prevent, reached THROUGH the guard
|
|
192
|
+
rather than around it, and it is the first thing a consumer reaches for when a
|
|
193
|
+
match target is not already stored.
|
|
194
|
+
|
|
195
|
+
Refused at registration rather than supported: matching a virtual field means
|
|
196
|
+
running user code for every candidate row, turning an indexed narrow into a full
|
|
197
|
+
scan. `mode: 'materialized'` is stored, already works, and is what the error
|
|
198
|
+
names. Both sides are refused — `to` reads the source record and `from` reads
|
|
199
|
+
the written record, and both are the stored shape. (The report covered `to`;
|
|
200
|
+
`from` had the identical defect.)
|
|
201
|
+
|
|
202
|
+
The virtual check runs even when the schema's field list is unreadable, unlike
|
|
203
|
+
the typo guard beside it. The typo guard needs a field list to compare against
|
|
204
|
+
and stays silent without one; this one does not — "declared, but never stored"
|
|
205
|
+
is provable from the declaration alone.
|
|
206
|
+
|
|
207
|
+
Second defect fixed in the same change: `viaFields` was missing from the guard's
|
|
208
|
+
key set entirely, so a `via()`-declared MATERIALIZED field — a perfectly valid
|
|
209
|
+
match target — was rejected as an undeclared typo. A guard that refuses valid
|
|
210
|
+
configurations is how people learn to stop trusting it, so both directions ship
|
|
211
|
+
together, each with a test that fails without its half of the fix.
|
|
212
|
+
|
|
3
213
|
## 0.7.0-pre.12
|
|
4
214
|
|
|
5
215
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -77,9 +77,10 @@ const all = await invoices.list()
|
|
|
77
77
|
- **Offline-first sync** — push/pull with optimistic concurrency on encrypted envelopes
|
|
78
78
|
- **Audit history** — full-copy snapshots with `history()`, `diff()`, `revert()`, `pruneHistory()`
|
|
79
79
|
- **No third-party runtime dependencies to install** — the only runtime dependency is
|
|
80
|
-
`@noy-db/attestation`, on the same version line.
|
|
81
|
-
|
|
82
|
-
|
|
80
|
+
`@noy-db/attestation`, on the same version line. `zod` and `zod-to-json-schema` are OPTIONAL
|
|
81
|
+
peers used by the persisted-schema converter: nothing is installed on your behalf, nothing is
|
|
82
|
+
vendored into the bundle, and if you do use Zod schemas the converter reads YOUR copy rather
|
|
83
|
+
than a second one frozen at hub's build time.
|
|
83
84
|
|
|
84
85
|
## Cross-vault queries
|
|
85
86
|
|
|
@@ -12,9 +12,8 @@ import "./chunk-LP7BEXCT.js";
|
|
|
12
12
|
import "./chunk-SL3JAAKN.js";
|
|
13
13
|
import "./chunk-N4CHCXDL.js";
|
|
14
14
|
import "./chunk-7O3Y4PPD.js";
|
|
15
|
-
import "./chunk-PZ5AY32C.js";
|
|
16
15
|
export {
|
|
17
16
|
UserApi,
|
|
18
17
|
createUserApi
|
|
19
18
|
};
|
|
20
|
-
//# sourceMappingURL=api-
|
|
19
|
+
//# sourceMappingURL=api-JPZT6BDD.js.map
|
package/dist/as/index.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import "../chunk-UBSFPGVJ.js";
|
|
2
|
-
import "../chunk-
|
|
2
|
+
import "../chunk-FUJSIP4K.js";
|
|
3
3
|
import {
|
|
4
4
|
applyListProjection
|
|
5
5
|
} from "../chunk-VG2ZSFYD.js";
|
|
6
6
|
import "../chunk-RXLYKVTF.js";
|
|
7
7
|
import "../chunk-VYYM3MEN.js";
|
|
8
|
-
import "../chunk-
|
|
8
|
+
import "../chunk-REDOOY2Q.js";
|
|
9
9
|
import "../chunk-Y2NBBT6K.js";
|
|
10
10
|
import "../chunk-WWNYSV7S.js";
|
|
11
11
|
import "../chunk-CIGAQBJR.js";
|
|
@@ -19,7 +19,6 @@ import "../chunk-N4CHCXDL.js";
|
|
|
19
19
|
import {
|
|
20
20
|
ValidationError
|
|
21
21
|
} from "../chunk-7O3Y4PPD.js";
|
|
22
|
-
import "../chunk-PZ5AY32C.js";
|
|
23
22
|
|
|
24
23
|
// src/port/as/active.ts
|
|
25
24
|
function redactChunk(chunk, ctx, redact) {
|
|
@@ -75,7 +74,7 @@ function contextFor(vault) {
|
|
|
75
74
|
// export fails instead.
|
|
76
75
|
describe: (collection) => vault.collection(collection).describe(),
|
|
77
76
|
plan: async (records, policy, formatId, idKey) => {
|
|
78
|
-
const { diffVault } = await import("../vault-diff-
|
|
77
|
+
const { diffVault } = await import("../vault-diff-C6WUJFNP.js");
|
|
79
78
|
const plan = await diffVault(vault, records, idKey ? { idKey } : {});
|
|
80
79
|
return {
|
|
81
80
|
plan,
|
package/dist/as/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/port/as/active.ts"],"sourcesContent":["/**\n * `withFormats()` — the service that consumes the `as-*` port.\n *\n * Everything with a correctness or security consequence lives here rather\n * than in each format: the capability gate, the vault read, and redaction.\n * A {@link NoydbFormat} receives records and returns bytes.\n *\n * That is the whole point of the inversion (ADR 0004). The conformance kit it\n * replaces could only check implementors who ran it; a format that cannot\n * reach a vault is gated whether its author cooperated or not.\n *\n * @module\n */\nimport type { NoydbFormat, ImportPlan } from './types.js'\nimport type {\n FormatsStrategy,\n FormatsContext,\n FormatExportOptions,\n FormatImportOptions,\n} from '../with/formats-strategy.js'\nimport type { ExportChunk } from '../../kernel/types.js'\nimport type { Vault } from '../../kernel/vault.js'\nimport type { ImportPolicy } from './types.js'\nimport { applyListProjection } from '../../with-shape/introspection/index.js'\nimport type { CollectionDescription, ListProjectionOptions } from '../../with-shape/introspection/index.js'\nimport { ValidationError } from '../../kernel/errors.js'\n\n/** Apply the redaction projection hub owns, so a format never asks for it. */\nfunction redactChunk(\n chunk: ExportChunk,\n ctx: FormatsContext,\n redact: FormatExportOptions['redact'],\n): ExportChunk {\n if (redact === undefined) return chunk\n const described = ctx.describe(chunk.collection) as CollectionDescription | undefined\n if (!described) {\n throw new Error(\n `vault.export: redaction was requested but '${chunk.collection}' has no description to redact against. ` +\n 'Open the collection with its classifiedFields / fieldMeta options first.',\n )\n }\n const opts: ListProjectionOptions | undefined =\n redact === true ? undefined : { sensitivity: redact.sensitivity as never }\n const records = chunk.records.map((r) =>\n applyListProjection(described, r as Record<string, unknown>, opts),\n )\n return { ...chunk, records: records as typeof chunk.records }\n}\n\n/**\n * Derive the narrow, format-facing capability from the vault.\n *\n * Lives here rather than on `Vault` because `kernel-surface` said so, and it\n * was right: this is service capability, not spine. The spine keeps two\n * delegating methods and nothing else.\n */\nfunction contextFor(vault: Vault): FormatsContext {\n return {\n // The empty-id guards are NOT defensive padding. The\n // plaintext tier is gated per-format, so a missing id means the calling\n // binding is broken — and without this it would reach the gate, miss the\n // allowlist, and surface as `ExportCapabilityError`: \"the owner did not\n // grant this\", which sends the reader to the keyring instead of to the\n // format. A loud TypeError names the real fault.\n assertCanExport: (tier, format) => {\n if (tier === 'bundle') return vault.assertCanExport('bundle')\n if (!format) throw new TypeError(\"assertCanExport('plaintext') requires a non-empty format id\")\n vault.assertCanExport('plaintext', format)\n },\n assertCanImport: (tier, format) => {\n if (tier === 'bundle') return vault.assertCanImport('bundle')\n if (!format) throw new TypeError(\"assertCanImport('plaintext') requires a non-empty format id\")\n vault.assertCanImport('plaintext', format)\n },\n // ⚠️ Filtered HERE, not passed to exportStream — `ExportStreamOptions`\n // has no `collections` field, so the option I first passed was silently\n // dropped and hub read every collection. Caught by an as-json test\n // expecting ['invoices'] and getting ['invoices','payments'].\n //\n // Post-read filtering is correct but not free: hub still DECRYPTS the\n // collections the caller excluded. Pushing the filter down into\n // exportStream is the real fix and is a hub change beyond this port.\n chunks: async function* (collections) {\n const wanted = collections ? new Set(collections) : null\n for await (const chunk of vault.exportStream({ granularity: 'collection' })) {\n if (wanted && !wanted.has(chunk.collection)) continue\n yield chunk\n }\n },\n // NOT swallowed. A caller who asked for redaction and silently got\n // unredacted output is the exact failure this port exists to remove — a\n // security duty that fails quiet. If the description cannot be read, the\n // export fails instead.\n describe: (collection) => vault.collection(collection).describe() as never,\n plan: async (records, policy: ImportPolicy, formatId: string, idKey?: string) => {\n const { diffVault } = await import('../../with-cargo/vault-diff.js')\n const plan = await diffVault(vault, records as never, idKey ? { idKey } : {})\n return {\n plan,\n policy,\n // The apply walk every as-* package had independently: added always,\n // modified unless insert-only, deleted only on replace — in one\n // transaction so a partial failure rolls back. Owned once, here.\n apply: async () => {\n await vault.noydb.transaction((tx) => {\n const txVault = tx.vault(vault.name)\n const reason = `import:${formatId}`\n for (const e of plan.added) {\n txVault.collection(e.collection).put(e.id, e.record as never, { reason })\n }\n if (policy !== 'insert-only') {\n for (const e of plan.modified) {\n txVault.collection(e.collection).put(e.id, e.record as never, { reason })\n }\n }\n if (policy === 'replace') {\n for (const e of plan.deleted) txVault.collection(e.collection).delete(e.id)\n }\n })\n },\n }\n },\n }\n}\n\n/**\n * Enable `vault.export()` / `vault.import()`.\n *\n * ```ts\n * const db = await createNoydb({ store, user, secret, formatsStrategy: withFormats() })\n * const csv = await vault.export(asCsv(), { collections: ['invoices'] })\n * ```\n */\nexport function withFormats(): FormatsStrategy {\n return {\n enabled: true,\n\n async exportWith<Out extends string | Uint8Array>(\n vault: Vault,\n format: NoydbFormat<Out>,\n options: FormatExportOptions = {},\n ): Promise<Out> {\n const ctx = contextFor(vault)\n // FIRST, and before a single record is read. The format is not in scope\n // yet and could not reach the vault if it were.\n if (format.tier === 'bundle') ctx.assertCanExport('bundle')\n else ctx.assertCanExport('plaintext', format.id)\n\n const chunks: ExportChunk[] = []\n for await (const chunk of ctx.chunks(options.collections)) {\n chunks.push(redactChunk(chunk, ctx, options.redact))\n }\n return format.encode(chunks)\n },\n\n async importWith<Out extends string | Uint8Array>(\n vault: Vault,\n format: NoydbFormat<Out>,\n input: Out,\n options: FormatImportOptions = {},\n ): Promise<ImportPlan> {\n const ctx = contextFor(vault)\n // Mirrors the export side: the import gate is two-tier as well, and a\n // bundle format importing under the plaintext tier would be a silent\n // downgrade in the other direction.\n if (format.tier === 'bundle') ctx.assertCanImport('bundle')\n else ctx.assertCanImport('plaintext', format.id)\n if (!format.decode) {\n throw new ValidationError(\n `vault.import: the '${format.id}' format is export-only — it declares no decode(). ` +\n 'Pick a format that round-trips, or write the records directly.',\n )\n }\n const decoded = await format.decode(input)\n const candidate: Record<string, readonly unknown[]> = {}\n for (const chunk of decoded) {\n const target = chunk.collection || options.collection\n if (!target) {\n throw new ValidationError(\n `vault.import: the '${format.id}' input names no collection and none was supplied. ` +\n 'Pass { collection } for formats whose payload is collection-less.',\n )\n }\n candidate[target] = [...(candidate[target] ?? []), ...chunk.records]\n }\n return ctx.plan(candidate, options.policy ?? 'merge', format.id, options.idKey)\n },\n }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/port/as/active.ts"],"sourcesContent":["/**\n * `withFormats()` — the service that consumes the `as-*` port.\n *\n * Everything with a correctness or security consequence lives here rather\n * than in each format: the capability gate, the vault read, and redaction.\n * A {@link NoydbFormat} receives records and returns bytes.\n *\n * That is the whole point of the inversion (ADR 0004). The conformance kit it\n * replaces could only check implementors who ran it; a format that cannot\n * reach a vault is gated whether its author cooperated or not.\n *\n * @module\n */\nimport type { NoydbFormat, ImportPlan } from './types.js'\nimport type {\n FormatsStrategy,\n FormatsContext,\n FormatExportOptions,\n FormatImportOptions,\n} from '../with/formats-strategy.js'\nimport type { ExportChunk } from '../../kernel/types.js'\nimport type { Vault } from '../../kernel/vault.js'\nimport type { ImportPolicy } from './types.js'\nimport { applyListProjection } from '../../with-shape/introspection/index.js'\nimport type { CollectionDescription, ListProjectionOptions } from '../../with-shape/introspection/index.js'\nimport { ValidationError } from '../../kernel/errors.js'\n\n/** Apply the redaction projection hub owns, so a format never asks for it. */\nfunction redactChunk(\n chunk: ExportChunk,\n ctx: FormatsContext,\n redact: FormatExportOptions['redact'],\n): ExportChunk {\n if (redact === undefined) return chunk\n const described = ctx.describe(chunk.collection) as CollectionDescription | undefined\n if (!described) {\n throw new Error(\n `vault.export: redaction was requested but '${chunk.collection}' has no description to redact against. ` +\n 'Open the collection with its classifiedFields / fieldMeta options first.',\n )\n }\n const opts: ListProjectionOptions | undefined =\n redact === true ? undefined : { sensitivity: redact.sensitivity as never }\n const records = chunk.records.map((r) =>\n applyListProjection(described, r as Record<string, unknown>, opts),\n )\n return { ...chunk, records: records as typeof chunk.records }\n}\n\n/**\n * Derive the narrow, format-facing capability from the vault.\n *\n * Lives here rather than on `Vault` because `kernel-surface` said so, and it\n * was right: this is service capability, not spine. The spine keeps two\n * delegating methods and nothing else.\n */\nfunction contextFor(vault: Vault): FormatsContext {\n return {\n // The empty-id guards are NOT defensive padding. The\n // plaintext tier is gated per-format, so a missing id means the calling\n // binding is broken — and without this it would reach the gate, miss the\n // allowlist, and surface as `ExportCapabilityError`: \"the owner did not\n // grant this\", which sends the reader to the keyring instead of to the\n // format. A loud TypeError names the real fault.\n assertCanExport: (tier, format) => {\n if (tier === 'bundle') return vault.assertCanExport('bundle')\n if (!format) throw new TypeError(\"assertCanExport('plaintext') requires a non-empty format id\")\n vault.assertCanExport('plaintext', format)\n },\n assertCanImport: (tier, format) => {\n if (tier === 'bundle') return vault.assertCanImport('bundle')\n if (!format) throw new TypeError(\"assertCanImport('plaintext') requires a non-empty format id\")\n vault.assertCanImport('plaintext', format)\n },\n // ⚠️ Filtered HERE, not passed to exportStream — `ExportStreamOptions`\n // has no `collections` field, so the option I first passed was silently\n // dropped and hub read every collection. Caught by an as-json test\n // expecting ['invoices'] and getting ['invoices','payments'].\n //\n // Post-read filtering is correct but not free: hub still DECRYPTS the\n // collections the caller excluded. Pushing the filter down into\n // exportStream is the real fix and is a hub change beyond this port.\n chunks: async function* (collections) {\n const wanted = collections ? new Set(collections) : null\n for await (const chunk of vault.exportStream({ granularity: 'collection' })) {\n if (wanted && !wanted.has(chunk.collection)) continue\n yield chunk\n }\n },\n // NOT swallowed. A caller who asked for redaction and silently got\n // unredacted output is the exact failure this port exists to remove — a\n // security duty that fails quiet. If the description cannot be read, the\n // export fails instead.\n describe: (collection) => vault.collection(collection).describe() as never,\n plan: async (records, policy: ImportPolicy, formatId: string, idKey?: string) => {\n const { diffVault } = await import('../../with-cargo/vault-diff.js')\n const plan = await diffVault(vault, records as never, idKey ? { idKey } : {})\n return {\n plan,\n policy,\n // The apply walk every as-* package had independently: added always,\n // modified unless insert-only, deleted only on replace — in one\n // transaction so a partial failure rolls back. Owned once, here.\n apply: async () => {\n await vault.noydb.transaction((tx) => {\n const txVault = tx.vault(vault.name)\n const reason = `import:${formatId}`\n for (const e of plan.added) {\n txVault.collection(e.collection).put(e.id, e.record as never, { reason })\n }\n if (policy !== 'insert-only') {\n for (const e of plan.modified) {\n txVault.collection(e.collection).put(e.id, e.record as never, { reason })\n }\n }\n if (policy === 'replace') {\n for (const e of plan.deleted) txVault.collection(e.collection).delete(e.id)\n }\n })\n },\n }\n },\n }\n}\n\n/**\n * Enable `vault.export()` / `vault.import()`.\n *\n * ```ts\n * const db = await createNoydb({ store, user, secret, formatsStrategy: withFormats() })\n * const csv = await vault.export(asCsv(), { collections: ['invoices'] })\n * ```\n */\nexport function withFormats(): FormatsStrategy {\n return {\n enabled: true,\n\n async exportWith<Out extends string | Uint8Array>(\n vault: Vault,\n format: NoydbFormat<Out>,\n options: FormatExportOptions = {},\n ): Promise<Out> {\n const ctx = contextFor(vault)\n // FIRST, and before a single record is read. The format is not in scope\n // yet and could not reach the vault if it were.\n if (format.tier === 'bundle') ctx.assertCanExport('bundle')\n else ctx.assertCanExport('plaintext', format.id)\n\n const chunks: ExportChunk[] = []\n for await (const chunk of ctx.chunks(options.collections)) {\n chunks.push(redactChunk(chunk, ctx, options.redact))\n }\n return format.encode(chunks)\n },\n\n async importWith<Out extends string | Uint8Array>(\n vault: Vault,\n format: NoydbFormat<Out>,\n input: Out,\n options: FormatImportOptions = {},\n ): Promise<ImportPlan> {\n const ctx = contextFor(vault)\n // Mirrors the export side: the import gate is two-tier as well, and a\n // bundle format importing under the plaintext tier would be a silent\n // downgrade in the other direction.\n if (format.tier === 'bundle') ctx.assertCanImport('bundle')\n else ctx.assertCanImport('plaintext', format.id)\n if (!format.decode) {\n throw new ValidationError(\n `vault.import: the '${format.id}' format is export-only — it declares no decode(). ` +\n 'Pick a format that round-trips, or write the records directly.',\n )\n }\n const decoded = await format.decode(input)\n const candidate: Record<string, readonly unknown[]> = {}\n for (const chunk of decoded) {\n const target = chunk.collection || options.collection\n if (!target) {\n throw new ValidationError(\n `vault.import: the '${format.id}' input names no collection and none was supplied. ` +\n 'Pass { collection } for formats whose payload is collection-less.',\n )\n }\n candidate[target] = [...(candidate[target] ?? []), ...chunk.records]\n }\n return ctx.plan(candidate, options.policy ?? 'merge', format.id, options.idKey)\n },\n }\n}\n"],"mappings":"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","names":[]}
|
package/dist/at/index.js
CHANGED
|
@@ -26,17 +26,16 @@ import {
|
|
|
26
26
|
AttestationError,
|
|
27
27
|
AttestationNotEnabledError
|
|
28
28
|
} from "../chunk-7O3Y4PPD.js";
|
|
29
|
-
import "../chunk-PZ5AY32C.js";
|
|
30
29
|
|
|
31
30
|
// src/with-audit/attestation/active.ts
|
|
32
31
|
function withAttestation() {
|
|
33
32
|
return {
|
|
34
33
|
async issueAttestation(ctx, args) {
|
|
35
|
-
const { issueAttestationCore: issueAttestationCore2 } = await import("../issue-
|
|
34
|
+
const { issueAttestationCore: issueAttestationCore2 } = await import("../issue-RH4H733Z.js");
|
|
36
35
|
return issueAttestationCore2(ctx, args);
|
|
37
36
|
},
|
|
38
37
|
async getDocumentSigningPublicKey(deps) {
|
|
39
|
-
const { loadSigner, loadOrCreateSigner } = await import("../signer-
|
|
38
|
+
const { loadSigner, loadOrCreateSigner } = await import("../signer-5K3WDHOC.js");
|
|
40
39
|
const existing = await loadSigner(deps.adapter, deps.vault, deps.getDEK);
|
|
41
40
|
if (existing) return { keyId: existing.keyId, publicKeyB64: existing.publicKeyB64 };
|
|
42
41
|
if (deps.role !== "owner") {
|
|
@@ -46,19 +45,19 @@ function withAttestation() {
|
|
|
46
45
|
return { keyId: signer.keyId, publicKeyB64: signer.publicKeyB64 };
|
|
47
46
|
},
|
|
48
47
|
async revokeAttestation(ctx, docId) {
|
|
49
|
-
const { revokeDocCore: revokeDocCore2 } = await import("../revoke-
|
|
48
|
+
const { revokeDocCore: revokeDocCore2 } = await import("../revoke-POHP4GPB.js");
|
|
50
49
|
return revokeDocCore2(ctx, docId);
|
|
51
50
|
},
|
|
52
51
|
async unrevokeAttestation(ctx, docId) {
|
|
53
|
-
const { unrevokeDocCore: unrevokeDocCore2 } = await import("../revoke-
|
|
52
|
+
const { unrevokeDocCore: unrevokeDocCore2 } = await import("../revoke-POHP4GPB.js");
|
|
54
53
|
return unrevokeDocCore2(ctx, docId);
|
|
55
54
|
},
|
|
56
55
|
async getRevokedDocIds(ctx) {
|
|
57
|
-
const { getRevokedDocIdsCore: getRevokedDocIdsCore2 } = await import("../revoke-
|
|
56
|
+
const { getRevokedDocIdsCore: getRevokedDocIdsCore2 } = await import("../revoke-POHP4GPB.js");
|
|
58
57
|
return getRevokedDocIdsCore2(ctx);
|
|
59
58
|
},
|
|
60
59
|
async publishRevocationList(ctx) {
|
|
61
|
-
const { publishRevocationListCore: publishRevocationListCore2 } = await import("../revoke-
|
|
60
|
+
const { publishRevocationListCore: publishRevocationListCore2 } = await import("../revoke-POHP4GPB.js");
|
|
62
61
|
return publishRevocationListCore2(ctx);
|
|
63
62
|
}
|
|
64
63
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/with-audit/attestation/active.ts","../../src/with-audit/attestation/index.ts"],"sourcesContent":["import { AttestationError } from '../../kernel/errors.js'\nimport type { AttestationStrategy } from './strategy.js'\n\n/**\n * Enable the attestation capability.\n * Pass to `createNoydb({ attestationStrategy: withAttestation() })` to make the\n * vault's `issueAttestation` / `getDocumentSigningPublicKey` / `revokeAttestation`\n * / `unrevokeAttestation` / `getRevokedDocIds` / `publishRevocationList` methods\n * live. The issue/revoke/signer engines are dynamically imported here, so they\n * stay out of the floor bundle until opted in.\n */\nexport function withAttestation(): AttestationStrategy {\n return {\n async issueAttestation(ctx, args) {\n const { issueAttestationCore } = await import('./issue.js')\n return issueAttestationCore(ctx, args)\n },\n async getDocumentSigningPublicKey(deps) {\n const { loadSigner, loadOrCreateSigner } = await import('./signer.js')\n // Reading an existing public key is open to any role that holds the\n // _attestations DEK — the public key is not secret. But MINTING the\n // signer is the firm's identity operation (same rule as issueAttestation):\n // a non-owner read must not silently create it.\n const existing = await loadSigner(deps.adapter, deps.vault, deps.getDEK)\n if (existing) return { keyId: existing.keyId, publicKeyB64: existing.publicKeyB64 }\n if (deps.role !== 'owner') {\n throw new AttestationError(`getDocumentSigningPublicKey: no document-signing key exists yet; only the 'owner' may mint it. Caller is '${deps.role}'. Have the owner issue an attestation (or call this) first.`)\n }\n const signer = await loadOrCreateSigner(deps.adapter, deps.vault, deps.getDEK)\n return { keyId: signer.keyId, publicKeyB64: signer.publicKeyB64 }\n },\n async revokeAttestation(ctx, docId) {\n const { revokeDocCore } = await import('./revoke.js')\n return revokeDocCore(ctx, docId)\n },\n async unrevokeAttestation(ctx, docId) {\n const { unrevokeDocCore } = await import('./revoke.js')\n return unrevokeDocCore(ctx, docId)\n },\n async getRevokedDocIds(ctx) {\n const { getRevokedDocIdsCore } = await import('./revoke.js')\n return getRevokedDocIdsCore(ctx)\n },\n async publishRevocationList(ctx) {\n const { publishRevocationListCore } = await import('./revoke.js')\n return publishRevocationListCore(ctx)\n },\n }\n}\n","/**\n * @category capability\n * Document attestation — issue side. Mint a signed, per-field commitment\n * for a record and emit a QR credential verifiable offline via\n * `@noy-db/attestation`. See design-history/2026-05-29-attestation-core-and-issue-design.md.\n */\nexport { withAttestation } from './active.js'\nexport { NO_ATTESTATION, type AttestationStrategy } from './strategy.js'\nexport { AttestationNotEnabledError } from '../../kernel/errors.js'\nexport { issueAttestationCore } from './issue.js'\nexport type { IssueContext, IssueArgs, IssueResult } from './issue.js'\nexport { ATTESTATIONS_COLLECTION } from './signer.js'\nexport { revokeDocCore, unrevokeDocCore, getRevokedDocIdsCore, publishRevocationListCore } from './revoke.js'\nexport type { RevokeContext } from './revoke.js'\nexport { AttestationError } from '../../kernel/errors.js'\n// Re-export the pure verifier surface so consumers can verify from one import:\nexport { verifyAttestation, decodeQr, verifyRevocationList, isRevoked, signRevocationList, type QrPayload, type AttestationFieldSchema, type VerifyResult, type VerifyInput, type RevocationList } from '@noy-db/attestation'\n"],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../../src/with-audit/attestation/active.ts","../../src/with-audit/attestation/index.ts"],"sourcesContent":["import { AttestationError } from '../../kernel/errors.js'\nimport type { AttestationStrategy } from './strategy.js'\n\n/**\n * Enable the attestation capability.\n * Pass to `createNoydb({ attestationStrategy: withAttestation() })` to make the\n * vault's `issueAttestation` / `getDocumentSigningPublicKey` / `revokeAttestation`\n * / `unrevokeAttestation` / `getRevokedDocIds` / `publishRevocationList` methods\n * live. The issue/revoke/signer engines are dynamically imported here, so they\n * stay out of the floor bundle until opted in.\n */\nexport function withAttestation(): AttestationStrategy {\n return {\n async issueAttestation(ctx, args) {\n const { issueAttestationCore } = await import('./issue.js')\n return issueAttestationCore(ctx, args)\n },\n async getDocumentSigningPublicKey(deps) {\n const { loadSigner, loadOrCreateSigner } = await import('./signer.js')\n // Reading an existing public key is open to any role that holds the\n // _attestations DEK — the public key is not secret. But MINTING the\n // signer is the firm's identity operation (same rule as issueAttestation):\n // a non-owner read must not silently create it.\n const existing = await loadSigner(deps.adapter, deps.vault, deps.getDEK)\n if (existing) return { keyId: existing.keyId, publicKeyB64: existing.publicKeyB64 }\n if (deps.role !== 'owner') {\n throw new AttestationError(`getDocumentSigningPublicKey: no document-signing key exists yet; only the 'owner' may mint it. Caller is '${deps.role}'. Have the owner issue an attestation (or call this) first.`)\n }\n const signer = await loadOrCreateSigner(deps.adapter, deps.vault, deps.getDEK)\n return { keyId: signer.keyId, publicKeyB64: signer.publicKeyB64 }\n },\n async revokeAttestation(ctx, docId) {\n const { revokeDocCore } = await import('./revoke.js')\n return revokeDocCore(ctx, docId)\n },\n async unrevokeAttestation(ctx, docId) {\n const { unrevokeDocCore } = await import('./revoke.js')\n return unrevokeDocCore(ctx, docId)\n },\n async getRevokedDocIds(ctx) {\n const { getRevokedDocIdsCore } = await import('./revoke.js')\n return getRevokedDocIdsCore(ctx)\n },\n async publishRevocationList(ctx) {\n const { publishRevocationListCore } = await import('./revoke.js')\n return publishRevocationListCore(ctx)\n },\n }\n}\n","/**\n * @category capability\n * Document attestation — issue side. Mint a signed, per-field commitment\n * for a record and emit a QR credential verifiable offline via\n * `@noy-db/attestation`. See design-history/2026-05-29-attestation-core-and-issue-design.md.\n */\nexport { withAttestation } from './active.js'\nexport { NO_ATTESTATION, type AttestationStrategy } from './strategy.js'\nexport { AttestationNotEnabledError } from '../../kernel/errors.js'\nexport { issueAttestationCore } from './issue.js'\nexport type { IssueContext, IssueArgs, IssueResult } from './issue.js'\nexport { ATTESTATIONS_COLLECTION } from './signer.js'\nexport { revokeDocCore, unrevokeDocCore, getRevokedDocIdsCore, publishRevocationListCore } from './revoke.js'\nexport type { RevokeContext } from './revoke.js'\nexport { AttestationError } from '../../kernel/errors.js'\n// Re-export the pure verifier surface so consumers can verify from one import:\nexport { verifyAttestation, decodeQr, verifyRevocationList, isRevoked, signRevocationList, type QrPayload, type AttestationFieldSchema, type VerifyResult, type VerifyInput, type RevocationList } from '@noy-db/attestation'\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAWO,SAAS,kBAAuC;AACrD,SAAO;AAAA,IACL,MAAM,iBAAiB,KAAK,MAAM;AAChC,YAAM,EAAE,sBAAAA,sBAAqB,IAAI,MAAM,OAAO,sBAAY;AAC1D,aAAOA,sBAAqB,KAAK,IAAI;AAAA,IACvC;AAAA,IACA,MAAM,4BAA4B,MAAM;AACtC,YAAM,EAAE,YAAY,mBAAmB,IAAI,MAAM,OAAO,uBAAa;AAKrE,YAAM,WAAW,MAAM,WAAW,KAAK,SAAS,KAAK,OAAO,KAAK,MAAM;AACvE,UAAI,SAAU,QAAO,EAAE,OAAO,SAAS,OAAO,cAAc,SAAS,aAAa;AAClF,UAAI,KAAK,SAAS,SAAS;AACzB,cAAM,IAAI,iBAAiB,6GAA6G,KAAK,IAAI,8DAA8D;AAAA,MACjN;AACA,YAAM,SAAS,MAAM,mBAAmB,KAAK,SAAS,KAAK,OAAO,KAAK,MAAM;AAC7E,aAAO,EAAE,OAAO,OAAO,OAAO,cAAc,OAAO,aAAa;AAAA,IAClE;AAAA,IACA,MAAM,kBAAkB,KAAK,OAAO;AAClC,YAAM,EAAE,eAAAC,eAAc,IAAI,MAAM,OAAO,uBAAa;AACpD,aAAOA,eAAc,KAAK,KAAK;AAAA,IACjC;AAAA,IACA,MAAM,oBAAoB,KAAK,OAAO;AACpC,YAAM,EAAE,iBAAAC,iBAAgB,IAAI,MAAM,OAAO,uBAAa;AACtD,aAAOA,iBAAgB,KAAK,KAAK;AAAA,IACnC;AAAA,IACA,MAAM,iBAAiB,KAAK;AAC1B,YAAM,EAAE,sBAAAC,sBAAqB,IAAI,MAAM,OAAO,uBAAa;AAC3D,aAAOA,sBAAqB,GAAG;AAAA,IACjC;AAAA,IACA,MAAM,sBAAsB,KAAK;AAC/B,YAAM,EAAE,2BAAAC,2BAA0B,IAAI,MAAM,OAAO,uBAAa;AAChE,aAAOA,2BAA0B,GAAG;AAAA,IACtC;AAAA,EACF;AACF;;;AChCA,SAAS,mBAAmB,UAAU,sBAAsB,WAAW,0BAAiI;","names":["issueAttestationCore","revokeDocCore","unrevokeDocCore","getRevokedDocIdsCore","publishRevocationListCore"]}
|
|
@@ -34,7 +34,6 @@ import {
|
|
|
34
34
|
BackupCorruptedError,
|
|
35
35
|
BackupLedgerError
|
|
36
36
|
} from "./chunk-7O3Y4PPD.js";
|
|
37
|
-
import "./chunk-PZ5AY32C.js";
|
|
38
37
|
|
|
39
38
|
// src/with-pod/backup.ts
|
|
40
39
|
async function dumpVault(ctx) {
|
|
@@ -262,4 +261,4 @@ export {
|
|
|
262
261
|
loadVault,
|
|
263
262
|
verifyBackupIntegrity
|
|
264
263
|
};
|
|
265
|
-
//# sourceMappingURL=backup-
|
|
264
|
+
//# sourceMappingURL=backup-AKWIMYWU.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/with-pod/backup.ts"],"sourcesContent":["/**\n * Vault backup / restore / integrity.\n *\n * Holds `dump()` (verifiable encrypted JSON backup, including the blob\n * collections so blob \"covers\" travel in the bundle), `load()` (restore +\n * post-load integrity gate), `verifyBackupIntegrity()` (chain + data-envelope\n * cross-check), and `exportJSON()` (plaintext per-collection export, built on\n * the vault's `exportStream`). Behaviour is byte-identical to the inline\n * `Vault` methods these functions replaced — every dependency the moving code\n * touched on `this.*` arrives via {@link BackupContext}. `Vault` keeps the\n * public methods as thin delegators.\n *\n * Internal — reached through `vault.dump()` / `vault.load()` / etc.\n */\nimport { NOYDB_BACKUP_VERSION } from '../kernel/types.js'\nimport { BackupLedgerError, BackupCorruptedError } from '../kernel/errors.js'\nimport { LEDGER_COLLECTION, LEDGER_DELTAS_COLLECTION } from '../with-commit/history/ledger/constants.js'\nimport { SCHEMAS_COLLECTION } from '../with-shape/persisted-schemas/storage.js'\nimport { SEQUENCE_COLLECTION } from '../with-commit/sequence/index.js'\nimport { MANIFEST_COLLECTION } from '../with-shape/manifest/reserved-collections.js'\n// #1025 — imported from the dependency-light `window.ts`, not `periods.ts`: that\n// module exists precisely so a consumer can name the period collections without\n// dragging the ledger hash-chain machinery (`sha256Hex`/`canonicalJson`) into a\n// bundle that opted out of the periods service. Same reason the sync engine's\n// period-scoped pull imports it.\nimport {\n PERIODS_COLLECTION,\n PERIOD_FREEZES_COLLECTION,\n PERIOD_ARCHIVES_COLLECTION,\n PERIOD_TARGET_PURGES_COLLECTION,\n PERIOD_REOPENS_COLLECTION,\n} from '../with-audit/periods/window.js'\nimport type {\n NoydbStore,\n EncryptedEnvelope,\n VaultBackup,\n VaultSnapshot,\n ExportStreamOptions,\n ExportChunk,\n} from '../kernel/types.js'\nimport type { LedgerStore } from '../with-commit/history/ledger/store.js'\nimport { buildRecordEnvelope } from '../kernel/enclave/index.js'\n\n/** Everything the moving backup methods touched on the vault's `this.*`. */\nexport interface BackupContext {\n /** The ciphertext store. */\n readonly adapter: NoydbStore\n /** Vault namespace name. */\n readonly vault: string\n /** The invoking keyring's user id (read fresh per call). */\n userId(): string\n /** The vault's ledger store, or null when history is off. */\n getLedgerOrNull(): LedgerStore | null\n /** Recompute an envelope's `payloadHash` (bound `historyStrategy.envelopePayloadHash`). */\n envelopePayloadHash(envelope: EncryptedEnvelope): Promise<string>\n /**\n * Refresh the in-memory keyring from the freshly-loaded keyring file and\n * rebuild the DEK resolver. No-op when the vault has no `reloadKeyring`\n * callback (plaintext vaults / test constructions).\n */\n reloadKeyringAndRebuildDEK(): Promise<void>\n /** Clear the vault's collection cache (post-load). */\n clearCollectionCache(): void\n /** Reset the ledger store so the next `ledger()` rebuilds its head cache. */\n resetLedgerStore(): void\n /** The vault's decrypt+ACL export stream (used by `exportJSON`). */\n exportStream(opts: ExportStreamOptions): AsyncIterableIterator<ExportChunk>\n}\n\n/** Result of {@link verifyBackupIntegrity}. */\nexport type VerifyBackupResult =\n | { readonly ok: true; readonly head: string; readonly length: number }\n | {\n readonly ok: false\n readonly kind: 'chain'\n readonly divergedAt: number\n readonly message: string\n }\n | {\n readonly ok: false\n readonly kind: 'data'\n readonly collection: string\n readonly id: string\n readonly message: string\n }\n\n/**\n * Dump vault as a verifiable encrypted JSON backup string.\n *\n * backups embed the current ledger head and the full `_ledger` +\n * `_ledger_deltas` internal collections so the receiver can run\n * `verifyBackupIntegrity()` after `load()` and detect any tampering between\n * dump and restore. Backups produced without a ledger skip the integrity check\n * with a warning — both modes round-trip cleanly.\n */\nexport async function dumpVault(ctx: BackupContext): Promise<string> {\n const snapshot = await ctx.adapter.loadAll(ctx.vault)\n\n // Load keyrings (separate path because loadAll filters them out\n // along with all other underscore-prefixed internal collections).\n const keyringIds = await ctx.adapter.list(ctx.vault, '_keyring')\n const keyrings: Record<string, unknown> = {}\n for (const keyringId of keyringIds) {\n const envelope = await ctx.adapter.get(ctx.vault, '_keyring', keyringId)\n if (envelope) {\n keyrings[keyringId] = JSON.parse(envelope._data)\n }\n }\n\n // Load the ledger entries + deltas so the receiver can replay\n // the chain after restore. Without this, `load()` would have an\n // empty ledger and `verifyBackupIntegrity()` would have nothing\n // to compare against.\n //\n // Also enumerate the blob collections so blob content (\"covers\")\n // travels in the bundle (the blob DEK already travels in `_keyring`).\n // Literals are inlined (not imported from blobs/blob-set.ts) to keep\n // the blob runtime out of this kernel hot path — they mirror\n // BLOB_INDEX/CHUNKS/EVICTION_AUDIT_COLLECTION and SLOTS/VERSIONS_PREFIX.\n // The collect-loop skips empty ids, so this no-ops without blobs.\n const internalSnapshot: VaultSnapshot = {}\n const internalNames = [\n LEDGER_COLLECTION, LEDGER_DELTAS_COLLECTION, SCHEMAS_COLLECTION, SEQUENCE_COLLECTION,\n MANIFEST_COLLECTION, // #941: the pod's manifest-set record(s) travel in the bundle too\n // #1025: the accounting-period close state + its companions. The bundle is\n // the backup/restore path, so without these a restore discarded the\n // hash-chained evidence that a month was ever closed — the artifact\n // `closePeriod` exists to produce — and dropped the write gate with it, so\n // the restored vault silently accepted back-dated writes into a sealed\n // month. The companions matter as much as the close: a bounded reopen\n // window (`reopenPeriod({ until })`) is state a restore must not lose.\n PERIODS_COLLECTION,\n PERIOD_REOPENS_COLLECTION,\n PERIOD_FREEZES_COLLECTION,\n PERIOD_ARCHIVES_COLLECTION,\n PERIOD_TARGET_PURGES_COLLECTION,\n '_history', // full-snapshot version history — so history()/getVersion()/diff() survive the bundle\n '_blob_index', '_blob_chunks', '_blob_eviction_audit',\n // #753 spec §7 Q3: `_blob_intent` markers travel too — restoring mid-op\n // state (a blob row without its marker) would reproduce the ambiguity\n // the journal exists to prevent. Note: `_mv_stale` does NOT travel\n // today (a pre-existing, separate gap) — observed here, tracked on\n // #761, not fixed in this pass.\n '_blob_intent',\n // #769: `_blob_slots_<c>` envelopes travel verbatim (raw ciphertext,\n // same DEK, same vault) — including any `SlotRecord.pendingRelease`\n // resume breadcrumb they carry. This is INTENTIONALLY the opposite of\n // `extract-partition`'s `reKeyBlobs`, which STRIPS `pendingRelease`:\n // a full-vault backup restores into the SAME vault it was taken from,\n // so a breadcrumb taken mid-rehome (together with its `_blob_intent`\n // marker, carried above) is resumable on restore — carry-and-resume is\n // correct here. A partition extraction crosses vaults: the breadcrumb's\n // old eTag may not exist in the destination, so it must be stripped\n // there instead. Asymmetry: backup = same-vault-resumable = carry;\n // partition = cross-vault = strip-breadcrumb-carry-marker.\n ...Object.keys(snapshot).flatMap((c) => [`_blob_slots_${c}`, `_blob_versions_${c}`]),\n ]\n for (const internalName of internalNames) {\n const ids = await ctx.adapter.list(ctx.vault, internalName)\n if (ids.length === 0) continue\n const records: Record<string, EncryptedEnvelope> = {}\n for (const id of ids) {\n const envelope = await ctx.adapter.get(ctx.vault, internalName, id)\n if (envelope) records[id] = envelope\n }\n internalSnapshot[internalName] = records\n }\n\n // Embed the ledger head if there's a chain. An empty ledger\n // (fresh vault) leaves `ledgerHead` undefined, which\n // load() treats the same as a legacy backup (no integrity\n // check, console warning). If history is not opted in,\n // `getLedgerOrNull` returns null and we skip embedding entirely\n // — the backup is still valid, just without the integrity head.\n const ledgerForHead = ctx.getLedgerOrNull()\n const head = ledgerForHead ? await ledgerForHead.head() : null\n const backup: VaultBackup = {\n _noydb_backup: NOYDB_BACKUP_VERSION,\n _compartment: ctx.vault,\n _exported_at: new Date().toISOString(),\n _exported_by: ctx.userId(),\n keyrings: keyrings as VaultBackup['keyrings'],\n collections: snapshot,\n ...(Object.keys(internalSnapshot).length > 0\n ? { _internal: internalSnapshot }\n : {}),\n ...(head\n ? {\n ledgerHead: {\n hash: head.hash,\n index: head.entry.index,\n ts: head.entry.ts,\n },\n }\n : {}),\n }\n\n return JSON.stringify(backup)\n}\n\n/**\n * Restore a vault from a verifiable backup. After loading, runs\n * `verifyBackupIntegrity()` to confirm the hash chain, the embedded head, and\n * every data envelope's payload hash. Legacy backups (no `ledgerHead`) load\n * with a console warning and skip the integrity check.\n */\nexport async function loadVault(ctx: BackupContext, backupJson: string): Promise<void> {\n const backup = JSON.parse(backupJson) as VaultBackup\n\n // 1. Restore data collections.\n await ctx.adapter.saveAll(ctx.vault, backup.collections)\n\n // 2. Restore keyrings.\n for (const [userId, keyringFile] of Object.entries(backup.keyrings)) {\n const envelope = buildRecordEnvelope({ collection: '_keyring', id: userId, version: 1 },\n { iv: '', data: JSON.stringify(keyringFile) })\n await ctx.adapter.put(ctx.vault, '_keyring', userId, envelope)\n }\n\n // 3. Restore internal collections (`_ledger`, `_ledger_deltas`).\n // Required so verifyBackupIntegrity has the chain to walk.\n if (backup._internal) {\n for (const [internalName, records] of Object.entries(backup._internal)) {\n for (const [id, envelope] of Object.entries(records)) {\n await ctx.adapter.put(ctx.vault, internalName, id, envelope)\n }\n }\n }\n\n // 4. Refresh the in-memory keyring from the freshly-loaded\n // keyring file. Without this, the Vault's getDEK\n // closure still holds the OLD session's DEKs, and every\n // decrypt of a loaded ledger entry / data envelope fails\n // with TamperedError because the DEK doesn't match the\n // ciphertext that was encrypted with the SOURCE user's DEK.\n // Skipped for plaintext vaults and for tests that\n // construct Vault without a reloadKeyring callback.\n await ctx.reloadKeyringAndRebuildDEK()\n\n // 5. Clear collection cache + reset the ledger store so the\n // next ledger() call rebuilds its head cache from the\n // freshly-loaded entries.\n ctx.clearCollectionCache()\n ctx.resetLedgerStore()\n\n // 5. Run the verification gate. Legacy backups (no ledgerHead)\n // skip this with a one-line warning so existing consumers can\n // still read them while migrating.\n if (!backup.ledgerHead) {\n console.warn(\n `[noy-db] Loaded a legacy backup with no ledgerHead — ` +\n `verifiable-backup integrity check skipped. ` +\n `Re-export with a ledger-aware build to get tamper detection.`,\n )\n return\n }\n\n const result = await verifyBackupIntegrity(ctx)\n if (!result.ok) {\n // Surface the most specific error class we can. The result\n // shape carries enough info for callers to inspect.\n if (result.kind === 'data') {\n throw new BackupCorruptedError(\n result.collection,\n result.id,\n result.message,\n )\n }\n throw new BackupLedgerError(result.message, result.divergedAt)\n }\n\n // 6. Cross-check: the freshly-verified head must match the\n // value embedded at dump time. A mismatch means someone\n // truncated or extended the chain after dump.\n if (result.head !== backup.ledgerHead.hash) {\n throw new BackupLedgerError(\n `Backup ledger head mismatch: embedded \"${backup.ledgerHead.hash}\" ` +\n `but reconstructed \"${result.head}\".`,\n )\n }\n}\n\n/**\n * End-to-end backup integrity check: `ledger.verify()` (hash chain) plus a data\n * envelope cross-check (every current record's `_data` hash must match the\n * latest `put` entry's `payloadHash`). Returns a discriminated union so callers\n * can distinguish chain vs data failures.\n */\nexport async function verifyBackupIntegrity(ctx: BackupContext): Promise<VerifyBackupResult> {\n // Step 1: chain verification. Without the history strategy there\n // is no ledger; an unaudited backup verifies trivially as `ok`\n // because there's nothing to diverge from.\n const ledgerForVerify = ctx.getLedgerOrNull()\n if (!ledgerForVerify) {\n return { ok: true, head: '', length: 0 }\n }\n const chainResult = await ledgerForVerify.verify()\n if (!chainResult.ok) {\n return {\n ok: false,\n kind: 'chain',\n divergedAt: chainResult.divergedAt,\n message:\n `Ledger chain diverged at index ${chainResult.divergedAt}: ` +\n `expected prevHash \"${chainResult.expected}\" but found \"${chainResult.actual}\".`,\n }\n }\n\n // Step 2: data envelope cross-check. Walk every entry in the\n // ledger and, for the LATEST `put` per (collection, id), recompute\n // the data envelope's payloadHash and compare. Earlier puts of the\n // same id are skipped because the data collection only holds the\n // current version — historical envelopes live in the deltas\n // collection (which is itself protected by the chain).\n // Reuse the ledger we already resolved in step 1.\n const allEntries = await ledgerForVerify.loadAllEntries()\n\n // Find the latest non-delete entry per (collection, id). Walk\n // the entries in reverse so we hit the latest first; mark each\n // (collection, id) as seen and skip subsequent entries.\n const seen = new Set<string>()\n const latest = new Map<\n string,\n { collection: string; id: string; expectedHash: string }\n >()\n for (let i = allEntries.length - 1; i >= 0; i--) {\n const entry = allEntries[i]\n if (!entry) continue\n // Amendment entries are multi-record audit entries whose\n // `collection` and `id` are empty strings — building a `\"/\"`\n // key here would mark that synthetic slot as seen and falsely\n // trip the data check on a record that never existed. Skip\n // them BEFORE the key/seen bookkeeping so they neither\n // tombstone real entries nor enter the latest map.\n if (entry.op === 'amendment' || entry.op === 'lifecycle') continue\n const key = `${entry.collection}/${entry.id}`\n if (seen.has(key)) continue\n seen.add(key)\n // For deletes the data collection should NOT have the record,\n // so we skip — there's nothing to cross-check. Marking the key\n // as seen above ensures any earlier `put` of the same id is\n // also skipped (the record was subsequently deleted).\n if (entry.op === 'delete') continue\n latest.set(key, {\n collection: entry.collection,\n id: entry.id,\n expectedHash: entry.payloadHash,\n })\n }\n\n for (const { collection, id, expectedHash } of latest.values()) {\n const envelope = await ctx.adapter.get(ctx.vault, collection, id)\n if (!envelope) {\n return {\n ok: false,\n kind: 'data',\n collection,\n id,\n message:\n `Ledger expects data record \"${collection}/${id}\" to exist, ` +\n `but the adapter has no envelope for it.`,\n }\n }\n const actualHash = await ctx.envelopePayloadHash(envelope)\n if (actualHash !== expectedHash) {\n return {\n ok: false,\n kind: 'data',\n collection,\n id,\n message:\n `Data envelope \"${collection}/${id}\" has been tampered with: ` +\n `expected payloadHash \"${expectedHash}\", got \"${actualHash}\".`,\n }\n }\n }\n\n return {\n ok: true,\n head: chainResult.head,\n length: chainResult.length,\n }\n}\n\n/**\n * Plaintext per-collection export, built on the vault's `exportStream`. Forces\n * collection granularity (record-by-record output makes no sense in a single\n * string) and merges dictionary snapshots across collections.\n */\nexport async function exportVaultJSON(ctx: BackupContext, opts: ExportStreamOptions = {}): Promise<string> {\n // Force per-collection granularity regardless of caller setting:\n // record-by-record output doesn't make sense in a single string.\n const collections: Record<\n string,\n {\n schema: null\n refs: Record<string, { target: string; mode: 'strict' | 'warn' | 'cascade' }>\n records: unknown[]\n }\n > = {}\n let ledgerHead: ExportChunk['ledgerHead'] | undefined\n // Merged dictionary snapshot across all collections.\n // Only populated when `resolveLabels` is not set.\n const allDictionaries: Record<\n string, // collection name\n Record<string, Record<string, Record<string, string>>>\n > = {}\n\n for await (const chunk of ctx.exportStream({\n granularity: 'collection',\n withLedgerHead: opts.withLedgerHead === true,\n // Thread the export locale so records are read at the\n // `export` layer (i18nText collapsed + dictKey/staticDict labels resolved).\n ...(opts.resolveLabels !== undefined ? { resolveLabels: opts.resolveLabels } : {}),\n })) {\n collections[chunk.collection] = {\n schema: null, // Standard Schema validators are not JSON-serializable\n refs: chunk.refs,\n records: chunk.records,\n }\n if (chunk.ledgerHead) ledgerHead = chunk.ledgerHead\n // Collect dictionary snapshots unless resolveLabels is set\n if (!opts.resolveLabels && chunk.dictionaries) {\n allDictionaries[chunk.collection] = chunk.dictionaries\n }\n }\n\n const hasDictionaries = Object.keys(allDictionaries).length > 0\n return JSON.stringify({\n _noydb_export: 1,\n _compartment: ctx.vault,\n _exported_at: new Date().toISOString(),\n _exported_by: ctx.userId(),\n collections,\n ...(hasDictionaries ? { _dictionaries: allDictionaries } : {}),\n ...(ledgerHead ? { ledgerHead } : {}),\n })\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/with-pod/backup.ts"],"sourcesContent":["/**\n * Vault backup / restore / integrity.\n *\n * Holds `dump()` (verifiable encrypted JSON backup, including the blob\n * collections so blob \"covers\" travel in the bundle), `load()` (restore +\n * post-load integrity gate), `verifyBackupIntegrity()` (chain + data-envelope\n * cross-check), and `exportJSON()` (plaintext per-collection export, built on\n * the vault's `exportStream`). Behaviour is byte-identical to the inline\n * `Vault` methods these functions replaced — every dependency the moving code\n * touched on `this.*` arrives via {@link BackupContext}. `Vault` keeps the\n * public methods as thin delegators.\n *\n * Internal — reached through `vault.dump()` / `vault.load()` / etc.\n */\nimport { NOYDB_BACKUP_VERSION } from '../kernel/types.js'\nimport { BackupLedgerError, BackupCorruptedError } from '../kernel/errors.js'\nimport { LEDGER_COLLECTION, LEDGER_DELTAS_COLLECTION } from '../with-commit/history/ledger/constants.js'\nimport { SCHEMAS_COLLECTION } from '../with-shape/persisted-schemas/storage.js'\nimport { SEQUENCE_COLLECTION } from '../with-commit/sequence/index.js'\nimport { MANIFEST_COLLECTION } from '../with-shape/manifest/reserved-collections.js'\n// #1025 — imported from the dependency-light `window.ts`, not `periods.ts`: that\n// module exists precisely so a consumer can name the period collections without\n// dragging the ledger hash-chain machinery (`sha256Hex`/`canonicalJson`) into a\n// bundle that opted out of the periods service. Same reason the sync engine's\n// period-scoped pull imports it.\nimport {\n PERIODS_COLLECTION,\n PERIOD_FREEZES_COLLECTION,\n PERIOD_ARCHIVES_COLLECTION,\n PERIOD_TARGET_PURGES_COLLECTION,\n PERIOD_REOPENS_COLLECTION,\n} from '../with-audit/periods/window.js'\nimport type {\n NoydbStore,\n EncryptedEnvelope,\n VaultBackup,\n VaultSnapshot,\n ExportStreamOptions,\n ExportChunk,\n} from '../kernel/types.js'\nimport type { LedgerStore } from '../with-commit/history/ledger/store.js'\nimport { buildRecordEnvelope } from '../kernel/enclave/index.js'\n\n/** Everything the moving backup methods touched on the vault's `this.*`. */\nexport interface BackupContext {\n /** The ciphertext store. */\n readonly adapter: NoydbStore\n /** Vault namespace name. */\n readonly vault: string\n /** The invoking keyring's user id (read fresh per call). */\n userId(): string\n /** The vault's ledger store, or null when history is off. */\n getLedgerOrNull(): LedgerStore | null\n /** Recompute an envelope's `payloadHash` (bound `historyStrategy.envelopePayloadHash`). */\n envelopePayloadHash(envelope: EncryptedEnvelope): Promise<string>\n /**\n * Refresh the in-memory keyring from the freshly-loaded keyring file and\n * rebuild the DEK resolver. No-op when the vault has no `reloadKeyring`\n * callback (plaintext vaults / test constructions).\n */\n reloadKeyringAndRebuildDEK(): Promise<void>\n /** Clear the vault's collection cache (post-load). */\n clearCollectionCache(): void\n /** Reset the ledger store so the next `ledger()` rebuilds its head cache. */\n resetLedgerStore(): void\n /** The vault's decrypt+ACL export stream (used by `exportJSON`). */\n exportStream(opts: ExportStreamOptions): AsyncIterableIterator<ExportChunk>\n}\n\n/** Result of {@link verifyBackupIntegrity}. */\nexport type VerifyBackupResult =\n | { readonly ok: true; readonly head: string; readonly length: number }\n | {\n readonly ok: false\n readonly kind: 'chain'\n readonly divergedAt: number\n readonly message: string\n }\n | {\n readonly ok: false\n readonly kind: 'data'\n readonly collection: string\n readonly id: string\n readonly message: string\n }\n\n/**\n * Dump vault as a verifiable encrypted JSON backup string.\n *\n * backups embed the current ledger head and the full `_ledger` +\n * `_ledger_deltas` internal collections so the receiver can run\n * `verifyBackupIntegrity()` after `load()` and detect any tampering between\n * dump and restore. Backups produced without a ledger skip the integrity check\n * with a warning — both modes round-trip cleanly.\n */\nexport async function dumpVault(ctx: BackupContext): Promise<string> {\n const snapshot = await ctx.adapter.loadAll(ctx.vault)\n\n // Load keyrings (separate path because loadAll filters them out\n // along with all other underscore-prefixed internal collections).\n const keyringIds = await ctx.adapter.list(ctx.vault, '_keyring')\n const keyrings: Record<string, unknown> = {}\n for (const keyringId of keyringIds) {\n const envelope = await ctx.adapter.get(ctx.vault, '_keyring', keyringId)\n if (envelope) {\n keyrings[keyringId] = JSON.parse(envelope._data)\n }\n }\n\n // Load the ledger entries + deltas so the receiver can replay\n // the chain after restore. Without this, `load()` would have an\n // empty ledger and `verifyBackupIntegrity()` would have nothing\n // to compare against.\n //\n // Also enumerate the blob collections so blob content (\"covers\")\n // travels in the bundle (the blob DEK already travels in `_keyring`).\n // Literals are inlined (not imported from blobs/blob-set.ts) to keep\n // the blob runtime out of this kernel hot path — they mirror\n // BLOB_INDEX/CHUNKS/EVICTION_AUDIT_COLLECTION and SLOTS/VERSIONS_PREFIX.\n // The collect-loop skips empty ids, so this no-ops without blobs.\n const internalSnapshot: VaultSnapshot = {}\n const internalNames = [\n LEDGER_COLLECTION, LEDGER_DELTAS_COLLECTION, SCHEMAS_COLLECTION, SEQUENCE_COLLECTION,\n MANIFEST_COLLECTION, // #941: the pod's manifest-set record(s) travel in the bundle too\n // #1025: the accounting-period close state + its companions. The bundle is\n // the backup/restore path, so without these a restore discarded the\n // hash-chained evidence that a month was ever closed — the artifact\n // `closePeriod` exists to produce — and dropped the write gate with it, so\n // the restored vault silently accepted back-dated writes into a sealed\n // month. The companions matter as much as the close: a bounded reopen\n // window (`reopenPeriod({ until })`) is state a restore must not lose.\n PERIODS_COLLECTION,\n PERIOD_REOPENS_COLLECTION,\n PERIOD_FREEZES_COLLECTION,\n PERIOD_ARCHIVES_COLLECTION,\n PERIOD_TARGET_PURGES_COLLECTION,\n '_history', // full-snapshot version history — so history()/getVersion()/diff() survive the bundle\n '_blob_index', '_blob_chunks', '_blob_eviction_audit',\n // #753 spec §7 Q3: `_blob_intent` markers travel too — restoring mid-op\n // state (a blob row without its marker) would reproduce the ambiguity\n // the journal exists to prevent. Note: `_mv_stale` does NOT travel\n // today (a pre-existing, separate gap) — observed here, tracked on\n // #761, not fixed in this pass.\n '_blob_intent',\n // #769: `_blob_slots_<c>` envelopes travel verbatim (raw ciphertext,\n // same DEK, same vault) — including any `SlotRecord.pendingRelease`\n // resume breadcrumb they carry. This is INTENTIONALLY the opposite of\n // `extract-partition`'s `reKeyBlobs`, which STRIPS `pendingRelease`:\n // a full-vault backup restores into the SAME vault it was taken from,\n // so a breadcrumb taken mid-rehome (together with its `_blob_intent`\n // marker, carried above) is resumable on restore — carry-and-resume is\n // correct here. A partition extraction crosses vaults: the breadcrumb's\n // old eTag may not exist in the destination, so it must be stripped\n // there instead. Asymmetry: backup = same-vault-resumable = carry;\n // partition = cross-vault = strip-breadcrumb-carry-marker.\n ...Object.keys(snapshot).flatMap((c) => [`_blob_slots_${c}`, `_blob_versions_${c}`]),\n ]\n for (const internalName of internalNames) {\n const ids = await ctx.adapter.list(ctx.vault, internalName)\n if (ids.length === 0) continue\n const records: Record<string, EncryptedEnvelope> = {}\n for (const id of ids) {\n const envelope = await ctx.adapter.get(ctx.vault, internalName, id)\n if (envelope) records[id] = envelope\n }\n internalSnapshot[internalName] = records\n }\n\n // Embed the ledger head if there's a chain. An empty ledger\n // (fresh vault) leaves `ledgerHead` undefined, which\n // load() treats the same as a legacy backup (no integrity\n // check, console warning). If history is not opted in,\n // `getLedgerOrNull` returns null and we skip embedding entirely\n // — the backup is still valid, just without the integrity head.\n const ledgerForHead = ctx.getLedgerOrNull()\n const head = ledgerForHead ? await ledgerForHead.head() : null\n const backup: VaultBackup = {\n _noydb_backup: NOYDB_BACKUP_VERSION,\n _compartment: ctx.vault,\n _exported_at: new Date().toISOString(),\n _exported_by: ctx.userId(),\n keyrings: keyrings as VaultBackup['keyrings'],\n collections: snapshot,\n ...(Object.keys(internalSnapshot).length > 0\n ? { _internal: internalSnapshot }\n : {}),\n ...(head\n ? {\n ledgerHead: {\n hash: head.hash,\n index: head.entry.index,\n ts: head.entry.ts,\n },\n }\n : {}),\n }\n\n return JSON.stringify(backup)\n}\n\n/**\n * Restore a vault from a verifiable backup. After loading, runs\n * `verifyBackupIntegrity()` to confirm the hash chain, the embedded head, and\n * every data envelope's payload hash. Legacy backups (no `ledgerHead`) load\n * with a console warning and skip the integrity check.\n */\nexport async function loadVault(ctx: BackupContext, backupJson: string): Promise<void> {\n const backup = JSON.parse(backupJson) as VaultBackup\n\n // 1. Restore data collections.\n await ctx.adapter.saveAll(ctx.vault, backup.collections)\n\n // 2. Restore keyrings.\n for (const [userId, keyringFile] of Object.entries(backup.keyrings)) {\n const envelope = buildRecordEnvelope({ collection: '_keyring', id: userId, version: 1 },\n { iv: '', data: JSON.stringify(keyringFile) })\n await ctx.adapter.put(ctx.vault, '_keyring', userId, envelope)\n }\n\n // 3. Restore internal collections (`_ledger`, `_ledger_deltas`).\n // Required so verifyBackupIntegrity has the chain to walk.\n if (backup._internal) {\n for (const [internalName, records] of Object.entries(backup._internal)) {\n for (const [id, envelope] of Object.entries(records)) {\n await ctx.adapter.put(ctx.vault, internalName, id, envelope)\n }\n }\n }\n\n // 4. Refresh the in-memory keyring from the freshly-loaded\n // keyring file. Without this, the Vault's getDEK\n // closure still holds the OLD session's DEKs, and every\n // decrypt of a loaded ledger entry / data envelope fails\n // with TamperedError because the DEK doesn't match the\n // ciphertext that was encrypted with the SOURCE user's DEK.\n // Skipped for plaintext vaults and for tests that\n // construct Vault without a reloadKeyring callback.\n await ctx.reloadKeyringAndRebuildDEK()\n\n // 5. Clear collection cache + reset the ledger store so the\n // next ledger() call rebuilds its head cache from the\n // freshly-loaded entries.\n ctx.clearCollectionCache()\n ctx.resetLedgerStore()\n\n // 5. Run the verification gate. Legacy backups (no ledgerHead)\n // skip this with a one-line warning so existing consumers can\n // still read them while migrating.\n if (!backup.ledgerHead) {\n console.warn(\n `[noy-db] Loaded a legacy backup with no ledgerHead — ` +\n `verifiable-backup integrity check skipped. ` +\n `Re-export with a ledger-aware build to get tamper detection.`,\n )\n return\n }\n\n const result = await verifyBackupIntegrity(ctx)\n if (!result.ok) {\n // Surface the most specific error class we can. The result\n // shape carries enough info for callers to inspect.\n if (result.kind === 'data') {\n throw new BackupCorruptedError(\n result.collection,\n result.id,\n result.message,\n )\n }\n throw new BackupLedgerError(result.message, result.divergedAt)\n }\n\n // 6. Cross-check: the freshly-verified head must match the\n // value embedded at dump time. A mismatch means someone\n // truncated or extended the chain after dump.\n if (result.head !== backup.ledgerHead.hash) {\n throw new BackupLedgerError(\n `Backup ledger head mismatch: embedded \"${backup.ledgerHead.hash}\" ` +\n `but reconstructed \"${result.head}\".`,\n )\n }\n}\n\n/**\n * End-to-end backup integrity check: `ledger.verify()` (hash chain) plus a data\n * envelope cross-check (every current record's `_data` hash must match the\n * latest `put` entry's `payloadHash`). Returns a discriminated union so callers\n * can distinguish chain vs data failures.\n */\nexport async function verifyBackupIntegrity(ctx: BackupContext): Promise<VerifyBackupResult> {\n // Step 1: chain verification. Without the history strategy there\n // is no ledger; an unaudited backup verifies trivially as `ok`\n // because there's nothing to diverge from.\n const ledgerForVerify = ctx.getLedgerOrNull()\n if (!ledgerForVerify) {\n return { ok: true, head: '', length: 0 }\n }\n const chainResult = await ledgerForVerify.verify()\n if (!chainResult.ok) {\n return {\n ok: false,\n kind: 'chain',\n divergedAt: chainResult.divergedAt,\n message:\n `Ledger chain diverged at index ${chainResult.divergedAt}: ` +\n `expected prevHash \"${chainResult.expected}\" but found \"${chainResult.actual}\".`,\n }\n }\n\n // Step 2: data envelope cross-check. Walk every entry in the\n // ledger and, for the LATEST `put` per (collection, id), recompute\n // the data envelope's payloadHash and compare. Earlier puts of the\n // same id are skipped because the data collection only holds the\n // current version — historical envelopes live in the deltas\n // collection (which is itself protected by the chain).\n // Reuse the ledger we already resolved in step 1.\n const allEntries = await ledgerForVerify.loadAllEntries()\n\n // Find the latest non-delete entry per (collection, id). Walk\n // the entries in reverse so we hit the latest first; mark each\n // (collection, id) as seen and skip subsequent entries.\n const seen = new Set<string>()\n const latest = new Map<\n string,\n { collection: string; id: string; expectedHash: string }\n >()\n for (let i = allEntries.length - 1; i >= 0; i--) {\n const entry = allEntries[i]\n if (!entry) continue\n // Amendment entries are multi-record audit entries whose\n // `collection` and `id` are empty strings — building a `\"/\"`\n // key here would mark that synthetic slot as seen and falsely\n // trip the data check on a record that never existed. Skip\n // them BEFORE the key/seen bookkeeping so they neither\n // tombstone real entries nor enter the latest map.\n if (entry.op === 'amendment' || entry.op === 'lifecycle') continue\n const key = `${entry.collection}/${entry.id}`\n if (seen.has(key)) continue\n seen.add(key)\n // For deletes the data collection should NOT have the record,\n // so we skip — there's nothing to cross-check. Marking the key\n // as seen above ensures any earlier `put` of the same id is\n // also skipped (the record was subsequently deleted).\n if (entry.op === 'delete') continue\n latest.set(key, {\n collection: entry.collection,\n id: entry.id,\n expectedHash: entry.payloadHash,\n })\n }\n\n for (const { collection, id, expectedHash } of latest.values()) {\n const envelope = await ctx.adapter.get(ctx.vault, collection, id)\n if (!envelope) {\n return {\n ok: false,\n kind: 'data',\n collection,\n id,\n message:\n `Ledger expects data record \"${collection}/${id}\" to exist, ` +\n `but the adapter has no envelope for it.`,\n }\n }\n const actualHash = await ctx.envelopePayloadHash(envelope)\n if (actualHash !== expectedHash) {\n return {\n ok: false,\n kind: 'data',\n collection,\n id,\n message:\n `Data envelope \"${collection}/${id}\" has been tampered with: ` +\n `expected payloadHash \"${expectedHash}\", got \"${actualHash}\".`,\n }\n }\n }\n\n return {\n ok: true,\n head: chainResult.head,\n length: chainResult.length,\n }\n}\n\n/**\n * Plaintext per-collection export, built on the vault's `exportStream`. Forces\n * collection granularity (record-by-record output makes no sense in a single\n * string) and merges dictionary snapshots across collections.\n */\nexport async function exportVaultJSON(ctx: BackupContext, opts: ExportStreamOptions = {}): Promise<string> {\n // Force per-collection granularity regardless of caller setting:\n // record-by-record output doesn't make sense in a single string.\n const collections: Record<\n string,\n {\n schema: null\n refs: Record<string, { target: string; mode: 'strict' | 'warn' | 'cascade' }>\n records: unknown[]\n }\n > = {}\n let ledgerHead: ExportChunk['ledgerHead'] | undefined\n // Merged dictionary snapshot across all collections.\n // Only populated when `resolveLabels` is not set.\n const allDictionaries: Record<\n string, // collection name\n Record<string, Record<string, Record<string, string>>>\n > = {}\n\n for await (const chunk of ctx.exportStream({\n granularity: 'collection',\n withLedgerHead: opts.withLedgerHead === true,\n // Thread the export locale so records are read at the\n // `export` layer (i18nText collapsed + dictKey/staticDict labels resolved).\n ...(opts.resolveLabels !== undefined ? { resolveLabels: opts.resolveLabels } : {}),\n })) {\n collections[chunk.collection] = {\n schema: null, // Standard Schema validators are not JSON-serializable\n refs: chunk.refs,\n records: chunk.records,\n }\n if (chunk.ledgerHead) ledgerHead = chunk.ledgerHead\n // Collect dictionary snapshots unless resolveLabels is set\n if (!opts.resolveLabels && chunk.dictionaries) {\n allDictionaries[chunk.collection] = chunk.dictionaries\n }\n }\n\n const hasDictionaries = Object.keys(allDictionaries).length > 0\n return JSON.stringify({\n _noydb_export: 1,\n _compartment: ctx.vault,\n _exported_at: new Date().toISOString(),\n _exported_by: ctx.userId(),\n collections,\n ...(hasDictionaries ? { _dictionaries: allDictionaries } : {}),\n ...(ledgerHead ? { ledgerHead } : {}),\n })\n}\n"],"mappings":"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","names":[]}
|
package/dist/blobs/index.js
CHANGED
package/dist/blobs/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/via/blob/active.ts"],"sourcesContent":["/**\n * Active blob strategy factory. Calling `blobs()` returns a\n * `BlobsStrategy` whose `openSlot` constructs a real `BlobSet` bound\n * to the caller's record. The returned strategy is passed into\n * `createNoydb({ blobsStrategy: blobs() })` to light up the\n * `collection.blob(id)` path.\n *\n * This module is only reachable through the `@noy-db/hub/blobs`\n * subpath — a consumer that never imports the subpath ships none of\n * this (ESM tree-shaking + hub's `\"sideEffects\": false`).\n */\n\nimport { BlobSet } from '../../with-shape/blobs/blob-set.js'\nimport type { BlobsStrategy } from '../../port/with/blob-strategy.js'\nimport { createBlobPinCache } from '../../with-shape/blobs/blob-pinning.js'\nimport type { BlobPinStore } from '../../with-shape/blobs/blob-pinning.js'\n\n/** Options for {@link withBlobs} (#808). */\nexport interface WithBlobsOptions {\n /**\n * Device-local backend for the offline-pin registry + external side-cache\n * (`collection.blob(id).pin()`, the `cacheBudget` LRU stamps). Default:\n * in-memory — pins then last one app session. Supply a durable,\n * DEVICE-LOCAL store (IndexedDB-backed on the web; it must never sync) to\n * keep pins across restarts. See `BlobPinStore`'s doc for the posture.\n */\n readonly pinStore?: BlobPinStore\n}\n\n/**\n * Build a default `BlobsStrategy` ready to pass into `createNoydb`.\n *\n * Named `withBlobs` (plugin-pattern canonical) rather than `blobs` to\n * avoid shadowing the very common local idiom\n * `const blobs = invoices.blob(id)` in user code.\n *\n * One `withBlobs()` instance owns one device-local pin registry + KPI\n * counter set (#808) — treat it as \"this device's\" blob service.\n *\n * @example\n * ```ts\n * import { createNoydb } from '@noy-db/hub'\n * import { withBlobs } from '@noy-db/hub/blobs'\n *\n * const db = await createNoydb({\n * store, user, secret,\n * blobsStrategy: withBlobs(),\n * })\n *\n * // Now live — delegates to BlobSet.\n * const vault = await db.openVault('acme')\n * await vault.collection('invoices').blob('inv-1').put('receipt.pdf', bytes)\n * ```\n */\nexport function withBlobs(options: WithBlobsOptions = {}): BlobsStrategy {\n const pinCache = createBlobPinCache(options.pinStore)\n return {\n openSlot(args) {\n return new BlobSet({ ...args, pinCache })\n },\n cacheStats() {\n return { ...pinCache.stats }\n },\n }\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../../src/via/blob/active.ts"],"sourcesContent":["/**\n * Active blob strategy factory. Calling `blobs()` returns a\n * `BlobsStrategy` whose `openSlot` constructs a real `BlobSet` bound\n * to the caller's record. The returned strategy is passed into\n * `createNoydb({ blobsStrategy: blobs() })` to light up the\n * `collection.blob(id)` path.\n *\n * This module is only reachable through the `@noy-db/hub/blobs`\n * subpath — a consumer that never imports the subpath ships none of\n * this (ESM tree-shaking + hub's `\"sideEffects\": false`).\n */\n\nimport { BlobSet } from '../../with-shape/blobs/blob-set.js'\nimport type { BlobsStrategy } from '../../port/with/blob-strategy.js'\nimport { createBlobPinCache } from '../../with-shape/blobs/blob-pinning.js'\nimport type { BlobPinStore } from '../../with-shape/blobs/blob-pinning.js'\n\n/** Options for {@link withBlobs} (#808). */\nexport interface WithBlobsOptions {\n /**\n * Device-local backend for the offline-pin registry + external side-cache\n * (`collection.blob(id).pin()`, the `cacheBudget` LRU stamps). Default:\n * in-memory — pins then last one app session. Supply a durable,\n * DEVICE-LOCAL store (IndexedDB-backed on the web; it must never sync) to\n * keep pins across restarts. See `BlobPinStore`'s doc for the posture.\n */\n readonly pinStore?: BlobPinStore\n}\n\n/**\n * Build a default `BlobsStrategy` ready to pass into `createNoydb`.\n *\n * Named `withBlobs` (plugin-pattern canonical) rather than `blobs` to\n * avoid shadowing the very common local idiom\n * `const blobs = invoices.blob(id)` in user code.\n *\n * One `withBlobs()` instance owns one device-local pin registry + KPI\n * counter set (#808) — treat it as \"this device's\" blob service.\n *\n * @example\n * ```ts\n * import { createNoydb } from '@noy-db/hub'\n * import { withBlobs } from '@noy-db/hub/blobs'\n *\n * const db = await createNoydb({\n * store, user, secret,\n * blobsStrategy: withBlobs(),\n * })\n *\n * // Now live — delegates to BlobSet.\n * const vault = await db.openVault('acme')\n * await vault.collection('invoices').blob('inv-1').put('receipt.pdf', bytes)\n * ```\n */\nexport function withBlobs(options: WithBlobsOptions = {}): BlobsStrategy {\n const pinCache = createBlobPinCache(options.pinStore)\n return {\n openSlot(args) {\n return new BlobSet({ ...args, pinCache })\n },\n cacheStats() {\n return { ...pinCache.stats }\n },\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsDO,SAAS,UAAU,UAA4B,CAAC,GAAkB;AACvE,QAAM,WAAW,mBAAmB,QAAQ,QAAQ;AACpD,SAAO;AAAA,IACL,SAAS,MAAM;AACb,aAAO,IAAI,QAAQ,EAAE,GAAG,MAAM,SAAS,CAAC;AAAA,IAC1C;AAAA,IACA,aAAa;AACX,aAAO,EAAE,GAAG,SAAS,MAAM;AAAA,IAC7B;AAAA,EACF;AACF;","names":[]}
|
package/dist/broker/index.js
CHANGED
|
@@ -17,7 +17,6 @@ import {
|
|
|
17
17
|
BrokerNotEnabledError,
|
|
18
18
|
BrokerProofError
|
|
19
19
|
} from "../chunk-7O3Y4PPD.js";
|
|
20
|
-
import "../chunk-PZ5AY32C.js";
|
|
21
20
|
|
|
22
21
|
// src/with-party/broker/active.ts
|
|
23
22
|
var DEFAULT_SKEW_MS = 6e4;
|
|
@@ -30,7 +29,7 @@ function withBroker(config) {
|
|
|
30
29
|
const cache = /* @__PURE__ */ new Map();
|
|
31
30
|
return {
|
|
32
31
|
async enroll(ctx) {
|
|
33
|
-
const { enrollSeed } = await import("../seed-
|
|
32
|
+
const { enrollSeed } = await import("../seed-UYR6YG3V.js");
|
|
34
33
|
return enrollSeed({ ...ctx, config });
|
|
35
34
|
},
|
|
36
35
|
async rotate(ctx) {
|
|
@@ -39,7 +38,7 @@ function withBroker(config) {
|
|
|
39
38
|
const inFlight = scoped.map(([, e]) => e.inFlight).filter((p) => p !== void 0);
|
|
40
39
|
await Promise.allSettled(inFlight);
|
|
41
40
|
for (const [key] of scoped) cache.delete(key);
|
|
42
|
-
const { rotateSeed } = await import("../seed-
|
|
41
|
+
const { rotateSeed } = await import("../seed-UYR6YG3V.js");
|
|
43
42
|
return rotateSeed({ ...ctx, config });
|
|
44
43
|
},
|
|
45
44
|
credentialSource(ctx, profile) {
|
|
@@ -56,7 +55,7 @@ function withBroker(config) {
|
|
|
56
55
|
const skewMs = config.skewMs ?? DEFAULT_SKEW_MS;
|
|
57
56
|
const capturedEntry = entry;
|
|
58
57
|
const inFlight = (async () => {
|
|
59
|
-
const { mintStoreCredentials } = await import("../seed-
|
|
58
|
+
const { mintStoreCredentials } = await import("../seed-UYR6YG3V.js");
|
|
60
59
|
const creds = await mintStoreCredentials({ ...ctx, config }, profile);
|
|
61
60
|
capturedEntry.cached = { creds, validUntil: cacheFloor(creds, skewMs) };
|
|
62
61
|
return creds;
|