@noy-db/hub 0.7.0-pre.13 → 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 +108 -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-BWXGSSFX.js → chunk-4SZMZAYN.js} +79 -16
- package/dist/chunk-4SZMZAYN.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-E2MYJGSG.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-IHFXR2VQ.js → chunk-BOBFL5TW.js} +2 -2
- package/dist/{chunk-ZVCC36XQ.js → chunk-DRB6IHPS.js} +55 -55
- package/dist/{chunk-AJQY44CE.js → chunk-EKVNVJVV.js} +3 -20
- 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-VGICZPVE.js → chunk-FUJSIP4K.js} +2 -2
- package/dist/{chunk-PKJ43CMD.js → chunk-NLOLF6LD.js} +2 -2
- package/dist/{chunk-SXCYYMVS.js → chunk-NOSRK3ZZ.js} +5 -5
- package/dist/{chunk-COTPDJR5.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-PDD75NUZ.js → chunk-SMH6SRX7.js} +2 -2
- package/dist/{chunk-QHW35ZON.js → chunk-TGPST6FK.js} +2 -2
- package/dist/{chunk-BMELYFHU.js → chunk-V2PNPF6J.js} +3 -3
- package/dist/{chunk-BERJ4AWW.js → chunk-VGXZT72I.js} +2 -2
- package/dist/{chunk-2Z47NMFK.js → chunk-WZW37ZHL.js} +2 -2
- 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-4ACKIDDY.js → collection-facade-VOWW732S.js} +3 -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 +3 -4
- package/dist/{derive-LY47HG7Q.js → derive-HHBRFS2U.js} +2 -3
- package/dist/directory/index.js +0 -1
- package/dist/{dispatch-MSTR5VBI.js → dispatch-DC3LQJ3S.js} +6 -7
- package/dist/{dispatch-MSTR5VBI.js.map → dispatch-DC3LQJ3S.js.map} +1 -1
- package/dist/{dispatch-PDP5TA73.js → dispatch-GGFBWQU7.js} +5 -7
- package/dist/{dispatch-PDP5TA73.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-YDDC5XGY.js → executor-5XJNV5H5.js} +1 -2
- 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 +26 -27
- 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 +0 -1
- 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 +2 -3
- 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-2C6QQJ5S.js → noydb-Y53CQHZK.js} +11 -12
- package/dist/overlay-views/index.js +0 -1
- package/dist/periods/index.js +1 -2
- package/dist/pod/index.js +12 -13
- 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-D2YXKRYC.js → registry-HDINK5QW.js} +4 -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-2CHKZTMK.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 -0
- package/dist/with-shape/introspection/describe.d.ts +2 -53
- 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-AJQY44CE.js.map +0 -1
- package/dist/chunk-BWXGSSFX.js.map +0 -1
- package/dist/chunk-PZ5AY32C.js +0 -10
- package/dist/chunk-URHRIAV6.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-E2MYJGSG.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-IHFXR2VQ.js.map → chunk-BOBFL5TW.js.map} +0 -0
- /package/dist/{chunk-ZVCC36XQ.js.map → chunk-DRB6IHPS.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-VGICZPVE.js.map → chunk-FUJSIP4K.js.map} +0 -0
- /package/dist/{chunk-PKJ43CMD.js.map → chunk-NLOLF6LD.js.map} +0 -0
- /package/dist/{chunk-SXCYYMVS.js.map → chunk-NOSRK3ZZ.js.map} +0 -0
- /package/dist/{chunk-COTPDJR5.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-PDD75NUZ.js.map → chunk-SMH6SRX7.js.map} +0 -0
- /package/dist/{chunk-QHW35ZON.js.map → chunk-TGPST6FK.js.map} +0 -0
- /package/dist/{chunk-BMELYFHU.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-4ACKIDDY.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-MZJXBWNM.js.map → executor-5XJNV5H5.js.map} +0 -0
- /package/dist/{executor-TOLPBHOC.js.map → executor-AQVXV5GZ.js.map} +0 -0
- /package/dist/{executor-YDDC5XGY.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-2C6QQJ5S.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-D2YXKRYC.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-2CHKZTMK.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
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/kernel/enclave/classify/verify.ts"],"sourcesContent":["/**\n * The enclave verify oracle (spec §3). Verdict-only egress: no path\n * distinguishes \"record missing\" / \"no digest\" / \"AAD-tamper\" / \"mismatch\"\n * beyond { ok: false } — existence oracles are oracles too. Every path that\n * cannot run a real comparison runs ONE dummy PBKDF2 + ONE dummy tag-compare\n * first (C4), so wall-clock cannot enumerate records/fields/answers. The\n * text door (verifyTextField) has no real PBKDF2 of its own, so it pays the\n * pad UNCONDITIONALLY — every outcome, including a real compare, costs the\n * same one PBKDF2 unit (no inverted exists-vs-absent oracle).\n * Elevated records (_tier > 0, with-audit tier moves) are non-comparable\n * through every door and pad-false exactly like missing records (#691) —\n * the tier-aware read surface is getAtTier, never verify.\n * Throws are reserved for caller/config bugs (ClassifiedVerifyError /\n * ClassifiedConfigError) and are exempt from the pad by design (R6 note).\n * @module\n */\nimport { generateSalt, base64ToBuffer, type EnclaveKey } from '../crypto.js'\nimport { pbkdf2VerifyDigest, VDIG_ITERATIONS } from './digest.js'\nimport { normalizeForVerify, type VerifyNormalizeMode } from './normalize.js'\nimport { blindedEqual } from './compare.js'\nimport { evaluateKofN } from './kofn.js'\nimport { openVdigPayload, type VdigPayload } from './vdig.js'\nimport { dualReadSealedSlot } from '../record-keys/sealed-slot.js'\nimport { ClassifiedConfigError, ClassifiedVerifyError } from '../../errors.js'\nimport type { EncryptedEnvelope, VdigFieldPolicy, ClassifiedVerdict } from '../../types.js'\n\nexport interface VerifyEngineCtx {\n readonly collection: string\n getEnvelope(id: string): Promise<EncryptedEnvelope | null>\n resolveCek(env: EncryptedEnvelope): Promise<EnclaveKey | undefined>\n getDEK(): Promise<EnclaveKey>\n readonly now: () => number\n}\n\n/**\n * C4 pad: one full-cost dummy digest + one dummy compare; result discarded.\n * Pad at `iter` when the caller holds an AUTHENTICATED payload.iter, so pad\n * cost tracks the real compare cost across future iteration bumps; the\n * fully-absent paths (no payload to read an iter from) use VDIG_ITERATIONS.\n */\nasync function padOnce(iter: number = VDIG_ITERATIONS): Promise<void> {\n const dummy = await pbkdf2VerifyDigest('noydb-classify-c4-pad', generateSalt(), iter)\n await blindedEqual(dummy, dummy)\n}\n\nasync function padFalse(): Promise<ClassifiedVerdict> {\n await padOnce()\n return { ok: false }\n}\n\n/** R6 verify-side transition evidence — config bug, fail-loud, pad-exempt. */\nfunction refuseSealedResidue(collection: string, field: string): never {\n throw new ClassifiedConfigError(collection,\n `field \"${field}\" carries a recoverable _sealed slot but no _vdig — storage-form ` +\n `transition detected (R6); refused fail-loud, never an ok:false masquerading as wrong-password`)\n}\n\nexport async function verifyDigestField(\n ctx: VerifyEngineCtx,\n id: string,\n field: string,\n candidate: string,\n policy: VdigFieldPolicy,\n): Promise<ClassifiedVerdict> {\n const env = await ctx.getEnvelope(id)\n // #691: an elevated envelope (_tier > 0) is non-comparable through every\n // tier-0 door — its key material is tier-DEK-wrapped and must never be\n // resolved here (the elevating session's warm cekCache would otherwise\n // leak past the tier audit). Exactly the missing-record pad path, checked\n // BEFORE the R6 residue probe so tier-0 callers learn nothing.\n if (env === null || (env._tier ?? 0) > 0) return padFalse()\n const blob = env._vdig?.[field]\n if (blob === undefined) {\n if (env._sealed?.[field] !== undefined) refuseSealedResidue(ctx.collection, field)\n return padFalse()\n }\n const cek = await ctx.resolveCek(env)\n if (cek === undefined) return padFalse()\n\n let payload: VdigPayload\n try {\n payload = await openVdigPayload(blob, cek, ctx.collection, id, field)\n } catch {\n return padFalse() // AAD/tamper (C1) → padded false; no tamper oracle to the caller\n }\n\n const normalized = normalizeForVerify(policy.normalize, candidate)\n const digest = await pbkdf2VerifyDigest(normalized, base64ToBuffer(payload.cur.salt), payload.iter)\n const ok = await blindedEqual(digest, base64ToBuffer(payload.cur.hash))\n if (!ok) return { ok: false } // I1: bare false — mustRotate never computed here\n\n if (policy.rotateDays !== undefined\n && ctx.now() > Date.parse(payload.cur.at) + policy.rotateDays * 86_400_000) {\n // Disclosing write-age-vs-policy to a SUCCESSFUL verifier is intended (audit F6).\n return { ok: true, mustRotate: true }\n }\n return { ok: true }\n}\n\nexport async function verifyTextField(\n ctx: VerifyEngineCtx,\n id: string,\n field: string,\n candidate: string,\n normalize: VerifyNormalizeMode,\n): Promise<ClassifiedVerdict> {\n // C4, single code path: (a) attempt the unseal where a slot exists, then\n // for EVERY outcome — present-correct, present-wrong, missing record,\n // missing slot, tampered — (b) run the one constant-cost pad, (c) run one\n // blindedEqual, (d) return the boolean. Unlike the digest door there is no\n // real PBKDF2 here (unseal is sub-ms AES-GCM), so skipping the pad on the\n // real compare would make exists-but-wrong ~100x FASTER than absent — an\n // inverted existence oracle. No branch below skips the dominant cost.\n const env = await ctx.getEnvelope(id)\n const blob = env?._sealed?.[field]\n let stored: string | undefined\n if (env !== null && (env._tier ?? 0) === 0 && blob !== undefined) { // #691: elevated ≡ missing\n const cek = await ctx.resolveCek(env)\n const dek = await ctx.getDEK()\n try {\n // Plaintext exists microseconds inside this function; only the boolean leaves.\n stored = String(JSON.parse(await dualReadSealedSlot(blob, field, ctx.collection, cek, dek)))\n } catch {\n stored = undefined // AAD/tamper (C1) → same padded compare tail as absent\n }\n }\n await padOnce()\n const a = new TextEncoder().encode(normalizeForVerify(normalize, candidate))\n const b = stored !== undefined\n ? new TextEncoder().encode(normalizeForVerify(normalize, stored))\n : a // no-compare dummy comparand — the verdict is forced false below\n const eq = await blindedEqual(a, b)\n return { ok: stored !== undefined && eq }\n}\n\nexport async function matchGroupFields(\n ctx: VerifyEngineCtx,\n id: string,\n answers: Record<string, string>,\n members: ReadonlyArray<{ readonly field: string; readonly policy: VdigFieldPolicy }>,\n opts: { readonly min: number },\n): Promise<{ readonly passed: boolean }> {\n // I2 step 1 — validate EVERYTHING up front, before any PBKDF2, throwing\n // uniformly at ~0 elapsed (no member-position leak via timing/throw type).\n if (!Number.isInteger(opts.min) || opts.min < 1 || opts.min > members.length) {\n throw new ClassifiedVerifyError(ctx.collection, '*',\n `matchGroup min ${opts.min} out of range 1..${members.length}`)\n }\n const normalized = new Map<string, string>()\n for (const m of members) {\n const answer = answers[m.field] // non-member answer keys: silently ignored\n if (answer === undefined) continue\n if (typeof answer !== 'string') {\n throw new ClassifiedVerifyError(ctx.collection, m.field, 'candidate must be a string')\n }\n normalized.set(m.field, normalizeForVerify(m.policy.normalize, answer))\n }\n\n const fetched = await ctx.getEnvelope(id)\n // #691: elevated ≡ missing — nulling the view here also skips the R6\n // residue probe (a tier-0 caller must not learn an elevated record's\n // storage form) and forces the cek-undefined pad path for every member.\n const env = fetched === null || (fetched._tier ?? 0) > 0 ? null : fetched\n if (env !== null) {\n for (const m of members) { // R6 evidence — uniform, before any PBKDF2\n if (env._sealed?.[m.field] !== undefined && env._vdig?.[m.field] === undefined) {\n refuseSealedResidue(ctx.collection, m.field)\n }\n }\n }\n const cek = env !== null ? await ctx.resolveCek(env) : undefined\n\n // I2 step 2+3 — iterate RESOLVED GROUP MEMBERS (denominator = |members|),\n // evaluate EVERY member (collect, never break), pad every no-compare slot.\n const results: boolean[] = []\n for (const m of members) {\n const candidate = normalized.get(m.field)\n const blob = env?._vdig?.[m.field]\n if (env === null || cek === undefined || blob === undefined) {\n await padOnce()\n results.push(false)\n continue\n }\n try {\n const payload = await openVdigPayload(blob, cek, ctx.collection, id, m.field)\n if (candidate === undefined) {\n // Slot present, answer absent: the payload's authenticated iter is in\n // hand — pad at IT (not the constant) so a future iteration bump\n // cannot desync this pad's cost from the real compare's cost.\n await padOnce(payload.iter)\n results.push(false)\n continue\n }\n const digest = await pbkdf2VerifyDigest(candidate, base64ToBuffer(payload.cur.salt), payload.iter)\n results.push(await blindedEqual(digest, base64ToBuffer(payload.cur.hash)))\n } catch {\n await padOnce()\n results.push(false)\n }\n }\n // Per-member results never appear in any return, error, or audit payload.\n return { passed: evaluateKofN(results, opts.min) }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/kernel/enclave/classify/verify.ts"],"sourcesContent":["/**\n * The enclave verify oracle (spec §3). Verdict-only egress: no path\n * distinguishes \"record missing\" / \"no digest\" / \"AAD-tamper\" / \"mismatch\"\n * beyond { ok: false } — existence oracles are oracles too. Every path that\n * cannot run a real comparison runs ONE dummy PBKDF2 + ONE dummy tag-compare\n * first (C4), so wall-clock cannot enumerate records/fields/answers. The\n * text door (verifyTextField) has no real PBKDF2 of its own, so it pays the\n * pad UNCONDITIONALLY — every outcome, including a real compare, costs the\n * same one PBKDF2 unit (no inverted exists-vs-absent oracle).\n * Elevated records (_tier > 0, with-audit tier moves) are non-comparable\n * through every door and pad-false exactly like missing records (#691) —\n * the tier-aware read surface is getAtTier, never verify.\n * Throws are reserved for caller/config bugs (ClassifiedVerifyError /\n * ClassifiedConfigError) and are exempt from the pad by design (R6 note).\n * @module\n */\nimport { generateSalt, base64ToBuffer, type EnclaveKey } from '../crypto.js'\nimport { pbkdf2VerifyDigest, VDIG_ITERATIONS } from './digest.js'\nimport { normalizeForVerify, type VerifyNormalizeMode } from './normalize.js'\nimport { blindedEqual } from './compare.js'\nimport { evaluateKofN } from './kofn.js'\nimport { openVdigPayload, type VdigPayload } from './vdig.js'\nimport { dualReadSealedSlot } from '../record-keys/sealed-slot.js'\nimport { ClassifiedConfigError, ClassifiedVerifyError } from '../../errors.js'\nimport type { EncryptedEnvelope, VdigFieldPolicy, ClassifiedVerdict } from '../../types.js'\n\nexport interface VerifyEngineCtx {\n readonly collection: string\n getEnvelope(id: string): Promise<EncryptedEnvelope | null>\n resolveCek(env: EncryptedEnvelope): Promise<EnclaveKey | undefined>\n getDEK(): Promise<EnclaveKey>\n readonly now: () => number\n}\n\n/**\n * C4 pad: one full-cost dummy digest + one dummy compare; result discarded.\n * Pad at `iter` when the caller holds an AUTHENTICATED payload.iter, so pad\n * cost tracks the real compare cost across future iteration bumps; the\n * fully-absent paths (no payload to read an iter from) use VDIG_ITERATIONS.\n */\nasync function padOnce(iter: number = VDIG_ITERATIONS): Promise<void> {\n const dummy = await pbkdf2VerifyDigest('noydb-classify-c4-pad', generateSalt(), iter)\n await blindedEqual(dummy, dummy)\n}\n\nasync function padFalse(): Promise<ClassifiedVerdict> {\n await padOnce()\n return { ok: false }\n}\n\n/** R6 verify-side transition evidence — config bug, fail-loud, pad-exempt. */\nfunction refuseSealedResidue(collection: string, field: string): never {\n throw new ClassifiedConfigError(collection,\n `field \"${field}\" carries a recoverable _sealed slot but no _vdig — storage-form ` +\n `transition detected (R6); refused fail-loud, never an ok:false masquerading as wrong-password`)\n}\n\nexport async function verifyDigestField(\n ctx: VerifyEngineCtx,\n id: string,\n field: string,\n candidate: string,\n policy: VdigFieldPolicy,\n): Promise<ClassifiedVerdict> {\n const env = await ctx.getEnvelope(id)\n // #691: an elevated envelope (_tier > 0) is non-comparable through every\n // tier-0 door — its key material is tier-DEK-wrapped and must never be\n // resolved here (the elevating session's warm cekCache would otherwise\n // leak past the tier audit). Exactly the missing-record pad path, checked\n // BEFORE the R6 residue probe so tier-0 callers learn nothing.\n if (env === null || (env._tier ?? 0) > 0) return padFalse()\n const blob = env._vdig?.[field]\n if (blob === undefined) {\n if (env._sealed?.[field] !== undefined) refuseSealedResidue(ctx.collection, field)\n return padFalse()\n }\n const cek = await ctx.resolveCek(env)\n if (cek === undefined) return padFalse()\n\n let payload: VdigPayload\n try {\n payload = await openVdigPayload(blob, cek, ctx.collection, id, field)\n } catch {\n return padFalse() // AAD/tamper (C1) → padded false; no tamper oracle to the caller\n }\n\n const normalized = normalizeForVerify(policy.normalize, candidate)\n const digest = await pbkdf2VerifyDigest(normalized, base64ToBuffer(payload.cur.salt), payload.iter)\n const ok = await blindedEqual(digest, base64ToBuffer(payload.cur.hash))\n if (!ok) return { ok: false } // I1: bare false — mustRotate never computed here\n\n if (policy.rotateDays !== undefined\n && ctx.now() > Date.parse(payload.cur.at) + policy.rotateDays * 86_400_000) {\n // Disclosing write-age-vs-policy to a SUCCESSFUL verifier is intended (audit F6).\n return { ok: true, mustRotate: true }\n }\n return { ok: true }\n}\n\nexport async function verifyTextField(\n ctx: VerifyEngineCtx,\n id: string,\n field: string,\n candidate: string,\n normalize: VerifyNormalizeMode,\n): Promise<ClassifiedVerdict> {\n // C4, single code path: (a) attempt the unseal where a slot exists, then\n // for EVERY outcome — present-correct, present-wrong, missing record,\n // missing slot, tampered — (b) run the one constant-cost pad, (c) run one\n // blindedEqual, (d) return the boolean. Unlike the digest door there is no\n // real PBKDF2 here (unseal is sub-ms AES-GCM), so skipping the pad on the\n // real compare would make exists-but-wrong ~100x FASTER than absent — an\n // inverted existence oracle. No branch below skips the dominant cost.\n const env = await ctx.getEnvelope(id)\n const blob = env?._sealed?.[field]\n let stored: string | undefined\n if (env !== null && (env._tier ?? 0) === 0 && blob !== undefined) { // #691: elevated ≡ missing\n const cek = await ctx.resolveCek(env)\n const dek = await ctx.getDEK()\n try {\n // Plaintext exists microseconds inside this function; only the boolean leaves.\n stored = String(JSON.parse(await dualReadSealedSlot(blob, field, ctx.collection, cek, dek)))\n } catch {\n stored = undefined // AAD/tamper (C1) → same padded compare tail as absent\n }\n }\n await padOnce()\n const a = new TextEncoder().encode(normalizeForVerify(normalize, candidate))\n const b = stored !== undefined\n ? new TextEncoder().encode(normalizeForVerify(normalize, stored))\n : a // no-compare dummy comparand — the verdict is forced false below\n const eq = await blindedEqual(a, b)\n return { ok: stored !== undefined && eq }\n}\n\nexport async function matchGroupFields(\n ctx: VerifyEngineCtx,\n id: string,\n answers: Record<string, string>,\n members: ReadonlyArray<{ readonly field: string; readonly policy: VdigFieldPolicy }>,\n opts: { readonly min: number },\n): Promise<{ readonly passed: boolean }> {\n // I2 step 1 — validate EVERYTHING up front, before any PBKDF2, throwing\n // uniformly at ~0 elapsed (no member-position leak via timing/throw type).\n if (!Number.isInteger(opts.min) || opts.min < 1 || opts.min > members.length) {\n throw new ClassifiedVerifyError(ctx.collection, '*',\n `matchGroup min ${opts.min} out of range 1..${members.length}`)\n }\n const normalized = new Map<string, string>()\n for (const m of members) {\n const answer = answers[m.field] // non-member answer keys: silently ignored\n if (answer === undefined) continue\n if (typeof answer !== 'string') {\n throw new ClassifiedVerifyError(ctx.collection, m.field, 'candidate must be a string')\n }\n normalized.set(m.field, normalizeForVerify(m.policy.normalize, answer))\n }\n\n const fetched = await ctx.getEnvelope(id)\n // #691: elevated ≡ missing — nulling the view here also skips the R6\n // residue probe (a tier-0 caller must not learn an elevated record's\n // storage form) and forces the cek-undefined pad path for every member.\n const env = fetched === null || (fetched._tier ?? 0) > 0 ? null : fetched\n if (env !== null) {\n for (const m of members) { // R6 evidence — uniform, before any PBKDF2\n if (env._sealed?.[m.field] !== undefined && env._vdig?.[m.field] === undefined) {\n refuseSealedResidue(ctx.collection, m.field)\n }\n }\n }\n const cek = env !== null ? await ctx.resolveCek(env) : undefined\n\n // I2 step 2+3 — iterate RESOLVED GROUP MEMBERS (denominator = |members|),\n // evaluate EVERY member (collect, never break), pad every no-compare slot.\n const results: boolean[] = []\n for (const m of members) {\n const candidate = normalized.get(m.field)\n const blob = env?._vdig?.[m.field]\n if (env === null || cek === undefined || blob === undefined) {\n await padOnce()\n results.push(false)\n continue\n }\n try {\n const payload = await openVdigPayload(blob, cek, ctx.collection, id, m.field)\n if (candidate === undefined) {\n // Slot present, answer absent: the payload's authenticated iter is in\n // hand — pad at IT (not the constant) so a future iteration bump\n // cannot desync this pad's cost from the real compare's cost.\n await padOnce(payload.iter)\n results.push(false)\n continue\n }\n const digest = await pbkdf2VerifyDigest(candidate, base64ToBuffer(payload.cur.salt), payload.iter)\n results.push(await blindedEqual(digest, base64ToBuffer(payload.cur.hash)))\n } catch {\n await padOnce()\n results.push(false)\n }\n }\n // Per-member results never appear in any return, error, or audit payload.\n return { passed: evaluateKofN(results, opts.min) }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
dumpVaultSchema
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-FUJSIP4K.js";
|
|
4
4
|
import "./chunk-RXLYKVTF.js";
|
|
5
5
|
import "./chunk-VYYM3MEN.js";
|
|
6
|
-
import "./chunk-
|
|
6
|
+
import "./chunk-REDOOY2Q.js";
|
|
7
7
|
import "./chunk-Y2NBBT6K.js";
|
|
8
8
|
import "./chunk-WWNYSV7S.js";
|
|
9
9
|
import "./chunk-CIGAQBJR.js";
|
|
@@ -15,8 +15,7 @@ import "./chunk-LP7BEXCT.js";
|
|
|
15
15
|
import "./chunk-SL3JAAKN.js";
|
|
16
16
|
import "./chunk-N4CHCXDL.js";
|
|
17
17
|
import "./chunk-7O3Y4PPD.js";
|
|
18
|
-
import "./chunk-PZ5AY32C.js";
|
|
19
18
|
export {
|
|
20
19
|
dumpVaultSchema
|
|
21
20
|
};
|
|
22
|
-
//# sourceMappingURL=walk-
|
|
21
|
+
//# sourceMappingURL=walk-ERAJ5RHO.js.map
|
|
@@ -6,6 +6,12 @@ interface RegisteredStrategy {
|
|
|
6
6
|
strategyHash: string;
|
|
7
7
|
readonly triggers: ReadonlyArray<NormalizedTrigger>;
|
|
8
8
|
}
|
|
9
|
+
/**
|
|
10
|
+
* Vault-internal registry of derivation strategies. Owned by `Vault`;
|
|
11
|
+
* not exported.
|
|
12
|
+
*
|
|
13
|
+
* @internal
|
|
14
|
+
*/
|
|
9
15
|
export declare class DerivationRegistry {
|
|
10
16
|
private readonly _bySource;
|
|
11
17
|
private readonly _byOutput;
|
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
* @module
|
|
12
12
|
*/
|
|
13
13
|
import type { FieldMeta } from './field-meta.js';
|
|
14
|
-
import { FieldMetaUnknownFieldError } from './field-meta.js';
|
|
14
|
+
import { FieldMetaUnknownFieldError, schemaFieldKeys } from './field-meta.js';
|
|
15
|
+
export { schemaFieldKeys };
|
|
15
16
|
import type { CollectionMeta } from './meta.js';
|
|
16
17
|
import type { ViaDescriptor, ViaPosture } from '../../kernel/via/index.js';
|
|
17
18
|
import type { DictKeyDescriptor, StaticDictDescriptor } from '../../via/i18n/dictionary.js';
|
|
@@ -186,58 +187,6 @@ export interface ZodFieldSlot {
|
|
|
186
187
|
*
|
|
187
188
|
* No static zod import — all zod access is lazy via derivePersistedSchema.
|
|
188
189
|
*/
|
|
189
|
-
/**
|
|
190
|
-
* Field KEYS from a configured validator, synchronously — the complement to
|
|
191
|
-
* {@link deriveZodFields}, which derives field TYPES and must be async.
|
|
192
|
-
*
|
|
193
|
-
* The distinction is the whole point (#1253). `describe()`'s sync path passed
|
|
194
|
-
* `zodFields: undefined`, so `fieldMeta` key-validation could never run there
|
|
195
|
-
* and a typo'd key became a PHANTOM FIELD carrying its declared `sensitivity`
|
|
196
|
-
* while the real field went undescribed — an inventory wrong in both
|
|
197
|
-
* directions, silently, on the surface `sensitivity` exists to serve.
|
|
198
|
-
*
|
|
199
|
-
* That was read as unavoidable ("the sync path cannot know the schema's
|
|
200
|
-
* fields"), which is true of types and false of keys: a Zod object exposes
|
|
201
|
-
* `.shape` directly, on both v3 and v4, with no JSON-Schema derivation and no
|
|
202
|
-
* `zod-to-json-schema` peer. The latter matters — on Zod 3 that peer is
|
|
203
|
-
* required for the async path, so the sync path is the only one some
|
|
204
|
-
* consumers can reach.
|
|
205
|
-
*
|
|
206
|
-
* A duck-typed probe, not a Zod dependency: an unrecognised validator returns
|
|
207
|
-
* `undefined` and the caller stays silent rather than guessing, so a validator
|
|
208
|
-
* hub cannot read never produces a false "unknown field" error.
|
|
209
|
-
*
|
|
210
|
-
* Wrappers (reported by the pilot on #1249's guard, and it equally affected
|
|
211
|
-
* #1253's): a wrapped object's `.shape` is undefined, so "has a schema" and
|
|
212
|
-
* "hub can enumerate its fields" silently diverged for exactly the schemas
|
|
213
|
-
* most worth guarding — validated ones.
|
|
214
|
-
*
|
|
215
|
-
* ONE RULE UNWRAPS ALL OF THEM: FOLLOW THE OUTPUT SIDE. These keys describe
|
|
216
|
-
* the PARSED record, so the only question a wrapper raises is whether it
|
|
217
|
-
* changes the parsed shape.
|
|
218
|
-
*
|
|
219
|
-
* Zod 3 `ZodEffects` — `.refine()`/`.superRefine()` (`effect.type` is
|
|
220
|
-
* `'refinement'`) and `z.preprocess()` (`'preprocess'`) both parse WITH
|
|
221
|
-
* the inner schema, so the output is the inner object: follow
|
|
222
|
-
* `_def.schema`. `.transform()` REPLACES the output, so the inner keys
|
|
223
|
-
* would be a lie: stay `undefined`, deliberately.
|
|
224
|
-
* Zod 4 `ZodPipe` — `z.preprocess()` is `pipe(transform -> object)` and
|
|
225
|
-
* `.transform()` is `pipe(object -> transform)`. Following `_def.out`
|
|
226
|
-
* resolves both correctly with no effect-kind test: preprocess reaches the
|
|
227
|
-
* object, transform reaches a `ZodTransform` that has no shape and no
|
|
228
|
-
* inner schema, so the loop returns `undefined` on its own.
|
|
229
|
-
*
|
|
230
|
-
* `preprocess` was the pilot's second finding (#1262): the first fix followed
|
|
231
|
-
* refinements only, which left four of their registered collections unguarded
|
|
232
|
-
* — including the one carrying their only `fieldMeta` PII declaration, i.e.
|
|
233
|
-
* precisely the collection #1253 was written for. It is one string away from
|
|
234
|
-
* `transform` and means the opposite thing, so a test pins the two apart.
|
|
235
|
-
*
|
|
236
|
-
* A duck-typed probe throughout: an unrecognised validator returns `undefined`
|
|
237
|
-
* and the caller stays silent rather than guessing. Bounded depth, so a cyclic
|
|
238
|
-
* duck cannot hang us.
|
|
239
|
-
*/
|
|
240
|
-
export declare function schemaFieldKeys(schema: unknown): readonly string[] | undefined;
|
|
241
190
|
export declare function deriveZodFields(schema: unknown): Promise<Record<string, ZodFieldSlot>>;
|
|
242
191
|
/**
|
|
243
192
|
* Resolve the persisted-schema `fieldIds` map for `describeAsync()`'s
|
|
@@ -59,3 +59,89 @@ export interface ResolvedMeta extends Partial<FieldMeta> {
|
|
|
59
59
|
export declare function humanizeFieldKey(key: string): string;
|
|
60
60
|
/** Merge one field's metadata: channel > zodMeta > inferred; label always present. */
|
|
61
61
|
export declare function resolveFieldMeta(key: string, inputs: MergeInputs): ResolvedMeta;
|
|
62
|
+
/**
|
|
63
|
+
* Field KEYS from a configured validator, synchronously — the complement to
|
|
64
|
+
* {@link deriveZodFields}, which derives field TYPES and must be async.
|
|
65
|
+
*
|
|
66
|
+
* The distinction is the whole point (#1253). `describe()`'s sync path passed
|
|
67
|
+
* `zodFields: undefined`, so `fieldMeta` key-validation could never run there
|
|
68
|
+
* and a typo'd key became a PHANTOM FIELD carrying its declared `sensitivity`
|
|
69
|
+
* while the real field went undescribed — an inventory wrong in both
|
|
70
|
+
* directions, silently, on the surface `sensitivity` exists to serve.
|
|
71
|
+
*
|
|
72
|
+
* That was read as unavoidable ("the sync path cannot know the schema's
|
|
73
|
+
* fields"), which is true of types and false of keys: a Zod object exposes
|
|
74
|
+
* `.shape` directly, on both v3 and v4, with no JSON-Schema derivation and no
|
|
75
|
+
* `zod-to-json-schema` peer. The latter matters — on Zod 3 that peer is
|
|
76
|
+
* required for the async path, so the sync path is the only one some
|
|
77
|
+
* consumers can reach.
|
|
78
|
+
*
|
|
79
|
+
* A duck-typed probe, not a Zod dependency: an unrecognised validator returns
|
|
80
|
+
* `undefined` and the caller stays silent rather than guessing, so a validator
|
|
81
|
+
* hub cannot read never produces a false "unknown field" error.
|
|
82
|
+
*
|
|
83
|
+
* Wrappers (reported by the pilot on #1249's guard, and it equally affected
|
|
84
|
+
* #1253's): a wrapped object's `.shape` is undefined, so "has a schema" and
|
|
85
|
+
* "hub can enumerate its fields" silently diverged for exactly the schemas
|
|
86
|
+
* most worth guarding — validated ones.
|
|
87
|
+
*
|
|
88
|
+
* ONE RULE UNWRAPS ALL OF THEM: FOLLOW THE OUTPUT SIDE. These keys describe
|
|
89
|
+
* the PARSED record, so the only question a wrapper raises is whether it
|
|
90
|
+
* changes the parsed shape.
|
|
91
|
+
*
|
|
92
|
+
* Zod 3 `ZodEffects` — `.refine()`/`.superRefine()` (`effect.type` is
|
|
93
|
+
* `'refinement'`) and `z.preprocess()` (`'preprocess'`) both parse WITH
|
|
94
|
+
* the inner schema, so the output is the inner object: follow
|
|
95
|
+
* `_def.schema`. `.transform()` REPLACES the output, so the inner keys
|
|
96
|
+
* would be a lie: stay `undefined`, deliberately.
|
|
97
|
+
* Zod 4 `ZodPipe` — `z.preprocess()` is `pipe(transform -> object)` and
|
|
98
|
+
* `.transform()` is `pipe(object -> transform)`. Following `_def.out`
|
|
99
|
+
* resolves both correctly with no effect-kind test: preprocess reaches the
|
|
100
|
+
* object, transform reaches a `ZodTransform` that has no shape and no
|
|
101
|
+
* inner schema, so the loop returns `undefined` on its own.
|
|
102
|
+
*
|
|
103
|
+
* `preprocess` was the pilot's second finding (#1262): the first fix followed
|
|
104
|
+
* refinements only, which left four of their registered collections unguarded
|
|
105
|
+
* — including the one carrying their only `fieldMeta` PII declaration, i.e.
|
|
106
|
+
* precisely the collection #1253 was written for. It is one string away from
|
|
107
|
+
* `transform` and means the opposite thing, so a test pins the two apart.
|
|
108
|
+
*
|
|
109
|
+
* A duck-typed probe throughout: an unrecognised validator returns `undefined`
|
|
110
|
+
* and the caller stays silent rather than guessing. Bounded depth, so a cyclic
|
|
111
|
+
* duck cannot hang us.
|
|
112
|
+
*/
|
|
113
|
+
export declare function schemaFieldKeys(schema: unknown): readonly string[] | undefined;
|
|
114
|
+
/**
|
|
115
|
+
* Registration-time `fieldMeta` key check (#1253 follow-up, the "refuse as early
|
|
116
|
+
* as the information allows" pass).
|
|
117
|
+
*
|
|
118
|
+
* The same validation `buildDescription` runs, hoisted to collection
|
|
119
|
+
* construction so a typo is refused when it is DECLARED rather than when
|
|
120
|
+
* someone happens to call `describe()`. A collection nobody describes was
|
|
121
|
+
* previously never checked at all — and `fieldMeta` is what carries
|
|
122
|
+
* `sensitivity`, so the un-checked case was the one with a data-classification
|
|
123
|
+
* inventory hanging off it.
|
|
124
|
+
*
|
|
125
|
+
* Deliberately NOT a replacement for the check inside `buildDescription`. There
|
|
126
|
+
* are three tiers of knowability, not two:
|
|
127
|
+
*
|
|
128
|
+
* always — a name declared in the config itself (see the virtual-field
|
|
129
|
+
* refusal in the derivation registry: "declared and never
|
|
130
|
+
* stored" needs no schema at all)
|
|
131
|
+
* at reg. — a validator whose `.shape` is readable synchronously: THIS
|
|
132
|
+
* at describe — a validator whose fields only exist after async JSON-Schema
|
|
133
|
+
* derivation (Zod 3 + `zod-to-json-schema`)
|
|
134
|
+
*
|
|
135
|
+
* The third tier is why `buildDescription` keeps its own call. Hoisting alone
|
|
136
|
+
* would move the check earlier for some collections and REMOVE it for others.
|
|
137
|
+
*
|
|
138
|
+
* Silent when the field set cannot be enumerated, exactly as the describe-time
|
|
139
|
+
* check is: a TS-generic collection names real fields that appear in no runtime
|
|
140
|
+
* config, and rejecting those teaches people to stop declaring `fieldMeta`.
|
|
141
|
+
*/
|
|
142
|
+
export declare function validateFieldMetaAtRegistration(input: {
|
|
143
|
+
readonly collection: string;
|
|
144
|
+
readonly schema?: unknown;
|
|
145
|
+
readonly fieldMeta?: Record<string, unknown> | undefined;
|
|
146
|
+
readonly configKeys: readonly string[];
|
|
147
|
+
}): void;
|
|
@@ -4,9 +4,9 @@ import {
|
|
|
4
4
|
freezeSnapshotOnly,
|
|
5
5
|
randomId,
|
|
6
6
|
withdrawAccessibleData
|
|
7
|
-
} from "./chunk-
|
|
8
|
-
import "./chunk-
|
|
9
|
-
import "./chunk-
|
|
7
|
+
} from "./chunk-TGPST6FK.js";
|
|
8
|
+
import "./chunk-EKZ6VZYF.js";
|
|
9
|
+
import "./chunk-NLOLF6LD.js";
|
|
10
10
|
import "./chunk-MLO75SFJ.js";
|
|
11
11
|
import "./chunk-CIGAQBJR.js";
|
|
12
12
|
import "./chunk-4RUFYGWG.js";
|
|
@@ -17,7 +17,6 @@ import "./chunk-LP7BEXCT.js";
|
|
|
17
17
|
import "./chunk-SL3JAAKN.js";
|
|
18
18
|
import "./chunk-N4CHCXDL.js";
|
|
19
19
|
import "./chunk-7O3Y4PPD.js";
|
|
20
|
-
import "./chunk-PZ5AY32C.js";
|
|
21
20
|
export {
|
|
22
21
|
FROZEN_SNAPSHOTS_COLLECTION,
|
|
23
22
|
freezeAndDeleteClosure,
|
|
@@ -25,4 +24,4 @@ export {
|
|
|
25
24
|
randomId,
|
|
26
25
|
withdrawAccessibleData
|
|
27
26
|
};
|
|
28
|
-
//# sourceMappingURL=withdraw-accessible-
|
|
27
|
+
//# sourceMappingURL=withdraw-accessible-42D32Z7G.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@noy-db/hub",
|
|
3
|
-
"version": "0.7.0-pre.
|
|
3
|
+
"version": "0.7.0-pre.14",
|
|
4
4
|
"description": "Zero-knowledge, offline-first, encrypted document store — core library with AES-256-GCM, PBKDF2, multi-user keyring, and sync engine",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "vLannaAi <vicio@lanna.ai>",
|
|
@@ -233,19 +233,23 @@
|
|
|
233
233
|
"node": ">=22.0.0"
|
|
234
234
|
},
|
|
235
235
|
"dependencies": {
|
|
236
|
-
"@noy-db/attestation": "0.7.0-pre.
|
|
236
|
+
"@noy-db/attestation": "0.7.0-pre.14"
|
|
237
237
|
},
|
|
238
238
|
"devDependencies": {
|
|
239
239
|
"@types/node": "^22.0.0",
|
|
240
240
|
"esbuild": "^0.25.0",
|
|
241
241
|
"zod": "^4.0.0",
|
|
242
242
|
"zod-to-json-schema": "^3.25.2",
|
|
243
|
-
"@noy-db/on-shamir": "0.7.0-pre.
|
|
243
|
+
"@noy-db/on-shamir": "0.7.0-pre.14"
|
|
244
244
|
},
|
|
245
245
|
"peerDependencies": {
|
|
246
|
+
"zod": "^4.0.0",
|
|
246
247
|
"zod-to-json-schema": "^3.25.0"
|
|
247
248
|
},
|
|
248
249
|
"peerDependenciesMeta": {
|
|
250
|
+
"zod": {
|
|
251
|
+
"optional": true
|
|
252
|
+
},
|
|
249
253
|
"zod-to-json-schema": {
|
|
250
254
|
"optional": true
|
|
251
255
|
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/with-formula/derivations/strategy-hash.ts","../src/with-formula/derivations/registry.ts"],"sourcesContent":["/**\n * Deterministic hash of a derivation strategy's \"shape\": source\n * collection, declared sibling sources, output keys, derive function\n * source. Used to detect\n * strategy drift: a record whose `_derivedFrom.strategyHash` doesn't\n * match the current strategy is considered stale.\n *\n * Web Crypto SHA-256 — no extra deps.\n */\nimport { sha256Hex } from '../../kernel/enclave/index.js'\n\nexport async function computeStrategyHash(\n source: string,\n outputKeys: readonly string[],\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n derive: (...args: any[]) => any,\n sources?: ReadonlyArray<string>,\n): Promise<string> {\n const canonical = JSON.stringify({\n source,\n outputs: [...outputKeys].sort(),\n derive: derive.toString(),\n // Declared sibling sources — adding/removing a trigger\n // collection invalidates cached derived records. Omitted when empty\n // so strategies without siblings keep their existing hash.\n ...(sources?.length ? { sources: [...sources].sort() } : {}),\n })\n const bytes = new TextEncoder().encode(canonical)\n return sha256Hex(bytes)\n}\n","import { computedEntryParts, type ComputedEntryParts } from '../../kernel/collection-config.js'\nimport { DerivationCycleError, DuplicateBehaviorNameError, ValidationError } from '../../kernel/errors.js'\nimport { ViaGraph, type FieldRef, type EdgeKind, type Grain } from '../../kernel/via/graph.js'\nimport { schemaFieldKeys } from '../../with-shape/introspection/describe.js'\nimport { computeStrategyHash } from './strategy-hash.js'\nimport { normalizeTriggerBy, type NormalizedTrigger } from './trigger-match.js'\nimport type { DerivationSpec } from './types.js'\n\n/**\n * Whole-record artifact-grain field marker (#638 Task 2). A derivation's\n * `derive()`/a rollup's `compute()` reads the WHOLE source record — there is\n * no single declared field to point at — so a trigger/output collection is\n * modelled as one artifact node per collection (`kernel/via/graph.ts`'s\n * \"Artifact-grain targets... modelled as a field node whose field is the\n * artifact key\" convention). MUST match `materialized-views/registry.ts`'s\n * and `vault.ts`'s overlay-edge marker so cross-registry edges (a derivation\n * feeding into / out of an MV or overlay collection) resolve to the SAME\n * graph node.\n */\nconst WHOLE_RECORD = '*'\n\n/** Strip this registry's `.${WHOLE_RECORD}` artifact suffix off a graph cycle\n * path entry, recovering the bare collection name — preserves the exact\n * pre-#638 `DerivationCycleError` message shape (behavior lock) for a pure\n * record-shape derivation cycle. A rollup's real-field target (not suffixed\n * `.${WHOLE_RECORD}`) is left untouched. */\nfunction stripArtifactSuffix(displayId: string): string {\n const suffix = `.${WHOLE_RECORD}`\n return displayId.endsWith(suffix) ? displayId.slice(0, -suffix.length) : displayId\n}\n\ninterface RegisteredStrategy {\n // Type-erased to allow the registry to hold heterogeneous strategies.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n spec: DerivationSpec<any, any>\n strategyHash: string\n readonly triggers: ReadonlyArray<NormalizedTrigger>\n}\n\n/**\n * Vault-internal registry of derivation strategies. Owned by `Vault`;\n * not exported.\n *\n * @internal\n */\n/**\n * Field names a `triggerBy` match may NOT target, and the ones it may (#1266).\n *\n * Lives HERE, not in `kernel/collection-config.ts` where computed-mode logic\n * otherwise sits, because that module is in the FLOOR bundle: putting twenty\n * lines there cost every consumer 5.2% of the floor whether or not they use\n * derivations. Registry code is already behind the derivations opt-in.\n *\n * A derivation matcher reads the STORED record, so a match target must be a\n * field that is stored. `mode: 'virtual'` computed fields are evaluated on the\n * READ path and never persisted — but they appear in `computed:` (and in\n * `via(computed(...))`) exactly like materialized ones, so the registration\n * guard accepted them and the fan-out then matched nothing, forever: the guard's\n * own stated failure mode, reached through the guard rather than around it.\n *\n * Rejected rather than supported. Matching a virtual field means running user\n * code for every candidate row, which turns an indexed narrow into a full scan\n * of the collection; `mode: 'materialized'` is stored, already works, and is\n * what the error points the caller at.\n *\n * `declared` also folds in `viaFields`, which the guard's key set previously\n * omitted entirely — a `via()`-declared MATERIALIZED field is a perfectly good\n * match target and was being rejected as a typo. An over-firing guard teaches\n * people to stop trusting it, so both directions are fixed together.\n */\nfunction matchTargetFieldNames(opts: {\n // Deliberately `unknown`-valued: the caller is generic over the record type\n // (`ComputedFields<T>`), and this reads only `mode`, so narrowing the value\n // type here would force a variance cast at every call site instead of one\n // here. Both shapes are duck-checked below.\n readonly computed?: Readonly<Record<string, unknown>> | undefined\n readonly viaFields?: Readonly<Record<string, unknown>> | undefined\n}): { readonly declared: readonly string[]; readonly virtual: readonly string[] } {\n const declared: string[] = []\n const virtual: string[] = []\n for (const [field, entry] of Object.entries(opts.computed ?? {})) {\n declared.push(field)\n const parts: ComputedEntryParts = computedEntryParts(entry as Parameters<typeof computedEntryParts>[0])\n if (parts.mode === 'virtual') virtual.push(field)\n }\n for (const [field, spec] of Object.entries(opts.viaFields ?? {})) {\n declared.push(field)\n const descriptors = (spec as { descriptors?: readonly unknown[] }).descriptors ?? []\n for (const d of descriptors) {\n if ((d as { _viaBrand?: unknown })._viaBrand === 'computed'\n && (d as { mode?: unknown }).mode === 'virtual') virtual.push(field)\n }\n }\n return { declared, virtual }\n}\n\nexport class DerivationRegistry {\n private readonly _bySource = new Map<string, RegisteredStrategy[]>()\n private readonly _byOutput = new Map<string, RegisteredStrategy[]>()\n private readonly _byName = new Map<string, RegisteredStrategy>()\n\n /**\n * Register a derivation strategy. If `spec.name` is given and already\n * registered by another derivation in this vault, throws\n * {@link DuplicateBehaviorNameError} before indexing.\n */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n async register(spec: DerivationSpec<any, any>): Promise<void> {\n if (spec.name !== undefined && this._byName.has(spec.name)) {\n throw new DuplicateBehaviorNameError(spec.name, 'derivation')\n }\n\n const outputKeys = Object.keys(spec.outputs)\n const strategyHash = await computeStrategyHash(spec.source, outputKeys, spec.derive, spec.sources)\n const triggers = normalizeTriggerBy(spec.triggerBy)\n const reg: RegisteredStrategy = { spec, strategyHash, triggers }\n\n if (spec.name !== undefined) this._byName.set(spec.name, reg)\n\n const fromSource = this._bySource.get(spec.source)\n if (fromSource) fromSource.push(reg)\n else this._bySource.set(spec.source, [reg])\n\n // Declared sibling sources index the SAME `reg` under each\n // extra collection so `strategiesForSource(extra)` returns it and a\n // sibling write re-fires the derivation. Sibling keys also enter\n // `_bySource`, so `validate()`'s cycle DFS walks them automatically.\n for (const extra of spec.sources ?? []) {\n const fromExtra = this._bySource.get(extra)\n if (fromExtra) fromExtra.push(reg)\n else this._bySource.set(extra, [reg])\n }\n\n // FK triggers index the SAME `reg` under each parent collection so\n // a parent write re-fires the derivation (fanned out to matching source\n // records in `dispatchDerivations`). Like sources[], these keys enter\n // `_bySource` so the cycle DFS walks the trigger→output edge.\n for (const t of triggers) {\n const fromTrigger = this._bySource.get(t.collection)\n if (fromTrigger) fromTrigger.push(reg)\n else this._bySource.set(t.collection, [reg])\n }\n\n // Rollup: a write to the child `from` collection recomputes\n // the parent (= spec.source = into) at id child[key]. Index under `from`\n // so a child write fires it; spec.source (into) is already indexed above,\n // so a parent write also recomputes its own aggregate (covers the\n // parent-created-after-children case). The from→into edge enters the cycle\n // DFS automatically.\n if (spec.rollup) {\n const fromRollup = this._bySource.get(spec.rollup.from)\n if (fromRollup) fromRollup.push(reg)\n else this._bySource.set(spec.rollup.from, [reg])\n }\n\n for (const key of outputKeys) {\n const output = spec.outputs[key]\n if (!output) continue\n const outputCollection = output.collection\n const arr = this._byOutput.get(outputCollection)\n if (arr) arr.push(reg)\n else this._byOutput.set(outputCollection, [reg])\n }\n }\n\n strategiesForSource(source: string): ReadonlyArray<RegisteredStrategy> {\n return this._bySource.get(source) ?? []\n }\n\n strategiesProducingOutput(collection: string): ReadonlyArray<RegisteredStrategy> {\n return this._byOutput.get(collection) ?? []\n }\n\n /**\n * The #1253-pattern typo guard for match fields (#1249): a misspelt\n * `to`/`from` silently matches nothing forever, so validate against the\n * collection's enumerable field set at the earliest point it exists.\n * `schemaFieldKeys(schema) === undefined` (TS-generic collection,\n * unreadable validator) is DELIBERATELY silent — those fields are real\n * and unenumerable. `configKeys` folds in the collection's other\n * non-schema field declarations (fieldMeta/moneyFields/dictKeyFields/\n * refs/computed); `denormExempt` is derived here from this registry's\n * own strategies rather than taken as a parameter — a field this\n * collection's own derivations write via `denorm` is never a typo.\n */\n validateFieldsFor(\n collectionName: string,\n schema: unknown,\n configKeys: ReadonlyArray<string>,\n viaSources: {\n readonly computed?: Readonly<Record<string, unknown>> | undefined\n readonly viaFields?: Readonly<Record<string, unknown>> | undefined\n } | undefined = undefined,\n ): void {\n const targets = matchTargetFieldNames(viaSources ?? {})\n const virtual = new Set(targets.virtual)\n const shapeKeys = schemaFieldKeys(schema)\n // The two checks have DIFFERENT preconditions, which is why one loop runs\n // both rather than an early return covering both. The typo check needs a\n // readable field list and must stay silent without one — a false \"unknown\n // field\" on a TS-generic collection would be worse than no check. The\n // virtual check needs nothing but the declaration in hand: \"declared, and\n // never stored\" is provable from the declaration alone (#1266).\n if (shapeKeys === undefined && virtual.size === 0) return\n const keys = shapeKeys === undefined ? undefined\n : new Set([...shapeKeys, ...configKeys, ...targets.declared])\n const denormExempt = new Set<string>()\n const regs = new Set([...this._bySource.values()].flat())\n for (const reg of regs) {\n if (reg.spec.source !== collectionName) continue\n for (const out of Object.values(reg.spec.outputs) as Array<{ collection?: string; denorm?: readonly string[] }>) {\n if (out.collection === collectionName) for (const d of out.denorm ?? []) denormExempt.add(d)\n }\n }\n const unknown = (f: string): boolean =>\n keys !== undefined && !keys.has(f) && !denormExempt.has(f)\n for (const reg of regs) {\n const name = reg.spec.name ?? reg.spec.source\n for (const t of reg.triggers) {\n for (const p of t.match) {\n // `to` reads the SOURCE record; `from` reads the WRITTEN record.\n // Both are the stored shape, so both refuse a virtual target.\n if (reg.spec.source === collectionName) {\n if (virtual.has(p.to)) throw new ValidationError(this._virtualMatchMessage(name, collectionName, p.to, 'to'))\n if (unknown(p.to)) {\n throw new ValidationError(\n `derivation \"${name}\": triggerBy match names source field \"${p.to}\", which \"${collectionName}\" does not declare — a typo here silently matches nothing forever`)\n }\n }\n if (t.collection === collectionName && p.from !== 'id') {\n if (virtual.has(p.from)) throw new ValidationError(this._virtualMatchMessage(name, collectionName, p.from, 'from'))\n if (unknown(p.from)) {\n throw new ValidationError(\n `derivation \"${name}\": triggerBy match reads \"${p.from}\" from written \"${collectionName}\" records, which that collection does not declare — a typo here silently matches nothing forever`)\n }\n }\n }\n }\n }\n }\n\n /** #1266 — one message shape for both match sides; see {@link matchTargetFieldNames}. */\n private _virtualMatchMessage(name: string, collectionName: string, field: string, side: 'to' | 'from'): string {\n const where = side === 'to'\n ? `names source field \"${field}\", which \"${collectionName}\"`\n : `reads \"${field}\" from written \"${collectionName}\" records, which that collection`\n return `derivation \"${name}\": triggerBy match ${where} declares as a VIRTUAL computed field — virtual fields are evaluated on read and never stored, so the matcher would read every candidate record and match nothing, forever. Declare it as \\`mode: 'materialized'\\` (stored, indexable) or match on a stored field.`\n }\n\n /**\n * All registered strategies as a flat, deduplicated array.\n * Each strategy is indexed once per source (not once per output key),\n * so iterating `_bySource.values()` naturally yields each strategy\n * exactly once per source — deduplication is handled by flattening\n * the per-source arrays and collecting into a Set by identity.\n *\n * Used by `dumpSchema()` / `describeDerivations()` in the introspection\n * walker to populate the derivations map.\n */\n all(): ReadonlyArray<RegisteredStrategy> {\n const seen = new Set<RegisteredStrategy>()\n for (const strategies of this._bySource.values()) {\n for (const s of strategies) seen.add(s)\n }\n return [...seen]\n }\n\n /**\n * Graph edges for #638 Task 2: one `'derivation'`/`'record'` edge per\n * (non-self-write) output key — target = the output collection (a\n * `WHOLE_RECORD` artifact node), sources = every collection that can\n * trigger this strategy (`source`, `sources[]`, `triggerBy[].collection`,\n * and — for a rollup — `rollup.from`), mirroring EXACTLY the trigger keys\n * `register()` indexes under `_bySource`. The self-write reverse-denorm\n * output (`output.collection === source` with `denorm` declared) is\n * skipped — the SAME condition the old local DFS used; it is not a cycle\n * (see the comment `validate()` used to carry, now on this skip).\n * Rollups ADDITIONALLY get a dedicated `'rollup'`/`'aggregate'` edge\n * targeting the real `rollup.field` on the parent (`source`) — the\n * self-write output above only covers the cycle-DFS skip, not\n * dispatch/taint (Tasks 3/4/6), which need the real field.\n */\n edges(): ReadonlyArray<{ readonly target: FieldRef; readonly sources: readonly FieldRef[]; readonly kind: EdgeKind; readonly grain: Grain }> {\n const out: Array<{ target: FieldRef; sources: FieldRef[]; kind: EdgeKind; grain: Grain }> = []\n for (const reg of this.all()) {\n const spec = reg.spec\n const triggerCollections = [\n spec.source,\n ...(spec.sources ?? []),\n ...(spec.triggerBy ?? []).map((t) => t.collection),\n ...(spec.rollup ? [spec.rollup.from] : []),\n ]\n const sources: FieldRef[] = triggerCollections.map((c) => ({ collection: c, field: WHOLE_RECORD }))\n for (const key of Object.keys(spec.outputs)) {\n const output = spec.outputs[key]\n if (!output) continue\n if (output.shape === 'record' && output.collection === spec.source && output.denorm !== undefined) continue\n out.push({ target: { collection: output.collection, field: WHOLE_RECORD }, sources, kind: 'derivation', grain: 'record' })\n }\n if (spec.rollup) {\n out.push({\n target: { collection: spec.source, field: spec.rollup.field },\n sources: [{ collection: spec.rollup.from, field: WHOLE_RECORD }],\n kind: 'rollup',\n grain: 'aggregate',\n })\n }\n }\n return out\n }\n\n /**\n * Cycle detection, delegated to `ViaGraph.assertAcyclic()` (#638 Task 2 —\n * retires the local DFS). Call after all `register()` calls complete (i.e.\n * at vault open). Throws `DerivationCycleError` on the first cycle found —\n * SAME class/timing as before; the message/path is byte-identical to the\n * pre-#638 shape for a pure record-shape derivation cycle (the\n * `WHOLE_RECORD` artifact suffix is stripped back to a bare collection\n * name before re-throwing).\n *\n * `graph` is the caller's shared per-vault graph, ALREADY carrying this\n * registry's `edges()` (the caller registers them — see `Vault._initDerivations`)\n * — omit it (e.g. this registry's own unit tests) to validate this\n * registry's edges in isolation against a throwaway graph.\n */\n validate(graph?: ViaGraph): void {\n const g = graph ?? new ViaGraph()\n if (!graph) {\n for (const edge of this.edges()) g.registerDerived(edge.target, edge.sources, edge.kind, edge.grain)\n }\n try {\n g.assertAcyclic()\n } catch (e) {\n if (e instanceof DerivationCycleError) throw new DerivationCycleError(e.path.map(stripArtifactSuffix))\n throw e\n }\n }\n}\n"],"mappings":"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","names":[]}
|