@noy-db/hub 0.1.0-pre.9 → 0.2.0-pre.1
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/dist/aggregate/index.cjs +91 -36
- package/dist/aggregate/index.cjs.map +1 -1
- package/dist/aggregate/index.d.cts +2 -2
- package/dist/aggregate/index.d.ts +2 -2
- package/dist/aggregate/index.js +16 -9
- package/dist/aggregate/index.js.map +1 -1
- package/dist/blobs/index.cjs.map +1 -1
- package/dist/blobs/index.d.cts +6 -6
- package/dist/blobs/index.d.ts +6 -6
- package/dist/blobs/index.js +4 -4
- package/dist/bundle/index.cjs +298 -7
- package/dist/bundle/index.cjs.map +1 -1
- package/dist/bundle/index.d.cts +6 -6
- package/dist/bundle/index.d.ts +6 -6
- package/dist/bundle/index.js +15 -4
- package/dist/{chunk-GOUT6DND.js → chunk-23TTQXVO.js} +173 -91
- package/dist/chunk-23TTQXVO.js.map +1 -0
- package/dist/{chunk-CIMZBAZB.js → chunk-2AXFIYHT.js} +1 -1
- package/dist/chunk-2AXFIYHT.js.map +1 -0
- package/dist/chunk-34YSDCDP.js +73 -0
- package/dist/chunk-34YSDCDP.js.map +1 -0
- package/dist/{chunk-AVVPZ4BC.js → chunk-4TFSM22V.js} +4 -4
- package/dist/{chunk-QGZRWRSL.js → chunk-537VFZTR.js} +4 -4
- package/dist/{chunk-M62XNWRA.js → chunk-5DWL3JBF.js} +2 -2
- package/dist/{chunk-PTVMYYON.js → chunk-5SCJ5UEF.js} +3 -3
- package/dist/chunk-5ZGZ6HIZ.js +100 -0
- package/dist/chunk-5ZGZ6HIZ.js.map +1 -0
- package/dist/chunk-6HPZY4ON.js +291 -0
- package/dist/chunk-6HPZY4ON.js.map +1 -0
- package/dist/{chunk-EXHNQEV4.js → chunk-7H6DOO3E.js} +239 -11
- package/dist/chunk-7H6DOO3E.js.map +1 -0
- package/dist/{chunk-ACLDOTNQ.js → chunk-ADQ5MQ54.js} +275 -3
- package/dist/chunk-ADQ5MQ54.js.map +1 -0
- package/dist/chunk-CBAHB2BF.js +893 -0
- package/dist/chunk-CBAHB2BF.js.map +1 -0
- package/dist/chunk-DPMFBCV6.js +296 -0
- package/dist/chunk-DPMFBCV6.js.map +1 -0
- package/dist/chunk-DYBQG5PQ.js +34 -0
- package/dist/chunk-DYBQG5PQ.js.map +1 -0
- package/dist/{chunk-ZFKD4QMV.js → chunk-DYECX3IX.js} +3 -3
- package/dist/chunk-EGQYGYIU.js +51 -0
- package/dist/chunk-EGQYGYIU.js.map +1 -0
- package/dist/chunk-FCXOFQAJ.js +79 -0
- package/dist/chunk-FCXOFQAJ.js.map +1 -0
- package/dist/chunk-HB3Z2GCR.js +124 -0
- package/dist/chunk-HB3Z2GCR.js.map +1 -0
- package/dist/{chunk-SCZXXXU4.js → chunk-I6MX32UC.js} +7 -32
- package/dist/chunk-I6MX32UC.js.map +1 -0
- package/dist/{chunk-VQBTTTUN.js → chunk-KESP7GOK.js} +4 -4
- package/dist/{chunk-VQBTTTUN.js.map → chunk-KESP7GOK.js.map} +1 -1
- package/dist/{chunk-NXFEYLVG.js → chunk-MIQHZESA.js} +4 -3
- package/dist/{chunk-NXFEYLVG.js.map → chunk-MIQHZESA.js.map} +1 -1
- package/dist/chunk-MKSA2V7A.js +19 -0
- package/dist/chunk-MKSA2V7A.js.map +1 -0
- package/dist/{chunk-M5INGEFC.js → chunk-MRIBLZL3.js} +3 -1
- package/dist/chunk-MRIBLZL3.js.map +1 -0
- package/dist/{chunk-MDDTIZUO.js → chunk-NIOHFJPJ.js} +6 -6
- package/dist/chunk-OMLIZL2P.js +61 -0
- package/dist/chunk-OMLIZL2P.js.map +1 -0
- package/dist/{chunk-USKYUS74.js → chunk-P7EQ2S5O.js} +2 -2
- package/dist/{chunk-WDM5XGGS.js → chunk-PA6R5ZCI.js} +181 -11
- package/dist/chunk-PA6R5ZCI.js.map +1 -0
- package/dist/chunk-PEULZC6M.js +118 -0
- package/dist/chunk-PEULZC6M.js.map +1 -0
- package/dist/chunk-RD5LYKD6.js +82 -0
- package/dist/chunk-RD5LYKD6.js.map +1 -0
- package/dist/chunk-SIZWEV2Y.js +145 -0
- package/dist/chunk-SIZWEV2Y.js.map +1 -0
- package/dist/{chunk-QAVUREFT.js → chunk-UA4RI7OT.js} +12 -6
- package/dist/chunk-UA4RI7OT.js.map +1 -0
- package/dist/chunk-UMLVJTYV.js +20 -0
- package/dist/chunk-UMLVJTYV.js.map +1 -0
- package/dist/chunk-UZXLQCHP.js +53 -0
- package/dist/chunk-UZXLQCHP.js.map +1 -0
- package/dist/{chunk-2CSJGFCB.js → chunk-VMIO4IXG.js} +5 -5
- package/dist/{chunk-MR4424N3.js → chunk-WCA2NROQ.js} +2 -2
- package/dist/{chunk-TDR6T5CJ.js → chunk-XGSOTWYX.js} +91 -132
- package/dist/chunk-XGSOTWYX.js.map +1 -0
- package/dist/{chunk-NPC4LFV5.js → chunk-YMYK7US4.js} +2 -2
- package/dist/{chunk-RKJ6OL7K.js → chunk-YS3POABP.js} +1 -1
- package/dist/chunk-YS3POABP.js.map +1 -0
- package/dist/chunk-Z72JH4KG.js +209 -0
- package/dist/chunk-Z72JH4KG.js.map +1 -0
- package/dist/{chunk-R36SIKES.js → chunk-ZNOEIM6Y.js} +2 -2
- package/dist/consent/index.cjs.map +1 -1
- package/dist/consent/index.d.cts +6 -6
- package/dist/consent/index.d.ts +6 -6
- package/dist/consent/index.js +3 -3
- package/dist/{crypto-IVKU7YTT.js → crypto-A7FRXYHC.js} +3 -3
- package/dist/{delegation-2DBS2EOH.js → delegation-YBA4X4JN.js} +5 -4
- package/dist/derivations/index.cjs +351 -0
- package/dist/derivations/index.cjs.map +1 -0
- package/dist/derivations/index.d.cts +71 -0
- package/dist/derivations/index.d.ts +71 -0
- package/dist/derivations/index.js +27 -0
- package/dist/{dev-unlock-BdPp68qn.d.ts → dev-unlock-D9s-loPr.d.ts} +1 -1
- package/dist/{dev-unlock-Da1B0TIK.d.cts → dev-unlock-DRwVSy2S.d.cts} +1 -1
- package/dist/executor-7E3VFGW7.js +11 -0
- package/dist/executor-CEWX2FQI.js +8 -0
- package/dist/executor-CEWX2FQI.js.map +1 -0
- package/dist/executor-X4SQ3ZLC.js +8 -0
- package/dist/executor-X4SQ3ZLC.js.map +1 -0
- package/dist/fanout-sidecar-VJ52RIEY.js +51 -0
- package/dist/fanout-sidecar-VJ52RIEY.js.map +1 -0
- package/dist/guards/index.cjs +315 -0
- package/dist/guards/index.cjs.map +1 -0
- package/dist/guards/index.d.cts +30 -0
- package/dist/guards/index.d.ts +30 -0
- package/dist/guards/index.js +29 -0
- package/dist/guards/index.js.map +1 -0
- package/dist/{hash-lsoL3eEW.d.ts → hash-DXXXusyk.d.ts} +1 -1
- package/dist/{hash-BEfzPKwo.d.cts → hash-DtRih9MQ.d.cts} +1 -1
- package/dist/history/index.cjs +8 -1
- package/dist/history/index.cjs.map +1 -1
- package/dist/history/index.d.cts +7 -7
- package/dist/history/index.d.ts +7 -7
- package/dist/history/index.js +6 -6
- package/dist/i18n/index.cjs +81 -0
- package/dist/i18n/index.cjs.map +1 -1
- package/dist/i18n/index.d.cts +6 -6
- package/dist/i18n/index.d.ts +6 -6
- package/dist/i18n/index.js +19 -6
- package/dist/i18n/index.js.map +1 -1
- package/dist/{index-8QDuznDr.d.ts → index-4agOpzqd.d.ts} +174 -3
- package/dist/{index-6xNpPsxR.d.cts → index-CNwA-B6-.d.ts} +303 -5
- package/dist/{index-DJTf9yxn.d.ts → index-CmVgTkqk.d.cts} +303 -5
- package/dist/{index-CywCC1qZ.d.cts → index-hdFvZkBP.d.cts} +174 -3
- package/dist/index.cjs +5615 -979
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +207 -16
- package/dist/index.d.ts +207 -16
- package/dist/index.js +2302 -741
- package/dist/index.js.map +1 -1
- package/dist/indexing/index.cjs +2 -0
- package/dist/indexing/index.cjs.map +1 -1
- package/dist/indexing/index.d.cts +3 -3
- package/dist/indexing/index.d.ts +3 -3
- package/dist/indexing/index.js +4 -4
- package/dist/{lazy-builder-CZVLKh0Z.d.cts → lazy-builder-C-rPfWG0.d.cts} +1 -1
- package/dist/{lazy-builder-BwEoBQZ9.d.ts → lazy-builder-Rpd-V3jP.d.ts} +1 -1
- package/dist/{ledger-QZTTHQAQ.js → ledger-3TXNP47J.js} +6 -6
- package/dist/ledger-3TXNP47J.js.map +1 -0
- package/dist/materialized-views/index.cjs +837 -0
- package/dist/materialized-views/index.cjs.map +1 -0
- package/dist/materialized-views/index.d.cts +183 -0
- package/dist/materialized-views/index.d.ts +183 -0
- package/dist/materialized-views/index.js +45 -0
- package/dist/materialized-views/index.js.map +1 -0
- package/dist/overlay-views/index.cjs +359 -0
- package/dist/overlay-views/index.cjs.map +1 -0
- package/dist/overlay-views/index.d.cts +81 -0
- package/dist/overlay-views/index.d.ts +81 -0
- package/dist/overlay-views/index.js +23 -0
- package/dist/overlay-views/index.js.map +1 -0
- package/dist/periods/index.cjs +7 -1
- package/dist/periods/index.cjs.map +1 -1
- package/dist/periods/index.d.cts +6 -6
- package/dist/periods/index.d.ts +6 -6
- package/dist/periods/index.js +6 -6
- package/dist/{predicate-SBHmi6D0.d.cts → predicate-Dnu81tsS.d.cts} +25 -1
- package/dist/{predicate-SBHmi6D0.d.ts → predicate-Dnu81tsS.d.ts} +25 -1
- package/dist/{public-envelope-6JTACYJV.js → public-envelope-PY6NKFLI.js} +4 -4
- package/dist/public-envelope-PY6NKFLI.js.map +1 -0
- package/dist/query/index.cjs +302 -124
- package/dist/query/index.cjs.map +1 -1
- package/dist/query/index.d.cts +3 -3
- package/dist/query/index.d.ts +3 -3
- package/dist/query/index.js +26 -11
- package/dist/read-only-facade-ITU6L7BL.js +7 -0
- package/dist/read-only-facade-ITU6L7BL.js.map +1 -0
- package/dist/registry-3L3N3PTG.js +10 -0
- package/dist/registry-3L3N3PTG.js.map +1 -0
- package/dist/registry-O47PUPSY.js +8 -0
- package/dist/registry-O47PUPSY.js.map +1 -0
- package/dist/registry-RFGGMVNJ.js +7 -0
- package/dist/registry-RFGGMVNJ.js.map +1 -0
- package/dist/registry-WLLMODKN.js +8 -0
- package/dist/registry-WLLMODKN.js.map +1 -0
- package/dist/session/index.cjs +7 -1
- package/dist/session/index.cjs.map +1 -1
- package/dist/session/index.d.cts +7 -7
- package/dist/session/index.d.ts +7 -7
- package/dist/session/index.js +10 -3
- package/dist/session/index.js.map +1 -1
- package/dist/shadow/index.cjs.map +1 -1
- package/dist/shadow/index.d.cts +6 -6
- package/dist/shadow/index.d.ts +6 -6
- package/dist/shadow/index.js +2 -2
- package/dist/stale-HSC5YO2O.js +13 -0
- package/dist/stale-HSC5YO2O.js.map +1 -0
- package/dist/store/index.cjs +14 -0
- package/dist/store/index.cjs.map +1 -1
- package/dist/store/index.d.cts +6 -6
- package/dist/store/index.d.ts +6 -6
- package/dist/store/index.js +5 -2
- package/dist/{strategy-D-SrOLCl.d.cts → strategy-DSTrsZ8t.d.cts} +72 -19
- package/dist/{strategy-D-SrOLCl.d.ts → strategy-DSTrsZ8t.d.ts} +72 -19
- package/dist/sync/index.cjs.map +1 -1
- package/dist/sync/index.d.cts +5 -5
- package/dist/sync/index.d.ts +5 -5
- package/dist/sync/index.js +4 -4
- package/dist/team/index.cjs +1554 -2
- package/dist/team/index.cjs.map +1 -1
- package/dist/team/index.d.cts +6 -6
- package/dist/team/index.d.ts +6 -6
- package/dist/team/index.js +76 -9
- package/dist/tx/index.cjs +296 -44
- package/dist/tx/index.cjs.map +1 -1
- package/dist/tx/index.d.cts +6 -6
- package/dist/tx/index.d.ts +6 -6
- package/dist/tx/index.js +2 -2
- package/dist/{types-Bnb82f5R.d.cts → types-C4lwMKKF.d.cts} +2605 -328
- package/dist/{types-Bo7NSXJr.d.ts → types-DW9RGSSs.d.ts} +2605 -328
- package/dist/util/index.cjs.map +1 -1
- package/dist/util/index.js +1 -1
- package/dist/with-derivation-C8LDlV7t.d.cts +13 -0
- package/dist/with-derivation-g-pGoMzL.d.ts +13 -0
- package/dist/with-guard-DWOCK4Ca.d.ts +18 -0
- package/dist/with-guard-jI1x9Z3k.d.cts +18 -0
- package/dist/with-materialized-view-DaKR-N6J.d.ts +27 -0
- package/dist/with-materialized-view-DcTx4H3j.d.cts +27 -0
- package/dist/with-overlayed-view-D-6oWAgM.d.cts +13 -0
- package/dist/with-overlayed-view-N7jYuNOS.d.ts +13 -0
- package/package.json +53 -2
- package/dist/chunk-4PWAI7Q4.js +0 -79
- package/dist/chunk-4PWAI7Q4.js.map +0 -1
- package/dist/chunk-ACLDOTNQ.js.map +0 -1
- package/dist/chunk-BTDCBVJW.js +0 -160
- package/dist/chunk-BTDCBVJW.js.map +0 -1
- package/dist/chunk-CIMZBAZB.js.map +0 -1
- package/dist/chunk-EXHNQEV4.js.map +0 -1
- package/dist/chunk-GOUT6DND.js.map +0 -1
- package/dist/chunk-M5INGEFC.js.map +0 -1
- package/dist/chunk-QAVUREFT.js.map +0 -1
- package/dist/chunk-RKJ6OL7K.js.map +0 -1
- package/dist/chunk-SCZXXXU4.js.map +0 -1
- package/dist/chunk-TDR6T5CJ.js.map +0 -1
- package/dist/chunk-WDM5XGGS.js.map +0 -1
- /package/dist/{chunk-AVVPZ4BC.js.map → chunk-4TFSM22V.js.map} +0 -0
- /package/dist/{chunk-QGZRWRSL.js.map → chunk-537VFZTR.js.map} +0 -0
- /package/dist/{chunk-M62XNWRA.js.map → chunk-5DWL3JBF.js.map} +0 -0
- /package/dist/{chunk-PTVMYYON.js.map → chunk-5SCJ5UEF.js.map} +0 -0
- /package/dist/{chunk-ZFKD4QMV.js.map → chunk-DYECX3IX.js.map} +0 -0
- /package/dist/{chunk-MDDTIZUO.js.map → chunk-NIOHFJPJ.js.map} +0 -0
- /package/dist/{chunk-USKYUS74.js.map → chunk-P7EQ2S5O.js.map} +0 -0
- /package/dist/{chunk-2CSJGFCB.js.map → chunk-VMIO4IXG.js.map} +0 -0
- /package/dist/{chunk-MR4424N3.js.map → chunk-WCA2NROQ.js.map} +0 -0
- /package/dist/{chunk-NPC4LFV5.js.map → chunk-YMYK7US4.js.map} +0 -0
- /package/dist/{chunk-R36SIKES.js.map → chunk-ZNOEIM6Y.js.map} +0 -0
- /package/dist/{crypto-IVKU7YTT.js.map → crypto-A7FRXYHC.js.map} +0 -0
- /package/dist/{delegation-2DBS2EOH.js.map → delegation-YBA4X4JN.js.map} +0 -0
- /package/dist/{ledger-QZTTHQAQ.js.map → derivations/index.js.map} +0 -0
- /package/dist/{public-envelope-6JTACYJV.js.map → executor-7E3VFGW7.js.map} +0 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { I as IndexStrategy, d as LazyQuery } from './lazy-builder-
|
|
2
|
-
import { A as AggregateStrategy } from './strategy-
|
|
1
|
+
import { I as IndexStrategy, d as LazyQuery } from './lazy-builder-Rpd-V3jP.js';
|
|
2
|
+
import { b as AggregateSpec, A as AggregateStrategy } from './strategy-DSTrsZ8t.js';
|
|
3
3
|
import { C as CrdtStrategy, a as CrdtMode, b as CrdtState } from './strategy-BSxFXGzb.js';
|
|
4
|
-
import { N as NoydbError,
|
|
5
|
-
import { I as IndexDef, C as CollectionIndexes } from './predicate-
|
|
4
|
+
import { N as NoydbError, Q as Query, ak as RefRegistry, ah as RefDescriptor, _ as JoinableSource, am as RefViolation, an as ScanBuilder } from './index-CNwA-B6-.js';
|
|
5
|
+
import { F as FieldClause, I as IndexDef, C as CollectionIndexes } from './predicate-Dnu81tsS.js';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Standard Schema v1 integration.
|
|
@@ -785,12 +785,17 @@ interface LedgerEntry {
|
|
|
785
785
|
readonly prevHash: string;
|
|
786
786
|
/**
|
|
787
787
|
* Which kind of mutation this entry records. only supports
|
|
788
|
-
* data operations (`put`, `delete`). Access-control
|
|
789
|
-
* (`grant`, `revoke`, `rotate`) will be added in a
|
|
790
|
-
* the keyring write path is instrumented — that's
|
|
791
|
-
* epic issue.
|
|
788
|
+
* data operations (`put`, `delete`, `amendment`). Access-control
|
|
789
|
+
* operations (`grant`, `revoke`, `rotate`) will be added in a
|
|
790
|
+
* follow-up once the keyring write path is instrumented — that's
|
|
791
|
+
* tracked in the epic issue.
|
|
792
|
+
*
|
|
793
|
+
* `'amendment'` is the multi-record audit entry written by the
|
|
794
|
+
* guards subsystem when an admin/owner uses `withTransactions(...)`
|
|
795
|
+
* to repair a constraint-violating state. See `amendment` field
|
|
796
|
+
* below for the structured payload.
|
|
792
797
|
*/
|
|
793
|
-
readonly op: 'put' | 'delete';
|
|
798
|
+
readonly op: 'put' | 'delete' | 'amendment';
|
|
794
799
|
/** The collection the mutation targeted. */
|
|
795
800
|
readonly collection: string;
|
|
796
801
|
/** The record id the mutation targeted. */
|
|
@@ -814,6 +819,17 @@ interface LedgerEntry {
|
|
|
814
819
|
* the file docstring.
|
|
815
820
|
*/
|
|
816
821
|
readonly payloadHash: string;
|
|
822
|
+
/**
|
|
823
|
+
* Optional human-readable tag describing why this mutation happened
|
|
824
|
+
* (#1). Threaded through `collection.put(_, _, { reason })`. Common
|
|
825
|
+
* values include `'import:csv'`, `'import:json'`, `'import:xlsx'` from
|
|
826
|
+
* `as-*` ImportPlan.apply(), but consumers can use any string for
|
|
827
|
+
* domain-specific audit filtering. Auto-strip via `canonicalJson` —
|
|
828
|
+
* absent on the wire, never serialized as `null`.
|
|
829
|
+
*
|
|
830
|
+
* Audit consumers filter: `entries.filter(e => e.reason?.startsWith('import:'))`.
|
|
831
|
+
*/
|
|
832
|
+
readonly reason?: string;
|
|
817
833
|
/**
|
|
818
834
|
* Optional hex-encoded sha256 of the encrypted JSON Patch delta
|
|
819
835
|
* blob stored alongside this entry in `_ledger_deltas/`. Present
|
|
@@ -837,6 +853,24 @@ interface LedgerEntry {
|
|
|
837
853
|
* entirely — never `{ deltaHash: undefined }`.
|
|
838
854
|
*/
|
|
839
855
|
readonly deltaHash?: string;
|
|
856
|
+
/**
|
|
857
|
+
* Present only when `op === 'amendment'`. Records the human reason,
|
|
858
|
+
* the role of the actor, the (collection, id, vBefore, vAfter) tuple
|
|
859
|
+
* for every record touched, and which guard invariants passed.
|
|
860
|
+
*
|
|
861
|
+
* See docs/superpowers/specs/2026-05-18-guards-design.md.
|
|
862
|
+
*/
|
|
863
|
+
readonly amendment?: {
|
|
864
|
+
readonly reason: string;
|
|
865
|
+
readonly role: 'admin' | 'owner';
|
|
866
|
+
readonly changes: ReadonlyArray<{
|
|
867
|
+
readonly collection: string;
|
|
868
|
+
readonly id: string;
|
|
869
|
+
readonly vBefore: number;
|
|
870
|
+
readonly vAfter: number;
|
|
871
|
+
}>;
|
|
872
|
+
readonly invariantsPassed: ReadonlyArray<string>;
|
|
873
|
+
};
|
|
840
874
|
}
|
|
841
875
|
/**
|
|
842
876
|
* Canonical (sort-stable) JSON encoder.
|
|
@@ -1056,6 +1090,20 @@ interface AppendInput {
|
|
|
1056
1090
|
* as the entry's `deltaHash` field.
|
|
1057
1091
|
*/
|
|
1058
1092
|
delta?: JsonPatch;
|
|
1093
|
+
/**
|
|
1094
|
+
* Present only for `op === 'amendment'` — structured audit
|
|
1095
|
+
* payload for multi-record repair operations performed via
|
|
1096
|
+
* `withTransactions(...)`. Carried through verbatim to the
|
|
1097
|
+
* resulting ledger entry.
|
|
1098
|
+
*/
|
|
1099
|
+
amendment?: LedgerEntry['amendment'];
|
|
1100
|
+
/**
|
|
1101
|
+
* Optional human-readable tag describing why this mutation happened
|
|
1102
|
+
* (#1). Threaded from `collection.put(_, _, { reason })`.
|
|
1103
|
+
* Carried verbatim onto the resulting ledger entry's `reason` field;
|
|
1104
|
+
* omitted from canonical JSON when undefined.
|
|
1105
|
+
*/
|
|
1106
|
+
reason?: string;
|
|
1059
1107
|
}
|
|
1060
1108
|
/**
|
|
1061
1109
|
* Result of `LedgerStore.verify()`. On success, `head` is the hash of
|
|
@@ -1949,8 +1997,9 @@ interface UnlockedKeyring {
|
|
|
1949
1997
|
*/
|
|
1950
1998
|
readonly exportCapability?: ExportCapability;
|
|
1951
1999
|
/**
|
|
1952
|
-
* `@noy-db/as-*` import capability
|
|
1953
|
-
* keyring was written before
|
|
2000
|
+
* `@noy-db/as-*` import capability. Absent when the
|
|
2001
|
+
* keyring was written before the import-capability extension
|
|
2002
|
+
* landed — default-closed semantics
|
|
1954
2003
|
* apply via `hasImportCapability` (no plaintext format granted, no
|
|
1955
2004
|
* bundle import granted, regardless of role).
|
|
1956
2005
|
*/
|
|
@@ -1970,6 +2019,65 @@ interface UnlockedKeyring {
|
|
|
1970
2019
|
*/
|
|
1971
2020
|
readonly policy?: VaultPolicyOnDisk;
|
|
1972
2021
|
}
|
|
2022
|
+
/** Load and unlock a user's keyring for a vault. */
|
|
2023
|
+
declare function loadKeyring(adapter: NoydbStore, vault: string, userId: string, passphrase: string): Promise<UnlockedKeyring>;
|
|
2024
|
+
/**
|
|
2025
|
+
* Create the initial owner keyring for a new vault.
|
|
2026
|
+
*
|
|
2027
|
+
* Pass `{ validate: true }` (or a `PassphrasePolicy`) to gate creation
|
|
2028
|
+
* on the phrase-format strength rules — `Noydb` threads this from
|
|
2029
|
+
* `NoydbOptions.validatePassphrase`. Direct callers (CLI, scripts,
|
|
2030
|
+
* test fixtures) opt in explicitly.
|
|
2031
|
+
*/
|
|
2032
|
+
declare function createOwnerKeyring(adapter: NoydbStore, vault: string, userId: string, passphrase: string, passphraseOpts?: PassphrasePolicy & {
|
|
2033
|
+
validate?: boolean;
|
|
2034
|
+
allowWeakPassphrase?: boolean;
|
|
2035
|
+
}): Promise<UnlockedKeyring>;
|
|
2036
|
+
/** Grant access to a new user. Caller must have grant privilege. */
|
|
2037
|
+
declare function grant(adapter: NoydbStore, vault: string, callerKeyring: UnlockedKeyring, options: GrantOptions): Promise<void>;
|
|
2038
|
+
/** Revoke a user's access. Optionally rotate keys for affected collections. */
|
|
2039
|
+
declare function revoke(adapter: NoydbStore, vault: string, callerKeyring: UnlockedKeyring, options: RevokeOptions): Promise<void>;
|
|
2040
|
+
/**
|
|
2041
|
+
* Mutate `role`, `displayName`, and/or `permissions` on an existing
|
|
2042
|
+
* keyring. Pure plaintext-header rewrite — no DEK rewrap, no KEK
|
|
2043
|
+
* required, no authenticator slots touched. Tier-2 enrollments and
|
|
2044
|
+
* recovery codes survive the operation.
|
|
2045
|
+
*
|
|
2046
|
+
* Role-elevation guard: BOTH the old role AND the new role must
|
|
2047
|
+
* satisfy `canUpdateRole(callerRole, _)`. This blocks the two
|
|
2048
|
+
* privilege-escalation shapes:
|
|
2049
|
+
* - admin elevates someone (or themselves) to owner
|
|
2050
|
+
* - admin demotes an owner to a role they then control
|
|
2051
|
+
*
|
|
2052
|
+
* Owner is always allowed. Admin manages admin / operator / viewer /
|
|
2053
|
+
* client laterally.
|
|
2054
|
+
*
|
|
2055
|
+
* Identity preserved: same userId, same DEK wrappings. Last-write-wins
|
|
2056
|
+
* through the standard keyring put (same concurrency story as `grant`
|
|
2057
|
+
* and `revoke`).
|
|
2058
|
+
*
|
|
2059
|
+
* @throws `NoAccessError` when no keyring exists for the target.
|
|
2060
|
+
* @throws `PermissionDeniedError` when the role hierarchy rejects.
|
|
2061
|
+
* @throws `ValidationError` when the diff is empty (nothing to update).
|
|
2062
|
+
*
|
|
2063
|
+
* @see #54
|
|
2064
|
+
*/
|
|
2065
|
+
declare function updateKeyringIdentity(adapter: NoydbStore, vault: string, callerKeyring: UnlockedKeyring, options: UpdateUserOptions): Promise<void>;
|
|
2066
|
+
/**
|
|
2067
|
+
* Change the user's passphrase. Re-wraps every DEK under the new KEK.
|
|
2068
|
+
*
|
|
2069
|
+
* Validates the new passphrase against the strength rules unless
|
|
2070
|
+
* `allowWeakPassphrase: true` is passed. Mirrors `rotatePassphrase`'s
|
|
2071
|
+
* default-on validation contract.
|
|
2072
|
+
*
|
|
2073
|
+
* `db.rotatePassphrase()` adds a `checkGate('rotate-passphrase')` step
|
|
2074
|
+
* on top of this primitive and additionally requires the OLD passphrase
|
|
2075
|
+
* for re-derivation; `changeSecret` reuses the cached unlocked KEK so
|
|
2076
|
+
* the OLD passphrase is not retyped.
|
|
2077
|
+
*/
|
|
2078
|
+
declare function changeSecret(adapter: NoydbStore, vault: string, keyring: UnlockedKeyring, newPassphrase: string, passphraseOpts?: PassphrasePolicy & {
|
|
2079
|
+
allowWeakPassphrase?: boolean;
|
|
2080
|
+
}): Promise<UnlockedKeyring>;
|
|
1973
2081
|
/**
|
|
1974
2082
|
* Recipient slot in a re-keyed `.noydb` bundle. Each slot becomes its
|
|
1975
2083
|
* own keyring file inside the bundle, sealed with its own passphrase.
|
|
@@ -2027,6 +2135,17 @@ interface BundleRecipient {
|
|
|
2027
2135
|
declare function buildRecipientKeyringFile(callerKeyring: UnlockedKeyring, recipient: BundleRecipient): Promise<KeyringFile>;
|
|
2028
2136
|
/** List all users with access to a vault. */
|
|
2029
2137
|
declare function listUsers(adapter: NoydbStore, vault: string): Promise<UserInfo[]>;
|
|
2138
|
+
/**
|
|
2139
|
+
* Optional filter knobs for {@link listUsersWithEnvelopes}.
|
|
2140
|
+
*
|
|
2141
|
+
* - `includeHidden` — when true, principals with `_meta/visibility/<id>`
|
|
2142
|
+
* set to `{ hidden: true }` are returned alongside everyone else.
|
|
2143
|
+
* Requires `owner` or `admin` callerRole; lower roles get
|
|
2144
|
+
* {@link import('../errors.js').PermissionDeniedError}.
|
|
2145
|
+
*/
|
|
2146
|
+
interface ListUsersOptions {
|
|
2147
|
+
readonly includeHidden?: boolean;
|
|
2148
|
+
}
|
|
2030
2149
|
/**
|
|
2031
2150
|
* Joined enumeration: every keyring + its `_users/<keyringId>`
|
|
2032
2151
|
* envelope side by side. Convenience for admin UIs that want to
|
|
@@ -2036,6 +2155,27 @@ declare function listUsers(adapter: NoydbStore, vault: string): Promise<UserInfo
|
|
|
2036
2155
|
* `userEnvelopeDek` is the vault's `_users` collection DEK
|
|
2037
2156
|
* (`vault.getDEK('_users')`); used to decrypt every envelope.
|
|
2038
2157
|
*
|
|
2158
|
+
* `callerRole` (#122) drives the directory-visibility checks:
|
|
2159
|
+
*
|
|
2160
|
+
* - When the vault's `_meta/directory` document has `enabled: false`,
|
|
2161
|
+
* only `owner` and `admin` callers may enumerate; anyone else gets
|
|
2162
|
+
* {@link import('../errors.js').DirectoryDisabledError}.
|
|
2163
|
+
* - Principals with `_meta/visibility/<id>` set to `{ hidden: true }`
|
|
2164
|
+
* are filtered out by default. `owner`/`admin` callers can pass
|
|
2165
|
+
* `{ includeHidden: true }` to see them; lower roles passing that
|
|
2166
|
+
* option get `PermissionDeniedError`.
|
|
2167
|
+
*
|
|
2168
|
+
* Honest caveat (#122): these filters are a UX hint, not a security
|
|
2169
|
+
* boundary. The keyring file is still listed at `_keyring/*` and the
|
|
2170
|
+
* envelope ciphertext at `_users/*`. A caller with direct store access
|
|
2171
|
+
* — or a caller that calls this function with `callerRole: 'owner'`
|
|
2172
|
+
* unconditionally — sees every principal. The protection is only as
|
|
2173
|
+
* strong as the role the calling layer passes in. The hub-level wrapper
|
|
2174
|
+
* on `Vault` sources `callerRole` from the unlocked keyring's `role`
|
|
2175
|
+
* field, which is signed-by-construction (it lives in the user's own
|
|
2176
|
+
* keyring file). See `docs/subsystems/user-envelope.md` →
|
|
2177
|
+
* "Directory visibility".
|
|
2178
|
+
*
|
|
2039
2179
|
* Principals without a persisted envelope (legacy keyrings predating
|
|
2040
2180
|
* the user-envelope feature) come back with `envelope: null`. The
|
|
2041
2181
|
* caller chooses how to render — usually "fall back to keyring's
|
|
@@ -2044,10 +2184,14 @@ declare function listUsers(adapter: NoydbStore, vault: string): Promise<UserInfo
|
|
|
2044
2184
|
* Order matches `listUsers()` (store-defined; sort if you need a
|
|
2045
2185
|
* stable display order).
|
|
2046
2186
|
*/
|
|
2047
|
-
declare function listUsersWithEnvelopes<T = unknown>(adapter: NoydbStore, vault: string, userEnvelopeDek: CryptoKey): Promise<Array<{
|
|
2187
|
+
declare function listUsersWithEnvelopes<T = unknown>(adapter: NoydbStore, vault: string, userEnvelopeDek: CryptoKey, callerRole: Role, options?: ListUsersOptions): Promise<Array<{
|
|
2048
2188
|
user: UserInfo;
|
|
2049
2189
|
envelope: UserEnvelope<T> | null;
|
|
2050
2190
|
}>>;
|
|
2191
|
+
/** Ensure a DEK exists for a collection. Generates one if new. */
|
|
2192
|
+
declare function ensureCollectionDEK(adapter: NoydbStore, vault: string, keyring: UnlockedKeyring): Promise<(collectionName: string) => Promise<CryptoKey>>;
|
|
2193
|
+
/** Persist a keyring file to the adapter. */
|
|
2194
|
+
declare function persistKeyring(adapter: NoydbStore, vault: string, keyring: UnlockedKeyring): Promise<void>;
|
|
2051
2195
|
/**
|
|
2052
2196
|
* Check whether a keyring is authorised for a given `@noy-db/as-*`
|
|
2053
2197
|
* export tier.
|
|
@@ -2946,6 +3090,283 @@ declare class SyncEngine {
|
|
|
2946
3090
|
private persistMeta;
|
|
2947
3091
|
}
|
|
2948
3092
|
|
|
3093
|
+
/**
|
|
3094
|
+
* **Wrap-DEKs primitive (#44)** — a single canonical shape for the
|
|
3095
|
+
* pattern of "serialize a DEK set, encrypt it under a credential-derived
|
|
3096
|
+
* AES-GCM key." Used by:
|
|
3097
|
+
*
|
|
3098
|
+
* - **tier-0** — paper recovery entries (`_meta/recovery-paper`),
|
|
3099
|
+
* credential = the printed code.
|
|
3100
|
+
* - **tier-2** — password authenticator slots (`KeyringFile.authenticators`,
|
|
3101
|
+
* `wrapKind: 'deks'`), credential = the user's password.
|
|
3102
|
+
*
|
|
3103
|
+
* **Not** used by `@noy-db/on-pin` — tier-3 wraps the DEK set under
|
|
3104
|
+
* the same conceptual pattern but at **100,000 PBKDF2 iterations**
|
|
3105
|
+
* (vs the 600,000 here), because the protection window for a PIN
|
|
3106
|
+
* slot is short (idle-timeout-bounded, typically 15 min) and 600k
|
|
3107
|
+
* iterations would make every PIN-resume noticeably slow. The wire
|
|
3108
|
+
* formats are deliberately incompatible. See `@noy-db/on-pin`'s
|
|
3109
|
+
* `PIN_PBKDF2_ITERATIONS` and the threat-model rationale in its
|
|
3110
|
+
* module docstring.
|
|
3111
|
+
*
|
|
3112
|
+
* Before #44, the same crypto lived in two places: `mintPaperRecoveryEntry`
|
|
3113
|
+
* (in `team/recovery.ts`) and `enrollPasswordAuthenticator` (in
|
|
3114
|
+
* `@noy-db/on-password`). Both functions did identical work — PBKDF2
|
|
3115
|
+
* the credential, AES-GCM-encrypt the JSON-serialized DEK set — but
|
|
3116
|
+
* their implementations had drifted apart enough that fixing a bug
|
|
3117
|
+
* in one wouldn't fix the other.
|
|
3118
|
+
*
|
|
3119
|
+
* This module owns the canonical implementation. Consumers compose:
|
|
3120
|
+
*
|
|
3121
|
+
* - `mintPaperRecoveryEntry` is now a thin wrapper that calls
|
|
3122
|
+
* `mintWrappedDeksBlob` and adds `{ codeId, enrolledAt }`.
|
|
3123
|
+
* - `enrollPasswordAuthenticator` calls `mintWrappedDeksBlob` and
|
|
3124
|
+
* wraps the result in the slot envelope.
|
|
3125
|
+
*
|
|
3126
|
+
* @module
|
|
3127
|
+
*/
|
|
3128
|
+
/**
|
|
3129
|
+
* The wrap-DEKs primitive — a serialized + AES-GCM-encrypted DEK set
|
|
3130
|
+
* keyed under a credential-derived key.
|
|
3131
|
+
*
|
|
3132
|
+
* All three fields are base64-encoded so the blob is JSON-safe and
|
|
3133
|
+
* round-trips through `_meta/*` envelopes (which carry plaintext
|
|
3134
|
+
* JSON in `_data`).
|
|
3135
|
+
*
|
|
3136
|
+
* Composition: `PaperRecoveryEntry extends WrappedDeksBlob` plus
|
|
3137
|
+
* `{ codeId, enrolledAt }`. `KeyringAuthenticatorWrappingDEKs`
|
|
3138
|
+
* carries the same three fields with `salt` stored in `meta` for
|
|
3139
|
+
* slot-format back-compat (#44 defers moving it to top-level).
|
|
3140
|
+
*/
|
|
3141
|
+
interface WrappedDeksBlob {
|
|
3142
|
+
/** Base64 PBKDF2 salt for the credential-derived wrapping key. */
|
|
3143
|
+
readonly salt: string;
|
|
3144
|
+
/** Base64 AES-GCM IV used for the `wrappedDeks` ciphertext. */
|
|
3145
|
+
readonly iv: string;
|
|
3146
|
+
/** Base64 AES-GCM ciphertext of `{ deks: { collection: base64rawDek } }`. */
|
|
3147
|
+
readonly wrappedDeks: string;
|
|
3148
|
+
}
|
|
3149
|
+
/**
|
|
3150
|
+
* Mint a fresh `WrappedDeksBlob` from a DEK set + a string credential.
|
|
3151
|
+
*
|
|
3152
|
+
* Generates a random salt + IV, derives a 256-bit AES-GCM key via
|
|
3153
|
+
* PBKDF2-SHA256(credential, salt, 600K), serializes the DEK set as
|
|
3154
|
+
* `{ deks: { coll: rawBase64 } }`, and AES-GCM-encrypts.
|
|
3155
|
+
*
|
|
3156
|
+
* The `credential` is the user-typed string (recovery code, password,
|
|
3157
|
+
* PIN). Caller normalization rules apply (e.g. paper
|
|
3158
|
+
* recovery uppercase-strips the code before reaching this function).
|
|
3159
|
+
*
|
|
3160
|
+
* @param deks - DEK set to wrap. Each DEK must be exportable via
|
|
3161
|
+
* `subtle.exportKey('raw', dek)` (the hub mints DEKs
|
|
3162
|
+
* this way; consumers feeding non-extractable keys
|
|
3163
|
+
* will get `InvalidAccessError` from WebCrypto).
|
|
3164
|
+
* @param credential - String input the consumer minted (paper code,
|
|
3165
|
+
* password, PIN). Treated as opaque bytes by PBKDF2.
|
|
3166
|
+
*/
|
|
3167
|
+
declare function mintWrappedDeksBlob(deks: Map<string, CryptoKey>, credential: string): Promise<WrappedDeksBlob>;
|
|
3168
|
+
/**
|
|
3169
|
+
* Reverse of {@link mintWrappedDeksBlob}. Re-derives the wrapping key
|
|
3170
|
+
* from the credential + stored salt, AES-GCM-decrypts the wrapped DEK
|
|
3171
|
+
* set, and re-imports each DEK as an extractable AES-GCM CryptoKey.
|
|
3172
|
+
*
|
|
3173
|
+
* Throws (AES-GCM auth tag failure) when the credential doesn't
|
|
3174
|
+
* match the blob. Callers iterating over multiple blobs (e.g. paper
|
|
3175
|
+
* recovery's "try every entry until one matches") should catch.
|
|
3176
|
+
*/
|
|
3177
|
+
declare function unwrapDeksFromBlob(blob: WrappedDeksBlob, credential: string): Promise<Map<string, CryptoKey>>;
|
|
3178
|
+
|
|
3179
|
+
/**
|
|
3180
|
+
* String-level Shamir provider injected into hub for `profile: 'shamir'`
|
|
3181
|
+
* recovery. Keeps hub free of any `@noy-db/on-shamir` import — hub never
|
|
3182
|
+
* sees `RawShare` or the share codecs. Implemented by
|
|
3183
|
+
* `shamirRecoveryProvider()` from `@noy-db/on-shamir`.
|
|
3184
|
+
*/
|
|
3185
|
+
interface ShamirRecoveryProvider {
|
|
3186
|
+
/** Split `secret` into `n` base32 share strings; any `k` recombine it. */
|
|
3187
|
+
splitToShares(secret: Uint8Array, k: number, n: number): string[];
|
|
3188
|
+
/**
|
|
3189
|
+
* Recombine `k`+ share strings into the secret. MUST throw on malformed,
|
|
3190
|
+
* truncated, insufficient, or mismatched shares.
|
|
3191
|
+
*/
|
|
3192
|
+
combineShares(shares: readonly string[]): Uint8Array;
|
|
3193
|
+
}
|
|
3194
|
+
|
|
3195
|
+
/**
|
|
3196
|
+
* Recovery profile persistence + dispatch — issue #10.
|
|
3197
|
+
*
|
|
3198
|
+
* v0.1.0-pre.5 wires the **paper** profile end-to-end through
|
|
3199
|
+
* `@noy-db/on-recovery`. The other three profiles (Shamir,
|
|
3200
|
+
* multi-channel, admin-mediated) ship the API surface and throw
|
|
3201
|
+
* {@link RecoveryProfileNotImplementedError} during use; per-profile
|
|
3202
|
+
* dispatch lands in follow-up issues.
|
|
3203
|
+
*
|
|
3204
|
+
* Storage layout:
|
|
3205
|
+
*
|
|
3206
|
+
* ```
|
|
3207
|
+
* _meta/recovery-paper — JSON { entries: RecoveryCodeEntry[] } produced by `on-recovery`.
|
|
3208
|
+
* _meta/recovery-shamir — reserved
|
|
3209
|
+
* _meta/recovery-multi — reserved
|
|
3210
|
+
* _meta/recovery-admin — reserved
|
|
3211
|
+
* ```
|
|
3212
|
+
*
|
|
3213
|
+
* Like `_meta/policy` and `_meta/handle`, the documents are plain JSON
|
|
3214
|
+
* with empty `_iv` — the recovery-code wrapping is what protects the
|
|
3215
|
+
* KEK; the entries themselves are inert without the user's code.
|
|
3216
|
+
*
|
|
3217
|
+
* @module
|
|
3218
|
+
*/
|
|
3219
|
+
|
|
3220
|
+
/**
|
|
3221
|
+
* One paper recovery code as persisted in `_meta/recovery-paper`.
|
|
3222
|
+
*
|
|
3223
|
+
* The hub's KEK is intentionally non-extractable (see `crypto.ts`),
|
|
3224
|
+
* so the recovery entry can't AES-KW-wrap the KEK directly. Instead
|
|
3225
|
+
* we wrap a serialized DEK set: the entry holds the AES-GCM
|
|
3226
|
+
* ciphertext of `{ deks: { collection: rawDekBase64 } }`. Recovery
|
|
3227
|
+
* deserializes the DEK set, then mints a fresh KEK from the new
|
|
3228
|
+
* passphrase and rewraps the DEKs under it.
|
|
3229
|
+
*
|
|
3230
|
+
* This is the same pattern `@noy-db/on-pin` uses for tier-3 quick
|
|
3231
|
+
* resume — the cryptographic guarantee is identical (AES-GCM with a
|
|
3232
|
+
* PBKDF2-derived key), and it sidesteps the non-extractable-KEK
|
|
3233
|
+
* constraint cleanly.
|
|
3234
|
+
*
|
|
3235
|
+
* Type-level composition (#44): `PaperRecoveryEntry extends
|
|
3236
|
+
* WrappedDeksBlob` — the three crypto fields (`salt`, `iv`,
|
|
3237
|
+
* `wrappedDeks`) come from the shared primitive; `codeId` and
|
|
3238
|
+
* `enrolledAt` are paper-recovery's own metadata. Wire format
|
|
3239
|
+
* unchanged.
|
|
3240
|
+
*/
|
|
3241
|
+
interface PaperRecoveryEntry extends WrappedDeksBlob {
|
|
3242
|
+
readonly codeId: string;
|
|
3243
|
+
readonly enrolledAt: string;
|
|
3244
|
+
}
|
|
3245
|
+
interface PaperRecoveryDoc {
|
|
3246
|
+
readonly _noydb_recovery: 1;
|
|
3247
|
+
readonly profile: 'paper';
|
|
3248
|
+
readonly entries: ReadonlyArray<PaperRecoveryEntry>;
|
|
3249
|
+
}
|
|
3250
|
+
/** Read the paper-recovery entries. Returns empty array when absent. */
|
|
3251
|
+
declare function loadPaperRecoveryEntries(store: NoydbStore, vault: string): Promise<ReadonlyArray<PaperRecoveryEntry>>;
|
|
3252
|
+
/** Replace the paper-recovery entries (used after burn-on-recovery). */
|
|
3253
|
+
declare function savePaperRecoveryEntries(store: NoydbStore, vault: string, entries: ReadonlyArray<PaperRecoveryEntry>): Promise<void>;
|
|
3254
|
+
/** Drop a single paper-recovery entry (burn-on-use). */
|
|
3255
|
+
declare function burnPaperRecoveryEntry(store: NoydbStore, vault: string, codeId: string): Promise<void>;
|
|
3256
|
+
/** Whether at least one recovery profile has any enrolled entries. */
|
|
3257
|
+
declare function hasRecoveryEnrolled(store: NoydbStore, vault: string): Promise<boolean>;
|
|
3258
|
+
/**
|
|
3259
|
+
* One Shamir-recovery entry as persisted in `_meta/recovery-shamir`.
|
|
3260
|
+
*
|
|
3261
|
+
* Like {@link PaperRecoveryEntry}, the entry composes
|
|
3262
|
+
* {@link WrappedDeksBlob} (DEKs wrapped under a fresh ephemeral
|
|
3263
|
+
* recovery secret) with profile-specific metadata. Unlike paper, the
|
|
3264
|
+
* "credential" was never visible to the user — it was 32 random
|
|
3265
|
+
* bytes split into N Shamir shares at enrollment. The shares ARE
|
|
3266
|
+
* the credential; the user holds them, the hub never sees them
|
|
3267
|
+
* again after `enrollRecovery` returns.
|
|
3268
|
+
*
|
|
3269
|
+
* Per the spec §5: the recovery secret is base64-encoded and
|
|
3270
|
+
* passed as the `credential` arg to
|
|
3271
|
+
* {@link mintWrappedDeksBlob} / {@link unwrapDeksFromBlob}. The
|
|
3272
|
+
* PBKDF2 round over high-entropy input is harmless overhead — it
|
|
3273
|
+
* keeps the shared primitive unchanged while letting Shamir reuse
|
|
3274
|
+
* the same wrapping pipeline as paper.
|
|
3275
|
+
*/
|
|
3276
|
+
interface ShamirRecoveryEntry extends WrappedDeksBlob {
|
|
3277
|
+
/** Stable id for this entry. Allows multiple Shamir splits to coexist. */
|
|
3278
|
+
readonly entryId: string;
|
|
3279
|
+
/** Threshold — minimum shares to reconstruct. */
|
|
3280
|
+
readonly k: number;
|
|
3281
|
+
/** Total shares minted at enrollment. */
|
|
3282
|
+
readonly n: number;
|
|
3283
|
+
/** x-coordinates of the n minted shares. Informational. Omitted as of 0.2
|
|
3284
|
+
* (string-level provider doesn't expose share x-coords); kept optional so
|
|
3285
|
+
* pre-0.2 entries still read. */
|
|
3286
|
+
readonly xCoords?: ReadonlyArray<number>;
|
|
3287
|
+
/** ISO timestamp. */
|
|
3288
|
+
readonly enrolledAt: string;
|
|
3289
|
+
/** Optional caller-supplied label (e.g., "2-of-3 board escrow"). */
|
|
3290
|
+
readonly label?: string;
|
|
3291
|
+
}
|
|
3292
|
+
interface ShamirRecoveryDoc {
|
|
3293
|
+
readonly _noydb_recovery: 1;
|
|
3294
|
+
readonly profile: 'shamir';
|
|
3295
|
+
readonly entries: ReadonlyArray<ShamirRecoveryEntry>;
|
|
3296
|
+
}
|
|
3297
|
+
/** Read the Shamir-recovery entries. Returns empty array when absent. */
|
|
3298
|
+
declare function loadShamirRecoveryEntries(store: NoydbStore, vault: string): Promise<ReadonlyArray<ShamirRecoveryEntry>>;
|
|
3299
|
+
/** Replace the Shamir-recovery entries (used by enrollment and rotation). */
|
|
3300
|
+
declare function saveShamirRecoveryEntries(store: NoydbStore, vault: string, entries: ReadonlyArray<ShamirRecoveryEntry>): Promise<void>;
|
|
3301
|
+
/**
|
|
3302
|
+
* Mint a fresh Shamir recovery entry from a DEK set.
|
|
3303
|
+
*
|
|
3304
|
+
* 1. Generates a 32-byte recovery secret.
|
|
3305
|
+
* 2. Wraps the DEK set under that secret via
|
|
3306
|
+
* {@link mintWrappedDeksBlob} (the recovery secret is base64-
|
|
3307
|
+
* encoded as the credential string — PBKDF2 over high-entropy
|
|
3308
|
+
* input is harmless overhead).
|
|
3309
|
+
* 3. Splits the recovery secret via Shamir into `n` shares with
|
|
3310
|
+
* threshold `k`.
|
|
3311
|
+
* 4. Zeros the in-memory recovery secret after wrapping + splitting.
|
|
3312
|
+
*
|
|
3313
|
+
* Returns:
|
|
3314
|
+
* - `entry` — the {@link ShamirRecoveryEntry} to persist.
|
|
3315
|
+
* - `shareStrings` — the `n` Base32-encoded share strings to
|
|
3316
|
+
* return to the caller. The HUB MUST NOT PERSIST THESE; once
|
|
3317
|
+
* returned they are the user's responsibility.
|
|
3318
|
+
*
|
|
3319
|
+
* @param deks - DEK set to wrap.
|
|
3320
|
+
* @param entryId - Stable id for this entry (caller-supplied or
|
|
3321
|
+
* hub-generated).
|
|
3322
|
+
* @param k - Threshold (>= 2).
|
|
3323
|
+
* @param n - Total shares (k <= n <= 255).
|
|
3324
|
+
* @param label - Optional caller label.
|
|
3325
|
+
*/
|
|
3326
|
+
declare function mintShamirRecoveryEntry(provider: ShamirRecoveryProvider, deks: Map<string, CryptoKey>, entryId: string, k: number, n: number, label?: string): Promise<{
|
|
3327
|
+
entry: ShamirRecoveryEntry;
|
|
3328
|
+
shareStrings: string[];
|
|
3329
|
+
}>;
|
|
3330
|
+
/**
|
|
3331
|
+
* Decrypt a Shamir recovery entry to recover the raw DEK set.
|
|
3332
|
+
*
|
|
3333
|
+
* Combines K or more `shares`, reconstructs the recovery secret,
|
|
3334
|
+
* unwraps the DEKs via {@link unwrapDeksFromBlob}.
|
|
3335
|
+
*
|
|
3336
|
+
* Throws (AES-GCM auth-tag mismatch) when the shares don't combine
|
|
3337
|
+
* to the secret originally used to mint the entry — typically
|
|
3338
|
+
* because they came from a different enrollment or were tampered
|
|
3339
|
+
* with. Callers iterating multiple entries should catch.
|
|
3340
|
+
*/
|
|
3341
|
+
declare function unwrapDeksFromShamirEntry(provider: ShamirRecoveryProvider, entry: ShamirRecoveryEntry, shareStrings: readonly string[]): Promise<Map<string, CryptoKey>>;
|
|
3342
|
+
/**
|
|
3343
|
+
* Generate one paper-recovery entry from an unlocked DEK set.
|
|
3344
|
+
*
|
|
3345
|
+
* Returns the serializable entry (persisted via
|
|
3346
|
+
* {@link savePaperRecoveryEntries}). The recovery flow unwraps the
|
|
3347
|
+
* DEK set, then mints a fresh KEK from the user's new passphrase.
|
|
3348
|
+
*
|
|
3349
|
+
* Thin wrapper over {@link mintWrappedDeksBlob} (#44) — the crypto
|
|
3350
|
+
* lives in the shared primitive; this function just adds paper-
|
|
3351
|
+
* recovery's own metadata (`codeId`, `enrolledAt`).
|
|
3352
|
+
*
|
|
3353
|
+
* @param deks Map of collection-name → DEK (extractable).
|
|
3354
|
+
* @param code The plaintext recovery code (caller-supplied;
|
|
3355
|
+
* pair this with `@noy-db/on-recovery`'s code
|
|
3356
|
+
* generator/parser if available).
|
|
3357
|
+
* @param codeId Stable id used by `burnPaperRecoveryEntry`.
|
|
3358
|
+
*/
|
|
3359
|
+
declare function mintPaperRecoveryEntry(deks: Map<string, CryptoKey>, code: string, codeId: string): Promise<PaperRecoveryEntry>;
|
|
3360
|
+
/**
|
|
3361
|
+
* Decrypt a recovery entry to recover the raw DEK set. Used by the
|
|
3362
|
+
* `recoverPassphrase` flow after the user's code has been parsed.
|
|
3363
|
+
*
|
|
3364
|
+
* Thin wrapper over {@link unwrapDeksFromBlob} (#44).
|
|
3365
|
+
*
|
|
3366
|
+
* @throws when the code does not match the entry (AES-GCM auth tag fail).
|
|
3367
|
+
*/
|
|
3368
|
+
declare function unwrapDeksFromPaperEntry(entry: PaperRecoveryEntry, code: string): Promise<Map<string, CryptoKey>>;
|
|
3369
|
+
|
|
2949
3370
|
/**
|
|
2950
3371
|
* Tier-2 authenticator slot management — issue #11.
|
|
2951
3372
|
*
|
|
@@ -3010,6 +3431,26 @@ declare function enrollAuthenticator(store: NoydbStore, vault: string, keyring:
|
|
|
3010
3431
|
interface UpdateAuthenticatorOptions {
|
|
3011
3432
|
readonly meta?: Record<string, unknown>;
|
|
3012
3433
|
}
|
|
3434
|
+
/**
|
|
3435
|
+
* Mutate a tier-2 authenticator slot's `meta` blob (slot rename,
|
|
3436
|
+
* label changes). The slot's `id`, `method`, and wrap material
|
|
3437
|
+
* (`wrapped_kek` for wrap-KEK; `wrapped_deks` + `iv` for wrap-DEKs)
|
|
3438
|
+
* are immutable through this entry point — the anti-slot-swap guard
|
|
3439
|
+
* is structural, not gate-driven, so even if the policy gate is
|
|
3440
|
+
* weakened a future caller cannot use this path to swap one slot's
|
|
3441
|
+
* crypto for another's.
|
|
3442
|
+
*
|
|
3443
|
+
* `meta` patch semantics:
|
|
3444
|
+
* - Top-level merge — absent keys preserved, present keys overwrite
|
|
3445
|
+
* - `null` value — delete that meta key
|
|
3446
|
+
* - Non-object values (string, number, boolean, array) — replace verbatim
|
|
3447
|
+
*
|
|
3448
|
+
* @throws `NoAccessError` when no slot with the given id exists.
|
|
3449
|
+
* @throws `ValidationError` when no patch field is provided.
|
|
3450
|
+
*
|
|
3451
|
+
* @see #55
|
|
3452
|
+
*/
|
|
3453
|
+
declare function updateAuthenticator(store: NoydbStore, vault: string, keyring: UnlockedKeyring, slotId: string, options: UpdateAuthenticatorOptions): Promise<UnlockedKeyring>;
|
|
3013
3454
|
/**
|
|
3014
3455
|
* Drop a slot by id. No-op if the slot doesn't exist (idempotent —
|
|
3015
3456
|
* removing a non-existent slot is a recoverable retry, not an error).
|
|
@@ -3044,8 +3485,8 @@ declare function findAuthenticator(keyring: UnlockedKeyring, slotId: string): Ke
|
|
|
3044
3485
|
/**
|
|
3045
3486
|
* Context handed to a {@link SlotRewrapCeremony} when `rotatePassphrase`
|
|
3046
3487
|
* preserves a tier-2 slot. The ceremony's job is to re-derive its
|
|
3047
|
-
* method-specific wrapping material (PRF assertion, PBKDF2 of
|
|
3048
|
-
*
|
|
3488
|
+
* method-specific wrapping material (PRF assertion, PBKDF2 of the
|
|
3489
|
+
* password, etc.) and wrap the freshly rewrapped DEK set under
|
|
3049
3490
|
* the new wrapping key.
|
|
3050
3491
|
*
|
|
3051
3492
|
* Two surfaces are exposed:
|
|
@@ -3123,7 +3564,15 @@ interface RotatePassphraseInput {
|
|
|
3123
3564
|
* slot's id or method (anti-slot-swap guard).
|
|
3124
3565
|
*/
|
|
3125
3566
|
declare function rotatePassphrase(store: NoydbStore, vault: string, userId: string, input: RotatePassphraseInput): Promise<UnlockedKeyring>;
|
|
3126
|
-
/**
|
|
3567
|
+
/**
|
|
3568
|
+
* Caller payload for {@link recoverPassphrase}.
|
|
3569
|
+
*
|
|
3570
|
+
* As of #196 slice 1, `paper` and `shamir` are wired end-to-end.
|
|
3571
|
+
* The remaining two profiles (`multi-channel`, `admin-mediated`)
|
|
3572
|
+
* stay outside the union and throw
|
|
3573
|
+
* {@link RecoveryProfileNotImplementedError} at the runtime guard
|
|
3574
|
+
* when bypassed via `as unknown as RecoveryProof`.
|
|
3575
|
+
*/
|
|
3127
3576
|
type RecoveryProof = {
|
|
3128
3577
|
readonly profile: 'paper';
|
|
3129
3578
|
readonly payload: {
|
|
@@ -3132,19 +3581,12 @@ type RecoveryProof = {
|
|
|
3132
3581
|
} | {
|
|
3133
3582
|
readonly profile: 'shamir';
|
|
3134
3583
|
readonly payload: {
|
|
3584
|
+
/** Optional disambiguator when multiple Shamir entries are enrolled.
|
|
3585
|
+
* When omitted, hub tries each entry until one combines. */
|
|
3586
|
+
readonly entryId?: string;
|
|
3587
|
+
/** K or more opaque share strings, as returned by `ShamirRecoveryProvider.splitToShares`. */
|
|
3135
3588
|
readonly shares: ReadonlyArray<string>;
|
|
3136
3589
|
};
|
|
3137
|
-
} | {
|
|
3138
|
-
readonly profile: 'multi-channel';
|
|
3139
|
-
readonly payload: {
|
|
3140
|
-
readonly proofs: ReadonlyArray<unknown>;
|
|
3141
|
-
};
|
|
3142
|
-
} | {
|
|
3143
|
-
readonly profile: 'admin-mediated';
|
|
3144
|
-
readonly payload: {
|
|
3145
|
-
readonly token: string;
|
|
3146
|
-
readonly factor?: unknown;
|
|
3147
|
-
};
|
|
3148
3590
|
};
|
|
3149
3591
|
interface RecoverPassphraseInput {
|
|
3150
3592
|
readonly newPassphrase: string;
|
|
@@ -3205,20 +3647,97 @@ interface RecoverPassphraseResult {
|
|
|
3205
3647
|
readonly newCodes: readonly string[];
|
|
3206
3648
|
}
|
|
3207
3649
|
/**
|
|
3208
|
-
*
|
|
3209
|
-
*
|
|
3210
|
-
*
|
|
3211
|
-
* {@link
|
|
3212
|
-
*
|
|
3213
|
-
* On success, the used recovery entry is burned (deleted from the
|
|
3214
|
-
* stored set).
|
|
3650
|
+
* Input for {@link Noydb.rotateRecovery} (#121) — deliberate
|
|
3651
|
+
* recovery-credential regeneration when the user knows their
|
|
3652
|
+
* passphrase but wants a fresh sheet (paper) or fresh shares
|
|
3653
|
+
* (shamir). Symmetric to {@link RotatePassphraseInput}.
|
|
3215
3654
|
*/
|
|
3216
|
-
|
|
3217
|
-
|
|
3655
|
+
type RotateRecoveryOptions = {
|
|
3656
|
+
readonly profile: 'paper';
|
|
3657
|
+
/** How many fresh codes to mint. Default: existing sheet size. */
|
|
3658
|
+
readonly count?: number;
|
|
3659
|
+
/** Optional code generator — see {@link RecoverPassphraseInput.codeGenerator}. */
|
|
3660
|
+
readonly codeGenerator?: () => string;
|
|
3661
|
+
} | {
|
|
3662
|
+
readonly profile: 'shamir';
|
|
3663
|
+
/** New threshold. */
|
|
3664
|
+
readonly k: number;
|
|
3665
|
+
/** New total share count. */
|
|
3666
|
+
readonly n: number;
|
|
3667
|
+
/** Disambiguator when multiple Shamir entries exist; required if there are 2+. */
|
|
3668
|
+
readonly entryId?: string;
|
|
3669
|
+
/** Optional updated label. */
|
|
3670
|
+
readonly label?: string;
|
|
3671
|
+
};
|
|
3218
3672
|
/**
|
|
3219
|
-
*
|
|
3673
|
+
* Result of {@link Noydb.rotateRecovery}. Shape varies by profile:
|
|
3220
3674
|
*
|
|
3221
|
-
* `
|
|
3675
|
+
* - `paper` → `{ newCodes: string[] }` (and `entryId === 'paper-batch'`)
|
|
3676
|
+
* - `shamir` → `{ newShares: string[], entryId }`
|
|
3677
|
+
*
|
|
3678
|
+
* `newCodes` is populated for paper rotations; `newShares` for
|
|
3679
|
+
* Shamir rotations. Both are show-once — the hub does not
|
|
3680
|
+
* retain them.
|
|
3681
|
+
*/
|
|
3682
|
+
interface RotateRecoveryResult {
|
|
3683
|
+
readonly newCodes?: readonly string[];
|
|
3684
|
+
readonly newShares?: readonly string[];
|
|
3685
|
+
readonly entryId?: string;
|
|
3686
|
+
}
|
|
3687
|
+
/**
|
|
3688
|
+
* Result of {@link Noydb.enrollRecovery}. Shape varies by profile:
|
|
3689
|
+
*
|
|
3690
|
+
* - `paper` → `{ entryId: 'paper-batch' }` (caller minted the
|
|
3691
|
+
* entries; this is a sentinel since paper enrollments are batch-shaped).
|
|
3692
|
+
* - `shamir` → `{ entryId, shares: string[] }` — shares are
|
|
3693
|
+
* show-once; the hub does not retain them.
|
|
3694
|
+
*/
|
|
3695
|
+
interface EnrollRecoveryResult {
|
|
3696
|
+
readonly entryId: string;
|
|
3697
|
+
readonly shares?: readonly string[];
|
|
3698
|
+
}
|
|
3699
|
+
/**
|
|
3700
|
+
* Input shape for {@link Noydb.enrollRecovery} and
|
|
3701
|
+
* {@link Noydb.openVaultAndEnrollRecovery} (#195). Discriminated
|
|
3702
|
+
* union over recovery profiles.
|
|
3703
|
+
*
|
|
3704
|
+
* - `paper`: caller pre-mints entries (typically via
|
|
3705
|
+
* `mintPaperRecoveryEntry` or `@noy-db/on-recovery`'s
|
|
3706
|
+
* `generateRecoveryCodeSet`) and passes them in. The hub stores
|
|
3707
|
+
* them and surfaces an opaque batch id.
|
|
3708
|
+
* - `shamir`: hub mints the recovery secret + the shares at
|
|
3709
|
+
* enrollment time. The shares are returned in
|
|
3710
|
+
* {@link EnrollRecoveryResult.shares} (show-once); the hub never
|
|
3711
|
+
* retains them.
|
|
3712
|
+
*
|
|
3713
|
+
* Multi-channel and admin-mediated will be added when the respective
|
|
3714
|
+
* dispatch slices ship.
|
|
3715
|
+
*/
|
|
3716
|
+
type RecoveryEnrollmentInput = {
|
|
3717
|
+
readonly profile: 'paper';
|
|
3718
|
+
readonly entries: ReadonlyArray<PaperRecoveryEntry>;
|
|
3719
|
+
} | {
|
|
3720
|
+
readonly profile: 'shamir';
|
|
3721
|
+
readonly k: number;
|
|
3722
|
+
readonly n: number;
|
|
3723
|
+
readonly label?: string;
|
|
3724
|
+
readonly entryId?: string;
|
|
3725
|
+
};
|
|
3726
|
+
/**
|
|
3727
|
+
* Reset the user's passphrase using a recovery proof. v0.1.0-pre.5
|
|
3728
|
+
* supports the `'paper'` profile via `@noy-db/on-recovery` entries
|
|
3729
|
+
* persisted in `_meta/recovery-paper`. The other three profiles throw
|
|
3730
|
+
* {@link RecoveryProfileNotImplementedError}.
|
|
3731
|
+
*
|
|
3732
|
+
* On success, the used recovery entry is burned (deleted from the
|
|
3733
|
+
* stored set).
|
|
3734
|
+
*/
|
|
3735
|
+
declare function recoverPassphrase(provider: ShamirRecoveryProvider | undefined, store: NoydbStore, vault: string, userId: string, input: RecoverPassphraseInput): Promise<UnlockedKeyring>;
|
|
3736
|
+
|
|
3737
|
+
/**
|
|
3738
|
+
* Atomic peer-recovery primitive — issues #33 + #34.
|
|
3739
|
+
*
|
|
3740
|
+
* `recoverUser` is a SEPARATE operation from `revoke + grant`. It
|
|
3222
3741
|
* exists because peer-recovery has different semantics than account
|
|
3223
3742
|
* removal-then-reissue:
|
|
3224
3743
|
*
|
|
@@ -3295,183 +3814,6 @@ interface RecoverUserOptions {
|
|
|
3295
3814
|
*/
|
|
3296
3815
|
declare function recoverUser(store: NoydbStore, vault: string, callerKeyring: UnlockedKeyring, options: RecoverUserOptions): Promise<void>;
|
|
3297
3816
|
|
|
3298
|
-
/**
|
|
3299
|
-
* **Wrap-DEKs primitive (#44)** — a single canonical shape for the
|
|
3300
|
-
* pattern of "serialize a DEK set, encrypt it under a credential-derived
|
|
3301
|
-
* AES-GCM key." Used by:
|
|
3302
|
-
*
|
|
3303
|
-
* - **tier-0** — paper recovery entries (`_meta/recovery-paper`),
|
|
3304
|
-
* credential = the printed code.
|
|
3305
|
-
* - **tier-2** — password authenticator slots (`KeyringFile.authenticators`,
|
|
3306
|
-
* `wrapKind: 'deks'`), credential = the daily password.
|
|
3307
|
-
*
|
|
3308
|
-
* **Not** used by `@noy-db/on-pin` — tier-3 wraps the DEK set under
|
|
3309
|
-
* the same conceptual pattern but at **100,000 PBKDF2 iterations**
|
|
3310
|
-
* (vs the 600,000 here), because the protection window for a PIN
|
|
3311
|
-
* slot is short (idle-timeout-bounded, typically 15 min) and 600k
|
|
3312
|
-
* iterations would make every PIN-resume noticeably slow. The wire
|
|
3313
|
-
* formats are deliberately incompatible. See `@noy-db/on-pin`'s
|
|
3314
|
-
* `PIN_PBKDF2_ITERATIONS` and the threat-model rationale in its
|
|
3315
|
-
* module docstring.
|
|
3316
|
-
*
|
|
3317
|
-
* Before #44, the same crypto lived in two places: `mintPaperRecoveryEntry`
|
|
3318
|
-
* (in `team/recovery.ts`) and `enrollPasswordAuthenticator` (in
|
|
3319
|
-
* `@noy-db/on-password`). Both functions did identical work — PBKDF2
|
|
3320
|
-
* the credential, AES-GCM-encrypt the JSON-serialized DEK set — but
|
|
3321
|
-
* their implementations had drifted apart enough that fixing a bug
|
|
3322
|
-
* in one wouldn't fix the other.
|
|
3323
|
-
*
|
|
3324
|
-
* This module owns the canonical implementation. Consumers compose:
|
|
3325
|
-
*
|
|
3326
|
-
* - `mintPaperRecoveryEntry` is now a thin wrapper that calls
|
|
3327
|
-
* `mintWrappedDeksBlob` and adds `{ codeId, enrolledAt }`.
|
|
3328
|
-
* - `enrollPasswordAuthenticator` calls `mintWrappedDeksBlob` and
|
|
3329
|
-
* wraps the result in the slot envelope.
|
|
3330
|
-
*
|
|
3331
|
-
* @module
|
|
3332
|
-
*/
|
|
3333
|
-
/**
|
|
3334
|
-
* The wrap-DEKs primitive — a serialized + AES-GCM-encrypted DEK set
|
|
3335
|
-
* keyed under a credential-derived key.
|
|
3336
|
-
*
|
|
3337
|
-
* All three fields are base64-encoded so the blob is JSON-safe and
|
|
3338
|
-
* round-trips through `_meta/*` envelopes (which carry plaintext
|
|
3339
|
-
* JSON in `_data`).
|
|
3340
|
-
*
|
|
3341
|
-
* Composition: `PaperRecoveryEntry extends WrappedDeksBlob` plus
|
|
3342
|
-
* `{ codeId, enrolledAt }`. `KeyringAuthenticatorWrappingDEKs`
|
|
3343
|
-
* carries the same three fields with `salt` stored in `meta` for
|
|
3344
|
-
* slot-format back-compat (#44 defers moving it to top-level).
|
|
3345
|
-
*/
|
|
3346
|
-
interface WrappedDeksBlob {
|
|
3347
|
-
/** Base64 PBKDF2 salt for the credential-derived wrapping key. */
|
|
3348
|
-
readonly salt: string;
|
|
3349
|
-
/** Base64 AES-GCM IV used for the `wrappedDeks` ciphertext. */
|
|
3350
|
-
readonly iv: string;
|
|
3351
|
-
/** Base64 AES-GCM ciphertext of `{ deks: { collection: base64rawDek } }`. */
|
|
3352
|
-
readonly wrappedDeks: string;
|
|
3353
|
-
}
|
|
3354
|
-
/**
|
|
3355
|
-
* Mint a fresh `WrappedDeksBlob` from a DEK set + a string credential.
|
|
3356
|
-
*
|
|
3357
|
-
* Generates a random salt + IV, derives a 256-bit AES-GCM key via
|
|
3358
|
-
* PBKDF2-SHA256(credential, salt, 600K), serializes the DEK set as
|
|
3359
|
-
* `{ deks: { coll: rawBase64 } }`, and AES-GCM-encrypts.
|
|
3360
|
-
*
|
|
3361
|
-
* The `credential` is the user-typed string (recovery code, daily
|
|
3362
|
-
* password, PIN). Caller normalization rules apply (e.g. paper
|
|
3363
|
-
* recovery uppercase-strips the code before reaching this function).
|
|
3364
|
-
*
|
|
3365
|
-
* @param deks - DEK set to wrap. Each DEK must be exportable via
|
|
3366
|
-
* `subtle.exportKey('raw', dek)` (the hub mints DEKs
|
|
3367
|
-
* this way; consumers feeding non-extractable keys
|
|
3368
|
-
* will get `InvalidAccessError` from WebCrypto).
|
|
3369
|
-
* @param credential - String input the consumer minted (paper code,
|
|
3370
|
-
* password, PIN). Treated as opaque bytes by PBKDF2.
|
|
3371
|
-
*/
|
|
3372
|
-
declare function mintWrappedDeksBlob(deks: Map<string, CryptoKey>, credential: string): Promise<WrappedDeksBlob>;
|
|
3373
|
-
/**
|
|
3374
|
-
* Reverse of {@link mintWrappedDeksBlob}. Re-derives the wrapping key
|
|
3375
|
-
* from the credential + stored salt, AES-GCM-decrypts the wrapped DEK
|
|
3376
|
-
* set, and re-imports each DEK as an extractable AES-GCM CryptoKey.
|
|
3377
|
-
*
|
|
3378
|
-
* Throws (AES-GCM auth tag failure) when the credential doesn't
|
|
3379
|
-
* match the blob. Callers iterating over multiple blobs (e.g. paper
|
|
3380
|
-
* recovery's "try every entry until one matches") should catch.
|
|
3381
|
-
*/
|
|
3382
|
-
declare function unwrapDeksFromBlob(blob: WrappedDeksBlob, credential: string): Promise<Map<string, CryptoKey>>;
|
|
3383
|
-
|
|
3384
|
-
/**
|
|
3385
|
-
* Recovery profile persistence + dispatch — issue #10.
|
|
3386
|
-
*
|
|
3387
|
-
* v0.1.0-pre.5 wires the **paper** profile end-to-end through
|
|
3388
|
-
* `@noy-db/on-recovery`. The other three profiles (Shamir,
|
|
3389
|
-
* multi-channel, admin-mediated) ship the API surface and throw
|
|
3390
|
-
* {@link RecoveryProfileNotImplementedError} during use; per-profile
|
|
3391
|
-
* dispatch lands in follow-up issues.
|
|
3392
|
-
*
|
|
3393
|
-
* Storage layout:
|
|
3394
|
-
*
|
|
3395
|
-
* ```
|
|
3396
|
-
* _meta/recovery-paper — JSON { entries: RecoveryCodeEntry[] } produced by `on-recovery`.
|
|
3397
|
-
* _meta/recovery-shamir — reserved
|
|
3398
|
-
* _meta/recovery-multi — reserved
|
|
3399
|
-
* _meta/recovery-admin — reserved
|
|
3400
|
-
* ```
|
|
3401
|
-
*
|
|
3402
|
-
* Like `_meta/policy` and `_meta/handle`, the documents are plain JSON
|
|
3403
|
-
* with empty `_iv` — the recovery-code wrapping is what protects the
|
|
3404
|
-
* KEK; the entries themselves are inert without the user's code.
|
|
3405
|
-
*
|
|
3406
|
-
* @module
|
|
3407
|
-
*/
|
|
3408
|
-
|
|
3409
|
-
/**
|
|
3410
|
-
* One paper recovery code as persisted in `_meta/recovery-paper`.
|
|
3411
|
-
*
|
|
3412
|
-
* The hub's KEK is intentionally non-extractable (see `crypto.ts`),
|
|
3413
|
-
* so the recovery entry can't AES-KW-wrap the KEK directly. Instead
|
|
3414
|
-
* we wrap a serialized DEK set: the entry holds the AES-GCM
|
|
3415
|
-
* ciphertext of `{ deks: { collection: rawDekBase64 } }`. Recovery
|
|
3416
|
-
* deserializes the DEK set, then mints a fresh KEK from the new
|
|
3417
|
-
* passphrase and rewraps the DEKs under it.
|
|
3418
|
-
*
|
|
3419
|
-
* This is the same pattern `@noy-db/on-pin` uses for tier-3 quick
|
|
3420
|
-
* resume — the cryptographic guarantee is identical (AES-GCM with a
|
|
3421
|
-
* PBKDF2-derived key), and it sidesteps the non-extractable-KEK
|
|
3422
|
-
* constraint cleanly.
|
|
3423
|
-
*
|
|
3424
|
-
* Type-level composition (#44): `PaperRecoveryEntry extends
|
|
3425
|
-
* WrappedDeksBlob` — the three crypto fields (`salt`, `iv`,
|
|
3426
|
-
* `wrappedDeks`) come from the shared primitive; `codeId` and
|
|
3427
|
-
* `enrolledAt` are paper-recovery's own metadata. Wire format
|
|
3428
|
-
* unchanged.
|
|
3429
|
-
*/
|
|
3430
|
-
interface PaperRecoveryEntry extends WrappedDeksBlob {
|
|
3431
|
-
readonly codeId: string;
|
|
3432
|
-
readonly enrolledAt: string;
|
|
3433
|
-
}
|
|
3434
|
-
interface PaperRecoveryDoc {
|
|
3435
|
-
readonly _noydb_recovery: 1;
|
|
3436
|
-
readonly profile: 'paper';
|
|
3437
|
-
readonly entries: ReadonlyArray<PaperRecoveryEntry>;
|
|
3438
|
-
}
|
|
3439
|
-
/** Read the paper-recovery entries. Returns empty array when absent. */
|
|
3440
|
-
declare function loadPaperRecoveryEntries(store: NoydbStore, vault: string): Promise<ReadonlyArray<PaperRecoveryEntry>>;
|
|
3441
|
-
/** Replace the paper-recovery entries (used after burn-on-recovery). */
|
|
3442
|
-
declare function savePaperRecoveryEntries(store: NoydbStore, vault: string, entries: ReadonlyArray<PaperRecoveryEntry>): Promise<void>;
|
|
3443
|
-
/** Drop a single paper-recovery entry (burn-on-use). */
|
|
3444
|
-
declare function burnPaperRecoveryEntry(store: NoydbStore, vault: string, codeId: string): Promise<void>;
|
|
3445
|
-
/** Whether at least one recovery profile has any enrolled entries. */
|
|
3446
|
-
declare function hasRecoveryEnrolled(store: NoydbStore, vault: string): Promise<boolean>;
|
|
3447
|
-
/**
|
|
3448
|
-
* Generate one paper-recovery entry from an unlocked DEK set.
|
|
3449
|
-
*
|
|
3450
|
-
* Returns the serializable entry (persisted via
|
|
3451
|
-
* {@link savePaperRecoveryEntries}). The recovery flow unwraps the
|
|
3452
|
-
* DEK set, then mints a fresh KEK from the user's new passphrase.
|
|
3453
|
-
*
|
|
3454
|
-
* Thin wrapper over {@link mintWrappedDeksBlob} (#44) — the crypto
|
|
3455
|
-
* lives in the shared primitive; this function just adds paper-
|
|
3456
|
-
* recovery's own metadata (`codeId`, `enrolledAt`).
|
|
3457
|
-
*
|
|
3458
|
-
* @param deks Map of collection-name → DEK (extractable).
|
|
3459
|
-
* @param code The plaintext recovery code (caller-supplied;
|
|
3460
|
-
* pair this with `@noy-db/on-recovery`'s code
|
|
3461
|
-
* generator/parser if available).
|
|
3462
|
-
* @param codeId Stable id used by `burnPaperRecoveryEntry`.
|
|
3463
|
-
*/
|
|
3464
|
-
declare function mintPaperRecoveryEntry(deks: Map<string, CryptoKey>, code: string, codeId: string): Promise<PaperRecoveryEntry>;
|
|
3465
|
-
/**
|
|
3466
|
-
* Decrypt a recovery entry to recover the raw DEK set. Used by the
|
|
3467
|
-
* `recoverPassphrase` flow after the user's code has been parsed.
|
|
3468
|
-
*
|
|
3469
|
-
* Thin wrapper over {@link unwrapDeksFromBlob} (#44).
|
|
3470
|
-
*
|
|
3471
|
-
* @throws when the code does not match the entry (AES-GCM auth tag fail).
|
|
3472
|
-
*/
|
|
3473
|
-
declare function unwrapDeksFromPaperEntry(entry: PaperRecoveryEntry, code: string): Promise<Map<string, CryptoKey>>;
|
|
3474
|
-
|
|
3475
3817
|
/**
|
|
3476
3818
|
* Public envelope — owner-curated plaintext metadata, readable
|
|
3477
3819
|
* before vault unlock or bundle decryption.
|
|
@@ -3692,6 +4034,33 @@ interface StagedOp {
|
|
|
3692
4034
|
id: string;
|
|
3693
4035
|
record?: unknown;
|
|
3694
4036
|
expectedVersion?: number;
|
|
4037
|
+
/**
|
|
4038
|
+
* Optional human-readable tag forwarded to the resulting ledger
|
|
4039
|
+
* entry's `reason` field (#1). Set by callers via
|
|
4040
|
+
* `tx.vault(v).collection(c).put(id, record, { reason })`.
|
|
4041
|
+
*/
|
|
4042
|
+
reason?: string;
|
|
4043
|
+
}
|
|
4044
|
+
/**
|
|
4045
|
+
* One executed op (main staged op or recursive side-effect like a
|
|
4046
|
+
* derivation output) paired with the envelope captured before the write.
|
|
4047
|
+
* `revertExecuted` walks this array in reverse on rollback.
|
|
4048
|
+
* @internal
|
|
4049
|
+
*/
|
|
4050
|
+
interface ExecutedOp {
|
|
4051
|
+
op: StagedOp;
|
|
4052
|
+
priorEnvelope: EncryptedEnvelope | null;
|
|
4053
|
+
}
|
|
4054
|
+
/**
|
|
4055
|
+
* Options accepted by `db.transaction({ amendment, reason }, fn)`.
|
|
4056
|
+
* Only the amendment variant uses these — a plain `db.transaction(fn)`
|
|
4057
|
+
* never sees this shape.
|
|
4058
|
+
*/
|
|
4059
|
+
interface AmendmentTxOptions {
|
|
4060
|
+
/** Opt into amendment mode. Required to be `true`. */
|
|
4061
|
+
readonly amendment: true;
|
|
4062
|
+
/** Human-readable rationale recorded in the ledger entry. Required. */
|
|
4063
|
+
readonly reason: string;
|
|
3695
4064
|
}
|
|
3696
4065
|
/**
|
|
3697
4066
|
* Transaction handle passed to the user's body. Use
|
|
@@ -3701,10 +4070,27 @@ interface StagedOp {
|
|
|
3701
4070
|
declare class TxContext {
|
|
3702
4071
|
/** @internal */
|
|
3703
4072
|
readonly _ops: StagedOp[];
|
|
4073
|
+
/**
|
|
4074
|
+
* @internal — write log built up in Phase 2. Each entry records the
|
|
4075
|
+
* envelope captured BEFORE the write so a mid-batch failure can
|
|
4076
|
+
* restore prior state via `revertExecuted`. Side-effect writes (e.g.
|
|
4077
|
+
* recursive derivation outputs fired inside `Collection.put`) are
|
|
4078
|
+
* appended here in execution order so they roll back alongside the
|
|
4079
|
+
* main staged ops (#133).
|
|
4080
|
+
*/
|
|
4081
|
+
readonly _executed: ExecutedOp[];
|
|
3704
4082
|
/** @internal */
|
|
3705
4083
|
readonly _db: Noydb;
|
|
4084
|
+
/**
|
|
4085
|
+
* @internal — true when this TxContext was opened in amendment
|
|
4086
|
+
* mode. Toggles the lazy-`beginAmendment` + role-check path on first
|
|
4087
|
+
* `tx.vault(name)` and unlocks the post-Phase-2 invariant + audit run.
|
|
4088
|
+
*/
|
|
4089
|
+
readonly _amendment: boolean;
|
|
4090
|
+
/** @internal — vaults that have already had `beginAmendment` called. */
|
|
4091
|
+
readonly _amendmentVaults: Map<string, Vault>;
|
|
3706
4092
|
/** @internal */
|
|
3707
|
-
constructor(db: Noydb);
|
|
4093
|
+
constructor(db: Noydb, amendment?: boolean);
|
|
3708
4094
|
/** Scope subsequent `collection()` calls to the named vault. */
|
|
3709
4095
|
vault(name: string): TxVault;
|
|
3710
4096
|
}
|
|
@@ -3743,6 +4129,7 @@ declare class TxCollection<T> {
|
|
|
3743
4129
|
*/
|
|
3744
4130
|
put(id: string, record: T, options?: {
|
|
3745
4131
|
expectedVersion?: number;
|
|
4132
|
+
reason?: string;
|
|
3746
4133
|
}): void;
|
|
3747
4134
|
/**
|
|
3748
4135
|
* Stage a delete. Does not write until the transaction body returns.
|
|
@@ -3754,14 +4141,16 @@ declare class TxCollection<T> {
|
|
|
3754
4141
|
}): void;
|
|
3755
4142
|
}
|
|
3756
4143
|
/**
|
|
3757
|
-
* Commit plan: pre-flight check + execution + revert plan.
|
|
3758
|
-
* from `runTransaction`.
|
|
4144
|
+
* Commit plan: pre-flight check + execution + revert plan.
|
|
3759
4145
|
*
|
|
3760
|
-
* @internal —
|
|
3761
|
-
*
|
|
3762
|
-
*
|
|
4146
|
+
* @internal — driven by `withTransactions()` (via `tx/active.ts`) for
|
|
4147
|
+
* user-facing `db.transaction(...)` calls and by the `amendment` path
|
|
4148
|
+
* in `noydb.ts`. `Collection.putManyAtomic` runs its own Phase 2 loop
|
|
4149
|
+
* but shares the `_activeTxContext` mechanism (and the `revertExecuted`
|
|
4150
|
+
* helper) so nested side-effect derivation writes get registered for
|
|
4151
|
+
* revert alongside the bulk-put source ops (#133).
|
|
3763
4152
|
*/
|
|
3764
|
-
declare function runTransaction<T>(db: Noydb, fn: (tx: TxContext) => Promise<T> | T): Promise<T>;
|
|
4153
|
+
declare function runTransaction<T>(db: Noydb, fn: (tx: TxContext) => Promise<T> | T, options?: AmendmentTxOptions): Promise<T>;
|
|
3765
4154
|
|
|
3766
4155
|
/**
|
|
3767
4156
|
* Policy gate DSL types — issue #9.
|
|
@@ -3788,7 +4177,7 @@ declare function runTransaction<T>(db: Noydb, fn: (tx: TxContext) => Promise<T>
|
|
|
3788
4177
|
* | `shamir` | k-of-n threshold share (`@noy-db/on-shamir`) | yes |
|
|
3789
4178
|
* | `webauthn-roaming` | hardware key (YubiKey, SoloKey, Titan) | yes (key portable) |
|
|
3790
4179
|
* | `webauthn-platform` | platform passkey (Touch ID, Face ID, Hello) | no (device-bound) |
|
|
3791
|
-
* | `password` | tier-2
|
|
4180
|
+
* | `password` | tier-2 password (`@noy-db/on-password`) | no |
|
|
3792
4181
|
* | `pin` | tier-3 quick-resume PIN (`@noy-db/on-pin`) | no |
|
|
3793
4182
|
*
|
|
3794
4183
|
* Off-device kinds (TOTP, email-OTP, recovery, shamir, roaming WebAuthn)
|
|
@@ -3845,6 +4234,16 @@ interface GatePolicy {
|
|
|
3845
4234
|
* configured policy as "no gate" (no-op).
|
|
3846
4235
|
*/
|
|
3847
4236
|
type BuiltInGateName = 'rotate-passphrase' | 'recover-passphrase' | 'enroll-authenticator' | 'remove-authenticator'
|
|
4237
|
+
/**
|
|
4238
|
+
* Authorize a deliberate paper-recovery-code regeneration —
|
|
4239
|
+
* `db.rotateRecovery` (#121). Symmetric to `rotate-passphrase` for
|
|
4240
|
+
* the case where the user remembers their passphrase but wants a
|
|
4241
|
+
* fresh sheet (lost the printout, suspect compromise of the off-site
|
|
4242
|
+
* copy). PERSONAL allows tier-1; STRICT requires an off-device
|
|
4243
|
+
* factor so a stolen unlocked laptop cannot silently mint a new
|
|
4244
|
+
* sheet for an attacker.
|
|
4245
|
+
*/
|
|
4246
|
+
| 'rotate-recovery'
|
|
3848
4247
|
/**
|
|
3849
4248
|
* Authorize a meta-only mutation on an existing authenticator slot —
|
|
3850
4249
|
* `db.updateAuthenticator` (#55). The slot's wrap material, id, and
|
|
@@ -3897,6 +4296,25 @@ interface FactorProof {
|
|
|
3897
4296
|
/** Method-specific payload. The engine treats it as opaque — verification is delegated. */
|
|
3898
4297
|
readonly payload?: unknown;
|
|
3899
4298
|
}
|
|
4299
|
+
/**
|
|
4300
|
+
* Bundle of factor proofs + session-context flags passed to a gated
|
|
4301
|
+
* Noydb method. Used as the optional last parameter of every method
|
|
4302
|
+
* that runs through `checkGate`: `db.grant`, `db.revoke`, `db.updateUser`,
|
|
4303
|
+
* `db.enrollAuthenticator`, `db.removeAuthenticator`, `db.updateAuthenticator`,
|
|
4304
|
+
* `db.enrollWebAuthn`, `db.rotatePassphrase`, `db.recoverPassphrase`,
|
|
4305
|
+
* `db.recoverUser`, `db.enrollUnlock`, `db.describeUserAuth`,
|
|
4306
|
+
* `db.describeAllUsersAuth`.
|
|
4307
|
+
*
|
|
4308
|
+
* Pre-#89 this type was inlined at every call site as
|
|
4309
|
+
* `{ factors?: ReadonlyArray<FactorProof>; sharedDevice?: boolean }`
|
|
4310
|
+
* and parameter names alternated between `factors` and `presented`.
|
|
4311
|
+
* Now exported so consumers can name their helpers and so the param
|
|
4312
|
+
* name converges to `factors` everywhere.
|
|
4313
|
+
*/
|
|
4314
|
+
interface FactorProofBundle {
|
|
4315
|
+
readonly factors?: ReadonlyArray<FactorProof>;
|
|
4316
|
+
readonly sharedDevice?: boolean;
|
|
4317
|
+
}
|
|
3900
4318
|
/** Active session tier — what the engine compares against `gate.minTier`. */
|
|
3901
4319
|
type ActiveTier = 1 | 2 | 3;
|
|
3902
4320
|
|
|
@@ -3918,6 +4336,14 @@ declare class Noydb {
|
|
|
3918
4336
|
* `_meta/policy` load; replaced by `db.updatePolicy()`.
|
|
3919
4337
|
*/
|
|
3920
4338
|
private readonly policyCache;
|
|
4339
|
+
/**
|
|
4340
|
+
* One-shot bypass for the managed-mode strong-recovery check (#195).
|
|
4341
|
+
* Set true by {@link openVaultAndEnrollRecovery} for the duration of
|
|
4342
|
+
* the bootstrap window so the keyring can be created before the
|
|
4343
|
+
* strong recovery is enrolled. Always cleared (try/finally).
|
|
4344
|
+
* @internal
|
|
4345
|
+
*/
|
|
4346
|
+
private _skipNextManagedRecoveryCheck;
|
|
3921
4347
|
/** Per-vault tier-3 (PIN / quick-resume) state — issue #11. */
|
|
3922
4348
|
private readonly quickUnlock;
|
|
3923
4349
|
/**
|
|
@@ -3933,6 +4359,17 @@ declare class Noydb {
|
|
|
3933
4359
|
private readonly txStrategy;
|
|
3934
4360
|
private readonly sessionStrategy;
|
|
3935
4361
|
private readonly syncStrategy;
|
|
4362
|
+
/**
|
|
4363
|
+
* Currently-running multi-record transaction, set by
|
|
4364
|
+
* `runTransaction` at the start of Phase 2 (commit) and cleared in
|
|
4365
|
+
* the same function's `finally` block. Side-effect writes triggered
|
|
4366
|
+
* during a staged op's `Collection.put` (today: eager derivation
|
|
4367
|
+
* outputs) register their pre-write envelope on `_executed` here so
|
|
4368
|
+
* a mid-batch failure rolls them back alongside the main staged ops
|
|
4369
|
+
* (#133). `null` outside of Phase 2.
|
|
4370
|
+
* @internal
|
|
4371
|
+
*/
|
|
4372
|
+
private _activeTxContext;
|
|
3936
4373
|
/**
|
|
3937
4374
|
* In-process translation cache. Key is `"${field}\x00${collection}\x00${from}\x00${to}\x00${text}"`.
|
|
3938
4375
|
* Cleared on `close()` alongside the KEK and DEKs.
|
|
@@ -3972,10 +4409,27 @@ declare class Noydb {
|
|
|
3972
4409
|
}): Promise<Vault>;
|
|
3973
4410
|
/** Synchronous vault access (must call openVault first, or auto-opens). */
|
|
3974
4411
|
vault(name: string): Vault;
|
|
3975
|
-
/**
|
|
3976
|
-
|
|
3977
|
-
|
|
3978
|
-
|
|
4412
|
+
/**
|
|
4413
|
+
* Grant access to a user for a vault.
|
|
4414
|
+
*
|
|
4415
|
+
* Gated by `enroll-user`. `STRICT_POLICY` requires a TOTP / email-OTP
|
|
4416
|
+
* factor proof so the operator affirmatively re-asserts identity at
|
|
4417
|
+
* the moment of grant; `PERSONAL_POLICY` accepts a tier-1 unlock alone.
|
|
4418
|
+
*
|
|
4419
|
+
* The legacy `requireReAuthFor: ['grant']` session-policy check still
|
|
4420
|
+
* fires on top — both are independent opt-ins.
|
|
4421
|
+
*/
|
|
4422
|
+
grant(vault: string, options: GrantOptions, factors?: FactorProofBundle): Promise<void>;
|
|
4423
|
+
/**
|
|
4424
|
+
* Revoke a user's access to a vault.
|
|
4425
|
+
*
|
|
4426
|
+
* Gated by `revoke-user`. `STRICT_POLICY` requires a TOTP / email-OTP
|
|
4427
|
+
* factor proof; `PERSONAL_POLICY` accepts a tier-1 unlock alone.
|
|
4428
|
+
*
|
|
4429
|
+
* The legacy `requireReAuthFor: ['revoke']` session-policy check still
|
|
4430
|
+
* fires on top — both are independent opt-ins.
|
|
4431
|
+
*/
|
|
4432
|
+
revoke(vault: string, options: RevokeOptions, factors?: FactorProofBundle): Promise<void>;
|
|
3979
4433
|
/**
|
|
3980
4434
|
* Mutate post-grant identity fields on an existing keyring — `role`,
|
|
3981
4435
|
* `displayName`, and/or `permissions`. Pure plaintext-header rewrite:
|
|
@@ -4018,10 +4472,7 @@ declare class Noydb {
|
|
|
4018
4472
|
*
|
|
4019
4473
|
* @see #54
|
|
4020
4474
|
*/
|
|
4021
|
-
updateUser(vault: string, options: UpdateUserOptions, factors?:
|
|
4022
|
-
factors?: ReadonlyArray<FactorProof>;
|
|
4023
|
-
sharedDevice?: boolean;
|
|
4024
|
-
}): Promise<void>;
|
|
4475
|
+
updateUser(vault: string, options: UpdateUserOptions, factors?: FactorProofBundle): Promise<void>;
|
|
4025
4476
|
/**
|
|
4026
4477
|
* Rotate the DEKs for the given collections in a vault.
|
|
4027
4478
|
*
|
|
@@ -4154,8 +4605,17 @@ declare class Noydb {
|
|
|
4154
4605
|
* ```
|
|
4155
4606
|
*/
|
|
4156
4607
|
queryAcross<T>(vaultIds: string[], fn: (vault: Vault) => Promise<T>, options?: QueryAcrossOptions): Promise<QueryAcrossResult<T>[]>;
|
|
4157
|
-
/**
|
|
4158
|
-
|
|
4608
|
+
/**
|
|
4609
|
+
* Change the current user's passphrase for a vault.
|
|
4610
|
+
*
|
|
4611
|
+
* Validates the new passphrase against the strength rules. Pass
|
|
4612
|
+
* `{ allowWeakPassphrase: true }` to skip — typically only useful for
|
|
4613
|
+
* fixtures and migrations. Pass a `PassphrasePolicy` to override the
|
|
4614
|
+
* default rules (e.g. consumer-tunable `pattern` / `customValidator`).
|
|
4615
|
+
*/
|
|
4616
|
+
changeSecret(vault: string, newPassphrase: string, options?: PassphrasePolicy & {
|
|
4617
|
+
allowWeakPassphrase?: boolean;
|
|
4618
|
+
}): Promise<void>;
|
|
4159
4619
|
/** Push local changes to remote for a vault. */
|
|
4160
4620
|
push(vault: string, options?: PushOptions): Promise<PushResult>;
|
|
4161
4621
|
/** Pull remote changes to local for a vault. */
|
|
@@ -4187,6 +4647,16 @@ declare class Noydb {
|
|
|
4187
4647
|
* which batches push/pull across sync peers.
|
|
4188
4648
|
*/
|
|
4189
4649
|
transaction<T>(fn: (tx: TxContext) => Promise<T> | T): Promise<T>;
|
|
4650
|
+
/**
|
|
4651
|
+
* Open an amendment-mode transaction. Requires `admin` or `owner`
|
|
4652
|
+
* role on every vault touched by the body; throws
|
|
4653
|
+
* `AmendmentForbiddenError` on first non-privileged `tx.vault(name)`
|
|
4654
|
+
* call. Guard `check` callbacks are SKIPPED inside an amendment —
|
|
4655
|
+
* the staged change-set is fed to each guard's `amendment.invariant`
|
|
4656
|
+
* after the body returns, and the multi-record summary is appended
|
|
4657
|
+
* to the vault's ledger as `op: 'amendment'`.
|
|
4658
|
+
*/
|
|
4659
|
+
transaction<T>(options: AmendmentTxOptions, fn: (tx: TxContext) => Promise<T> | T): Promise<T>;
|
|
4190
4660
|
/**
|
|
4191
4661
|
* Create a sync transaction for the given vault.
|
|
4192
4662
|
* The vault must already be open via `openVault()`.
|
|
@@ -4202,13 +4672,57 @@ declare class Noydb {
|
|
|
4202
4672
|
* @internal
|
|
4203
4673
|
*/
|
|
4204
4674
|
get _store(): NoydbStore;
|
|
4205
|
-
/** Get sync status for a vault. */
|
|
4206
|
-
syncStatus(vault: string): SyncStatus;
|
|
4207
|
-
private getSyncEngine;
|
|
4208
|
-
on<K extends keyof NoydbEventMap>(event: K, handler: (data: NoydbEventMap[K]) => void): void;
|
|
4209
|
-
off<K extends keyof NoydbEventMap>(event: K, handler: (data: NoydbEventMap[K]) => void): void;
|
|
4210
4675
|
/**
|
|
4211
|
-
*
|
|
4676
|
+
* Currently-running multi-record transaction, or `null` outside
|
|
4677
|
+
* Phase 2. `Collection.dispatchDerivations` consults this so a
|
|
4678
|
+
* recursive derived-output write inside `Collection.put` can register
|
|
4679
|
+
* its envelope onto `ctx._executed` and roll back with the main
|
|
4680
|
+
* staged ops on mid-batch failure (#133).
|
|
4681
|
+
*
|
|
4682
|
+
* @internal
|
|
4683
|
+
*/
|
|
4684
|
+
get _activeTxContextOrNull(): TxContext | null;
|
|
4685
|
+
/**
|
|
4686
|
+
* Called by `runTransaction` at Phase 2 start, and by
|
|
4687
|
+
* `Collection.putManyAtomic` (via `derivationSource.setActiveTxContext`)
|
|
4688
|
+
* for its own Phase 2 loop. Nested or concurrent (non-nested)
|
|
4689
|
+
* transactions on the same Noydb instance are NOT supported —
|
|
4690
|
+
* overwriting an active context means another transaction is still
|
|
4691
|
+
* running and its `_executed` list would be cross-contaminated by
|
|
4692
|
+
* the nested writes. We tolerate the overwrite (best-effort, no
|
|
4693
|
+
* throw) to keep the rare interleaving from breaking consumers who
|
|
4694
|
+
* currently get lucky with timing, but applications should ensure
|
|
4695
|
+
* their multi-record commits are serialised on a single Noydb.
|
|
4696
|
+
*
|
|
4697
|
+
* @internal
|
|
4698
|
+
*/
|
|
4699
|
+
_setActiveTxContext(ctx: TxContext): void;
|
|
4700
|
+
/**
|
|
4701
|
+
* Factory for a transient `TxContext` bound to this Noydb. Used by
|
|
4702
|
+
* `Collection.putManyAtomic` (via `derivationSource.createTxContext`)
|
|
4703
|
+
* to publish an active context for the duration of its bulk-atomic
|
|
4704
|
+
* Phase 2 loop, so recursive derivation-output writes register on
|
|
4705
|
+
* `ctx._executed` and roll back together with the source ops (#133).
|
|
4706
|
+
*
|
|
4707
|
+
* @internal
|
|
4708
|
+
*/
|
|
4709
|
+
_createTxContext(): TxContext;
|
|
4710
|
+
/**
|
|
4711
|
+
* Called by `runTransaction` in its `finally`. Only clears when the
|
|
4712
|
+
* passed ctx matches the active one — a defensive no-op if some
|
|
4713
|
+
* other code path already cleared it.
|
|
4714
|
+
*
|
|
4715
|
+
* @internal
|
|
4716
|
+
*/
|
|
4717
|
+
_clearActiveTxContext(ctx: TxContext): void;
|
|
4718
|
+
/** Get sync status for a vault. */
|
|
4719
|
+
syncStatus(vault: string): SyncStatus;
|
|
4720
|
+
private requireShamirProvider;
|
|
4721
|
+
private getSyncEngine;
|
|
4722
|
+
on<K extends keyof NoydbEventMap>(event: K, handler: (data: NoydbEventMap[K]) => void): void;
|
|
4723
|
+
off<K extends keyof NoydbEventMap>(event: K, handler: (data: NoydbEventMap[K]) => void): void;
|
|
4724
|
+
/**
|
|
4725
|
+
* Soft-lock a single vault: clear its in-memory keyring, DEKs, vault
|
|
4212
4726
|
* instance, sync engine, policy enforcer, and active-tier entry —
|
|
4213
4727
|
* WITHOUT destroying the `Noydb` instance.
|
|
4214
4728
|
*
|
|
@@ -4259,6 +4773,27 @@ declare class Noydb {
|
|
|
4259
4773
|
* change is fundamentally a privilege-management action).
|
|
4260
4774
|
*/
|
|
4261
4775
|
updatePolicy(vault: string, override: Partial<VaultPolicy>): Promise<VaultPolicy>;
|
|
4776
|
+
/**
|
|
4777
|
+
* Read the current vault-level user-directory toggle (#122). Returns
|
|
4778
|
+
* the default-on shape (`{ enabled: true }`) when no `_meta/directory`
|
|
4779
|
+
* document has been persisted yet.
|
|
4780
|
+
*
|
|
4781
|
+
* No role gate — anyone who can open the vault can read the toggle.
|
|
4782
|
+
*/
|
|
4783
|
+
getDirectoryEnabled(vault: string): Promise<boolean>;
|
|
4784
|
+
/**
|
|
4785
|
+
* Toggle the vault's user-directory listing on or off (#122).
|
|
4786
|
+
* Owner-only. When disabled, `listUsersWithEnvelopes()` throws
|
|
4787
|
+
* {@link import('./errors.js').DirectoryDisabledError} for callers
|
|
4788
|
+
* whose role is neither `owner` nor `admin`.
|
|
4789
|
+
*
|
|
4790
|
+
* Honest caveat: this is a UX flag, not a privacy guarantee. The
|
|
4791
|
+
* keyring file at `_keyring/<userId>` and the envelope ciphertext at
|
|
4792
|
+
* `_users/<keyringId>` remain observable to anyone with direct store
|
|
4793
|
+
* read access — only the hub-level enumeration is gated. See
|
|
4794
|
+
* `docs/subsystems/user-envelope.md` → "Directory visibility".
|
|
4795
|
+
*/
|
|
4796
|
+
setDirectoryEnabled(vault: string, enabled: boolean): Promise<void>;
|
|
4262
4797
|
/**
|
|
4263
4798
|
* Evaluate a policy gate against the active session tier and the
|
|
4264
4799
|
* presented factor proofs. Throws {@link PolicyDeniedError} on
|
|
@@ -4269,22 +4804,33 @@ declare class Noydb {
|
|
|
4269
4804
|
* or app-defined (`app:*`).
|
|
4270
4805
|
* @param presented Caller-supplied factor proofs.
|
|
4271
4806
|
*/
|
|
4272
|
-
checkGate(vault: string, gate: GateName,
|
|
4273
|
-
factors?: ReadonlyArray<FactorProof>;
|
|
4274
|
-
sharedDevice?: boolean;
|
|
4275
|
-
}): Promise<void>;
|
|
4807
|
+
checkGate(vault: string, gate: GateName, factors?: FactorProofBundle): Promise<void>;
|
|
4276
4808
|
/** Read or persist the vault policy at `_meta/policy` on first open. */
|
|
4277
4809
|
private bootstrapPolicy;
|
|
4278
4810
|
/**
|
|
4279
|
-
* Throw {@link RecoveryNotEnrolledError}
|
|
4280
|
-
*
|
|
4281
|
-
*
|
|
4282
|
-
*
|
|
4811
|
+
* Throw {@link RecoveryNotEnrolledError} or
|
|
4812
|
+
* {@link ManagedRecoveryNotEnrolledError} when recovery enrollment
|
|
4813
|
+
* is missing.
|
|
4814
|
+
*
|
|
4815
|
+
* Two enforcement modes:
|
|
4816
|
+
*
|
|
4817
|
+
* 1. **Managed-mode mandatory strong-recovery (#195).** When
|
|
4818
|
+
* `passphraseMode === 'managed'`, the vault MUST have at least
|
|
4819
|
+
* one **strong** recovery profile (Shamir today). Paper alone is
|
|
4820
|
+
* rejected because under managed mode the user has no memorized
|
|
4821
|
+
* passphrase, so losing the paper sheet = losing every record.
|
|
4822
|
+
* This check is unconditional — independent of `requireRecovery`
|
|
4823
|
+
* and the `recover-passphrase` gate.
|
|
4824
|
+
*
|
|
4825
|
+
* 2. **Opt-in strict mandatory-recovery.** When
|
|
4826
|
+
* `requireRecovery: true` is set on createNoydb (and the gate is
|
|
4827
|
+
* not explicitly disabled), require ANY recovery profile (paper
|
|
4828
|
+
* or shamir). This is the v0.x default-off behavior; v1.0 may
|
|
4829
|
+
* flip it default-on.
|
|
4283
4830
|
*
|
|
4284
|
-
* The
|
|
4285
|
-
*
|
|
4286
|
-
*
|
|
4287
|
-
* apps that want the spec-mandated check turn it on per-vault.
|
|
4831
|
+
* The managed-mode check fires from {@link bootstrapPolicy} unless
|
|
4832
|
+
* the `skipManagedCheck` flag is set (used by
|
|
4833
|
+
* {@link openVaultAndEnrollRecovery} to allow atomic create-and-enroll).
|
|
4288
4834
|
*/
|
|
4289
4835
|
private assertRecoveryEnrolled;
|
|
4290
4836
|
/**
|
|
@@ -4305,19 +4851,13 @@ declare class Noydb {
|
|
|
4305
4851
|
* Gated by `enroll-authenticator`; `presented` carries any factor
|
|
4306
4852
|
* proofs the active policy demands.
|
|
4307
4853
|
*/
|
|
4308
|
-
enrollAuthenticator(vault: string, options: EnrollAuthenticatorOptions,
|
|
4309
|
-
factors?: ReadonlyArray<FactorProof>;
|
|
4310
|
-
sharedDevice?: boolean;
|
|
4311
|
-
}): Promise<void>;
|
|
4854
|
+
enrollAuthenticator(vault: string, options: EnrollAuthenticatorOptions, factors?: FactorProofBundle): Promise<void>;
|
|
4312
4855
|
/**
|
|
4313
4856
|
* Remove a tier-2 authenticator slot. Idempotent — removing a
|
|
4314
4857
|
* non-existent slot is a successful no-op. Gated by
|
|
4315
4858
|
* `remove-authenticator`.
|
|
4316
4859
|
*/
|
|
4317
|
-
removeAuthenticator(vault: string, slotId: string,
|
|
4318
|
-
factors?: ReadonlyArray<FactorProof>;
|
|
4319
|
-
sharedDevice?: boolean;
|
|
4320
|
-
}): Promise<void>;
|
|
4860
|
+
removeAuthenticator(vault: string, slotId: string, factors?: FactorProofBundle): Promise<void>;
|
|
4321
4861
|
/** Read the slot list for a vault. Internal — `describeAuthConfig` (#13) consumes this. */
|
|
4322
4862
|
listAuthenticators(vault: string): Promise<ReadonlyArray<KeyringAuthenticator>>;
|
|
4323
4863
|
/**
|
|
@@ -4348,10 +4888,7 @@ declare class Noydb {
|
|
|
4348
4888
|
*
|
|
4349
4889
|
* @see #55
|
|
4350
4890
|
*/
|
|
4351
|
-
updateAuthenticator(vault: string, slotId: string, options: UpdateAuthenticatorOptions,
|
|
4352
|
-
factors?: ReadonlyArray<FactorProof>;
|
|
4353
|
-
sharedDevice?: boolean;
|
|
4354
|
-
}): Promise<void>;
|
|
4891
|
+
updateAuthenticator(vault: string, slotId: string, options: UpdateAuthenticatorOptions, factors?: FactorProofBundle): Promise<void>;
|
|
4355
4892
|
/**
|
|
4356
4893
|
* Native WebAuthn enrollment using the **real** internal keyring (#16).
|
|
4357
4894
|
*
|
|
@@ -4399,10 +4936,7 @@ declare class Noydb {
|
|
|
4399
4936
|
*
|
|
4400
4937
|
* @see #16
|
|
4401
4938
|
*/
|
|
4402
|
-
enrollWebAuthn(vault: string, ceremony: (keyring: UnlockedKeyring) => Promise<EnrollAuthenticatorOptions>,
|
|
4403
|
-
factors?: ReadonlyArray<FactorProof>;
|
|
4404
|
-
sharedDevice?: boolean;
|
|
4405
|
-
}): Promise<{
|
|
4939
|
+
enrollWebAuthn(vault: string, ceremony: (keyring: UnlockedKeyring) => Promise<EnrollAuthenticatorOptions>, factors?: FactorProofBundle): Promise<{
|
|
4406
4940
|
credentialId: string;
|
|
4407
4941
|
}>;
|
|
4408
4942
|
/**
|
|
@@ -4463,15 +4997,9 @@ declare class Noydb {
|
|
|
4463
4997
|
* disabled). Sanitization is allowlist-based — never renders cred
|
|
4464
4998
|
* ids, password hashes, secrets, or any field outside the allowlist.
|
|
4465
4999
|
*/
|
|
4466
|
-
describeUserAuth(vault: string, userId: string, factors?:
|
|
4467
|
-
factors?: ReadonlyArray<FactorProof>;
|
|
4468
|
-
sharedDevice?: boolean;
|
|
4469
|
-
}): Promise<string>;
|
|
5000
|
+
describeUserAuth(vault: string, userId: string, factors?: FactorProofBundle): Promise<string>;
|
|
4470
5001
|
/** Bulk variant for owner dashboards. Gated by `view-user-auth`. */
|
|
4471
|
-
describeAllUsersAuth(vault: string, factors?: {
|
|
4472
|
-
factors?: ReadonlyArray<FactorProof>;
|
|
4473
|
-
sharedDevice?: boolean;
|
|
4474
|
-
}): Promise<Array<{
|
|
5002
|
+
describeAllUsersAuth(vault: string, factors?: FactorProofBundle): Promise<Array<{
|
|
4475
5003
|
userId: string;
|
|
4476
5004
|
description: string;
|
|
4477
5005
|
}>>;
|
|
@@ -4489,10 +5017,7 @@ declare class Noydb {
|
|
|
4489
5017
|
* @throws `PolicyDeniedError` when the gate denies (missing factor, …).
|
|
4490
5018
|
* @throws `InvalidKeyError` when `oldPassphrase` is wrong.
|
|
4491
5019
|
*/
|
|
4492
|
-
rotatePassphrase(vault: string, input: RotatePassphraseInput, factors?:
|
|
4493
|
-
factors?: ReadonlyArray<FactorProof>;
|
|
4494
|
-
sharedDevice?: boolean;
|
|
4495
|
-
}): Promise<void>;
|
|
5020
|
+
rotatePassphrase(vault: string, input: RotatePassphraseInput, factors?: FactorProofBundle): Promise<void>;
|
|
4496
5021
|
/**
|
|
4497
5022
|
* Reset the passphrase using a recovery proof (user forgot the old).
|
|
4498
5023
|
* v0.1.0-pre.5 supports the `'paper'` profile end-to-end; the
|
|
@@ -4500,10 +5025,124 @@ declare class Noydb {
|
|
|
4500
5025
|
*
|
|
4501
5026
|
* Burns the used recovery entry on success.
|
|
4502
5027
|
*/
|
|
4503
|
-
recoverPassphrase(vault: string, input: RecoverPassphraseInput, factors?:
|
|
4504
|
-
|
|
4505
|
-
|
|
4506
|
-
|
|
5028
|
+
recoverPassphrase(vault: string, input: RecoverPassphraseInput, factors?: FactorProofBundle): Promise<RecoverPassphraseResult>;
|
|
5029
|
+
/**
|
|
5030
|
+
* Deliberate paper-recovery-code regeneration (#121). User knows their
|
|
5031
|
+
* passphrase but wants a fresh sheet — they lost the printout or
|
|
5032
|
+
* suspect compromise of the off-site copy.
|
|
5033
|
+
*
|
|
5034
|
+
* Symmetric to {@link rotatePassphrase} for the recovery profile:
|
|
5035
|
+
* gated, audit-trackable, ergonomic. Replaces (not appends) the
|
|
5036
|
+
* paper sheet under `_meta/recovery-paper` in a single envelope `put`.
|
|
5037
|
+
*
|
|
5038
|
+
* Gated by the `rotate-recovery` policy gate:
|
|
5039
|
+
* - PERSONAL_POLICY: `{ minTier: 1 }` — knowing the passphrase
|
|
5040
|
+
* suffices, matching the pre-#121 low-level flow's bar.
|
|
5041
|
+
* - STRICT_POLICY: `{ minTier: 1, factors: [{ anyOf: ['totp',
|
|
5042
|
+
* 'email-otp', 'webauthn-roaming'] }] }` — rotation is an
|
|
5043
|
+
* off-site-trust event; require an off-device factor so a
|
|
5044
|
+
* stolen unlocked laptop cannot silently mint a sheet for the
|
|
5045
|
+
* attacker.
|
|
5046
|
+
*
|
|
5047
|
+
* Defaults `count` to the existing sheet size so consumers aren't
|
|
5048
|
+
* surprised by a different code count. Explicit `count` overrides.
|
|
5049
|
+
*
|
|
5050
|
+
* @throws {@link RecoveryProfileNotImplementedError} when `profile`
|
|
5051
|
+
* is anything other than `'paper'` (v1 dispatch limit).
|
|
5052
|
+
* @throws {@link PolicyDeniedError} when the gate denies (missing
|
|
5053
|
+
* factor, tier mismatch, ...).
|
|
5054
|
+
* @throws on missing paper sheet — "nothing to rotate" surfaces as
|
|
5055
|
+
* an error rather than silently minting an entire new sheet.
|
|
5056
|
+
*
|
|
5057
|
+
* @example Default count + show-once UI
|
|
5058
|
+
* ```ts
|
|
5059
|
+
* const { newCodes } = await db.rotateRecovery('acme', { profile: 'paper' })
|
|
5060
|
+
* showCodesToUser(newCodes)
|
|
5061
|
+
* ```
|
|
5062
|
+
*
|
|
5063
|
+
* @example STRICT-policy site with TOTP factor proof
|
|
5064
|
+
* ```ts
|
|
5065
|
+
* await db.rotateRecovery(
|
|
5066
|
+
* 'acme',
|
|
5067
|
+
* { profile: 'paper', count: 10 },
|
|
5068
|
+
* { factors: [{ kind: 'totp', proof: '123456' }] },
|
|
5069
|
+
* )
|
|
5070
|
+
* ```
|
|
5071
|
+
*/
|
|
5072
|
+
rotateRecovery(vault: string, options: RotateRecoveryOptions, factors?: FactorProofBundle): Promise<RotateRecoveryResult>;
|
|
5073
|
+
private rotateRecoveryPaper;
|
|
5074
|
+
private rotateRecoveryShamir;
|
|
5075
|
+
/**
|
|
5076
|
+
* **Atomic create-and-enroll for managed-mode vaults (#195).**
|
|
5077
|
+
*
|
|
5078
|
+
* Bootstraps a managed-mode vault and enrolls strong recovery in
|
|
5079
|
+
* a single ceremony. Under `passphraseMode: 'managed'`, every
|
|
5080
|
+
* `openVault` call requires a strong recovery profile (Shamir
|
|
5081
|
+
* today) to be enrolled — otherwise it throws
|
|
5082
|
+
* {@link ManagedRecoveryNotEnrolledError}. This method bypasses
|
|
5083
|
+
* the check temporarily so the keyring can be created, enrolls
|
|
5084
|
+
* the supplied recovery profile(s), then returns the vault.
|
|
5085
|
+
*
|
|
5086
|
+
* For Shamir enrollments, the show-once share strings come back
|
|
5087
|
+
* in `recoveryEnrollments[i].shares`. The hub never retains them
|
|
5088
|
+
* — the caller MUST display them to the user (once) before any
|
|
5089
|
+
* subsequent operation.
|
|
5090
|
+
*
|
|
5091
|
+
* Paper alone is NOT a strong profile under managed mode; passing
|
|
5092
|
+
* `{ profile: 'paper', ... }` without an accompanying shamir entry
|
|
5093
|
+
* is rejected at validation time.
|
|
5094
|
+
*
|
|
5095
|
+
* ```ts
|
|
5096
|
+
* const db = await createNoydb({
|
|
5097
|
+
* store, user: 'alice',
|
|
5098
|
+
* passphraseMode: 'managed',
|
|
5099
|
+
* sealingKey: macosKeychainSealingProvider({ ... }),
|
|
5100
|
+
* })
|
|
5101
|
+
*
|
|
5102
|
+
* const { vault, recoveryEnrollments } = await db.openVaultAndEnrollRecovery('acme', {
|
|
5103
|
+
* recovery: [{ profile: 'shamir', k: 2, n: 3 }],
|
|
5104
|
+
* })
|
|
5105
|
+
* for (const r of recoveryEnrollments) {
|
|
5106
|
+
* if (r.shares) showSharesToUser(r.shares) // ONCE
|
|
5107
|
+
* }
|
|
5108
|
+
* ```
|
|
5109
|
+
*
|
|
5110
|
+
* @throws ValidationError if recovery is empty, or contains no
|
|
5111
|
+
* strong profile under managed mode.
|
|
5112
|
+
*/
|
|
5113
|
+
openVaultAndEnrollRecovery(vault: string, opts: {
|
|
5114
|
+
readonly recovery: ReadonlyArray<RecoveryEnrollmentInput>;
|
|
5115
|
+
readonly locale?: string;
|
|
5116
|
+
}): Promise<{
|
|
5117
|
+
readonly vault: Vault;
|
|
5118
|
+
readonly recoveryEnrollments: ReadonlyArray<EnrollRecoveryResult>;
|
|
5119
|
+
}>;
|
|
5120
|
+
/**
|
|
5121
|
+
* **Recovery flow under managed-passphrase mode (#195).**
|
|
5122
|
+
*
|
|
5123
|
+
* Replaces the sealed passphrase of a managed-mode vault with a
|
|
5124
|
+
* fresh 256-bit random, sealed under the configured
|
|
5125
|
+
* `SealingKeyProvider`. The user never sees the new passphrase.
|
|
5126
|
+
*
|
|
5127
|
+
* Internally:
|
|
5128
|
+
* 1. Verify the recovery proof (Shamir today) and unwrap the
|
|
5129
|
+
* DEK set.
|
|
5130
|
+
* 2. Mint a fresh 256-bit random as the new effective passphrase.
|
|
5131
|
+
* 3. Rewrap the DEK set under a fresh KEK derived from the new
|
|
5132
|
+
* passphrase (via the existing `recoverPassphrase` path).
|
|
5133
|
+
* 4. Seal the random bytes under the provider and overwrite
|
|
5134
|
+
* `_meta/sealed-passphrase`.
|
|
5135
|
+
* 5. Drop the keyring cache so the next operation re-derives.
|
|
5136
|
+
*
|
|
5137
|
+
* The vault's strong-recovery enrollment is preserved across
|
|
5138
|
+
* recovery (Shamir entries are not burned on use — see #196).
|
|
5139
|
+
*
|
|
5140
|
+
* @throws ValidationError if the Noydb instance is not in managed mode.
|
|
5141
|
+
*/
|
|
5142
|
+
recoverManagedPassphrase(vault: string, options: {
|
|
5143
|
+
readonly recoveryProof: RecoveryProof;
|
|
5144
|
+
readonly passphrasePolicy?: PassphrasePolicy;
|
|
5145
|
+
}): Promise<void>;
|
|
4507
5146
|
/**
|
|
4508
5147
|
* Atomic peer-recovery — re-wraps an EXISTING user's keyring under
|
|
4509
5148
|
* a fresh temp passphrase in a single store write. Closes #34's
|
|
@@ -4547,10 +5186,7 @@ declare class Noydb {
|
|
|
4547
5186
|
*
|
|
4548
5187
|
* @see #33 #34 — the issues this method closes.
|
|
4549
5188
|
*/
|
|
4550
|
-
recoverUser(vault: string, options: RecoverUserOptions, factors?:
|
|
4551
|
-
factors?: ReadonlyArray<FactorProof>;
|
|
4552
|
-
sharedDevice?: boolean;
|
|
4553
|
-
}): Promise<void>;
|
|
5189
|
+
recoverUser(vault: string, options: RecoverUserOptions, factors?: FactorProofBundle): Promise<void>;
|
|
4554
5190
|
/**
|
|
4555
5191
|
* Persist a recovery enrollment. v0.1.0-pre.5 accepts the `'paper'`
|
|
4556
5192
|
* profile.
|
|
@@ -4582,13 +5218,11 @@ declare class Noydb {
|
|
|
4582
5218
|
* await db.enrollRecovery('acme', { profile: 'paper', entries })
|
|
4583
5219
|
* ```
|
|
4584
5220
|
*/
|
|
4585
|
-
enrollRecovery(vault: string, enrollment:
|
|
4586
|
-
|
|
4587
|
-
entries: ReadonlyArray<PaperRecoveryEntry>;
|
|
4588
|
-
}): Promise<void>;
|
|
4589
|
-
/** Read the persisted paper-recovery entries. Used by `describeAuthConfig` (#13). */
|
|
5221
|
+
enrollRecovery(vault: string, enrollment: RecoveryEnrollmentInput): Promise<EnrollRecoveryResult>;
|
|
5222
|
+
/** Read the persisted recovery entries (paper + Shamir). Used by `describeAuthConfig` (#13). */
|
|
4590
5223
|
listRecoveryEntries(vault: string): Promise<{
|
|
4591
5224
|
paper: ReadonlyArray<PaperRecoveryEntry>;
|
|
5225
|
+
shamir: ReadonlyArray<ShamirRecoveryEntry>;
|
|
4592
5226
|
}>;
|
|
4593
5227
|
/**
|
|
4594
5228
|
* Register a tier-3 quick-unlock state for the vault. The state is
|
|
@@ -4599,10 +5233,7 @@ declare class Noydb {
|
|
|
4599
5233
|
* Gated by `rotate-unlock` (the same gate covers "set" and "rotate"
|
|
4600
5234
|
* because tier-3 is a single-slot rolling secret).
|
|
4601
5235
|
*/
|
|
4602
|
-
enrollUnlock(vault: string, state: QuickUnlockState,
|
|
4603
|
-
factors?: ReadonlyArray<FactorProof>;
|
|
4604
|
-
sharedDevice?: boolean;
|
|
4605
|
-
}): Promise<void>;
|
|
5236
|
+
enrollUnlock(vault: string, state: QuickUnlockState, factors?: FactorProofBundle): Promise<void>;
|
|
4606
5237
|
/**
|
|
4607
5238
|
* Resume a session via the registered tier-3 state. The verifier is
|
|
4608
5239
|
* `@noy-db/on-pin/resumePin` (or compatible). On success, mark the
|
|
@@ -4618,8 +5249,17 @@ declare class Noydb {
|
|
|
4618
5249
|
/**
|
|
4619
5250
|
* Public accessor for the unlocked keyring of a vault — issue #28.
|
|
4620
5251
|
*
|
|
4621
|
-
* Returns
|
|
4622
|
-
*
|
|
5252
|
+
* Returns a **defensive shallow copy** so consumers can read the DEK
|
|
5253
|
+
* map and authenticator list without the risk of mutating the hub's
|
|
5254
|
+
* internal cache (#88). Internal hub code paths use a live reference
|
|
5255
|
+
* via `getKeyringInternal`; ceremonies and external consumers always
|
|
5256
|
+
* get a snapshot.
|
|
5257
|
+
*
|
|
5258
|
+
* The CryptoKey values inside `deks` are not cloned — Web Crypto
|
|
5259
|
+
* keys are opaque handles, and a shared handle is intentional
|
|
5260
|
+
* (encrypt / decrypt go through the same key the cache holds).
|
|
5261
|
+
* Only the container Map / authenticator array is fresh.
|
|
5262
|
+
*
|
|
4623
5263
|
* Used by `@noy-db/on-*` ceremonies that need the live DEK set
|
|
4624
5264
|
* (paper recovery via {@link mintPaperRecoveryEntry}, tier-3 PIN
|
|
4625
5265
|
* enrolment via on-pin's `enrollPin`, custom on-* ceremonies that
|
|
@@ -4634,11 +5274,19 @@ declare class Noydb {
|
|
|
4634
5274
|
* ```ts
|
|
4635
5275
|
* const keyring = await db.getKeyring('acme')
|
|
4636
5276
|
* // keyring.deks: Map<collection, CryptoKey>
|
|
4637
|
-
* // keyring.kek: CryptoKey (
|
|
5277
|
+
* // keyring.kek: CryptoKey | null (null for tier-3 / wrap-DEKs sessions)
|
|
4638
5278
|
* // keyring.role / .permissions / .authenticators
|
|
4639
5279
|
* ```
|
|
4640
5280
|
*/
|
|
4641
5281
|
getKeyring(vault: string): Promise<UnlockedKeyring>;
|
|
5282
|
+
/**
|
|
5283
|
+
* Live-reference variant used by the hub's own code paths. Internal
|
|
5284
|
+
* mutations on `deks` (e.g. {@link ensureCollectionDEK} adding a
|
|
5285
|
+
* collection key) need to land on the cached keyring so subsequent
|
|
5286
|
+
* accesses see them. Not exposed publicly — callers outside hub
|
|
5287
|
+
* should use {@link getKeyring}, which returns a defensive copy.
|
|
5288
|
+
*/
|
|
5289
|
+
private getKeyringInternal;
|
|
4642
5290
|
}
|
|
4643
5291
|
/** Create a new NOYDB instance. */
|
|
4644
5292
|
declare function createNoydb(options: NoydbOptions): Promise<Noydb>;
|
|
@@ -4711,24 +5359,777 @@ interface IssueDelegationOptions {
|
|
|
4711
5359
|
readonly until: Date | string;
|
|
4712
5360
|
}
|
|
4713
5361
|
/**
|
|
4714
|
-
* Build and persist a delegation token. The caller must hold a tier-N
|
|
4715
|
-
* DEK and must have already located the target user's keyring file
|
|
4716
|
-
* (so the `wrappedDek` can be re-wrapped against their KEK).
|
|
5362
|
+
* Build and persist a delegation token. The caller must hold a tier-N
|
|
5363
|
+
* DEK and must have already located the target user's keyring file
|
|
5364
|
+
* (so the `wrappedDek` can be re-wrapped against their KEK).
|
|
5365
|
+
*/
|
|
5366
|
+
declare function issueDelegation(store: NoydbStore, vault: string, grantor: UnlockedKeyring, targetKek: CryptoKey | null, delegationsDek: CryptoKey, opts: IssueDelegationOptions): Promise<DelegationToken>;
|
|
5367
|
+
/**
|
|
5368
|
+
* Enumerate every live (non-expired) delegation addressed to `toUser`
|
|
5369
|
+
* and merge the unwrapped tier DEKs into their keyring. Returns the
|
|
5370
|
+
* list of merged delegations so the caller can register per-access
|
|
5371
|
+
* audit context.
|
|
5372
|
+
*/
|
|
5373
|
+
declare function loadActiveDelegations(store: NoydbStore, vault: string, user: UnlockedKeyring, delegationsDek: CryptoKey, now?: Date): Promise<DelegationToken[]>;
|
|
5374
|
+
/**
|
|
5375
|
+
* Revoke a delegation by id — the caller resolves the envelope and
|
|
5376
|
+
* issues a `delete`. Provided as a stable helper so the naming is
|
|
5377
|
+
* symmetric to `issueDelegation`.
|
|
5378
|
+
*/
|
|
5379
|
+
declare function revokeDelegation(store: NoydbStore, vault: string, id: string): Promise<void>;
|
|
5380
|
+
|
|
5381
|
+
/**
|
|
5382
|
+
* Minimum read surface exposed to guard `check` functions. Intentionally
|
|
5383
|
+
* narrow — guards can read other collections but never write.
|
|
5384
|
+
*
|
|
5385
|
+
* `query()` returns the same chainable builder used elsewhere. `Query<T>`
|
|
5386
|
+
* has no write terminals (no `.update()` / `.delete()`) so exposing it
|
|
5387
|
+
* here preserves the read-only contract while letting guards aggregate
|
|
5388
|
+
* with `.where().aggregate()` / `.groupBy()` / `.join()` instead of
|
|
5389
|
+
* decrypting every sibling row via `.list()`.
|
|
5390
|
+
*/
|
|
5391
|
+
interface ReadOnlyVaultFacade$1 {
|
|
5392
|
+
collection<T = unknown>(name: string): {
|
|
5393
|
+
get(id: string): Promise<T | null>;
|
|
5394
|
+
list(): Promise<T[]>;
|
|
5395
|
+
query(): Query<T>;
|
|
5396
|
+
};
|
|
5397
|
+
}
|
|
5398
|
+
/**
|
|
5399
|
+
* Runtime context passed to `check` and `invariant` callbacks.
|
|
5400
|
+
* `existing` is the currently-persisted record (null for inserts).
|
|
5401
|
+
*/
|
|
5402
|
+
interface GuardContext<T> {
|
|
5403
|
+
existing: T | null;
|
|
5404
|
+
vault: ReadOnlyVaultFacade$1;
|
|
5405
|
+
userId: string;
|
|
5406
|
+
role: Role;
|
|
5407
|
+
}
|
|
5408
|
+
/**
|
|
5409
|
+
* One {before, after} pair handed to an `invariant` function. `before`
|
|
5410
|
+
* is null for inserts; `after` reflects the proposed post-commit record.
|
|
5411
|
+
*/
|
|
5412
|
+
interface GuardChange<T> {
|
|
5413
|
+
before: T | null;
|
|
5414
|
+
after: T;
|
|
5415
|
+
}
|
|
5416
|
+
/** @internal — output of {@link withGuard}. */
|
|
5417
|
+
interface GuardStrategyHandle<T extends Record<string, unknown>> {
|
|
5418
|
+
readonly __noydb_strategy: 'guard';
|
|
5419
|
+
readonly spec: GuardStrategy<T>;
|
|
5420
|
+
}
|
|
5421
|
+
/**
|
|
5422
|
+
* Existential erasure of `GuardStrategyHandle<T>` — used as the
|
|
5423
|
+
* element type of `ReadonlyArray<>` fields where the per-handle T
|
|
5424
|
+
* differs (e.g. `guardStrategies: [invoiceGuard, disbursementGuard]`).
|
|
5425
|
+
*
|
|
5426
|
+
* Background: `GuardStrategyHandle<T>` is INVARIANT in T because T
|
|
5427
|
+
* appears in callback positions on the spec (`check(incoming: T, ctx)`,
|
|
5428
|
+
* `invariant(changes: ReadonlyArray<GuardChange<T>>, ctx)`). So
|
|
5429
|
+
* `Handle<Invoice>` is not assignable to `Handle<Record<string, unknown>>`.
|
|
5430
|
+
* A bounded existential ("there exists some T satisfying the constraint
|
|
5431
|
+
* such that this is a Handle<T>") is the right shape; TypeScript has
|
|
5432
|
+
* no first-class existentials, so we fake it with a structurally narrow
|
|
5433
|
+
* interface that ERASES T from both the discriminant and the spec.
|
|
5434
|
+
*
|
|
5435
|
+
* Consumers continue to construct typed handles via `withGuard<T>(...)`
|
|
5436
|
+
* which returns `GuardStrategyHandle<T>`. Both `Handle<Invoice>` and
|
|
5437
|
+
* `Handle<Disbursement>` structurally assign to `GuardStrategyHandleAny`,
|
|
5438
|
+
* so an array of them is `GuardStrategyHandleAny[]`.
|
|
5439
|
+
*
|
|
5440
|
+
* Internal code that needs T re-narrows via the runtime discriminant
|
|
5441
|
+
* (`__noydb_strategy === 'guard'`) plus per-handle type information
|
|
5442
|
+
* carried by the registry.
|
|
5443
|
+
*
|
|
5444
|
+
* NOT exported from the public barrel — keeping this internal
|
|
5445
|
+
* discourages consumers from constructing it directly. Used only as
|
|
5446
|
+
* the array-element type on `Vault` / `NoydbOptions.guardStrategies`.
|
|
5447
|
+
*
|
|
5448
|
+
* @internal
|
|
5449
|
+
*/
|
|
5450
|
+
interface GuardStrategyHandleAny {
|
|
5451
|
+
readonly __noydb_strategy: 'guard';
|
|
5452
|
+
readonly spec: GuardStrategy<any>;
|
|
5453
|
+
}
|
|
5454
|
+
/** Public registration shape. See `withGuard()`. */
|
|
5455
|
+
interface GuardStrategy<T extends Record<string, unknown>> {
|
|
5456
|
+
collection: string;
|
|
5457
|
+
/**
|
|
5458
|
+
* Fires on `Collection.put` (insert + update). The `incoming` argument
|
|
5459
|
+
* is the record being written. Throw to cancel the put.
|
|
5460
|
+
*
|
|
5461
|
+
* Does NOT fire on `Collection.delete` — use {@link onDelete} for
|
|
5462
|
+
* delete-time validation. Skipped during an amendment transaction
|
|
5463
|
+
* (`db.transaction({ amendment: true })`) — admin/owner override.
|
|
5464
|
+
*/
|
|
5465
|
+
check?: (incoming: T, ctx: GuardContext<T>) => Promise<void> | void;
|
|
5466
|
+
/**
|
|
5467
|
+
* Fires on user-initiated `Collection.delete` before the adapter
|
|
5468
|
+
* delete and before the ledger append. The `existing` argument is
|
|
5469
|
+
* the currently-persisted record. Throw to cancel the delete — no
|
|
5470
|
+
* partial state, no tombstone ledger entry.
|
|
5471
|
+
*
|
|
5472
|
+
* Skipped during an amendment transaction (admin/owner override) —
|
|
5473
|
+
* amendments are the unlock primitive. To make a delete TRULY
|
|
5474
|
+
* unconditional (e.g. legal-document immutability rules), pair
|
|
5475
|
+
* `onDelete` with an `amendment.invariant` that re-throws on any
|
|
5476
|
+
* `before !== null && after === null` change:
|
|
5477
|
+
*
|
|
5478
|
+
* ```ts
|
|
5479
|
+
* withGuard<Receipt>({
|
|
5480
|
+
* collection: 'receipts',
|
|
5481
|
+
* onDelete: () => { throw new RecordLockedError(...) },
|
|
5482
|
+
* amendment: {
|
|
5483
|
+
* roles: ['admin', 'owner'],
|
|
5484
|
+
* invariant: (changes) => {
|
|
5485
|
+
* for (const c of changes) {
|
|
5486
|
+
* if (c.before !== null && c.after === null) {
|
|
5487
|
+
* throw new RecordLockedError(...) // wrapped as InvariantError
|
|
5488
|
+
* }
|
|
5489
|
+
* }
|
|
5490
|
+
* },
|
|
5491
|
+
* },
|
|
5492
|
+
* })
|
|
5493
|
+
* ```
|
|
5494
|
+
*
|
|
5495
|
+
* Also skipped on system-internal deletes (derivation tombstones from
|
|
5496
|
+
* #144, MV refresh from Dim 14 v2) — those use `_internalDelete`
|
|
5497
|
+
* which bypasses every user-facing delete hook. Housekeeping ops are
|
|
5498
|
+
* NOT user-initiated and should not trip user invariants.
|
|
5499
|
+
*
|
|
5500
|
+
* Delete of an absent record is a no-op and does not consult any
|
|
5501
|
+
* guard, matching the idempotent-delete contract.
|
|
5502
|
+
*/
|
|
5503
|
+
onDelete?: (existing: T, ctx: GuardContext<T>) => Promise<void> | void;
|
|
5504
|
+
frozenFields?: {
|
|
5505
|
+
when: (existing: T) => boolean;
|
|
5506
|
+
fields: ReadonlyArray<keyof T>;
|
|
5507
|
+
};
|
|
5508
|
+
amendment?: {
|
|
5509
|
+
roles: ReadonlyArray<'admin' | 'owner'>;
|
|
5510
|
+
invariant: (changes: ReadonlyArray<GuardChange<T>>, ctx: GuardContext<T>) => Promise<void> | void;
|
|
5511
|
+
};
|
|
5512
|
+
}
|
|
5513
|
+
|
|
5514
|
+
/**
|
|
5515
|
+
* Runtime context handed to `derive(source, ctx)`. Mirrors `GuardContext`'s
|
|
5516
|
+
* narrow shape: read-only vault access, no write capability, no
|
|
5517
|
+
* transaction handle. Determinism is the consumer's responsibility — the
|
|
5518
|
+
* strategy hash includes `derive.toString()`, so the source string fixes
|
|
5519
|
+
* the function's inputs; whatever sibling reads `derive` performs must
|
|
5520
|
+
* yield the same outputs for the same source.
|
|
5521
|
+
*/
|
|
5522
|
+
interface DerivationContext {
|
|
5523
|
+
vault: ReadOnlyVaultFacade$1;
|
|
5524
|
+
}
|
|
5525
|
+
/**
|
|
5526
|
+
* Metadata that travels inside the `_data` payload of a derived record.
|
|
5527
|
+
* Lives in encrypted payload, not in the unencrypted envelope — the
|
|
5528
|
+
* storage backend cannot infer the derivation graph from listing.
|
|
5529
|
+
*/
|
|
5530
|
+
interface DerivedFromMeta {
|
|
5531
|
+
/** Source collection name. */
|
|
5532
|
+
readonly source: string;
|
|
5533
|
+
/** Source record id. */
|
|
5534
|
+
readonly sourceId: string;
|
|
5535
|
+
/** `_v` of the source at derivation time. */
|
|
5536
|
+
readonly sourceVersion: number;
|
|
5537
|
+
/** ISO timestamp when this output was derived. */
|
|
5538
|
+
readonly derivedAt: string;
|
|
5539
|
+
/**
|
|
5540
|
+
* SHA-256 of (source + outputs map keys + derive function source).
|
|
5541
|
+
* Changes when the strategy changes → forces `vault.deriveAll` to
|
|
5542
|
+
* recompute on next visit.
|
|
5543
|
+
*/
|
|
5544
|
+
readonly strategyHash: string;
|
|
5545
|
+
}
|
|
5546
|
+
/** Record-shape output — one source row produces (optionally) one output row at the source's id. */
|
|
5547
|
+
interface RecordOutputSpec {
|
|
5548
|
+
shape: 'record';
|
|
5549
|
+
collection: string;
|
|
5550
|
+
/**
|
|
5551
|
+
* When `true`, the `derive` function may return `null` (or
|
|
5552
|
+
* `undefined`) for this output key. The executor interprets that as
|
|
5553
|
+
* "no output for this invocation": a previously-emitted output at
|
|
5554
|
+
* the same id is deleted (mirroring the empty-group / empty-aggregate
|
|
5555
|
+
* semantics flagged in #142); a never-emitted output is a silent
|
|
5556
|
+
* no-op. When `false` (default), returning `null` throws
|
|
5557
|
+
* `DerivationOutputShapeError` — same as v1.
|
|
5558
|
+
*/
|
|
5559
|
+
optional?: boolean;
|
|
5560
|
+
}
|
|
5561
|
+
/**
|
|
5562
|
+
* Array-shape output (#200) — one source row produces a variable-length
|
|
5563
|
+
* list of output rows, each with its own id (from the `key` extractor).
|
|
5564
|
+
*
|
|
5565
|
+
* On every source-row change, the dispatcher diffs the previously
|
|
5566
|
+
* emitted key set against the new one: removed keys are deleted via
|
|
5567
|
+
* `_internalDelete`, new and unchanged keys are upserted via
|
|
5568
|
+
* `Collection.put`. Strict-mode rollback is preserved via the existing
|
|
5569
|
+
* `_executed` tracking.
|
|
5570
|
+
*
|
|
5571
|
+
* Storage of the per-source-row key set lives at
|
|
5572
|
+
* `_meta/derivations-fanout/<source>/<sourceId>/<outputKey>` as a
|
|
5573
|
+
* plain JSON sidecar — keeps dispatch cost O(1) per source row.
|
|
5574
|
+
*
|
|
5575
|
+
* **Slice 1 limitation**: only `lifecycle: 'eager'` is supported.
|
|
5576
|
+
* Registering an array-shape output with `lifecycle: 'lazy'` throws
|
|
5577
|
+
* at `withDerivation` construction time.
|
|
5578
|
+
*/
|
|
5579
|
+
interface ArrayOutputSpec {
|
|
5580
|
+
shape: 'array';
|
|
5581
|
+
collection: string;
|
|
5582
|
+
/**
|
|
5583
|
+
* Stable identity extractor for each derived row. Called on every
|
|
5584
|
+
* row returned by `derive`. The string MUST be unique within a
|
|
5585
|
+
* single invocation — duplicate keys throw
|
|
5586
|
+
* `DerivationOutputShapeError`.
|
|
5587
|
+
*
|
|
5588
|
+
* Type is intentionally `(out: Record<string, unknown>) => string`
|
|
5589
|
+
* (not generic) because OutputSpec is type-erased at the registry
|
|
5590
|
+
* level. Strategy-level inference still produces typed `out`
|
|
5591
|
+
* through the strategy's `outputs` map.
|
|
5592
|
+
*/
|
|
5593
|
+
key: (output: Record<string, unknown>) => string;
|
|
5594
|
+
/**
|
|
5595
|
+
* Cap on derived rows per source-row invocation. Defaults to 64.
|
|
5596
|
+
* Raise for carry-forward cases (e.g. monthly expansion of
|
|
5597
|
+
* multi-year contracts). Exceeding the cap throws
|
|
5598
|
+
* `DerivationCapExceededError` BEFORE any writes — partial fanout
|
|
5599
|
+
* is never persisted.
|
|
5600
|
+
*/
|
|
5601
|
+
maxFanout?: number;
|
|
5602
|
+
}
|
|
5603
|
+
/** Discriminated union — record + array. */
|
|
5604
|
+
type OutputSpec = RecordOutputSpec | ArrayOutputSpec;
|
|
5605
|
+
/**
|
|
5606
|
+
* Registration shape passed to `withDerivation()`.
|
|
5607
|
+
*
|
|
5608
|
+
* @typeParam TSource - the source record type
|
|
5609
|
+
* @typeParam TOutputs - map of output-key → output record type
|
|
5610
|
+
*/
|
|
5611
|
+
interface DerivationStrategy<TSource extends Record<string, unknown>, TOutputs extends Record<string, Record<string, unknown>>> {
|
|
5612
|
+
/** Source collection name. */
|
|
5613
|
+
source: string;
|
|
5614
|
+
/** v1: only deterministic derivations supported. */
|
|
5615
|
+
deterministic: true;
|
|
5616
|
+
/**
|
|
5617
|
+
* Output declarations keyed by name. The `derive` function's return
|
|
5618
|
+
* value must have the same keys.
|
|
5619
|
+
*/
|
|
5620
|
+
outputs: {
|
|
5621
|
+
[K in keyof TOutputs]: OutputSpec;
|
|
5622
|
+
};
|
|
5623
|
+
/**
|
|
5624
|
+
* Pure function from source to outputs. Runs on plaintext, after DEK
|
|
5625
|
+
* unwrap. Returns a map of named outputs. Each output is encrypted +
|
|
5626
|
+
* stored via the existing `Collection.put` pipeline.
|
|
5627
|
+
*
|
|
5628
|
+
* `ctx.vault` is the same `ReadOnlyVaultFacade` guards see — fetch
|
|
5629
|
+
* sibling records via `ctx.vault.collection<T>(name).get(id)` /
|
|
5630
|
+
* `.list()` / `.query()`. The vault accessor is read-only; there is
|
|
5631
|
+
* no path to a writer from `ctx`.
|
|
5632
|
+
*/
|
|
5633
|
+
derive: (source: TSource, ctx: DerivationContext) => Promise<TOutputs> | TOutputs;
|
|
5634
|
+
/**
|
|
5635
|
+
* `'eager'` runs `derive` synchronously inside the source-write
|
|
5636
|
+
* transaction. `'lazy'` marks outputs stale on source-change and
|
|
5637
|
+
* derives on first read.
|
|
5638
|
+
*/
|
|
5639
|
+
lifecycle: 'eager' | 'lazy' | {
|
|
5640
|
+
mode: 'eager' | 'lazy';
|
|
5641
|
+
maxDepth?: number;
|
|
5642
|
+
};
|
|
5643
|
+
/**
|
|
5644
|
+
* `true` = any output failure rolls back the source write (only with
|
|
5645
|
+
* `withTransactions`). `false` = isolate per-output failure, log,
|
|
5646
|
+
* continue. Default `false`.
|
|
5647
|
+
*/
|
|
5648
|
+
strict?: boolean;
|
|
5649
|
+
}
|
|
5650
|
+
/** Returned by `withDerivation()` and consumed by `createNoydb`. */
|
|
5651
|
+
interface DerivationStrategyHandle {
|
|
5652
|
+
readonly __noydb_strategy: 'derivation';
|
|
5653
|
+
readonly spec: DerivationStrategy<any, any>;
|
|
5654
|
+
}
|
|
5655
|
+
|
|
5656
|
+
interface RegisteredStrategy {
|
|
5657
|
+
spec: DerivationStrategy<any, any>;
|
|
5658
|
+
strategyHash: string;
|
|
5659
|
+
}
|
|
5660
|
+
/**
|
|
5661
|
+
* Vault-internal registry of derivation strategies. Owned by `Vault`;
|
|
5662
|
+
* not exported.
|
|
5663
|
+
*
|
|
5664
|
+
* @internal
|
|
5665
|
+
*/
|
|
5666
|
+
declare class DerivationRegistry {
|
|
5667
|
+
private readonly _bySource;
|
|
5668
|
+
private readonly _byOutput;
|
|
5669
|
+
register(spec: DerivationStrategy<any, any>): Promise<void>;
|
|
5670
|
+
strategiesForSource(source: string): ReadonlyArray<RegisteredStrategy>;
|
|
5671
|
+
strategiesProducingOutput(collection: string): ReadonlyArray<RegisteredStrategy>;
|
|
5672
|
+
/**
|
|
5673
|
+
* Cycle detection over the source → output → … graph. Call after all
|
|
5674
|
+
* `register()` calls complete (i.e. at vault open). Throws
|
|
5675
|
+
* `DerivationCycleError` on the first cycle found.
|
|
5676
|
+
*/
|
|
5677
|
+
validate(): void;
|
|
5678
|
+
}
|
|
5679
|
+
|
|
5680
|
+
/**
|
|
5681
|
+
* Minimal vault-shaped accessor passed to the MV `query()` callback.
|
|
5682
|
+
* Defined as a structural interface so the strategy types don't have
|
|
5683
|
+
* to import the full `Vault` class (avoids a circular import). The
|
|
5684
|
+
* Vault implements this shape natively.
|
|
5685
|
+
*/
|
|
5686
|
+
interface MVQueryContext {
|
|
5687
|
+
collection<T extends Record<string, unknown>>(name: string): Collection<T>;
|
|
5688
|
+
}
|
|
5689
|
+
/**
|
|
5690
|
+
* Metadata that travels inside the `_data` payload of a materialized
|
|
5691
|
+
* row. Lives in encrypted payload, not in the unencrypted envelope —
|
|
5692
|
+
* the storage backend cannot infer the MV graph from listing.
|
|
5693
|
+
*
|
|
5694
|
+
* Extends the `_derivedFrom` precedent from v1: same encryption shape,
|
|
5695
|
+
* same "metadata-inside-data" location.
|
|
5696
|
+
*/
|
|
5697
|
+
interface MaterializedFromMeta {
|
|
5698
|
+
/** Stable identity for the MV that emitted this row. */
|
|
5699
|
+
readonly mvName: string;
|
|
5700
|
+
/**
|
|
5701
|
+
* SHA-256 of (mvName + canonical query plan + dependency-set).
|
|
5702
|
+
* Changes when the query structure changes → forces refresh on
|
|
5703
|
+
* next visit (parallels v1's `strategyHash`).
|
|
5704
|
+
*/
|
|
5705
|
+
readonly queryHash: string;
|
|
5706
|
+
/**
|
|
5707
|
+
* Map from source collection name → `_v` of the source row(s) that
|
|
5708
|
+
* contributed to this MV row at materialization time. For aggregates
|
|
5709
|
+
* over many rows, this is `max(_v)` per source collection — coarse
|
|
5710
|
+
* but sufficient for stale detection.
|
|
5711
|
+
*/
|
|
5712
|
+
readonly sourceVersions: Record<string, number>;
|
|
5713
|
+
/** ISO timestamp when this row was materialized. */
|
|
5714
|
+
readonly materializedAt: string;
|
|
5715
|
+
}
|
|
5716
|
+
/** Output routing for an MV. Optional — when omitted, writes to a collection named after `name`. */
|
|
5717
|
+
interface MaterializedViewOutput {
|
|
5718
|
+
/** Output collection name. Defaults to `name`. */
|
|
5719
|
+
collection?: string;
|
|
5720
|
+
/**
|
|
5721
|
+
* For same-collection-as-source MVs — see § Same-collection partition
|
|
5722
|
+
* discriminator in the v2 spec. The cycle detector resolves the
|
|
5723
|
+
* same-collection edge IFF the query has a where-clause that
|
|
5724
|
+
* provably excludes `partition.value` (supports `==` against a
|
|
5725
|
+
* different value, `!=` against the value, and `in` lists that
|
|
5726
|
+
* don't contain it). Naïve same-collection MVs without a disjoint
|
|
5727
|
+
* clause throw `MaterializedViewCycleError` at vault open.
|
|
5728
|
+
*/
|
|
5729
|
+
partition?: {
|
|
5730
|
+
field: string;
|
|
5731
|
+
value: unknown;
|
|
5732
|
+
};
|
|
5733
|
+
}
|
|
5734
|
+
/**
|
|
5735
|
+
* One arm of a UNION materialized view. Reads rows from `collection`,
|
|
5736
|
+
* then maps each into the MV's row shape via `map`.
|
|
5737
|
+
*
|
|
5738
|
+
* The per-source `map` is the schema-unification boundary — sibling
|
|
5739
|
+
* collections can have different schemas, and `map` is where they
|
|
5740
|
+
* meet the MV's row type. The hub does NOT compare schemas across
|
|
5741
|
+
* arms; consumer responsibility is that every arm's `map` returns
|
|
5742
|
+
* the same shape (the strategy's `TRow` type parameter enforces this
|
|
5743
|
+
* at compile time).
|
|
5744
|
+
*/
|
|
5745
|
+
interface UnionSource<TRow extends Record<string, unknown>> {
|
|
5746
|
+
/** Source collection name. Must exist in the vault. */
|
|
5747
|
+
readonly collection: string;
|
|
5748
|
+
/**
|
|
5749
|
+
* Pure function from a source row to the unified MV row shape.
|
|
5750
|
+
* Called once per source row at materialization time. Each arm's
|
|
5751
|
+
* mapped output is concatenated into a single stream before
|
|
5752
|
+
* `groupBy` + `aggregate` run.
|
|
5753
|
+
*/
|
|
5754
|
+
readonly map: (sourceRow: Record<string, unknown>) => TRow;
|
|
5755
|
+
}
|
|
5756
|
+
/**
|
|
5757
|
+
* Registration shape passed to `withMaterializedView()`.
|
|
5758
|
+
*
|
|
5759
|
+
* @typeParam TRow - the materialized row type (the query's result row)
|
|
5760
|
+
*/
|
|
5761
|
+
interface MaterializedViewStrategy<TRow extends Record<string, unknown>> {
|
|
5762
|
+
/**
|
|
5763
|
+
* Stable identity for this view. Used as the output collection name
|
|
5764
|
+
* unless `output.collection` overrides. Must be unique within the vault.
|
|
5765
|
+
*/
|
|
5766
|
+
name: string;
|
|
5767
|
+
/**
|
|
5768
|
+
* Declared query (single-source mode). Called at registration time
|
|
5769
|
+
* with a vault-shaped accessor so the closure can compose collections
|
|
5770
|
+
* without pre-existing in-scope references; called again at each
|
|
5771
|
+
* refresh.
|
|
5772
|
+
*
|
|
5773
|
+
* Built via the same `Query<T>` chainable builder used elsewhere —
|
|
5774
|
+
* `.where()`, `.join()`, `.groupBy()`, `.aggregate()`. The
|
|
5775
|
+
* dependency analyzer walks the returned plan to determine source
|
|
5776
|
+
* collections.
|
|
5777
|
+
*
|
|
5778
|
+
* Mutually exclusive with {@link unionSources}: a strategy must
|
|
5779
|
+
* declare exactly one of `query` (single-source) or `unionSources`
|
|
5780
|
+
* (multi-source UNION). Registration throws
|
|
5781
|
+
* `MaterializedViewConfigError` if both are set or neither is set.
|
|
5782
|
+
*/
|
|
5783
|
+
query?: (db: MVQueryContext) => Query<TRow>;
|
|
5784
|
+
/**
|
|
5785
|
+
* UNION-form sources (#165): an explicit list of sibling collections
|
|
5786
|
+
* that contribute rows to a single MV. Each arm's `map` projects a
|
|
5787
|
+
* source row into the MV's unified row shape; the mapped streams are
|
|
5788
|
+
* concatenated, then {@link groupBy} + {@link aggregate} run on the
|
|
5789
|
+
* combined output.
|
|
5790
|
+
*
|
|
5791
|
+
* Mutually exclusive with {@link query}. Registration throws
|
|
5792
|
+
* `MaterializedViewConfigError` if both are set, if `unionSources`
|
|
5793
|
+
* has fewer than 2 arms, or if two arms name the same `collection`.
|
|
5794
|
+
*
|
|
5795
|
+
* UNION mode replaces the dependency-analyzer path: the source
|
|
5796
|
+
* collections come directly from `unionSources[].collection`, and
|
|
5797
|
+
* {@link sources} is ignored.
|
|
5798
|
+
*/
|
|
5799
|
+
unionSources?: ReadonlyArray<UnionSource<TRow>>;
|
|
5800
|
+
/**
|
|
5801
|
+
* Group-key field(s) for UNION mode (#165). Applied to the
|
|
5802
|
+
* concatenated mapped-row stream from {@link unionSources} before
|
|
5803
|
+
* {@link aggregate} runs. Accepts a single field name or a tuple of
|
|
5804
|
+
* field names for multi-key grouping (same shape as
|
|
5805
|
+
* `Query.groupBy(...fields)`).
|
|
5806
|
+
*
|
|
5807
|
+
* UNION-mode only. Ignored if {@link query} is set — single-source
|
|
5808
|
+
* grouping is expressed inside the `Query<T>` returned from `query()`
|
|
5809
|
+
* via `.groupBy(...).aggregate(...)`.
|
|
5810
|
+
*/
|
|
5811
|
+
groupBy?: string | ReadonlyArray<string>;
|
|
5812
|
+
/**
|
|
5813
|
+
* Aggregation spec for UNION mode (#165). Applied per-group after
|
|
5814
|
+
* {@link groupBy} buckets the concatenated mapped-row stream from
|
|
5815
|
+
* {@link unionSources}. Same shape as the `AggregateSpec` passed to
|
|
5816
|
+
* `Query.aggregate()`.
|
|
5817
|
+
*
|
|
5818
|
+
* UNION-mode only. Ignored if {@link query} is set.
|
|
5819
|
+
*/
|
|
5820
|
+
aggregate?: AggregateSpec;
|
|
5821
|
+
/**
|
|
5822
|
+
* Pure function from a materialized row → stable id used in the
|
|
5823
|
+
* output collection. Required — explicit always beats default-with-pitfalls
|
|
5824
|
+
* (see niwat-review of #149 round 1 for the slash-collision rationale).
|
|
5825
|
+
*/
|
|
5826
|
+
rowKey: (row: TRow) => string;
|
|
5827
|
+
/**
|
|
5828
|
+
* Explicit source collections (#152). Required when `query()` returns
|
|
5829
|
+
* an `Aggregation` or `GroupedAggregation` rather than a `Query<T>`
|
|
5830
|
+
* — the dependency analyzer can't introspect through `groupBy().aggregate()`
|
|
5831
|
+
* back to the source. Optional for plain `Query<T>` results — the
|
|
5832
|
+
* analyzer extracts dependencies automatically from the query plan.
|
|
5833
|
+
*
|
|
5834
|
+
* When set, takes precedence over auto-analysis.
|
|
5835
|
+
*/
|
|
5836
|
+
sources?: ReadonlyArray<string>;
|
|
5837
|
+
/**
|
|
5838
|
+
* Declared deterministic predicates (#153). Each entry pairs a
|
|
5839
|
+
* consumer-stable `hash` with a function. The `query()` callback's
|
|
5840
|
+
* Query<T> can invoke them via `.wherePredicate(name, ctx?)`. The
|
|
5841
|
+
* predicate's `hash` + a canonical-JSON hash of `ctx` both fold
|
|
5842
|
+
* into `queryHash` — bumping either forces refresh on next visit.
|
|
5843
|
+
*
|
|
5844
|
+
* Consumer responsibility: bump `hash` when the function's semantics
|
|
5845
|
+
* change. Failing to bump after a non-equivalent change leaves
|
|
5846
|
+
* stale rows around until the next explicit refresh.
|
|
5847
|
+
*/
|
|
5848
|
+
predicates?: {
|
|
5849
|
+
[name: string]: {
|
|
5850
|
+
hash: string;
|
|
5851
|
+
fn: (row: TRow, ctx?: unknown) => boolean;
|
|
5852
|
+
};
|
|
5853
|
+
};
|
|
5854
|
+
/**
|
|
5855
|
+
* Refresh policy.
|
|
5856
|
+
*
|
|
5857
|
+
* - `'eager'` — re-materialize synchronously inside the source-write
|
|
5858
|
+
* transaction (composes with `withTransactions` for strict-mode
|
|
5859
|
+
* rollback).
|
|
5860
|
+
* - `'lazy'` — mark stale on source-change; materialize on first
|
|
5861
|
+
* read of the MV.
|
|
5862
|
+
* - `'manual'` — only materializes when `vault.refreshView(name)` is
|
|
5863
|
+
* called. Useful for very expensive MVs or time-dependent queries
|
|
5864
|
+
* whose `ctx` changes externally.
|
|
5865
|
+
*/
|
|
5866
|
+
refresh: 'eager' | 'lazy' | 'manual';
|
|
5867
|
+
/** Output routing. Optional; defaults to writing the collection named after `name`. */
|
|
5868
|
+
output?: MaterializedViewOutput;
|
|
5869
|
+
/**
|
|
5870
|
+
* What to do when a re-materialization produces zero rows for a key
|
|
5871
|
+
* that previously had rows.
|
|
5872
|
+
*
|
|
5873
|
+
* - `'delete'` (default) — tombstone the prior MV row via
|
|
5874
|
+
* `Collection._internalDelete` (system housekeeping bypasses user
|
|
5875
|
+
* `onDelete` guards on the output collection — see PR #148's
|
|
5876
|
+
* composition fix).
|
|
5877
|
+
* - `'keep'` — leave the prior MV row in place. Useful when zero
|
|
5878
|
+
* is a meaningful state.
|
|
5879
|
+
*/
|
|
5880
|
+
onEmpty?: 'delete' | 'keep';
|
|
5881
|
+
/**
|
|
5882
|
+
* `true` re-throws on any row-write failure → composes with
|
|
5883
|
+
* `withTransactions` to roll back the source-write atomically via
|
|
5884
|
+
* `revertExecuted` (#133). Default `false` (failed rows are
|
|
5885
|
+
* isolated; other rows commit).
|
|
5886
|
+
*/
|
|
5887
|
+
strict?: boolean;
|
|
5888
|
+
/**
|
|
5889
|
+
* Row-count ceiling for the materialized output. Throws
|
|
5890
|
+
* `MaterializedViewTooLargeError` before any writes when exceeded
|
|
5891
|
+
* — keeps the rollback clean. Default `100_000`; override per-MV
|
|
5892
|
+
* when the domain warrants it.
|
|
5893
|
+
*/
|
|
5894
|
+
maxRows?: number;
|
|
5895
|
+
}
|
|
5896
|
+
/** Returned by `withMaterializedView()` and consumed by `createNoydb`. */
|
|
5897
|
+
interface MaterializedViewStrategyHandle {
|
|
5898
|
+
readonly __noydb_strategy: 'materialized-view';
|
|
5899
|
+
readonly spec: MaterializedViewStrategy<any>;
|
|
5900
|
+
}
|
|
5901
|
+
|
|
5902
|
+
/**
|
|
5903
|
+
* One registered MV strategy alongside its derived metadata. Stored
|
|
5904
|
+
* type-erased on `TRow` so the registry can hold heterogeneous MVs.
|
|
5905
|
+
*/
|
|
5906
|
+
interface RegisteredMV {
|
|
5907
|
+
readonly spec: MaterializedViewStrategy<any>;
|
|
5908
|
+
/** Output collection name (`spec.output?.collection ?? spec.name`). */
|
|
5909
|
+
readonly outputCollection: string;
|
|
5910
|
+
/** Set of source collections; populated at registration via the analyzer. */
|
|
5911
|
+
readonly dependencies: ReadonlySet<string>;
|
|
5912
|
+
/** Canonical `queryHash` — `_materializedFrom.queryHash` for every emitted row. */
|
|
5913
|
+
readonly queryHash: string;
|
|
5914
|
+
/**
|
|
5915
|
+
* Top-level FieldClauses on the partition field, captured at
|
|
5916
|
+
* registration time. Used by the cycle detector to resolve
|
|
5917
|
+
* same-collection-as-source edges via the partition-discriminator
|
|
5918
|
+
* check (#152). Empty when `spec.output?.partition` is undefined.
|
|
5919
|
+
*/
|
|
5920
|
+
readonly partitionClauses: readonly FieldClause[];
|
|
5921
|
+
}
|
|
5922
|
+
/**
|
|
5923
|
+
* Vault-internal registry of MV strategies. Owned by `Vault`; not
|
|
5924
|
+
* exported. Parallel to v1's `DerivationRegistry`; the two graphs share
|
|
5925
|
+
* a single cycle-detection pass at vault open (see `validate`).
|
|
5926
|
+
*
|
|
5927
|
+
* @internal
|
|
5928
|
+
*/
|
|
5929
|
+
declare class MaterializedViewRegistry {
|
|
5930
|
+
/** Keyed by `spec.name`. */
|
|
5931
|
+
private readonly _byName;
|
|
5932
|
+
/** Keyed by dependency source-collection → MVs that depend on it. */
|
|
5933
|
+
private readonly _bySource;
|
|
5934
|
+
/**
|
|
5935
|
+
* Register an MV. Invokes `spec.query()` once at registration time to
|
|
5936
|
+
* read the plan + join context; the resulting `Query<T>` is discarded
|
|
5937
|
+
* after dependency extraction. `vault.collection(...)` must therefore
|
|
5938
|
+
* be functional by the time this runs — typically wired from
|
|
5939
|
+
* `Vault._initMaterializedViews` after collection bootstrap.
|
|
5940
|
+
*
|
|
5941
|
+
* Throws `MaterializedViewSourceUnknownError` if the analyzer
|
|
5942
|
+
* surfaces a dependency the vault doesn't know about (when a
|
|
5943
|
+
* `knownCollections` checker is supplied).
|
|
5944
|
+
*/
|
|
5945
|
+
register(spec: MaterializedViewStrategy<any>, db: MVQueryContext, options?: {
|
|
5946
|
+
knownCollections?: (name: string) => boolean;
|
|
5947
|
+
}): Promise<void>;
|
|
5948
|
+
/** All MVs that depend on `source`, in registration order. */
|
|
5949
|
+
mvsForSource(source: string): ReadonlyArray<RegisteredMV>;
|
|
5950
|
+
/** Single MV by name, or `undefined`. */
|
|
5951
|
+
byName(name: string): RegisteredMV | undefined;
|
|
5952
|
+
/** Iterate over every registered MV. */
|
|
5953
|
+
all(): ReadonlyArray<RegisteredMV>;
|
|
5954
|
+
/**
|
|
5955
|
+
* Cycle detection over the combined derivation + MV graph. Edges:
|
|
5956
|
+
* - Derivation: derivation.source → output.collection (each output)
|
|
5957
|
+
* - MV: every dep in MV.dependencies → MV.outputCollection
|
|
5958
|
+
*
|
|
5959
|
+
* Throws `MaterializedViewCycleError` if the cycle's terminal node
|
|
5960
|
+
* is an MV output collection; otherwise (a pure-derivation cycle)
|
|
5961
|
+
* the caller's `DerivationRegistry.validate()` will surface
|
|
5962
|
+
* `DerivationCycleError` separately at vault open.
|
|
5963
|
+
*
|
|
5964
|
+
* Call AFTER all `register()` calls complete.
|
|
5965
|
+
*/
|
|
5966
|
+
validate(derivationRegistry?: DerivationRegistry | null): void;
|
|
5967
|
+
}
|
|
5968
|
+
|
|
5969
|
+
/**
|
|
5970
|
+
* Read-shadow overlay primitive (#154, MV v2 spec § Composition with
|
|
5971
|
+
* operator-editable lifecycle). Binds an MV's read-only base output
|
|
5972
|
+
* to a separate user-writable overlay collection; reads merge via a
|
|
5973
|
+
* single shadow predicate, writes route to the overlay.
|
|
5974
|
+
*
|
|
5975
|
+
* v2 ships the read-shadow variant only — arbitrary `mergePolicy`
|
|
5976
|
+
* callbacks are deferred to v3.
|
|
5977
|
+
*/
|
|
5978
|
+
interface OverlayedViewStrategy {
|
|
5979
|
+
/**
|
|
5980
|
+
* Virtual collection name. `vault.collection(name)` returns a
|
|
5981
|
+
* proxy that merges `base` and `overlay` per the shadow rule.
|
|
5982
|
+
* Writes to the proxy route to the `overlay` collection. The name
|
|
5983
|
+
* must be unique within the vault — collisions with MV outputs or
|
|
5984
|
+
* concrete source collections throw `OverlayNameCollisionError` at
|
|
5985
|
+
* vault open.
|
|
5986
|
+
*/
|
|
5987
|
+
name: string;
|
|
5988
|
+
/**
|
|
5989
|
+
* The collection providing the default rows. Typically an MV's
|
|
5990
|
+
* output collection. Must be a CONCRETE collection (a real source
|
|
5991
|
+
* or an MV output) — not itself another overlay's virtual name.
|
|
5992
|
+
* Multi-overlay stacking is a v3 non-goal; the constraint is
|
|
5993
|
+
* enforced at vault open via `OverlayBaseIsVirtualError`.
|
|
5994
|
+
*/
|
|
5995
|
+
base: string;
|
|
5996
|
+
/**
|
|
5997
|
+
* User-writable collection that carries overrides. Must be a real,
|
|
5998
|
+
* vault-known collection that is NOT an MV-output collection. The
|
|
5999
|
+
* overlay's `withGuard` / `withDerivation` registrations apply to
|
|
6000
|
+
* direct writes; the virtual layer's `put(record)` also flows
|
|
6001
|
+
* through the overlay's normal write pipeline.
|
|
6002
|
+
*/
|
|
6003
|
+
overlay: string;
|
|
6004
|
+
/**
|
|
6005
|
+
* Single-field shadow predicate. When `overlay[shadowField] ===
|
|
6006
|
+
* shadowValue` for a given id, virtual-collection reads of that id
|
|
6007
|
+
* return the overlay row; otherwise reads return the base row.
|
|
6008
|
+
*
|
|
6009
|
+
* Niwat's canonical example: `dataStatus === 'override'` flips a
|
|
6010
|
+
* row into operator-controlled mode.
|
|
6011
|
+
*
|
|
6012
|
+
* No callback merge, no priority lattice, no field-level merge —
|
|
6013
|
+
* v2 stays explicitly narrow.
|
|
6014
|
+
*/
|
|
6015
|
+
shadowField: string;
|
|
6016
|
+
shadowValue: unknown;
|
|
6017
|
+
}
|
|
6018
|
+
/** Returned by `withOverlayedView()` and consumed by `createNoydb`. */
|
|
6019
|
+
interface OverlayedViewStrategyHandle {
|
|
6020
|
+
readonly __noydb_strategy: 'overlayed-view';
|
|
6021
|
+
readonly spec: OverlayedViewStrategy;
|
|
6022
|
+
}
|
|
6023
|
+
|
|
6024
|
+
/**
|
|
6025
|
+
* Vault-internal registry of overlay strategies. Resolves the base
|
|
6026
|
+
* MV's `rowKey` lazily so virtual-collection writes can derive ids
|
|
6027
|
+
* from the row.
|
|
6028
|
+
*
|
|
6029
|
+
* @internal
|
|
4717
6030
|
*/
|
|
4718
|
-
declare
|
|
6031
|
+
declare class OverlayedViewRegistry {
|
|
6032
|
+
private readonly _byName;
|
|
6033
|
+
/**
|
|
6034
|
+
* Register an overlay. Validates name uniqueness, base concreteness,
|
|
6035
|
+
* and overlay availability AGAINST the MV registry — overlays
|
|
6036
|
+
* declared without the MV registry context skip cross-registry
|
|
6037
|
+
* checks but still validate self-consistency.
|
|
6038
|
+
*/
|
|
6039
|
+
register(spec: OverlayedViewStrategy, options: {
|
|
6040
|
+
isOverlayName?: (name: string) => boolean;
|
|
6041
|
+
isMVOutput?: (name: string) => boolean;
|
|
6042
|
+
isKnownCollection?: (name: string) => boolean;
|
|
6043
|
+
}): void;
|
|
6044
|
+
byName(name: string): OverlayedViewStrategy | undefined;
|
|
6045
|
+
/** All overlay virtual names. */
|
|
6046
|
+
names(): ReadonlySet<string>;
|
|
6047
|
+
isOverlay(name: string): boolean;
|
|
6048
|
+
/**
|
|
6049
|
+
* Resolve the `rowKey` function for an overlay's base MV. Returns
|
|
6050
|
+
* `undefined` if the base isn't an MV (raw source collection) or
|
|
6051
|
+
* if the MV registry isn't supplied. Used by the virtual-collection
|
|
6052
|
+
* proxy to derive ids from `put(record)` calls.
|
|
6053
|
+
*/
|
|
6054
|
+
resolveBaseRowKey(name: string, mvRegistry: MaterializedViewRegistry | null): ((row: Record<string, unknown>) => string) | undefined;
|
|
6055
|
+
}
|
|
6056
|
+
|
|
4719
6057
|
/**
|
|
4720
|
-
*
|
|
4721
|
-
*
|
|
4722
|
-
*
|
|
4723
|
-
*
|
|
6058
|
+
* Vault-internal singleton that holds the guard graph and dispatches
|
|
6059
|
+
* per-collection guard execution. Owned by `Vault`; not exported.
|
|
6060
|
+
*
|
|
6061
|
+
* @internal
|
|
4724
6062
|
*/
|
|
4725
|
-
|
|
6063
|
+
type AnyGuard = GuardStrategy<Record<string, unknown>>;
|
|
6064
|
+
type AnyChange = GuardChange<Record<string, unknown>>;
|
|
6065
|
+
declare class GuardRegistry {
|
|
6066
|
+
private readonly _byCollection;
|
|
6067
|
+
private _amendmentChanges;
|
|
6068
|
+
private _amendmentMeta;
|
|
6069
|
+
/** Register a guard. Multiple guards per collection are allowed. */
|
|
6070
|
+
register<T extends Record<string, unknown>>(spec: GuardStrategy<T>): void;
|
|
6071
|
+
/** All guards registered against `collection` in registration order. */
|
|
6072
|
+
guardsFor(collection: string): ReadonlyArray<AnyGuard>;
|
|
6073
|
+
/**
|
|
6074
|
+
* Run every guard's `check` for this collection. First throw wins —
|
|
6075
|
+
* remaining guards are not invoked. Guards without a `check` skip.
|
|
6076
|
+
*/
|
|
6077
|
+
runChecks<T>(collection: string, incoming: T, ctx: GuardContext<T>): Promise<void>;
|
|
6078
|
+
/**
|
|
6079
|
+
* Run every guard's `onDelete` for this collection. First throw wins —
|
|
6080
|
+
* remaining guards are not invoked. Guards without an `onDelete` skip.
|
|
6081
|
+
* Mirrors {@link runChecks} but for the delete path.
|
|
6082
|
+
*/
|
|
6083
|
+
runOnDelete<T>(collection: string, existing: T, ctx: GuardContext<T>): Promise<void>;
|
|
6084
|
+
/** True if any guard for `collection` declares an `amendment` block. */
|
|
6085
|
+
hasAmendment(collection: string): boolean;
|
|
6086
|
+
/** Open a new amendment change-collection window. */
|
|
6087
|
+
beginAmendment(): void;
|
|
6088
|
+
/** True iff we're currently inside an amendment transaction. */
|
|
6089
|
+
isAmendmentActive(): boolean;
|
|
6090
|
+
/**
|
|
6091
|
+
* Record a {before, after} pair for the active amendment. `vBefore`
|
|
6092
|
+
* and `vAfter` are stored in a parallel meta structure so the public
|
|
6093
|
+
* {@link GuardChange} shape handed to invariant callbacks stays
|
|
6094
|
+
* `{ before, after }` only — the audit ledger reads version metadata
|
|
6095
|
+
* via {@link consumeMeta}.
|
|
6096
|
+
*/
|
|
6097
|
+
collectChange<T>(collection: string, id: string, before: T | null, after: T, vBefore?: number, vAfter?: number): void;
|
|
6098
|
+
/**
|
|
6099
|
+
* Drain the change-set and close the amendment window. The caller
|
|
6100
|
+
* (transaction commit) feeds these to each affected guard's invariant.
|
|
6101
|
+
*/
|
|
6102
|
+
consumeChanges(): ReadonlyMap<string, ReadonlyArray<AnyChange>>;
|
|
6103
|
+
/**
|
|
6104
|
+
* Drain the parallel id/version metadata captured during the
|
|
6105
|
+
* amendment. Returned as a flat list with `collection` denormalised
|
|
6106
|
+
* so the audit ledger can emit one `{ collection, id, vBefore,
|
|
6107
|
+
* vAfter }` tuple per record. Must be called AFTER
|
|
6108
|
+
* {@link consumeChanges} (or independently) — calling it closes the
|
|
6109
|
+
* meta window in the same way.
|
|
6110
|
+
*/
|
|
6111
|
+
consumeMeta(): ReadonlyArray<{
|
|
6112
|
+
collection: string;
|
|
6113
|
+
id: string;
|
|
6114
|
+
vBefore: number;
|
|
6115
|
+
vAfter: number;
|
|
6116
|
+
}>;
|
|
6117
|
+
}
|
|
6118
|
+
|
|
4726
6119
|
/**
|
|
4727
|
-
*
|
|
4728
|
-
*
|
|
4729
|
-
*
|
|
6120
|
+
* Minimal read-only wrapper over a `Vault`. Used as `ctx.vault` inside
|
|
6121
|
+
* guard callbacks so they can fetch related records without acquiring
|
|
6122
|
+
* any write capability.
|
|
4730
6123
|
*/
|
|
4731
|
-
declare
|
|
6124
|
+
declare class ReadOnlyVaultFacade implements ReadOnlyVaultFacade$1 {
|
|
6125
|
+
private readonly _vault;
|
|
6126
|
+
constructor(vault: Vault);
|
|
6127
|
+
collection<T = unknown>(name: string): {
|
|
6128
|
+
get(id: string): Promise<T | null>;
|
|
6129
|
+
list(): Promise<T[]>;
|
|
6130
|
+
query(): Query<T>;
|
|
6131
|
+
};
|
|
6132
|
+
}
|
|
4732
6133
|
|
|
4733
6134
|
/**
|
|
4734
6135
|
* `vault.exportBlobs()` — bulk blob extraction primitive.
|
|
@@ -5104,6 +6505,50 @@ declare function magicLinkGrantRecordId(token: string, index: number): string;
|
|
|
5104
6505
|
*/
|
|
5105
6506
|
declare function isMagicLinkGrantExpired(payload: MagicLinkGrantPayload, now?: Date): boolean;
|
|
5106
6507
|
|
|
6508
|
+
/**
|
|
6509
|
+
* Type surface for the user-list visibility subsystem (#122).
|
|
6510
|
+
*
|
|
6511
|
+
* Two complementary flags:
|
|
6512
|
+
* - {@link DirectoryConfig} — vault-level "is the directory listing
|
|
6513
|
+
* enabled at all?" toggle. Owner-only mutation.
|
|
6514
|
+
* - {@link UserVisibility} — per-user "hide me from teammate listings"
|
|
6515
|
+
* opt-out. Self-mutation via `vault.user.setMyVisibility`.
|
|
6516
|
+
*
|
|
6517
|
+
* Both flags live in the existing `_meta` collection as plaintext-bypass
|
|
6518
|
+
* sidecars (`_iv: ''`). Neither is a security boundary — the keyring
|
|
6519
|
+
* file is still observable at `_keyring/*` and the envelope ciphertext
|
|
6520
|
+
* is still at `_users/*` to anyone with direct store read access. The
|
|
6521
|
+
* flags exist to keep admin-UI listings tidy, not to hide principals
|
|
6522
|
+
* from a determined attacker.
|
|
6523
|
+
*
|
|
6524
|
+
* @see docs/subsystems/user-envelope.md → Directory visibility
|
|
6525
|
+
*
|
|
6526
|
+
* @module
|
|
6527
|
+
*/
|
|
6528
|
+
/**
|
|
6529
|
+
* Vault-level directory toggle. Persisted at `_meta/directory`.
|
|
6530
|
+
*
|
|
6531
|
+
* - `enabled: true` (default when no document exists) — every authenticated
|
|
6532
|
+
* caller can enumerate users via `listUsersWithEnvelopes`.
|
|
6533
|
+
* - `enabled: false` — only `owner` and `admin` callers can enumerate;
|
|
6534
|
+
* anyone else gets {@link import('../errors.js').DirectoryDisabledError}.
|
|
6535
|
+
*/
|
|
6536
|
+
interface DirectoryConfig {
|
|
6537
|
+
readonly enabled: boolean;
|
|
6538
|
+
}
|
|
6539
|
+
/**
|
|
6540
|
+
* Per-user visibility flag. Persisted at `_meta/visibility/<keyringId>`.
|
|
6541
|
+
*
|
|
6542
|
+
* - `hidden: false` (default when no document exists) — the user shows up
|
|
6543
|
+
* in `listUsersWithEnvelopes` like any other principal.
|
|
6544
|
+
* - `hidden: true` — the user is filtered out of the default listing.
|
|
6545
|
+
* `owner`/`admin` callers can still see them by passing
|
|
6546
|
+
* `{ includeHidden: true }`.
|
|
6547
|
+
*/
|
|
6548
|
+
interface UserVisibility {
|
|
6549
|
+
readonly hidden: boolean;
|
|
6550
|
+
}
|
|
6551
|
+
|
|
5107
6552
|
/**
|
|
5108
6553
|
* Public `vault.user.*` API surface.
|
|
5109
6554
|
*
|
|
@@ -5238,6 +6683,30 @@ declare class UserApi {
|
|
|
5238
6683
|
* Gated by `edit-own-profile`. See `updateMe` for `presented` usage.
|
|
5239
6684
|
*/
|
|
5240
6685
|
setMe<T = unknown>(payload: T, presented?: UserEnvelopePresented): Promise<UserEnvelope<T>>;
|
|
6686
|
+
/**
|
|
6687
|
+
* Read the current user's visibility flag from
|
|
6688
|
+
* `_meta/visibility/<keyringId>`. Returns `{ hidden: false }` when no
|
|
6689
|
+
* document has been persisted (the default-visible case).
|
|
6690
|
+
*/
|
|
6691
|
+
getMyVisibility(): Promise<UserVisibility>;
|
|
6692
|
+
/**
|
|
6693
|
+
* Update the current user's visibility in the team directory.
|
|
6694
|
+
*
|
|
6695
|
+
* - `hidden: true` — opt out of the default `listUsersWithEnvelopes`
|
|
6696
|
+
* listing. `owner`/`admin` callers can still see the user by passing
|
|
6697
|
+
* `{ includeHidden: true }`.
|
|
6698
|
+
* - `hidden: false` — opt back in.
|
|
6699
|
+
*
|
|
6700
|
+
* Own-only by construction: the keyringId argument doesn't exist on
|
|
6701
|
+
* this method, so no caller can hide or unhide another principal.
|
|
6702
|
+
*
|
|
6703
|
+
* Honest caveat: this is a UX flag, not a privacy guarantee. The
|
|
6704
|
+
* envelope ciphertext at `_users/<keyringId>` and the keyring file at
|
|
6705
|
+
* `_keyring/<userId>` are both still observable to anyone with direct
|
|
6706
|
+
* store read access. See `docs/subsystems/user-envelope.md` →
|
|
6707
|
+
* "Directory visibility".
|
|
6708
|
+
*/
|
|
6709
|
+
setMyVisibility(visibility: UserVisibility): Promise<void>;
|
|
5241
6710
|
/**
|
|
5242
6711
|
* Read another principal's envelope by their keyringId. Returns null
|
|
5243
6712
|
* if the principal exists but has no envelope yet, or if the
|
|
@@ -5297,6 +6766,159 @@ declare class UserApi {
|
|
|
5297
6766
|
private fireChange;
|
|
5298
6767
|
}
|
|
5299
6768
|
|
|
6769
|
+
/**
|
|
6770
|
+
* Persisted-schema envelope shape.
|
|
6771
|
+
*
|
|
6772
|
+
* Stored encrypted under `_schemas/<collection>` with the same DEK as the
|
|
6773
|
+
* collection's records. Auditors who can unlock the collection's data can
|
|
6774
|
+
* also read its schema; nothing more.
|
|
6775
|
+
*
|
|
6776
|
+
* @see docs/superpowers/specs/2026-05-22-schema-dump-design.md
|
|
6777
|
+
*
|
|
6778
|
+
* @module
|
|
6779
|
+
*/
|
|
6780
|
+
/** Family of Standard Schema v1 validator the persisted snapshot was derived from. */
|
|
6781
|
+
type PersistedSchemaKind = 'Zod' | 'Valibot' | 'ArkType' | 'Effect' | 'Unknown';
|
|
6782
|
+
/**
|
|
6783
|
+
* Plaintext payload encrypted into the `_data` field of the
|
|
6784
|
+
* `_schemas/<collection>` envelope. The wrapper `EncryptedEnvelope` adds
|
|
6785
|
+
* `_noydb`, `_v`, `_ts`, `_iv`, `_data` per the standard noy-db record
|
|
6786
|
+
* format.
|
|
6787
|
+
*/
|
|
6788
|
+
interface PersistedSchemaEnvelope {
|
|
6789
|
+
readonly _noydb_schema: 1;
|
|
6790
|
+
/** Detected validator family. */
|
|
6791
|
+
readonly kind: PersistedSchemaKind;
|
|
6792
|
+
/**
|
|
6793
|
+
* JSON Schema (Draft 2020-12) derived from the validator. Null when
|
|
6794
|
+
* derivation isn't yet supported for `kind`; in that case `reason` is
|
|
6795
|
+
* populated.
|
|
6796
|
+
*/
|
|
6797
|
+
readonly jsonSchema: object | null;
|
|
6798
|
+
/** SHA-256 (hex) of the canonicalised JSON Schema, or null when unavailable. */
|
|
6799
|
+
readonly hash: string | null;
|
|
6800
|
+
/** Human-readable reason when `jsonSchema` is null. */
|
|
6801
|
+
readonly reason?: string;
|
|
6802
|
+
/** ISO-8601 timestamp of the most recent derivation write. */
|
|
6803
|
+
readonly derivedAt: string;
|
|
6804
|
+
}
|
|
6805
|
+
|
|
6806
|
+
/**
|
|
6807
|
+
* Types for {@link VaultSchemaSnapshot} — the structured object returned
|
|
6808
|
+
* by `vault.dumpSchema()`. Consumed by the upcoming `noydb describe`
|
|
6809
|
+
* CLI to emit human-readable YAML/JSON audit output.
|
|
6810
|
+
*
|
|
6811
|
+
* @see docs/superpowers/specs/2026-05-22-schema-dump-design.md
|
|
6812
|
+
*
|
|
6813
|
+
* @module
|
|
6814
|
+
*/
|
|
6815
|
+
|
|
6816
|
+
/** Where the field-level info in the snapshot came from. */
|
|
6817
|
+
type FieldSource = 'persisted' | 'live-validator' | 'sampled' | 'unknown';
|
|
6818
|
+
interface FieldDescriptor {
|
|
6819
|
+
/** Inferred type tag: 'string' | 'number' | 'boolean' | 'enum' | 'object' | 'array' | 'null' | 'opaque'. */
|
|
6820
|
+
readonly type: string;
|
|
6821
|
+
/** Where this field info was sourced from. */
|
|
6822
|
+
readonly source: FieldSource;
|
|
6823
|
+
/** Optional constraints — minLength, maxLength, enum values, gt, etc. */
|
|
6824
|
+
readonly constraints?: Record<string, unknown>;
|
|
6825
|
+
/** True when the schema marks this field optional. */
|
|
6826
|
+
readonly optional?: boolean;
|
|
6827
|
+
/** Foreign-key target as `<collection>.<field>` when declared. */
|
|
6828
|
+
readonly references?: string;
|
|
6829
|
+
}
|
|
6830
|
+
interface CollectionStats {
|
|
6831
|
+
readonly records: number;
|
|
6832
|
+
readonly bytes: number;
|
|
6833
|
+
readonly bytesAvg: number;
|
|
6834
|
+
readonly bytesMin: number;
|
|
6835
|
+
readonly bytesMax: number;
|
|
6836
|
+
/** ISO-8601 from min(_ts) across envelopes. Empty string when no records. */
|
|
6837
|
+
readonly oldest: string;
|
|
6838
|
+
/** ISO-8601 from max(_ts) across envelopes. Empty string when no records. */
|
|
6839
|
+
readonly newest: string;
|
|
6840
|
+
}
|
|
6841
|
+
interface CollectionDescriptor {
|
|
6842
|
+
readonly fields: Record<string, FieldDescriptor>;
|
|
6843
|
+
readonly indexes: ReadonlyArray<{
|
|
6844
|
+
readonly fields: ReadonlyArray<string>;
|
|
6845
|
+
readonly unique?: boolean;
|
|
6846
|
+
}>;
|
|
6847
|
+
readonly refs: Record<string, {
|
|
6848
|
+
readonly target: string;
|
|
6849
|
+
readonly mode: 'strict' | 'warn' | 'cascade';
|
|
6850
|
+
}>;
|
|
6851
|
+
readonly validator?: {
|
|
6852
|
+
readonly kind: PersistedSchemaKind;
|
|
6853
|
+
readonly source: 'persisted' | 'live-validator';
|
|
6854
|
+
};
|
|
6855
|
+
readonly stats?: CollectionStats;
|
|
6856
|
+
}
|
|
6857
|
+
interface MaterializedViewDescriptor {
|
|
6858
|
+
readonly sources: ReadonlyArray<string>;
|
|
6859
|
+
readonly groupBy?: ReadonlyArray<string>;
|
|
6860
|
+
readonly aggregate?: Record<string, string>;
|
|
6861
|
+
readonly refresh: string;
|
|
6862
|
+
readonly stats?: CollectionStats;
|
|
6863
|
+
}
|
|
6864
|
+
interface OverlayViewDescriptor {
|
|
6865
|
+
readonly base: string;
|
|
6866
|
+
readonly overlay: string;
|
|
6867
|
+
}
|
|
6868
|
+
interface DerivationDescriptor {
|
|
6869
|
+
readonly source: string;
|
|
6870
|
+
readonly outputs: ReadonlyArray<string>;
|
|
6871
|
+
}
|
|
6872
|
+
interface InternalCollectionStats {
|
|
6873
|
+
readonly records: number;
|
|
6874
|
+
readonly bytes: number;
|
|
6875
|
+
}
|
|
6876
|
+
interface VaultSchemaSnapshot {
|
|
6877
|
+
readonly _noydb_snapshot: 1;
|
|
6878
|
+
readonly vault: string;
|
|
6879
|
+
readonly emittedAt: string;
|
|
6880
|
+
readonly subsystems: Record<string, boolean>;
|
|
6881
|
+
readonly aclRoles?: ReadonlyArray<string>;
|
|
6882
|
+
readonly collections: Record<string, CollectionDescriptor>;
|
|
6883
|
+
readonly materializedViews: Record<string, MaterializedViewDescriptor>;
|
|
6884
|
+
readonly overlayViews: Record<string, OverlayViewDescriptor>;
|
|
6885
|
+
readonly derivations: Record<string, DerivationDescriptor>;
|
|
6886
|
+
/** Only present when `dumpSchema({ withStats: true })` was called. */
|
|
6887
|
+
readonly internal?: Record<string, InternalCollectionStats>;
|
|
6888
|
+
}
|
|
6889
|
+
interface DumpSchemaOptions {
|
|
6890
|
+
/** When true, walk every collection's envelopes to compute counters. Default `false`. */
|
|
6891
|
+
readonly withStats?: boolean;
|
|
6892
|
+
/** Sample N records per collection lacking a persisted/live schema. Default 50. `0` disables sampling. */
|
|
6893
|
+
readonly sampleSize?: number;
|
|
6894
|
+
}
|
|
6895
|
+
|
|
6896
|
+
/**
|
|
6897
|
+
* Orchestrate the structural walk of a Vault, producing a
|
|
6898
|
+
* {@link VaultSchemaSnapshot}. Called from `Vault.dumpSchema()`.
|
|
6899
|
+
*
|
|
6900
|
+
* @module
|
|
6901
|
+
*/
|
|
6902
|
+
|
|
6903
|
+
/**
|
|
6904
|
+
* The minimal slice of Vault internal state the walker needs.
|
|
6905
|
+
* Exposed via `vault._introspectState()` to keep the public Vault
|
|
6906
|
+
* surface narrow.
|
|
6907
|
+
*
|
|
6908
|
+
* @internal
|
|
6909
|
+
*/
|
|
6910
|
+
interface VaultIntrospectState {
|
|
6911
|
+
readonly name: string;
|
|
6912
|
+
readonly adapter: NoydbStore;
|
|
6913
|
+
readonly collectionCache: Map<string, Collection<unknown>>;
|
|
6914
|
+
readonly refRegistry: RefRegistry;
|
|
6915
|
+
readonly getDEK: (collectionName: string) => Promise<CryptoKey>;
|
|
6916
|
+
readonly subsystems: Record<string, boolean>;
|
|
6917
|
+
readonly mvRegistry: unknown;
|
|
6918
|
+
readonly overlayRegistry: unknown;
|
|
6919
|
+
readonly derivationRegistry: unknown;
|
|
6920
|
+
}
|
|
6921
|
+
|
|
5300
6922
|
/** A vault (tenant namespace) containing collections. */
|
|
5301
6923
|
declare class Vault {
|
|
5302
6924
|
private readonly adapter;
|
|
@@ -5339,6 +6961,40 @@ declare class Vault {
|
|
|
5339
6961
|
private readonly historyStrategy;
|
|
5340
6962
|
private readonly i18nStrategy;
|
|
5341
6963
|
private readonly syncStrategy;
|
|
6964
|
+
/**
|
|
6965
|
+
* Per-vault guard registry. `null` until `_initGuards()` runs; stays
|
|
6966
|
+
* `null` for vaults that never register any guard strategy. The
|
|
6967
|
+
* runtime class is dynamic-imported on demand so consumers that
|
|
6968
|
+
* never use guards don't pull `GuardRegistry`/`GuardExecutor` into
|
|
6969
|
+
* their bundle (#130).
|
|
6970
|
+
*/
|
|
6971
|
+
private guardRegistry;
|
|
6972
|
+
/**
|
|
6973
|
+
* Per-vault derivation registry. Same lazy-load contract as
|
|
6974
|
+
* `guardRegistry` — `null` until `_initDerivations()` runs with at
|
|
6975
|
+
* least one strategy handle. See #130 for the bundle motivation.
|
|
6976
|
+
*/
|
|
6977
|
+
private derivationRegistry;
|
|
6978
|
+
/**
|
|
6979
|
+
* Per-vault materialized-view registry (#143/#150). Same lazy-load
|
|
6980
|
+
* contract as `derivationRegistry` — `null` until
|
|
6981
|
+
* `_initMaterializedViews()` runs with at least one MV handle.
|
|
6982
|
+
*/
|
|
6983
|
+
private materializedViewRegistry;
|
|
6984
|
+
/**
|
|
6985
|
+
* Per-vault overlay registry (#154). Same lazy-load contract as
|
|
6986
|
+
* `materializedViewRegistry` — `null` until `_initOverlayedViews()`
|
|
6987
|
+
* runs with at least one handle.
|
|
6988
|
+
*/
|
|
6989
|
+
private overlayedViewRegistry;
|
|
6990
|
+
/**
|
|
6991
|
+
* Cached read-only facade handed to guard callbacks via `ctx.vault`,
|
|
6992
|
+
* and to derivation callbacks via `derive(source, ctx)`. Allocated
|
|
6993
|
+
* eagerly inside `_initGuards()` and/or `_initDerivations()` so read
|
|
6994
|
+
* accessors stay synchronous (callers in `tx/transaction.ts` rely on
|
|
6995
|
+
* that). Stays `null` for vaults with neither subsystem configured.
|
|
6996
|
+
*/
|
|
6997
|
+
private readOnlyFacade;
|
|
5342
6998
|
private getDEK;
|
|
5343
6999
|
/**
|
|
5344
7000
|
* Per-principal user envelope API.
|
|
@@ -5386,6 +7042,16 @@ declare class Vault {
|
|
|
5386
7042
|
* docstring.
|
|
5387
7043
|
*/
|
|
5388
7044
|
private ledgerStore;
|
|
7045
|
+
/**
|
|
7046
|
+
* Background writes for persisted-schema envelopes (#schema-dump v0
|
|
7047
|
+
* slice 1). One promise per `collection({ persistJsonSchema: true })`
|
|
7048
|
+
* registration that actually fired a derive call. Fire-and-forget
|
|
7049
|
+
* from the collection factory; tests await
|
|
7050
|
+
* {@link _drainPendingSchemaWrites} before asserting on storage.
|
|
7051
|
+
* Production code does not need to drain — the writes are
|
|
7052
|
+
* idempotent fingerprints, not correctness invariants.
|
|
7053
|
+
*/
|
|
7054
|
+
private _pendingSchemaWrites;
|
|
5389
7055
|
/**
|
|
5390
7056
|
* Per-vault foreign-key reference registry. Collections
|
|
5391
7057
|
* register their `refs` option here on construction; the
|
|
@@ -5497,6 +7163,7 @@ declare class Vault {
|
|
|
5497
7163
|
historyStrategy?: HistoryStrategy | undefined;
|
|
5498
7164
|
i18nStrategy?: I18nStrategy | undefined;
|
|
5499
7165
|
syncStrategy?: SyncStrategy | undefined;
|
|
7166
|
+
guardStrategies?: ReadonlyArray<GuardStrategyHandleAny> | undefined;
|
|
5500
7167
|
});
|
|
5501
7168
|
/**
|
|
5502
7169
|
* Construct (or reconstruct) the lazy DEK resolver. Captures the
|
|
@@ -5569,7 +7236,26 @@ declare class Vault {
|
|
|
5569
7236
|
tiers?: readonly number[];
|
|
5570
7237
|
/** — how lower-tier reads see above-tier records. */
|
|
5571
7238
|
tierMode?: TierMode;
|
|
7239
|
+
/**
|
|
7240
|
+
* Opt-in persisted JSON Schema. When `true` AND a Zod `schema` is
|
|
7241
|
+
* provided, hub derives a JSON Schema via `zod-to-json-schema`
|
|
7242
|
+
* (optional peer-dep) and writes an encrypted snapshot to
|
|
7243
|
+
* `_schemas/<collectionName>`. Re-runs on every open; hash-skip
|
|
7244
|
+
* avoids write churn when the schema is unchanged.
|
|
7245
|
+
*
|
|
7246
|
+
* Default: `false`. Non-Zod Standard Schema validators receive a
|
|
7247
|
+
* stub envelope flagging the kind without a JSON Schema body.
|
|
7248
|
+
*
|
|
7249
|
+
* @see docs/superpowers/specs/2026-05-22-schema-dump-design.md
|
|
7250
|
+
*/
|
|
7251
|
+
persistJsonSchema?: boolean;
|
|
5572
7252
|
}): Collection<T>;
|
|
7253
|
+
/**
|
|
7254
|
+
* Await all background persisted-schema writes triggered by
|
|
7255
|
+
* `collection({ persistJsonSchema: true })` calls on this vault.
|
|
7256
|
+
* Used in tests; production code does not need to call this.
|
|
7257
|
+
*/
|
|
7258
|
+
_drainPendingSchemaWrites(): Promise<void>;
|
|
5573
7259
|
/**
|
|
5574
7260
|
* Validate i18nText fields on a `put()`. Called by Collection just
|
|
5575
7261
|
* before the adapter write, after schema validation. Throws
|
|
@@ -5859,6 +7545,124 @@ declare class Vault {
|
|
|
5859
7545
|
* throws on null; this one stays silent so the off-path no-ops.
|
|
5860
7546
|
*/
|
|
5861
7547
|
private getLedgerOrNull;
|
|
7548
|
+
/**
|
|
7549
|
+
* @internal — called by `Noydb.openVault` after construction.
|
|
7550
|
+
* Dynamic-imports `GuardRegistry` + `ReadOnlyVaultFacade` and seeds
|
|
7551
|
+
* the registry with the supplied strategy handles. No-op when the
|
|
7552
|
+
* handles array is empty — keeps the guard subsystem out of the
|
|
7553
|
+
* floor bundle for consumers that don't use guards (#130).
|
|
7554
|
+
*
|
|
7555
|
+
* The read-only facade is eagerly instantiated here so the sync
|
|
7556
|
+
* accessor `_getReadOnlyFacade()` (called from the tx amendment
|
|
7557
|
+
* runner) stays synchronous.
|
|
7558
|
+
*/
|
|
7559
|
+
_initGuards(handles: ReadonlyArray<GuardStrategyHandleAny>): Promise<void>;
|
|
7560
|
+
/**
|
|
7561
|
+
* @internal — Collection.put calls into this. Returns `null` for
|
|
7562
|
+
* vaults that never registered any guard strategy. Callers MUST
|
|
7563
|
+
* gate on null (the existing `if (this.guardSource)` branches in
|
|
7564
|
+
* `Collection` already do this transitively).
|
|
7565
|
+
*/
|
|
7566
|
+
_getGuardRegistry(): GuardRegistry | null;
|
|
7567
|
+
/**
|
|
7568
|
+
* @internal — called by `Noydb.openVault` after construction.
|
|
7569
|
+
* Dynamic-imports `DerivationRegistry` and registers the supplied
|
|
7570
|
+
* derivation strategies (async because `strategyHash` computation
|
|
7571
|
+
* goes through `crypto.subtle.digest`). No-op when the handles
|
|
7572
|
+
* array is empty — keeps the derivation subsystem out of the floor
|
|
7573
|
+
* bundle for consumers that don't use derivations (#130). Throws
|
|
7574
|
+
* `DerivationCycleError` if a cycle is detected after registration.
|
|
7575
|
+
*/
|
|
7576
|
+
_initDerivations(handles: ReadonlyArray<DerivationStrategyHandle>): Promise<void>;
|
|
7577
|
+
/**
|
|
7578
|
+
* @internal — consumed by `Collection.put` at write-time. Returns
|
|
7579
|
+
* `null` for vaults that never registered any derivation strategy.
|
|
7580
|
+
*/
|
|
7581
|
+
_getDerivationRegistry(): DerivationRegistry | null;
|
|
7582
|
+
/**
|
|
7583
|
+
* @internal — called by `Noydb.openVault` after collections are
|
|
7584
|
+
* wired. Dynamic-imports `MaterializedViewRegistry`, registers each
|
|
7585
|
+
* MV spec (which invokes its `query()` once for dependency
|
|
7586
|
+
* analysis), then runs the unified cycle detection across the MV +
|
|
7587
|
+
* derivation graphs. No-op when the handles array is empty — keeps
|
|
7588
|
+
* the MV subsystem out of the floor bundle (mirrors v1 #130).
|
|
7589
|
+
* Throws `MaterializedViewCycleError` if a cycle is detected.
|
|
7590
|
+
*/
|
|
7591
|
+
_initMaterializedViews(handles: ReadonlyArray<MaterializedViewStrategyHandle>): Promise<void>;
|
|
7592
|
+
/**
|
|
7593
|
+
* @internal — consumed by `Collection.put` at write-time. Returns
|
|
7594
|
+
* `null` for vaults that never registered any MV strategy.
|
|
7595
|
+
*/
|
|
7596
|
+
_getMaterializedViewRegistry(): MaterializedViewRegistry | null;
|
|
7597
|
+
/**
|
|
7598
|
+
* @internal — called by `Noydb.openVault` after MVs are wired.
|
|
7599
|
+
* Dynamic-imports `OverlayedViewRegistry`, registers each spec,
|
|
7600
|
+
* validates against the MV registry for name/base/overlay collisions.
|
|
7601
|
+
* Throws on validation failure.
|
|
7602
|
+
*/
|
|
7603
|
+
_initOverlayedViews(handles: ReadonlyArray<OverlayedViewStrategyHandle>): Promise<void>;
|
|
7604
|
+
/**
|
|
7605
|
+
* @internal — consumed by `Vault.collection()`. Returns `null` for
|
|
7606
|
+
* vaults with no overlays registered.
|
|
7607
|
+
*/
|
|
7608
|
+
_getOverlayedViewRegistry(): OverlayedViewRegistry | null;
|
|
7609
|
+
/**
|
|
7610
|
+
* Manual re-materialize for a single registered MV (#151). Useful
|
|
7611
|
+
* for `refresh: 'manual'` MVs (whose consumer drives refreshes
|
|
7612
|
+
* externally), for stale-bit recovery on vault re-open, and as the
|
|
7613
|
+
* explicit bulk-recompute escape hatch after a strategy change.
|
|
7614
|
+
*
|
|
7615
|
+
* Returns `{ written, deleted, failed }`. `deleted` is always 0 in
|
|
7616
|
+
* foundation + this sub-issue — tombstoning lands in #152.
|
|
7617
|
+
*
|
|
7618
|
+
* Throws if `name` is not a registered MV.
|
|
7619
|
+
*/
|
|
7620
|
+
refreshView(name: string): Promise<{
|
|
7621
|
+
written: number;
|
|
7622
|
+
deleted: number;
|
|
7623
|
+
failed: number;
|
|
7624
|
+
}>;
|
|
7625
|
+
/**
|
|
7626
|
+
* Re-derive every record in the named source collection. Useful
|
|
7627
|
+
* after a strategy change to bring previously-derived records
|
|
7628
|
+
* up-to-date.
|
|
7629
|
+
*
|
|
7630
|
+
* Sequential in v1; parallelisation deferred to v2.
|
|
7631
|
+
*/
|
|
7632
|
+
deriveAll(sourceCollection: string): Promise<{
|
|
7633
|
+
derived: number;
|
|
7634
|
+
failed: number;
|
|
7635
|
+
}>;
|
|
7636
|
+
/**
|
|
7637
|
+
* @internal — exposed for `runTransaction({ amendment: true })` so
|
|
7638
|
+
* the amendment invariant runner can pass the SAME read-only vault
|
|
7639
|
+
* facade that the per-record `Collection.put` guard hook uses
|
|
7640
|
+
* (`guardSource.readOnlyVault()` above). Eagerly instantiated by
|
|
7641
|
+
* `_initGuards()` so this accessor stays synchronous; returns
|
|
7642
|
+
* `null` for vaults that never registered any guard (amendments
|
|
7643
|
+
* require at least one guard, so the caller should never see null).
|
|
7644
|
+
*/
|
|
7645
|
+
_getReadOnlyFacade(): ReadOnlyVaultFacade | null;
|
|
7646
|
+
/**
|
|
7647
|
+
* Internal lazy-allocator for the read-only facade. Used by the
|
|
7648
|
+
* per-collection `guardSource.readOnlyVault` callback when guards
|
|
7649
|
+
* ARE configured but `_initGuards()` raced with the first guard
|
|
7650
|
+
* invocation (theoretically impossible — `Noydb.openVault` awaits
|
|
7651
|
+
* `_initGuards` before returning — but we keep the defensive lazy
|
|
7652
|
+
* path so the closure's contract stays "always returns a facade").
|
|
7653
|
+
*/
|
|
7654
|
+
private _ensureReadOnlyFacade;
|
|
7655
|
+
/**
|
|
7656
|
+
* @internal — exposed for `runTransaction({ amendment: true })`
|
|
7657
|
+
* to append the structured `op: 'amendment'` audit entry without
|
|
7658
|
+
* dragging this private accessor onto the public surface or
|
|
7659
|
+
* forcing the tx executor to depend on the history-strategy
|
|
7660
|
+
* shape directly. Returns `null` when no history strategy is
|
|
7661
|
+
* configured, in which case the amendment commits silently
|
|
7662
|
+
* (the records still write through; only the multi-record
|
|
7663
|
+
* audit summary is skipped).
|
|
7664
|
+
*/
|
|
7665
|
+
_getLedgerOrNull(): LedgerStore | null;
|
|
5862
7666
|
/**
|
|
5863
7667
|
* Return a read-only view of this vault as it existed at
|
|
5864
7668
|
* `timestamp`. Time-machine queries are reconstructed from the
|
|
@@ -6062,6 +7866,34 @@ declare class Vault {
|
|
|
6062
7866
|
private _decryptPeriodRecord;
|
|
6063
7867
|
/** List all collection names in this vault. */
|
|
6064
7868
|
collections(): Promise<string[]>;
|
|
7869
|
+
/**
|
|
7870
|
+
* Emit a structured introspection snapshot of this vault — vault name,
|
|
7871
|
+
* subsystem opt-in matrix, collections + their fields, materialized
|
|
7872
|
+
* views, overlay views, derivations. With `withStats: true`, walks
|
|
7873
|
+
* every collection's envelopes to compute record counts, byte totals,
|
|
7874
|
+
* and oldest/newest timestamps.
|
|
7875
|
+
*
|
|
7876
|
+
* Consumed by the `noydb describe` CLI to produce human-readable
|
|
7877
|
+
* audit YAML/JSON from a `.noydb` bundle.
|
|
7878
|
+
*
|
|
7879
|
+
* Field provenance:
|
|
7880
|
+
* - `persisted`: read from `_schemas/<col>` envelope (Route B opt-in)
|
|
7881
|
+
* - `live-validator`: derived in-process from a Zod schema attached
|
|
7882
|
+
* to the live `Collection`
|
|
7883
|
+
* - `sampled`: inferred from decrypted records (deferred to a follow-up)
|
|
7884
|
+
* - `unknown`: no schema info available
|
|
7885
|
+
*
|
|
7886
|
+
* @see docs/superpowers/specs/2026-05-22-schema-dump-design.md
|
|
7887
|
+
*/
|
|
7888
|
+
dumpSchema(opts?: DumpSchemaOptions): Promise<VaultSchemaSnapshot>;
|
|
7889
|
+
/**
|
|
7890
|
+
* Internal accessor for {@link dumpVaultSchema}. Exposes the structural
|
|
7891
|
+
* state the walker needs (collection cache, registries, ref registry,
|
|
7892
|
+
* adapter) without widening the public Vault surface.
|
|
7893
|
+
*
|
|
7894
|
+
* @internal
|
|
7895
|
+
*/
|
|
7896
|
+
_introspectState(): VaultIntrospectState;
|
|
6065
7897
|
/**
|
|
6066
7898
|
* Return the stable opaque bundle handle for this vault,
|
|
6067
7899
|
* generating and persisting a fresh ULID on first call.
|
|
@@ -6729,6 +8561,34 @@ declare class Collection<T> {
|
|
|
6729
8561
|
* adapter on first use.
|
|
6730
8562
|
*/
|
|
6731
8563
|
private readonly periodGuard;
|
|
8564
|
+
/**
|
|
8565
|
+
* Optional back-reference to the owning vault's guard registry + a
|
|
8566
|
+
* read-only vault facade. When present, `Collection.put` and
|
|
8567
|
+
* `Collection.delete` consult the registry for guards declared
|
|
8568
|
+
* against this collection and run their `check` + `frozenFields`
|
|
8569
|
+
* before the adapter write. Absent in unit tests that construct
|
|
8570
|
+
* a Collection directly; production code always sets it via
|
|
8571
|
+
* `Vault.collection()`.
|
|
8572
|
+
*
|
|
8573
|
+
* Typed structurally rather than as `Vault` to avoid a circular
|
|
8574
|
+
* import (mirrors the `refEnforcer` / `joinResolver` pattern).
|
|
8575
|
+
*/
|
|
8576
|
+
private readonly guardSource;
|
|
8577
|
+
/**
|
|
8578
|
+
* Vault-internal hook for derivation dispatch. When set,
|
|
8579
|
+
* `Collection.put` consults the registry after the source-write
|
|
8580
|
+
* commits and writes derived outputs through `getCollection(name).put`.
|
|
8581
|
+
* Same structural-interface pattern as `guardSource` to avoid a
|
|
8582
|
+
* circular Vault import.
|
|
8583
|
+
*/
|
|
8584
|
+
private readonly derivationSource;
|
|
8585
|
+
/**
|
|
8586
|
+
* Vault-internal hook for materialized-view dispatch (#143/#150).
|
|
8587
|
+
* Parallel to `derivationSource` — when set, `Collection.put` fires
|
|
8588
|
+
* `MaterializedViewRegistry.onSourceWrite` after the source-write
|
|
8589
|
+
* commits + after `dispatchDerivations` has run.
|
|
8590
|
+
*/
|
|
8591
|
+
private readonly materializedViewSource;
|
|
6732
8592
|
/**
|
|
6733
8593
|
* Optional back-reference to the owning compartment's ref
|
|
6734
8594
|
* enforcer. When present, `Collection.put` calls
|
|
@@ -6982,6 +8842,63 @@ declare class Collection<T> {
|
|
|
6982
8842
|
ts: string | null;
|
|
6983
8843
|
record: Record<string, unknown> | null;
|
|
6984
8844
|
} | null, incoming: Record<string, unknown> | null) => Promise<void>;
|
|
8845
|
+
/**
|
|
8846
|
+
* Optional back-reference to the owning vault's guard registry +
|
|
8847
|
+
* read-only facade. When present, put/delete consult registered
|
|
8848
|
+
* guards for this collection. Same structural-interface pattern
|
|
8849
|
+
* as `refEnforcer` to avoid a circular Vault import.
|
|
8850
|
+
*/
|
|
8851
|
+
guardSource?: {
|
|
8852
|
+
registry(): GuardRegistry;
|
|
8853
|
+
readOnlyVault(): ReadOnlyVaultFacade$1;
|
|
8854
|
+
} | undefined;
|
|
8855
|
+
/**
|
|
8856
|
+
* Optional back-reference to the owning vault's derivation
|
|
8857
|
+
* registry + collection accessor. When present, successful
|
|
8858
|
+
* `put()` dispatches registered derivation strategies for the
|
|
8859
|
+
* source collection. Same structural-interface pattern as
|
|
8860
|
+
* `guardSource` to avoid a circular Vault import.
|
|
8861
|
+
*/
|
|
8862
|
+
derivationSource?: {
|
|
8863
|
+
registry(): DerivationRegistry;
|
|
8864
|
+
getCollection(name: string): Collection<Record<string, unknown>>;
|
|
8865
|
+
/**
|
|
8866
|
+
* Read-only vault facade handed to `derive(source, ctx)` so a
|
|
8867
|
+
* derivation can fetch sibling records (#147). Same shape and
|
|
8868
|
+
* instance the guards subsystem uses for `check(incoming, ctx)`.
|
|
8869
|
+
*/
|
|
8870
|
+
getReadOnlyFacade(): ReadOnlyVaultFacade$1;
|
|
8871
|
+
/**
|
|
8872
|
+
* Read access to the owning Noydb's currently-active multi-record
|
|
8873
|
+
* transaction context, or `null` when no transaction is running.
|
|
8874
|
+
* `dispatchDerivations` consults this so a recursive derived-output
|
|
8875
|
+
* write can register its pre-write envelope onto `ctx._executed`
|
|
8876
|
+
* and roll back alongside the source op on mid-batch failure (#133).
|
|
8877
|
+
*/
|
|
8878
|
+
getActiveTxContext(): TxContext | null;
|
|
8879
|
+
/**
|
|
8880
|
+
* Construct a transient TxContext bound to the owning Noydb. Used
|
|
8881
|
+
* by `Collection.putManyAtomic` to publish an active context for
|
|
8882
|
+
* its Phase 2 loop (#133).
|
|
8883
|
+
*/
|
|
8884
|
+
createTxContext(): TxContext;
|
|
8885
|
+
/** Publish a TxContext for the duration of a bulk-atomic loop. */
|
|
8886
|
+
setActiveTxContext(ctx: TxContext): void;
|
|
8887
|
+
/** Drop a previously-published TxContext. */
|
|
8888
|
+
clearActiveTxContext(ctx: TxContext): void;
|
|
8889
|
+
} | undefined;
|
|
8890
|
+
/**
|
|
8891
|
+
* Vault-internal hook for materialized-view dispatch (#143/#150).
|
|
8892
|
+
* Parallel to `derivationSource`. When set, `Collection.put` fires
|
|
8893
|
+
* registered MV `onSourceWrite` after the standard derivation
|
|
8894
|
+
* dispatch.
|
|
8895
|
+
*/
|
|
8896
|
+
materializedViewSource?: {
|
|
8897
|
+
registry(): MaterializedViewRegistry;
|
|
8898
|
+
getCollection(name: string): Collection<any>;
|
|
8899
|
+
getActiveTxContext(): TxContext | null;
|
|
8900
|
+
getQueryContext(): MVQueryContext;
|
|
8901
|
+
} | undefined;
|
|
6985
8902
|
});
|
|
6986
8903
|
/**
|
|
6987
8904
|
* Return the Standard Schema validator attached to this collection,
|
|
@@ -7032,10 +8949,109 @@ declare class Collection<T> {
|
|
|
7032
8949
|
staleMs?: number;
|
|
7033
8950
|
pollIntervalMs?: number;
|
|
7034
8951
|
}): PresenceHandle<P>;
|
|
7035
|
-
/**
|
|
7036
|
-
|
|
8952
|
+
/**
|
|
8953
|
+
* Create or update a record.
|
|
8954
|
+
*
|
|
8955
|
+
* @param id Record identifier.
|
|
8956
|
+
* @param record The record body (validated by the collection's schema
|
|
8957
|
+
* if one was attached at `vault.collection(...)` time).
|
|
8958
|
+
* @param options Optional metadata for audit + import workflows.
|
|
8959
|
+
* `reason` is stamped onto the resulting ledger entry
|
|
8960
|
+
* (see #1) so audit consumers can filter via
|
|
8961
|
+
* `entries.filter(e => e.reason?.startsWith('import:'))`.
|
|
8962
|
+
*/
|
|
8963
|
+
put(id: string, record: T, options?: {
|
|
8964
|
+
readonly reason?: string;
|
|
8965
|
+
}): Promise<void>;
|
|
8966
|
+
/**
|
|
8967
|
+
* Fire registered MV strategies whose dependency set includes this
|
|
8968
|
+
* collection. Eager-mode MVs re-materialize inline via
|
|
8969
|
+
* `MaterializedViewExecutor.refresh`; lazy / manual modes are
|
|
8970
|
+
* no-ops in the foundation (subtask #150) — wired in #151.
|
|
8971
|
+
*
|
|
8972
|
+
* Skips entirely when the record being written is itself an
|
|
8973
|
+
* MV-emitted row (carries `_materializedFrom`) — defensive guard
|
|
8974
|
+
* against missed cycle detection.
|
|
8975
|
+
*
|
|
8976
|
+
* @internal
|
|
8977
|
+
*/
|
|
8978
|
+
private dispatchMaterializedViews;
|
|
8979
|
+
/**
|
|
8980
|
+
* Fire registered derivation strategies for this source collection.
|
|
8981
|
+
* Eager mode runs `derive` inline and writes each output via the
|
|
8982
|
+
* sibling `Collection.put`; lazy mode marks dependent outputs stale
|
|
8983
|
+
* (D11 stub today). Errors in non-strict mode are logged and
|
|
8984
|
+
* skipped; strict mode propagates the first failing output's error.
|
|
8985
|
+
*
|
|
8986
|
+
* Skips entirely when the record being written is itself a derived
|
|
8987
|
+
* output (carries `_derivedFrom`) — defensive guard against missed
|
|
8988
|
+
* cycle detection.
|
|
8989
|
+
*/
|
|
8990
|
+
private dispatchDerivations;
|
|
7037
8991
|
/** Delete a record by ID. */
|
|
7038
8992
|
delete(id: string): Promise<void>;
|
|
8993
|
+
/**
|
|
8994
|
+
* @internal — system-internal delete that bypasses user-facing
|
|
8995
|
+
* delete hooks (`onDelete`, accounting-period guard, FK ref
|
|
8996
|
+
* enforcer). Used by derivation tombstones (#144) and MV refresh
|
|
8997
|
+
* (Dim 14 v2) — system housekeeping shouldn't trip user invariants
|
|
8998
|
+
* registered against the output collection. The ledger entry and
|
|
8999
|
+
* history snapshot still fire so backup integrity and time-travel
|
|
9000
|
+
* reconstruction stay consistent.
|
|
9001
|
+
*
|
|
9002
|
+
* Returns silently for delete-of-absent (idempotent contract — both
|
|
9003
|
+
* paths honour this: the `txCtx === null` path also reads the prior
|
|
9004
|
+
* envelope and short-circuits before the ledger/event side-effects).
|
|
9005
|
+
*
|
|
9006
|
+
* When a `txCtx` is supplied, the prior envelope is captured and
|
|
9007
|
+
* pushed onto `txCtx._executed` BEFORE the delete fires — mirrors
|
|
9008
|
+
* the #133 rollback hardening for puts. Callers outside a
|
|
9009
|
+
* multi-record transaction pass `null` and skip the tracking.
|
|
9010
|
+
*
|
|
9011
|
+
* Amendment composition: if `_internalDelete` runs while a vault's
|
|
9012
|
+
* `GuardRegistry` has an amendment window open, the `{before, after:
|
|
9013
|
+
* null}` change pair is pushed onto the amendment change-set the
|
|
9014
|
+
* same way a user-initiated delete would. The `onDelete` user-hook
|
|
9015
|
+
* is still skipped (housekeeping must not trip user invariants in
|
|
9016
|
+
* normal mode), but the amendment's invariant DOES see the change
|
|
9017
|
+
* — so a `RCT-CANCEL-001`-style invariant pairing can reject a
|
|
9018
|
+
* derivation-driven tombstone fired during an admin amendment.
|
|
9019
|
+
*
|
|
9020
|
+
* Constraint to surface to consumers: output collections of
|
|
9021
|
+
* derivations with `optional: true` outputs should not be the
|
|
9022
|
+
* targets of `strict` or `cascade` inbound foreign-key refs —
|
|
9023
|
+
* `_internalDelete` bypasses the ref enforcer by design (the
|
|
9024
|
+
* `onDelete` bypass primitive). Treat the housekeeping path as
|
|
9025
|
+
* "system can tombstone its own emissions regardless of FK shape."
|
|
9026
|
+
*
|
|
9027
|
+
* Permission handling is unchanged: the caller must still hold
|
|
9028
|
+
* write permission on the collection (derivations run under the
|
|
9029
|
+
* user's keyring).
|
|
9030
|
+
*/
|
|
9031
|
+
_internalDelete(id: string, txCtx?: TxContext | null): Promise<void>;
|
|
9032
|
+
private _doDelete;
|
|
9033
|
+
/**
|
|
9034
|
+
* Cascade deletes of array-shape derived rows when a source row is
|
|
9035
|
+
* deleted (#200). Reads each registered strategy's fanout sidecar
|
|
9036
|
+
* for this source id, deletes every listed derived row, then
|
|
9037
|
+
* deletes the sidecar itself.
|
|
9038
|
+
*
|
|
9039
|
+
* Record-shape derivations are skipped — see _doDelete's comment
|
|
9040
|
+
* for why the asymmetry is correct.
|
|
9041
|
+
*
|
|
9042
|
+
* @internal
|
|
9043
|
+
*/
|
|
9044
|
+
private dispatchArrayDerivationsOnDelete;
|
|
9045
|
+
/**
|
|
9046
|
+
* Mirror of {@link dispatchMaterializedViews} for the delete path
|
|
9047
|
+
* (#181). No record content is available (it's gone), so the
|
|
9048
|
+
* `_materializedFrom` skip used by the put-side dispatch doesn't
|
|
9049
|
+
* apply here — instead, the recursion guard is the `internal` gate
|
|
9050
|
+
* at the `_doDelete` call site above.
|
|
9051
|
+
*
|
|
9052
|
+
* @internal
|
|
9053
|
+
*/
|
|
9054
|
+
private dispatchMaterializedViewsOnDelete;
|
|
7039
9055
|
/**
|
|
7040
9056
|
* List all records in the collection.
|
|
7041
9057
|
*
|
|
@@ -7110,6 +9126,15 @@ declare class Collection<T> {
|
|
|
7110
9126
|
* the filtered records directly (the API). Prefer the chainable
|
|
7111
9127
|
* form for new code.
|
|
7112
9128
|
*
|
|
9129
|
+
* **Lazy-MV gap (#157):** `query()` is synchronous and does NOT
|
|
9130
|
+
* trigger lazy materialized-view resolve-on-read. If this
|
|
9131
|
+
* collection is a lazy MV's output and the MV is currently stale,
|
|
9132
|
+
* `query().toArray()` returns the pre-stale snapshot. To force a
|
|
9133
|
+
* fresh read on a lazy MV, either call `list()` (which DOES
|
|
9134
|
+
* trigger resolve) or `vault.refreshView(mvName)` before querying.
|
|
9135
|
+
* The proper fix — extending `QuerySource` with an async prepare
|
|
9136
|
+
* hook — is a separate PR.
|
|
9137
|
+
*
|
|
7113
9138
|
* @example
|
|
7114
9139
|
* ```ts
|
|
7115
9140
|
* // New chainable API:
|
|
@@ -7262,6 +9287,11 @@ declare class Collection<T> {
|
|
|
7262
9287
|
* .aggregate({ total: sum('amount'), n: count() })
|
|
7263
9288
|
* ```
|
|
7264
9289
|
*
|
|
9290
|
+
* **Lazy-MV gap (#157):** `scan()` is synchronous-build and does
|
|
9291
|
+
* NOT trigger lazy materialized-view resolve-on-read. For lazy
|
|
9292
|
+
* MVs, call `list()` (which DOES resolve) or `vault.refreshView(name)`
|
|
9293
|
+
* before scanning. Same shape as the `query()` limitation.
|
|
9294
|
+
*
|
|
7265
9295
|
* Returns a `ScanBuilder<T>` instead of the raw async iterator
|
|
7266
9296
|
* that previous versions used. The builder implements
|
|
7267
9297
|
* `AsyncIterable<T>`, so every existing `for await … of` call
|
|
@@ -7280,6 +9310,22 @@ declare class Collection<T> {
|
|
|
7280
9310
|
/** Decrypt a page of envelopes returned by `adapter.listPage`. */
|
|
7281
9311
|
private decryptPage;
|
|
7282
9312
|
/** Load all records from adapter into memory cache. */
|
|
9313
|
+
/**
|
|
9314
|
+
* @internal — refresh the in-memory cache entry for a single id by
|
|
9315
|
+
* re-reading from the adapter. Used by the transaction executor's
|
|
9316
|
+
* Phase-3 revert path: that path writes the prior envelope directly
|
|
9317
|
+
* via the raw store (to avoid re-firing Collection-level side
|
|
9318
|
+
* effects), which would otherwise leave this Collection's eager
|
|
9319
|
+
* cache holding the rolled-back value. After revert, the executor
|
|
9320
|
+
* calls this hook so subsequent `get` / `query` reads see the
|
|
9321
|
+
* actual on-disk state.
|
|
9322
|
+
*
|
|
9323
|
+
* Lazy mode: drops the LRU entry; the next `get` repopulates from
|
|
9324
|
+
* the adapter. Eager mode: re-reads the envelope and either sets
|
|
9325
|
+
* the cache entry (record still present) or deletes it (record was
|
|
9326
|
+
* gone before the tx and the revert deleted it again).
|
|
9327
|
+
*/
|
|
9328
|
+
_invalidateCacheEntry(id: string): Promise<void>;
|
|
7283
9329
|
private ensureHydrated;
|
|
7284
9330
|
/** Hydrate from a pre-loaded snapshot (used by Vault). */
|
|
7285
9331
|
hydrateFromSnapshot(records: Record<string, EncryptedEnvelope>): Promise<void>;
|
|
@@ -7704,7 +9750,7 @@ interface ShadowStrategy {
|
|
|
7704
9750
|
* @internal
|
|
7705
9751
|
*/
|
|
7706
9752
|
interface TxStrategy {
|
|
7707
|
-
runTransaction<T>(db: Noydb, fn: (tx: TxContext) => Promise<T> | T): Promise<T>;
|
|
9753
|
+
runTransaction<T>(db: Noydb, fn: (tx: TxContext) => Promise<T> | T, options?: AmendmentTxOptions): Promise<T>;
|
|
7708
9754
|
}
|
|
7709
9755
|
|
|
7710
9756
|
/**
|
|
@@ -7834,6 +9880,160 @@ interface SessionStrategy {
|
|
|
7834
9880
|
revokeAllSessions(): void;
|
|
7835
9881
|
}
|
|
7836
9882
|
|
|
9883
|
+
/**
|
|
9884
|
+
* Managed-passphrase mode — issue #14, rubber-hose-resistant vaults.
|
|
9885
|
+
*
|
|
9886
|
+
* A vault mode where the passphrase is machine-generated and never
|
|
9887
|
+
* exposed to the user, sealed under a developer-provided
|
|
9888
|
+
* {@link SealingKeyProvider} (macOS Keychain, Windows Credential
|
|
9889
|
+
* Manager, libsecret, AWS KMS, …). The user has no secret to give
|
|
9890
|
+
* up to coercion — they can't reveal what they don't know.
|
|
9891
|
+
*
|
|
9892
|
+
* ## Components in this file
|
|
9893
|
+
*
|
|
9894
|
+
* - {@link SealingKeyProvider} — the interface concrete providers
|
|
9895
|
+
* implement. Provider implementations live OUTSIDE hub (per-
|
|
9896
|
+
* platform packages).
|
|
9897
|
+
* - {@link MemorySealingKeyProvider} — in-memory test provider; uses
|
|
9898
|
+
* a deterministic per-instance "key" so two providers with
|
|
9899
|
+
* different ids cannot unseal each other's outputs.
|
|
9900
|
+
* - {@link loadSealedPassphrase} / {@link saveSealedPassphrase} —
|
|
9901
|
+
* plaintext envelope storage at `_meta/sealed-passphrase`.
|
|
9902
|
+
* Mirrors the `_meta/handle` and `_meta/public-envelope` AES-
|
|
9903
|
+
* GCM-bypassed patterns. The sealing layer (provider's job)
|
|
9904
|
+
* is the security boundary; hub doesn't have a key to encrypt
|
|
9905
|
+
* with at this layer — that's the whole point of the design.
|
|
9906
|
+
* - {@link resolveManagedSecret} — orchestrates the "generate +
|
|
9907
|
+
* seal + persist on first open; unseal on reopen" flow.
|
|
9908
|
+
* Returns the plaintext passphrase string that the rest of the
|
|
9909
|
+
* `createNoydb` keyring path consumes.
|
|
9910
|
+
*
|
|
9911
|
+
* Slice 1 of #14. Deferred to follow-ups:
|
|
9912
|
+
* - Block `rotate-passphrase` policy gate under managed mode.
|
|
9913
|
+
* - Mandatory strong-recovery enforcement (depends on #10).
|
|
9914
|
+
* - Recovery flow under managed mode (generates fresh sealed phrase).
|
|
9915
|
+
*
|
|
9916
|
+
* @see docs/subsystems/session-tiers.md → Managed-passphrase mode
|
|
9917
|
+
*
|
|
9918
|
+
* @module
|
|
9919
|
+
*/
|
|
9920
|
+
|
|
9921
|
+
/**
|
|
9922
|
+
* The contract concrete providers (per-platform key stores) implement
|
|
9923
|
+
* to seal and unseal a hub-generated random passphrase. The plaintext
|
|
9924
|
+
* passphrase NEVER leaves hub-controlled memory in unsealed form —
|
|
9925
|
+
* the provider receives the bytes, returns opaque sealed bytes, and
|
|
9926
|
+
* later reverses the operation. Hub treats the sealed bytes as
|
|
9927
|
+
* fully opaque.
|
|
9928
|
+
*
|
|
9929
|
+
* Implementations live OUTSIDE `@noy-db/hub` (separate packages
|
|
9930
|
+
* per the issue's "Concrete providers (live outside hub)" note):
|
|
9931
|
+
*
|
|
9932
|
+
* | Platform | Package (TBD) | Backing |
|
|
9933
|
+
* |---|---|---|
|
|
9934
|
+
* | macOS | `@noy-db/seal-macos-keychain` | Security.framework |
|
|
9935
|
+
* | Windows | `@noy-db/seal-wincred` | Credential Manager |
|
|
9936
|
+
* | Linux | `@noy-db/seal-libsecret` | libsecret / secret-service |
|
|
9937
|
+
* | Cloud / server | `@noy-db/seal-aws-kms` | AWS KMS Decrypt |
|
|
9938
|
+
*/
|
|
9939
|
+
interface SealingKeyProvider {
|
|
9940
|
+
/**
|
|
9941
|
+
* Non-sensitive identifier disclosed in the persisted envelope.
|
|
9942
|
+
* Surfaced to consumers via `loadSealedPassphrase().providerId` so
|
|
9943
|
+
* a vault opened with the wrong provider class can detect the
|
|
9944
|
+
* mismatch and surface a clear error. NOT secret — fine to log.
|
|
9945
|
+
*
|
|
9946
|
+
* Suggested format: `<family>:<scope>` — e.g. `macos-keychain:com.acme.app`,
|
|
9947
|
+
* `aws-kms:arn:aws:kms:us-east-1:123:key/abc`. The hub never
|
|
9948
|
+
* parses this; it's purely audit metadata.
|
|
9949
|
+
*/
|
|
9950
|
+
readonly id: string;
|
|
9951
|
+
/** Seal raw passphrase bytes. Output bytes are opaque to hub. */
|
|
9952
|
+
seal(passphrase: Uint8Array): Promise<Uint8Array>;
|
|
9953
|
+
/**
|
|
9954
|
+
* Reverse {@link seal}. MUST throw on tamper, wrong-provider, or
|
|
9955
|
+
* any other failure — hub treats a thrown error as "this provider
|
|
9956
|
+
* cannot unlock this vault" and surfaces it to the caller.
|
|
9957
|
+
*/
|
|
9958
|
+
unseal(sealed: Uint8Array): Promise<Uint8Array>;
|
|
9959
|
+
}
|
|
9960
|
+
/**
|
|
9961
|
+
* In-memory test provider. NOT secure — uses a deterministic
|
|
9962
|
+
* per-instance "key" (16-byte SHA-256 of `id`) XOR'd over the
|
|
9963
|
+
* passphrase plus a 4-byte provider-id fingerprint prefix. The XOR is
|
|
9964
|
+
* sufficient to make different `id` values produce mutually-unsealable
|
|
9965
|
+
* outputs (the contract tests for that), but offers ZERO real
|
|
9966
|
+
* confidentiality — never use outside tests.
|
|
9967
|
+
*
|
|
9968
|
+
* Replace with a real platform provider in production.
|
|
9969
|
+
*/
|
|
9970
|
+
declare class MemorySealingKeyProvider implements SealingKeyProvider {
|
|
9971
|
+
readonly id: string;
|
|
9972
|
+
private readonly fingerprint;
|
|
9973
|
+
private readonly keyBytes;
|
|
9974
|
+
constructor(opts: {
|
|
9975
|
+
id: string;
|
|
9976
|
+
});
|
|
9977
|
+
seal(passphrase: Uint8Array): Promise<Uint8Array>;
|
|
9978
|
+
unseal(sealed: Uint8Array): Promise<Uint8Array>;
|
|
9979
|
+
}
|
|
9980
|
+
/** Reserved id for the managed-passphrase envelope under `_meta`. */
|
|
9981
|
+
declare const SEALED_PASSPHRASE_RECORD_ID: "sealed-passphrase";
|
|
9982
|
+
/** Plaintext payload stored inside the `_meta/sealed-passphrase` envelope. */
|
|
9983
|
+
interface SealedPassphrase {
|
|
9984
|
+
readonly _noydb_sealed: 1;
|
|
9985
|
+
readonly providerId: string;
|
|
9986
|
+
/** Sealed bytes. Base64-encoded on the wire; decoded on load. */
|
|
9987
|
+
readonly sealed: Uint8Array;
|
|
9988
|
+
}
|
|
9989
|
+
/**
|
|
9990
|
+
* Wire-format envelope persisted at `_meta/sealed-passphrase` for
|
|
9991
|
+
* managed-mode vaults. The provider produces raw sealed bytes via
|
|
9992
|
+
* {@link SealingKeyProvider.seal}; this wrapper carries the dispatch
|
|
9993
|
+
* metadata hub needs to pick the right provider on the unseal path.
|
|
9994
|
+
*
|
|
9995
|
+
* Stability boundary: once shipped, the wire format only grows by
|
|
9996
|
+
* adding optional fields. See the at-* sealing dimension foundation
|
|
9997
|
+
* doc, §11.9.1.
|
|
9998
|
+
*
|
|
9999
|
+
* v1 shape (this release): `{ v: 1, _noydb_sealed: 1, pid, payload }`.
|
|
10000
|
+
*
|
|
10001
|
+
* Legacy shape (pre.14, pre.15): `{ _noydb_sealed: 1, providerId, sealed }`
|
|
10002
|
+
* — accepted on read for backwards compatibility; never produced on
|
|
10003
|
+
* write going forward.
|
|
10004
|
+
*/
|
|
10005
|
+
interface SealedEnvelope {
|
|
10006
|
+
/** Envelope schema version. v1 is the shape shipped in pre.16. */
|
|
10007
|
+
readonly v: 1;
|
|
10008
|
+
/** Magic marker for forensics + legacy-shape detection. */
|
|
10009
|
+
readonly _noydb_sealed: 1;
|
|
10010
|
+
/** Matches the producing provider's `.id`. Dispatch key on unseal. */
|
|
10011
|
+
readonly pid: string;
|
|
10012
|
+
/** Sealed bytes from the provider, base64-encoded on the wire. */
|
|
10013
|
+
readonly payload: string;
|
|
10014
|
+
}
|
|
10015
|
+
/**
|
|
10016
|
+
* Parse a `_meta/sealed-passphrase` `_data` JSON string into the
|
|
10017
|
+
* in-memory {@link SealedPassphrase} representation. Accepts both:
|
|
10018
|
+
*
|
|
10019
|
+
* 1. v1 wire format `{ v: 1, _noydb_sealed: 1, pid, payload }` —
|
|
10020
|
+
* the shape produced from pre.16 onward.
|
|
10021
|
+
* 2. Legacy wire format `{ _noydb_sealed: 1, providerId, sealed }` —
|
|
10022
|
+
* the shape produced in pre.14/pre.15. Read-only; never written
|
|
10023
|
+
* going forward.
|
|
10024
|
+
*
|
|
10025
|
+
* Returns `undefined` for any input that doesn't match either shape,
|
|
10026
|
+
* so callers can fall back to "no managed-mode envelope present."
|
|
10027
|
+
*
|
|
10028
|
+
* @internal — exported only for the migration safety-net test suite.
|
|
10029
|
+
*/
|
|
10030
|
+
declare function parseSealedEnvelope(raw: unknown): SealedPassphrase | undefined;
|
|
10031
|
+
declare function saveSealedPassphrase(store: NoydbStore, vault: string, payload: {
|
|
10032
|
+
readonly providerId: string;
|
|
10033
|
+
readonly sealed: Uint8Array;
|
|
10034
|
+
}): Promise<void>;
|
|
10035
|
+
declare function loadSealedPassphrase(store: NoydbStore, vault: string): Promise<SealedPassphrase | undefined>;
|
|
10036
|
+
|
|
7837
10037
|
/**
|
|
7838
10038
|
* Core types — the {@link NoydbStore} interface, envelope format, roles, and
|
|
7839
10039
|
* all configuration shapes consumed by {@link createNoydb}.
|
|
@@ -7892,7 +10092,7 @@ type Permission = 'rw' | 'ro';
|
|
|
7892
10092
|
* `'*'` is the wildcard collection matching all collections in the vault.
|
|
7893
10093
|
*/
|
|
7894
10094
|
type Permissions = Record<string, Permission>;
|
|
7895
|
-
/** The encrypted wrapper stored by
|
|
10095
|
+
/** The encrypted wrapper stored by stores. Stores only ever see this. */
|
|
7896
10096
|
interface EncryptedEnvelope {
|
|
7897
10097
|
readonly _noydb: typeof NOYDB_FORMAT_VERSION;
|
|
7898
10098
|
readonly _v: number;
|
|
@@ -7995,8 +10195,8 @@ interface ListPageResult {
|
|
|
7995
10195
|
}
|
|
7996
10196
|
interface NoydbStore {
|
|
7997
10197
|
/**
|
|
7998
|
-
* Optional human-readable
|
|
7999
|
-
* Used in diagnostic messages and the listPage fallback warning.
|
|
10198
|
+
* Optional human-readable store name (e.g. 'memory', 'file', 'dynamo').
|
|
10199
|
+
* Used in diagnostic messages and the listPage fallback warning. Stores
|
|
8000
10200
|
* are encouraged to set this so logs are clearer about which backend is
|
|
8001
10201
|
* involved when something goes wrong.
|
|
8002
10202
|
*/
|
|
@@ -8017,22 +10217,22 @@ interface NoydbStore {
|
|
|
8017
10217
|
ping?(): Promise<boolean>;
|
|
8018
10218
|
/**
|
|
8019
10219
|
* Optional: list record IDs in a collection that have `_ts` after `since`.
|
|
8020
|
-
* Used by partial sync (`pull({ modifiedSince })`).
|
|
10220
|
+
* Used by partial sync (`pull({ modifiedSince })`). Stores that omit this
|
|
8021
10221
|
* fall back to a full `loadAll` + client-side timestamp filter.
|
|
8022
10222
|
*/
|
|
8023
10223
|
listSince?(vault: string, collection: string, since: string): Promise<string[]>;
|
|
8024
10224
|
/**
|
|
8025
|
-
* Optional pagination extension.
|
|
8026
|
-
* the streaming `Collection.scan()` fast path;
|
|
10225
|
+
* Optional pagination extension. Stores that implement `listPage` get
|
|
10226
|
+
* the streaming `Collection.scan()` fast path; stores that don't are
|
|
8027
10227
|
* silently fallen back to a full `loadAll()` + slice (with a one-time
|
|
8028
10228
|
* console.warn).
|
|
8029
10229
|
*
|
|
8030
|
-
* `cursor` is opaque to the core — each
|
|
10230
|
+
* `cursor` is opaque to the core — each store encodes its own paging
|
|
8031
10231
|
* state (DynamoDB: base64 LastEvaluatedKey JSON; S3: ContinuationToken;
|
|
8032
10232
|
* memory/file/browser: numeric offset of a sorted id list). Pass
|
|
8033
10233
|
* `undefined` to start from the beginning.
|
|
8034
10234
|
*
|
|
8035
|
-
* `limit` is a soft upper bound on `items.length`.
|
|
10235
|
+
* `limit` is a soft upper bound on `items.length`. Stores MAY return
|
|
8036
10236
|
* fewer items even when more exist (e.g. if the underlying store has
|
|
8037
10237
|
* its own page size cap), and MUST signal "no more pages" by returning
|
|
8038
10238
|
* `nextCursor: null`.
|
|
@@ -8245,7 +10445,7 @@ type RecoveryEnrollment = {
|
|
|
8245
10445
|
* metadata only.
|
|
8246
10446
|
*/
|
|
8247
10447
|
interface KeyringAuthenticatorBase {
|
|
8248
|
-
/** Caller-chosen identifier — e.g. `'webauthn-yubikey-blue'`, `'oidc-google'`, `'password
|
|
10448
|
+
/** Caller-chosen identifier — e.g. `'webauthn-yubikey-blue'`, `'oidc-google'`, `'password'`. */
|
|
8249
10449
|
readonly id: string;
|
|
8250
10450
|
/** Method family — selects which `@noy-db/on-*` package handles unlock. */
|
|
8251
10451
|
readonly method: 'webauthn' | 'oidc' | 'password';
|
|
@@ -8330,6 +10530,21 @@ interface KeyringFile {
|
|
|
8330
10530
|
readonly salt: string;
|
|
8331
10531
|
readonly created_at: string;
|
|
8332
10532
|
readonly granted_by: string;
|
|
10533
|
+
/**
|
|
10534
|
+
* Passphrase canary — base64 AES-KW-wrapped form of a known constant
|
|
10535
|
+
* 256-bit value, wrapped under the keyring's KEK (#113).
|
|
10536
|
+
*
|
|
10537
|
+
* Optional: pre-#113 keyrings load with no canary and fall back to
|
|
10538
|
+
* the multi-DEK corruption heuristic from #82. Keyrings written after
|
|
10539
|
+
* #113 carry one and let `loadKeyring` distinguish wrong-passphrase
|
|
10540
|
+
* from corruption even when ALL DEKs (including a single-DEK keyring's
|
|
10541
|
+
* sole DEK) are corrupted.
|
|
10542
|
+
*
|
|
10543
|
+
* AES-KW is deterministic — every write site mints fresh on each
|
|
10544
|
+
* persist; same KEK + same constant input always produces the same
|
|
10545
|
+
* ciphertext, so this round-trips without state.
|
|
10546
|
+
*/
|
|
10547
|
+
readonly canary?: string;
|
|
8333
10548
|
/**
|
|
8334
10549
|
* Tier-2 authenticator slots (multi-slot keyring extension).
|
|
8335
10550
|
* Optional / append-only: keyring files written before the
|
|
@@ -8573,7 +10788,7 @@ interface PullOptions {
|
|
|
8573
10788
|
collections?: string[];
|
|
8574
10789
|
/**
|
|
8575
10790
|
* Only pull records with `_ts` strictly after this ISO timestamp.
|
|
8576
|
-
*
|
|
10791
|
+
* Stores that implement `listSince` use it directly; others fall back
|
|
8577
10792
|
* to a full scan with client-side filtering.
|
|
8578
10793
|
*/
|
|
8579
10794
|
modifiedSince?: string;
|
|
@@ -8737,6 +10952,11 @@ interface GrantOptions {
|
|
|
8737
10952
|
* keyring put (same concurrency story as `db.grant` / `db.revoke`).
|
|
8738
10953
|
*
|
|
8739
10954
|
* Top-level fields are partial-merge: absent fields are not modified.
|
|
10955
|
+
* `null` on `displayName` clears the field (stored as the empty string;
|
|
10956
|
+
* UI consumers typically render the empty case by falling back to the
|
|
10957
|
+
* user id). `undefined` / absent leaves the field untouched. Mirrors
|
|
10958
|
+
* the `null`-as-clear convention `UserApi.updateMe` uses (#57).
|
|
10959
|
+
*
|
|
8740
10960
|
* `permissions`, however, is a **full replacement** at the map level —
|
|
8741
10961
|
* passing `{ invoices: 'rw' }` REPLACES the entire permissions map,
|
|
8742
10962
|
* silently dropping any other entries. To partially update, read the
|
|
@@ -8755,7 +10975,7 @@ interface GrantOptions {
|
|
|
8755
10975
|
interface UpdateUserOptions {
|
|
8756
10976
|
readonly userId: string;
|
|
8757
10977
|
readonly role?: Role;
|
|
8758
|
-
readonly displayName?: string;
|
|
10978
|
+
readonly displayName?: string | null;
|
|
8759
10979
|
readonly permissions?: Permissions;
|
|
8760
10980
|
}
|
|
8761
10981
|
interface RevokeOptions {
|
|
@@ -8798,8 +11018,8 @@ interface AccessibleVault {
|
|
|
8798
11018
|
*/
|
|
8799
11019
|
interface ListAccessibleVaultsOptions {
|
|
8800
11020
|
/**
|
|
8801
|
-
* Minimum role the caller must hold to include a
|
|
8802
|
-
* result.
|
|
11021
|
+
* Minimum role the caller must hold to include a vault in the
|
|
11022
|
+
* result. Vaults where the caller's role is strictly *below*
|
|
8803
11023
|
* this threshold are silently excluded. Defaults to `'client'`,
|
|
8804
11024
|
* which means "every vault I can unwrap is returned." Set to
|
|
8805
11025
|
* `'admin'` for "vaults where I can grant/revoke," or
|
|
@@ -9326,6 +11546,37 @@ interface NoydbOptions {
|
|
|
9326
11546
|
* @internal
|
|
9327
11547
|
*/
|
|
9328
11548
|
readonly syncStrategy?: SyncStrategy;
|
|
11549
|
+
/**
|
|
11550
|
+
* Optional guard strategies — collection-level write guards. Each
|
|
11551
|
+
* handle is the output of `withGuard()` from `@noy-db/hub/guards`.
|
|
11552
|
+
* Multiple guards per collection are allowed; they are dispatched
|
|
11553
|
+
* in registration order on `collection.put()`.
|
|
11554
|
+
*/
|
|
11555
|
+
readonly guardStrategies?: ReadonlyArray<GuardStrategyHandleAny>;
|
|
11556
|
+
/**
|
|
11557
|
+
* Optional derivation strategies — source-to-output projections that
|
|
11558
|
+
* fire on `collection.put()`. Each handle is the output of
|
|
11559
|
+
* `withDerivation()` from `@noy-db/hub/derivations`. The vault
|
|
11560
|
+
* validates the derivation graph for cycles on `openVault`; a cyclic
|
|
11561
|
+
* graph throws `DerivationCycleError`.
|
|
11562
|
+
*/
|
|
11563
|
+
readonly derivationStrategies?: ReadonlyArray<DerivationStrategyHandle>;
|
|
11564
|
+
/**
|
|
11565
|
+
* Optional materialized-view strategies (#143, foundation in #150).
|
|
11566
|
+
* Each handle returned by `withMaterializedView()` from
|
|
11567
|
+
* `@noy-db/hub/materialized-views`. The vault runs unified cycle
|
|
11568
|
+
* detection across the MV + derivation graphs at `openVault`; a
|
|
11569
|
+
* cyclic graph throws `MaterializedViewCycleError`.
|
|
11570
|
+
*/
|
|
11571
|
+
readonly materializedViewStrategies?: ReadonlyArray<MaterializedViewStrategyHandle>;
|
|
11572
|
+
/**
|
|
11573
|
+
* Optional overlay strategies (#154). Each handle returned by
|
|
11574
|
+
* `withOverlayedView()` from `@noy-db/hub/overlay-views`. The vault
|
|
11575
|
+
* validates name uniqueness + base concreteness + overlay
|
|
11576
|
+
* availability at `openVault`; a clash throws one of the
|
|
11577
|
+
* `Overlay*Error` family.
|
|
11578
|
+
*/
|
|
11579
|
+
readonly overlayedViewStrategies?: ReadonlyArray<OverlayedViewStrategyHandle>;
|
|
9329
11580
|
/** Optional remote store(s) for sync. Accepts a single store, a SyncTarget, or an array. */
|
|
9330
11581
|
readonly sync?: NoydbStore | SyncTarget | SyncTarget[];
|
|
9331
11582
|
/** User identifier. */
|
|
@@ -9371,6 +11622,32 @@ interface NoydbOptions {
|
|
|
9371
11622
|
* subsequent sessions.
|
|
9372
11623
|
*/
|
|
9373
11624
|
readonly getKeyring?: (vault: string) => Promise<UnlockedKeyring>;
|
|
11625
|
+
/**
|
|
11626
|
+
* Passphrase mode (#14). Default `'standard'`.
|
|
11627
|
+
*
|
|
11628
|
+
* - `'standard'` — the legacy flow. `secret` supplies the
|
|
11629
|
+
* plaintext passphrase, the user knows it, and the policy gate
|
|
11630
|
+
* `rotate-passphrase` is enabled.
|
|
11631
|
+
* - `'managed'` — rubber-hose-resistant mode. Hub generates a
|
|
11632
|
+
* 256-bit random passphrase at first open and seals it under
|
|
11633
|
+
* the provided `sealingKey`. The user never sees or types the
|
|
11634
|
+
* passphrase, defeating the $5-wrench attack. Mutually
|
|
11635
|
+
* exclusive with `secret` and `getKeyring`.
|
|
11636
|
+
*
|
|
11637
|
+
* @see docs/subsystems/session-tiers.md → Managed-passphrase mode
|
|
11638
|
+
*/
|
|
11639
|
+
readonly passphraseMode?: 'standard' | 'managed';
|
|
11640
|
+
/**
|
|
11641
|
+
* Provider that seals/unseals the auto-generated managed-mode
|
|
11642
|
+
* passphrase. Required when `passphraseMode === 'managed'`; ignored
|
|
11643
|
+
* otherwise. Implementations live in per-platform packages
|
|
11644
|
+
* (`@noy-db/seal-macos-keychain`, `@noy-db/seal-wincred`,
|
|
11645
|
+
* `@noy-db/seal-libsecret`, `@noy-db/seal-aws-kms`, …).
|
|
11646
|
+
*/
|
|
11647
|
+
readonly sealingKey?: SealingKeyProvider;
|
|
11648
|
+
/** Required to use `profile: 'shamir'` recovery. Pass
|
|
11649
|
+
* `shamirRecoveryProvider()` from `@noy-db/on-shamir`. */
|
|
11650
|
+
readonly shamirRecovery?: ShamirRecoveryProvider;
|
|
9374
11651
|
/** Auth method. Default: 'passphrase'. */
|
|
9375
11652
|
readonly auth?: 'passphrase' | 'biometric';
|
|
9376
11653
|
/** Enable encryption. Default: true. */
|
|
@@ -9574,4 +11851,4 @@ interface DeleteManyResult {
|
|
|
9574
11851
|
}>;
|
|
9575
11852
|
}
|
|
9576
11853
|
|
|
9577
|
-
export { type ConsentAuditEntry as $, type BlobObject as A, type BlobStrategy as B, type BlobPutOptions as C, DICT_COLLECTION_PREFIX as D, type BlobResponseOptions as E, BlobSet as F, type BlobStrategyOpenArgs as G, type CompactRunOptions as H, type I18nStrategy as I, type CompactionContext as J, type CompactionResult as K, DEFAULT_CHUNK_SIZE as L, EXPORT_AUDIT_COLLECTION as M, ExportBlobsAbortedError as N, type ExportBlobsAuditEntry as O, PolicyEnforcer as P, type ExportBlobsHandle as Q, type ExportBlobsOptions as R, type SessionStrategy as S, type ExportedBlob as T, type SlotInfo as U, type SlotRecord as V, type VersionRecord as W, createExportBlobsHandle as X, runCompaction as Y, type ConsentStrategy as Z, CONSENT_AUDIT_COLLECTION as _, type DictEntry as a, type BuiltInGateName as a$, type ConsentAuditFilter as a0, type ConsentContext as a1, type ConsentOp as a2, loadConsentEntries as a3, writeConsentEntry as a4, type PeriodsStrategy as a5, type CarryForwardContext as a6, type ClosePeriodOptions as a7, type OpenPeriodOptions as a8, PERIODS_COLLECTION as a9, type DiffEntry as aA, type JsonPatch as aB, type JsonPatchOp as aC, type LedgerEntry as aD, LedgerStore as aE, type VaultEngine as aF, VaultInstant as aG, type VerifyResult as aH, applyPatch as aI, canonicalJson as aJ, computePatch as aK, diff as aL, formatDiff as aM, hashEntry as aN, paddedIndex as aO, parseIndex as aP, sha256Hex as aQ, type UserEnvelope as aR, type PublicEnvelope as aS, type GateName as aT, type GatePolicy as aU, type VaultPolicy as aV, type ActiveTier as aW, type FactorProof as aX, Vault as aY, type AccessibleVault as aZ, BUNDLE_STORE_POLICY as a_, type PeriodRecord as aa, type ReadOnlyCollection as ab, appendPeriodLedgerEntry as ac, assertTsWritable as ad, chainAnchor as ae, loadPeriods as af, validatePeriodName as ag, type ShadowStrategy as ah, CollectionFrame as ai, VaultFrame as aj, type TxStrategy as ak, TxCollection as al, TxContext as am, TxVault as an, runTransaction as ao, type SyncStrategy as ap, type Role as aq, type UnlockedKeyring as ar, type HistoryStrategy as as, type NoydbStore as at, type HistoryOptions as au, type EncryptedEnvelope as av, type PruneOptions as aw, type AppendInput as ax, type ChangeType as ay, CollectionInstant as az, type DictKeyDescriptor as b, type Permission as b$, type BundleRecipient as b0, type CacheOptions as b1, type CacheStats as b2, type ChangeEvent as b3, Collection as b4, type CollectionChangeEvent as b5, type CollectionConflictResolver as b6, type Conflict as b7, type ConflictPolicy as b8, type ConflictStrategy as b9, type KeyringAuthenticator as bA, type KeyringFile as bB, type ListAccessibleVaultsOptions as bC, type ListPageResult as bD, type LiveUserEnvelope as bE, type LocaleReadOptions as bF, Lru as bG, type LruOptions as bH, type LruStats as bI, MAGIC_LINK_CONTENT_INFO_PREFIX as bJ, MAGIC_LINK_GRANTS_COLLECTION as bK, MAGIC_LINK_KEK_INFO_PREFIX as bL, type MagicLinkGrantPayload as bM, type MagicLinkGrantRecord as bN, NOYDB_BACKUP_VERSION as bO, NOYDB_FORMAT_VERSION as bP, NOYDB_KEYRING_VERSION as bQ, NOYDB_SYNC_VERSION as bR, Noydb as bS, type NoydbBundleStore as bT, type NoydbEventMap as bU, type NoydbOptions as bV, PUBLIC_ENVELOPE_FIELDS as bW, type PaperRecoveryDoc as bX, type PaperRecoveryEntry as bY, type PassphrasePolicy as bZ, type PassphraseValidationResult as b_, type CrossTierAccessEvent as ba, DEFAULT_PUBLIC_ENVELOPE_SCHEMA as bb, DELEGATIONS_COLLECTION as bc, type DeepPartial as bd, type DeepPartialOrNull as be, type DelegationToken as bf, type DeleteManyResult as bg, type DirtyEntry as bh, ELEVATION_AUDIT_COLLECTION as bi, ElevatedHandle as bj, type EnrollAuthenticatorOptions as bk, type ExportCapability as bl, type ExportChunk as bm, type ExportFormat as bn, type ExportStreamOptions as bo, type FactorKind as bp, type FactorRequirement as bq, type GhostRecord as br, type GrantOptions as bs, type HistoryConfig as bt, type HistoryEntry as bu, INDEXED_STORE_POLICY as bv, type ImportCapability as bw, type InferOutput as bx, type IssueDelegationOptions as by, type IssueMagicLinkGrantOptions as bz, DictionaryHandle as c, type UserInfo as c$, type Permissions as c0, type PlaintextTranslatorContext as c1, type PlaintextTranslatorFn as c2, PresenceHandle as c3, type PresencePeer as c4, type PublicEnvelopeField as c5, type PublicEnvelopeSchema as c6, type PublicEnvelopeText as c7, type PullMode as c8, type PullOptions as c9, type StandardSchemaV1Issue as cA, type StandardSchemaV1SyncResult as cB, type StoreAuth as cC, type StoreAuthKind as cD, type StoreCapabilities as cE, SyncEngine as cF, type SyncMetadata as cG, type SyncPolicy as cH, SyncScheduler as cI, type SyncSchedulerStatus as cJ, type SyncStatus as cK, type SyncTarget as cL, type SyncTargetRole as cM, SyncTransaction as cN, type SyncTransactionResult as cO, type TierMode as cP, type TranslatorAuditEntry as cQ, type TxOp as cR, USER_ENVELOPE_COLLECTION as cS, USER_ENVELOPE_MAX_BYTES as cT, type Unsubscribe as cU, type UpdateAuthenticatorOptions as cV, type UpdateUserOptions as cW, UserApi as cX, type UserEnvelopeCheckGate as cY, UserEnvelopeOversizedError as cZ, type UserEnvelopePresented as c_, type PullPolicy as ca, type PullResult as cb, type PushMode as cc, type PushOptions as cd, type PushPolicy as ce, type PushResult as cf, type PutManyItemOptions as cg, type PutManyOptions as ch, type PutManyResult as ci, type QueryAcrossOptions as cj, type QueryAcrossResult as ck, type QuickUnlockState as cl, QuickUnlockStore as cm, type ReAuthOperation as cn, type RecoverPassphraseInput as co, type RecoverPassphraseResult as cp, type RecoverUserOptions as cq, type RecoveryProof as cr, type ResolvedPublicEnvelopeSchema as cs, type RevokeOptions as ct, type RotatePassphraseInput as cu, type SessionPolicy as cv, type SetPublicEnvelopeInput as cw, type SlotRewrapCeremony as cx, type SlotRewrapContext as cy, type StandardSchemaV1 as cz, type DictionaryOptions as d, type VaultBackup as d0, type VaultPolicyOnDisk as d1, type VaultSnapshot as d2, type WarningRules as d3, WeakPassphraseError as d4, type WeakPassphraseReason as d5, type WrappedDeksBlob as d6, assertStrongPassphrase as d7, buildRecipientKeyringFile as d8, burnPaperRecoveryEntry as d9, recoverUser as dA, removeAuthenticator as dB, resolveSchema as dC, revokeDelegation as dD, revokeMagicLinkGrant as dE, savePaperRecoveryEntries as dF, unwrapDeksFromBlob as dG, unwrapDeksFromPaperEntry as dH, unwrapMagicLinkGrant as dI, validatePassphrase as dJ, validatePublicEnvelopeInput as dK, validateSchemaInput as dL, validateSchemaOutput as dM, writeMagicLinkGrant as dN, createNoydb as da, createStore as db, deriveMagicLinkContentKey as dc, enrollAuthenticator as dd, estimateEntropy as de, evaluateExportCapability as df, evaluateImportCapability as dg, findAuthenticator as dh, hasExportCapability as di, hasImportCapability as dj, hasRecoveryEnrolled as dk, isMagicLinkGrantExpired as dl, isPublicEnvelope as dm, issueDelegation as dn, recoverPassphrase as dp, rotatePassphrase as dq, listMagicLinkGrants as dr, listUsers as ds, listUsersWithEnvelopes as dt, loadActiveDelegations as du, loadPaperRecoveryEntries as dv, magicLinkGrantRecordId as dw, mintPaperRecoveryEntry as dx, mintWrappedDeksBlob as dy, readMagicLinkGrantRecord as dz, type I18nTextDescriptor as e, type I18nTextOptions as f, applyI18nLocale as g, dictCollectionName as h, dictKey as i, i18nText as j, isDictCollectionName as k, isDictKeyDescriptor as l, isI18nTextDescriptor as m, createEnforcer as n, validateSessionPolicy as o, BLOB_CHUNKS_COLLECTION as p, BLOB_COLLECTION as q, resolveI18nText as r, BLOB_EVICTION_AUDIT_COLLECTION as s, BLOB_INDEX_COLLECTION as t, BLOB_SLOTS_PREFIX as u, validateI18nTextValue as v, BLOB_VERSIONS_PREFIX as w, type BlobEvictionEntry as x, type BlobFieldPolicy as y, type BlobFieldsConfig as z };
|
|
11854
|
+
export { type ConsentAuditEntry as $, type BlobObject as A, type BlobStrategy as B, type BlobPutOptions as C, DICT_COLLECTION_PREFIX as D, type BlobResponseOptions as E, BlobSet as F, type BlobStrategyOpenArgs as G, type CompactRunOptions as H, type I18nStrategy as I, type CompactionContext as J, type CompactionResult as K, DEFAULT_CHUNK_SIZE as L, EXPORT_AUDIT_COLLECTION as M, ExportBlobsAbortedError as N, type ExportBlobsAuditEntry as O, PolicyEnforcer as P, type ExportBlobsHandle as Q, type ExportBlobsOptions as R, type SessionStrategy as S, type ExportedBlob as T, type SlotInfo as U, type SlotRecord as V, type VersionRecord as W, createExportBlobsHandle as X, runCompaction as Y, type ConsentStrategy as Z, CONSENT_AUDIT_COLLECTION as _, type DictEntry as a, VaultInstant as a$, type ConsentAuditFilter as a0, type ConsentContext as a1, type ConsentOp as a2, loadConsentEntries as a3, writeConsentEntry as a4, type PeriodsStrategy as a5, type CarryForwardContext as a6, type ClosePeriodOptions as a7, type OpenPeriodOptions as a8, PERIODS_COLLECTION as a9, type DerivationStrategyHandle as aA, type DerivedFromMeta as aB, type OutputSpec as aC, type RecordOutputSpec as aD, type MaterializedViewStrategy as aE, type MaterializedViewStrategyHandle as aF, type OverlayedViewStrategy as aG, Collection as aH, OverlayedViewRegistry as aI, type OverlayedViewStrategyHandle as aJ, type SyncStrategy as aK, type Role as aL, type UnlockedKeyring as aM, type HistoryStrategy as aN, type NoydbStore as aO, type HistoryOptions as aP, type EncryptedEnvelope as aQ, type PruneOptions as aR, type AppendInput as aS, type ChangeType as aT, CollectionInstant as aU, type DiffEntry as aV, type JsonPatch as aW, type JsonPatchOp as aX, type LedgerEntry as aY, LedgerStore as aZ, type VaultEngine as a_, type PeriodRecord as aa, type ReadOnlyCollection as ab, appendPeriodLedgerEntry as ac, assertTsWritable as ad, chainAnchor as ae, loadPeriods as af, validatePeriodName as ag, type GuardStrategy as ah, type GuardChange as ai, type GuardContext as aj, GuardRegistry as ak, type GuardStrategyHandle as al, ReadOnlyVaultFacade as am, type ShadowStrategy as an, CollectionFrame as ao, VaultFrame as ap, type TxStrategy as aq, type AmendmentTxOptions as ar, TxCollection as as, TxContext as at, TxVault as au, runTransaction as av, type DerivationStrategy as aw, type DerivationContext as ax, type ArrayOutputSpec as ay, DerivationRegistry as az, type DictKeyDescriptor as b, type FactorRequirement as b$, type VerifyResult as b0, applyPatch as b1, canonicalJson as b2, computePatch as b3, diff as b4, formatDiff as b5, hashEntry as b6, paddedIndex as b7, parseIndex as b8, sha256Hex as b9, type CollectionDescriptor as bA, type CollectionStats as bB, type Conflict as bC, type ConflictPolicy as bD, type ConflictStrategy as bE, type CrossTierAccessEvent as bF, DEFAULT_PUBLIC_ENVELOPE_SCHEMA as bG, DELEGATIONS_COLLECTION as bH, type DeepPartial as bI, type DeepPartialOrNull as bJ, type DelegationToken as bK, type DeleteManyResult as bL, type DerivationDescriptor as bM, type DirtyEntry as bN, type DumpSchemaOptions as bO, ELEVATION_AUDIT_COLLECTION as bP, ElevatedHandle as bQ, type EnrollAuthenticatorOptions as bR, type EnrollAuthenticatorWrappingDEKsOptions as bS, type EnrollAuthenticatorWrappingKEKOptions as bT, type EnrollRecoveryResult as bU, type ExportCapability as bV, type ExportChunk as bW, type ExportFormat as bX, type ExportStreamOptions as bY, type FactorKind as bZ, type FactorProofBundle as b_, type MVQueryContext as ba, type RegisteredMV as bb, MaterializedViewRegistry as bc, type MaterializedFromMeta as bd, type MaterializedViewOutput as be, type UnionSource as bf, type UserEnvelope as bg, type PublicEnvelope as bh, type GateName as bi, type GatePolicy as bj, type VaultPolicy as bk, type ActiveTier as bl, type FactorProof as bm, type PersistedSchemaEnvelope as bn, type DirectoryConfig as bo, type UserVisibility as bp, Vault as bq, type AccessibleVault as br, BUNDLE_STORE_POLICY as bs, type BuiltInGateName as bt, type BundleRecipient as bu, type CacheOptions as bv, type CacheStats as bw, type ChangeEvent as bx, type CollectionChangeEvent as by, type CollectionConflictResolver as bz, DictionaryHandle as c, type PutManyItemOptions as c$, type FieldDescriptor as c0, type FieldSource as c1, type GhostRecord as c2, type GrantOptions as c3, type HistoryConfig as c4, type HistoryEntry as c5, INDEXED_STORE_POLICY as c6, type ImportCapability as c7, type InferOutput as c8, type InternalCollectionStats as c9, type NoydbBundleStore as cA, type NoydbEventMap as cB, type NoydbOptions as cC, type OverlayViewDescriptor as cD, PUBLIC_ENVELOPE_FIELDS as cE, type PaperRecoveryDoc as cF, type PaperRecoveryEntry as cG, type PassphrasePolicy as cH, type PassphraseValidationResult as cI, type Permission as cJ, type Permissions as cK, type PersistedSchemaKind as cL, type PlaintextTranslatorContext as cM, type PlaintextTranslatorFn as cN, PresenceHandle as cO, type PresencePeer as cP, type PublicEnvelopeField as cQ, type PublicEnvelopeSchema as cR, type PublicEnvelopeText as cS, type PullMode as cT, type PullOptions as cU, type PullPolicy as cV, type PullResult as cW, type PushMode as cX, type PushOptions as cY, type PushPolicy as cZ, type PushResult as c_, type IssueDelegationOptions as ca, type IssueMagicLinkGrantOptions as cb, type KeyringAuthenticator as cc, type KeyringAuthenticatorWrappingDEKs as cd, type KeyringAuthenticatorWrappingKEK as ce, type KeyringFile as cf, type ListAccessibleVaultsOptions as cg, type ListPageResult as ch, type ListUsersOptions as ci, type LiveUserEnvelope as cj, type LocaleReadOptions as ck, Lru as cl, type LruOptions as cm, type LruStats as cn, MAGIC_LINK_CONTENT_INFO_PREFIX as co, MAGIC_LINK_GRANTS_COLLECTION as cp, MAGIC_LINK_KEK_INFO_PREFIX as cq, type MagicLinkGrantPayload as cr, type MagicLinkGrantRecord as cs, type MaterializedViewDescriptor as ct, MemorySealingKeyProvider as cu, NOYDB_BACKUP_VERSION as cv, NOYDB_FORMAT_VERSION as cw, NOYDB_KEYRING_VERSION as cx, NOYDB_SYNC_VERSION as cy, Noydb as cz, type DictionaryOptions as d, type WeakPassphraseReason as d$, type PutManyOptions as d0, type PutManyResult as d1, type QueryAcrossOptions as d2, type QueryAcrossResult as d3, type QuickUnlockState as d4, QuickUnlockStore as d5, type ReAuthOperation as d6, type RecoverPassphraseInput as d7, type RecoverPassphraseResult as d8, type RecoverUserOptions as d9, type SyncPolicy as dA, SyncScheduler as dB, type SyncSchedulerStatus as dC, type SyncStatus as dD, type SyncTarget as dE, type SyncTargetRole as dF, SyncTransaction as dG, type SyncTransactionResult as dH, type TierMode as dI, type TranslatorAuditEntry as dJ, type TxOp as dK, USER_ENVELOPE_COLLECTION as dL, USER_ENVELOPE_MAX_BYTES as dM, type Unsubscribe as dN, type UpdateAuthenticatorOptions as dO, type UpdateUserOptions as dP, UserApi as dQ, type UserEnvelopeCheckGate as dR, UserEnvelopeOversizedError as dS, type UserEnvelopePresented as dT, type UserInfo as dU, type VaultBackup as dV, type VaultPolicyOnDisk as dW, type VaultSchemaSnapshot as dX, type VaultSnapshot as dY, type WarningRules as dZ, WeakPassphraseError as d_, type RecoveryProof as da, type ResolvedPublicEnvelopeSchema as db, type RevokeOptions as dc, type RotatePassphraseInput as dd, type RotateRecoveryOptions as de, type RotateRecoveryResult as df, SEALED_PASSPHRASE_RECORD_ID as dg, type SealedEnvelope as dh, type SealedPassphrase as di, type SealingKeyProvider as dj, type SessionPolicy as dk, type SetPublicEnvelopeInput as dl, type ShamirRecoveryDoc as dm, type ShamirRecoveryEntry as dn, type ShamirRecoveryProvider as dp, type SlotRewrapCeremony as dq, type SlotRewrapContext as dr, type StandardSchemaV1 as ds, type StandardSchemaV1Issue as dt, type StandardSchemaV1SyncResult as du, type StoreAuth as dv, type StoreAuthKind as dw, type StoreCapabilities as dx, SyncEngine as dy, type SyncMetadata as dz, type I18nTextDescriptor as e, type WrappedDeksBlob as e0, assertStrongPassphrase as e1, buildRecipientKeyringFile as e2, burnPaperRecoveryEntry as e3, createNoydb as e4, createStore as e5, deriveMagicLinkContentKey as e6, enrollAuthenticator as e7, estimateEntropy as e8, evaluateExportCapability as e9, revokeDelegation as eA, revokeMagicLinkGrant as eB, savePaperRecoveryEntries as eC, saveSealedPassphrase as eD, saveShamirRecoveryEntries as eE, unwrapDeksFromBlob as eF, unwrapDeksFromPaperEntry as eG, unwrapDeksFromShamirEntry as eH, unwrapMagicLinkGrant as eI, validatePassphrase as eJ, validatePublicEnvelopeInput as eK, validateSchemaInput as eL, validateSchemaOutput as eM, writeMagicLinkGrant as eN, changeSecret as eO, createOwnerKeyring as eP, ensureCollectionDEK as eQ, grant as eR, loadKeyring as eS, persistKeyring as eT, revoke as eU, updateAuthenticator as eV, updateKeyringIdentity as eW, evaluateImportCapability as ea, findAuthenticator as eb, hasExportCapability as ec, hasImportCapability as ed, hasRecoveryEnrolled as ee, isMagicLinkGrantExpired as ef, isPublicEnvelope as eg, issueDelegation as eh, recoverPassphrase as ei, rotatePassphrase as ej, listMagicLinkGrants as ek, listUsers as el, listUsersWithEnvelopes as em, loadActiveDelegations as en, loadPaperRecoveryEntries as eo, loadSealedPassphrase as ep, loadShamirRecoveryEntries as eq, magicLinkGrantRecordId as er, mintPaperRecoveryEntry as es, mintShamirRecoveryEntry as et, mintWrappedDeksBlob as eu, parseSealedEnvelope as ev, readMagicLinkGrantRecord as ew, recoverUser as ex, removeAuthenticator as ey, resolveSchema as ez, type I18nTextOptions as f, applyI18nLocale as g, dictCollectionName as h, dictKey as i, i18nText as j, isDictCollectionName as k, isDictKeyDescriptor as l, isI18nTextDescriptor as m, createEnforcer as n, validateSessionPolicy as o, BLOB_CHUNKS_COLLECTION as p, BLOB_COLLECTION as q, resolveI18nText as r, BLOB_EVICTION_AUDIT_COLLECTION as s, BLOB_INDEX_COLLECTION as t, BLOB_SLOTS_PREFIX as u, validateI18nTextValue as v, BLOB_VERSIONS_PREFIX as w, type BlobEvictionEntry as x, type BlobFieldPolicy as y, type BlobFieldsConfig as z };
|