@noy-db/hub 0.2.0-pre.20 → 0.2.0-pre.23
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/attestation/index.d.cts +1 -1
- package/dist/attestation/index.d.ts +1 -1
- package/dist/blobs/index.d.cts +3 -3
- package/dist/blobs/index.d.ts +3 -3
- package/dist/bundle/index.cjs +21664 -21247
- package/dist/bundle/index.cjs.map +1 -1
- package/dist/bundle/index.d.cts +3 -3
- package/dist/bundle/index.d.ts +3 -3
- package/dist/bundle/index.js +1 -1
- package/dist/{chunk-7PH4OPBZ.js → chunk-P65YMN5V.js} +388 -5
- package/dist/chunk-P65YMN5V.js.map +1 -0
- package/dist/consent/index.d.cts +2 -2
- package/dist/consent/index.d.ts +2 -2
- package/dist/derivations/index.d.cts +3 -3
- package/dist/derivations/index.d.ts +3 -3
- package/dist/{dev-unlock-CY0HIZA0.d.cts → dev-unlock-Bw7iBD1D.d.cts} +1 -1
- package/dist/{dev-unlock-CpKSkl2c.d.ts → dev-unlock-DzDzLTdZ.d.ts} +1 -1
- package/dist/guards/index.d.cts +3 -3
- package/dist/guards/index.d.ts +3 -3
- package/dist/{hash-BSd0-_L8.d.cts → hash-C52X_-m5.d.cts} +1 -1
- package/dist/{hash-BnBQx39y.d.ts → hash-DepR-xVc.d.ts} +1 -1
- package/dist/history/index.d.cts +3 -3
- package/dist/history/index.d.ts +3 -3
- package/dist/i18n/index.d.cts +2 -2
- package/dist/i18n/index.d.ts +2 -2
- package/dist/index.cjs +396 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +10 -10
- package/dist/index.d.ts +10 -10
- package/dist/index.js +16 -2
- package/dist/index.js.map +1 -1
- package/dist/materialized-views/index.d.cts +3 -3
- package/dist/materialized-views/index.d.ts +3 -3
- package/dist/{mime-magic-BnJCGJzB.d.cts → mime-magic-Cxf9B_Dm.d.cts} +1 -1
- package/dist/{mime-magic-CjSyakO4.d.ts → mime-magic-Dejetix_.d.ts} +1 -1
- package/dist/{noydb-ZZCRF6TE.js → noydb-VGR2HLDB.js} +3 -2
- package/dist/overlay-views/index.d.cts +3 -3
- package/dist/overlay-views/index.d.ts +3 -3
- package/dist/periods/index.d.cts +2 -2
- package/dist/periods/index.d.ts +2 -2
- package/dist/session/index.d.cts +3 -3
- package/dist/session/index.d.ts +3 -3
- package/dist/shadow/index.d.cts +2 -2
- package/dist/shadow/index.d.ts +2 -2
- package/dist/snapshots/index.d.cts +2 -2
- package/dist/snapshots/index.d.ts +2 -2
- package/dist/store/index.d.cts +2 -2
- package/dist/store/index.d.ts +2 -2
- package/dist/sync/index.d.cts +1 -1
- package/dist/sync/index.d.ts +1 -1
- package/dist/team/index.d.cts +2 -2
- package/dist/team/index.d.ts +2 -2
- package/dist/{transition-guard-Dmpqzg-_.d.cts → transition-guard-BcLyTGYq.d.cts} +1 -1
- package/dist/{transition-guard-D4bfIAiW.d.ts → transition-guard-Ctxapq1b.d.ts} +1 -1
- package/dist/tx/index.d.cts +2 -2
- package/dist/tx/index.d.ts +2 -2
- package/dist/{types-DyOI6XZ_.d.cts → types-Bhs2i_Ll.d.cts} +260 -4
- package/dist/{types-DLfWFr6U.d.ts → types-DONgts0n.d.ts} +260 -4
- package/dist/{ulid-LaxfH2tK.d.cts → ulid-Bg-IBJyA.d.cts} +1 -1
- package/dist/{ulid-B2L_aqVA.d.ts → ulid-Dwt3JEcy.d.ts} +1 -1
- package/dist/{with-materialized-view-CeZYGJVf.d.cts → with-materialized-view-BYb3p9wT.d.cts} +1 -1
- package/dist/{with-materialized-view-DNULSxoP.d.ts → with-materialized-view-CyVLOr09.d.ts} +1 -1
- package/dist/{with-overlayed-view-C9joG7UZ.d.ts → with-overlayed-view-BhLRxqwI.d.ts} +1 -1
- package/dist/{with-overlayed-view-kdcPGHih.d.cts → with-overlayed-view-LGrQ984e.d.cts} +1 -1
- package/dist/{with-rollup-s58XAeWO.d.cts → with-rollup-Bj8c7ttB.d.cts} +1 -1
- package/dist/{with-rollup-DJDbrxjf.d.ts → with-rollup-CO8ibRcK.d.ts} +1 -1
- package/package.json +3 -3
- package/dist/chunk-7PH4OPBZ.js.map +0 -1
- /package/dist/{noydb-ZZCRF6TE.js.map → noydb-VGR2HLDB.js.map} +0 -0
package/dist/index.cjs
CHANGED
|
@@ -6582,6 +6582,7 @@ __export(src_exports, {
|
|
|
6582
6582
|
VaultInstant: () => VaultInstant,
|
|
6583
6583
|
VaultTemplateNotFoundError: () => VaultTemplateNotFoundError,
|
|
6584
6584
|
WeakPassphraseError: () => WeakPassphraseError,
|
|
6585
|
+
WithdrawalRequestError: () => WithdrawalRequestError,
|
|
6585
6586
|
activeSessionCount: () => activeSessionCount,
|
|
6586
6587
|
additiveOnly: () => additiveOnly,
|
|
6587
6588
|
aesGcmOpen: () => aesGcmOpen,
|
|
@@ -6589,6 +6590,7 @@ __export(src_exports, {
|
|
|
6589
6590
|
applyI18nLocale: () => applyI18nLocale,
|
|
6590
6591
|
applyJoins: () => applyJoins,
|
|
6591
6592
|
applyPatch: () => applyPatch,
|
|
6593
|
+
approveWithdrawal: () => approveWithdrawal,
|
|
6592
6594
|
asMoney: () => asMoney,
|
|
6593
6595
|
assertStrongPassphrase: () => assertStrongPassphrase,
|
|
6594
6596
|
assertTierAccess: () => assertTierAccess,
|
|
@@ -6647,6 +6649,7 @@ __export(src_exports, {
|
|
|
6647
6649
|
evaluateFieldClause: () => evaluateFieldClause,
|
|
6648
6650
|
evaluateImportCapability: () => evaluateImportCapability,
|
|
6649
6651
|
executePlan: () => executePlan,
|
|
6652
|
+
exportAccessibleData: () => exportAccessibleData,
|
|
6650
6653
|
findAuthenticator: () => findAuthenticator,
|
|
6651
6654
|
formatDiff: () => formatDiff,
|
|
6652
6655
|
generateULID: () => generateULID,
|
|
@@ -6685,6 +6688,7 @@ __export(src_exports, {
|
|
|
6685
6688
|
listUserEnvelopeIds: () => listUserEnvelopeIds,
|
|
6686
6689
|
listUsers: () => listUsers,
|
|
6687
6690
|
listUsersWithEnvelopes: () => listUsersWithEnvelopes,
|
|
6691
|
+
listWithdrawalRequests: () => listWithdrawalRequests,
|
|
6688
6692
|
loadActiveDelegations: () => loadActiveDelegations,
|
|
6689
6693
|
loadDevUnlock: () => loadDevUnlock,
|
|
6690
6694
|
loadPaperRecoveryEntries: () => loadPaperRecoveryEntries,
|
|
@@ -6729,7 +6733,9 @@ __export(src_exports, {
|
|
|
6729
6733
|
reduceRecords: () => reduceRecords,
|
|
6730
6734
|
ref: () => ref,
|
|
6731
6735
|
refArray: () => refArray,
|
|
6736
|
+
rejectWithdrawal: () => rejectWithdrawal,
|
|
6732
6737
|
removeAuthenticator: () => removeAuthenticator,
|
|
6738
|
+
requestWithdrawal: () => requestWithdrawal,
|
|
6733
6739
|
resetBrotliSupportCache: () => resetBrotliSupportCache,
|
|
6734
6740
|
resetJoinWarnings: () => resetJoinWarnings,
|
|
6735
6741
|
resolveCrdtSnapshot: () => resolveCrdtSnapshot,
|
|
@@ -6782,6 +6788,7 @@ __export(src_exports, {
|
|
|
6782
6788
|
withOverlayedView: () => withOverlayedView,
|
|
6783
6789
|
withRetry: () => withRetry,
|
|
6784
6790
|
withRollup: () => withRollup,
|
|
6791
|
+
withdrawAccessibleData: () => withdrawAccessibleData,
|
|
6785
6792
|
wrapBundleStore: () => wrapBundleStore,
|
|
6786
6793
|
wrapStore: () => wrapStore,
|
|
6787
6794
|
writeMagicLinkGrant: () => writeMagicLinkGrant,
|
|
@@ -10323,6 +10330,270 @@ async function readNoydbBundle(bytes, opts = {}) {
|
|
|
10323
10330
|
return { header, dumpJson: dump, autoUnlock };
|
|
10324
10331
|
}
|
|
10325
10332
|
|
|
10333
|
+
// src/bundle/export-accessible.ts
|
|
10334
|
+
function resolveAccessibleCollections(keyring, scope, writableOnly = false) {
|
|
10335
|
+
let collections;
|
|
10336
|
+
if (keyring.role === "operator" || keyring.role === "client") {
|
|
10337
|
+
collections = Object.entries(keyring.permissions).filter(([, mode]) => !writableOnly || mode === "rw").map(([c]) => c);
|
|
10338
|
+
}
|
|
10339
|
+
if (scope?.collections) {
|
|
10340
|
+
const allow = new Set(scope.collections);
|
|
10341
|
+
collections = (collections ?? [...scope.collections]).filter((c) => allow.has(c));
|
|
10342
|
+
}
|
|
10343
|
+
return collections;
|
|
10344
|
+
}
|
|
10345
|
+
async function buildAccessibleBundle(vault, collections, reKey, compression = "auto") {
|
|
10346
|
+
return writeNoydbBundle(vault, {
|
|
10347
|
+
compression,
|
|
10348
|
+
...collections !== void 0 ? { collections } : {},
|
|
10349
|
+
...reKey ? { exportPassphrase: reKey.passphrase } : {}
|
|
10350
|
+
});
|
|
10351
|
+
}
|
|
10352
|
+
async function exportAccessibleData(vault, opts = {}) {
|
|
10353
|
+
const { keyring } = vault._introspectState();
|
|
10354
|
+
const collections = resolveAccessibleCollections(keyring, opts.scope);
|
|
10355
|
+
const bytes = await buildAccessibleBundle(vault, collections, opts.reKey, opts.compression ?? "auto");
|
|
10356
|
+
await vault._getLedgerOrNull()?.append({
|
|
10357
|
+
op: "lifecycle",
|
|
10358
|
+
collection: "",
|
|
10359
|
+
id: "",
|
|
10360
|
+
version: 0,
|
|
10361
|
+
actor: keyring.userId,
|
|
10362
|
+
payloadHash: "",
|
|
10363
|
+
reason: `user-export:${keyring.userId}`
|
|
10364
|
+
});
|
|
10365
|
+
return bytes;
|
|
10366
|
+
}
|
|
10367
|
+
|
|
10368
|
+
// src/bundle/withdraw-accessible.ts
|
|
10369
|
+
init_crypto();
|
|
10370
|
+
init_errors();
|
|
10371
|
+
init_types();
|
|
10372
|
+
var FROZEN_SNAPSHOTS_COLLECTION = "_frozen_snapshots";
|
|
10373
|
+
var ENC = new TextEncoder();
|
|
10374
|
+
function randomId() {
|
|
10375
|
+
const b = globalThis.crypto.getRandomValues(new Uint8Array(12));
|
|
10376
|
+
return Array.from(b, (x) => x.toString(16).padStart(2, "0")).join("");
|
|
10377
|
+
}
|
|
10378
|
+
async function freezeAndDeleteClosure(vault, collections, opts) {
|
|
10379
|
+
const { name: vaultName, adapter } = vault._introspectState();
|
|
10380
|
+
const closure = [];
|
|
10381
|
+
for (const c of collections) {
|
|
10382
|
+
for (const id of await adapter.list(vaultName, c)) closure.push({ collection: c, id });
|
|
10383
|
+
}
|
|
10384
|
+
let snapshot;
|
|
10385
|
+
if (opts.disposition === "freeze") {
|
|
10386
|
+
const withdrawalId = opts.withdrawalId ?? `wd-${randomId()}`;
|
|
10387
|
+
const snap = {};
|
|
10388
|
+
for (const { collection, id } of closure) {
|
|
10389
|
+
const env = await adapter.get(vaultName, collection, id);
|
|
10390
|
+
if (env) (snap[collection] ??= {})[id] = env;
|
|
10391
|
+
}
|
|
10392
|
+
const frozenAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
10393
|
+
const body = JSON.stringify({ withdrawalId, frozenAt, by: opts.actorUserId, collections: snap });
|
|
10394
|
+
const sha = await sha256Hex(ENC.encode(body));
|
|
10395
|
+
await adapter.put(
|
|
10396
|
+
vaultName,
|
|
10397
|
+
FROZEN_SNAPSHOTS_COLLECTION,
|
|
10398
|
+
withdrawalId,
|
|
10399
|
+
{ _noydb: NOYDB_FORMAT_VERSION, _v: 1, _ts: frozenAt, _iv: "", _data: body, _by: opts.actorUserId },
|
|
10400
|
+
0
|
|
10401
|
+
);
|
|
10402
|
+
await vault._getLedgerOrNull()?.append({
|
|
10403
|
+
op: "lifecycle",
|
|
10404
|
+
collection: "",
|
|
10405
|
+
id: "",
|
|
10406
|
+
version: 0,
|
|
10407
|
+
actor: opts.actorUserId,
|
|
10408
|
+
payloadHash: "",
|
|
10409
|
+
reason: `withdrawal-frozen-snapshot:${withdrawalId}:${sha}`
|
|
10410
|
+
});
|
|
10411
|
+
snapshot = { withdrawalId, sha256: sha, recordCount: closure.length, frozenAt };
|
|
10412
|
+
}
|
|
10413
|
+
for (const { collection, id } of closure) {
|
|
10414
|
+
await vault.collection(collection).delete(id);
|
|
10415
|
+
}
|
|
10416
|
+
return snapshot;
|
|
10417
|
+
}
|
|
10418
|
+
async function withdrawAccessibleData(vault, opts) {
|
|
10419
|
+
const { keyring } = vault._introspectState();
|
|
10420
|
+
const disposition = opts.disposition ?? "delete";
|
|
10421
|
+
if (keyring.role === "owner" || keyring.role === "admin") {
|
|
10422
|
+
throw new ReadOnlyError(
|
|
10423
|
+
"unilateralWithdrawal is the scoped self-service path; an owner/admin should use extractPartition"
|
|
10424
|
+
);
|
|
10425
|
+
}
|
|
10426
|
+
if (keyring.role === "client" || keyring.role === "viewer") {
|
|
10427
|
+
throw new ReadOnlyError(
|
|
10428
|
+
"read-only role cannot self-serve a destructive withdrawal \u2014 use requestWithdrawal (two-party)"
|
|
10429
|
+
);
|
|
10430
|
+
}
|
|
10431
|
+
const collections = resolveAccessibleCollections(keyring, opts.scope, true);
|
|
10432
|
+
if (!collections || collections.length === 0) {
|
|
10433
|
+
throw new ReadOnlyError(
|
|
10434
|
+
"unilateralWithdrawal requires rw access on the withdrawn collections \u2014 use requestWithdrawal for read-only scope"
|
|
10435
|
+
);
|
|
10436
|
+
}
|
|
10437
|
+
const bundle = await buildAccessibleBundle(vault, collections, opts.reKey);
|
|
10438
|
+
const snapshot = await freezeAndDeleteClosure(vault, collections, {
|
|
10439
|
+
disposition,
|
|
10440
|
+
actorUserId: keyring.userId,
|
|
10441
|
+
...opts.withdrawalId ? { withdrawalId: opts.withdrawalId } : {}
|
|
10442
|
+
});
|
|
10443
|
+
await vault._getLedgerOrNull()?.append({
|
|
10444
|
+
op: "lifecycle",
|
|
10445
|
+
collection: "",
|
|
10446
|
+
id: "",
|
|
10447
|
+
version: 0,
|
|
10448
|
+
actor: keyring.userId,
|
|
10449
|
+
payloadHash: "",
|
|
10450
|
+
reason: `user-unilateral-withdrawal:${keyring.userId}:${disposition}:${opts.legalBasis}`
|
|
10451
|
+
});
|
|
10452
|
+
return snapshot ? { bundle, snapshot } : { bundle };
|
|
10453
|
+
}
|
|
10454
|
+
|
|
10455
|
+
// src/bundle/request-withdrawal.ts
|
|
10456
|
+
init_types();
|
|
10457
|
+
init_errors();
|
|
10458
|
+
var WITHDRAWAL_REQUESTS_COLLECTION = "_user_withdrawal_requests";
|
|
10459
|
+
var WithdrawalRequestError = class extends NoydbError {
|
|
10460
|
+
constructor(message) {
|
|
10461
|
+
super("WITHDRAWAL_REQUEST", message);
|
|
10462
|
+
this.name = "WithdrawalRequestError";
|
|
10463
|
+
}
|
|
10464
|
+
};
|
|
10465
|
+
function writeRequest(vault, req, expectedVersion) {
|
|
10466
|
+
const { name: vaultName, adapter } = vault._introspectState();
|
|
10467
|
+
const body = JSON.stringify(req);
|
|
10468
|
+
const env = {
|
|
10469
|
+
_noydb: NOYDB_FORMAT_VERSION,
|
|
10470
|
+
_v: expectedVersion + 1,
|
|
10471
|
+
_ts: req.decidedAt ?? req.requestedAt,
|
|
10472
|
+
_iv: "",
|
|
10473
|
+
_data: body,
|
|
10474
|
+
_by: req.decidedBy ?? req.requester
|
|
10475
|
+
};
|
|
10476
|
+
return adapter.put(vaultName, WITHDRAWAL_REQUESTS_COLLECTION, req.requestId, env, expectedVersion);
|
|
10477
|
+
}
|
|
10478
|
+
async function readRequest(vault, requestId) {
|
|
10479
|
+
const { name: vaultName, adapter } = vault._introspectState();
|
|
10480
|
+
const env = await adapter.get(vaultName, WITHDRAWAL_REQUESTS_COLLECTION, requestId);
|
|
10481
|
+
if (!env) throw new WithdrawalRequestError(`withdrawal request "${requestId}" not found`);
|
|
10482
|
+
return { req: JSON.parse(env._data), version: env._v };
|
|
10483
|
+
}
|
|
10484
|
+
async function requestWithdrawal(vault, opts = {}) {
|
|
10485
|
+
const { keyring } = vault._introspectState();
|
|
10486
|
+
const collections = resolveAccessibleCollections(keyring, opts.scope, false);
|
|
10487
|
+
if (!collections || collections.length === 0) {
|
|
10488
|
+
throw new WithdrawalRequestError(
|
|
10489
|
+
"requestWithdrawal needs a concrete scope.collections (the caller has all-collection access \u2014 name the collections to withdraw)"
|
|
10490
|
+
);
|
|
10491
|
+
}
|
|
10492
|
+
const requestId = `wr-${randomId()}`;
|
|
10493
|
+
const requestedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
10494
|
+
const req = {
|
|
10495
|
+
requestId,
|
|
10496
|
+
requester: keyring.userId,
|
|
10497
|
+
collections,
|
|
10498
|
+
disposition: opts.disposition ?? "delete",
|
|
10499
|
+
status: "pending",
|
|
10500
|
+
requestedAt,
|
|
10501
|
+
...opts.legalBasis ? { legalBasis: opts.legalBasis } : {},
|
|
10502
|
+
...opts.expiresInMs ? { expiresAt: new Date(Date.parse(requestedAt) + opts.expiresInMs).toISOString() } : {}
|
|
10503
|
+
};
|
|
10504
|
+
await writeRequest(vault, req, 0);
|
|
10505
|
+
await vault._getLedgerOrNull()?.append({
|
|
10506
|
+
op: "lifecycle",
|
|
10507
|
+
collection: "",
|
|
10508
|
+
id: "",
|
|
10509
|
+
version: 0,
|
|
10510
|
+
actor: keyring.userId,
|
|
10511
|
+
payloadHash: "",
|
|
10512
|
+
reason: `user-withdrawal-request:${requestId}:${keyring.userId}`
|
|
10513
|
+
});
|
|
10514
|
+
return { requestId, status: "pending", ...req.expiresAt ? { expiresAt: req.expiresAt } : {} };
|
|
10515
|
+
}
|
|
10516
|
+
async function listWithdrawalRequests(vault, opts = {}) {
|
|
10517
|
+
const { name: vaultName, adapter } = vault._introspectState();
|
|
10518
|
+
const ids = await adapter.list(vaultName, WITHDRAWAL_REQUESTS_COLLECTION);
|
|
10519
|
+
const out = [];
|
|
10520
|
+
for (const id of ids) {
|
|
10521
|
+
const env = await adapter.get(vaultName, WITHDRAWAL_REQUESTS_COLLECTION, id);
|
|
10522
|
+
if (!env) continue;
|
|
10523
|
+
const req = JSON.parse(env._data);
|
|
10524
|
+
if (!opts.status || req.status === opts.status) out.push(req);
|
|
10525
|
+
}
|
|
10526
|
+
return out;
|
|
10527
|
+
}
|
|
10528
|
+
function assertApprover(vault) {
|
|
10529
|
+
const { keyring } = vault._introspectState();
|
|
10530
|
+
if (keyring.role !== "owner" && keyring.role !== "admin") {
|
|
10531
|
+
throw new WithdrawalRequestError("approveWithdrawal / rejectWithdrawal require an owner or admin");
|
|
10532
|
+
}
|
|
10533
|
+
return keyring.userId;
|
|
10534
|
+
}
|
|
10535
|
+
function assertPending(req) {
|
|
10536
|
+
if (req.status !== "pending") {
|
|
10537
|
+
throw new WithdrawalRequestError(`withdrawal request "${req.requestId}" is already ${req.status}`);
|
|
10538
|
+
}
|
|
10539
|
+
if (req.expiresAt && Date.now() > Date.parse(req.expiresAt)) {
|
|
10540
|
+
throw new WithdrawalRequestError(`withdrawal request "${req.requestId}" expired at ${req.expiresAt}`);
|
|
10541
|
+
}
|
|
10542
|
+
}
|
|
10543
|
+
async function approveWithdrawal(vault, requestId, opts = {}) {
|
|
10544
|
+
const approver = assertApprover(vault);
|
|
10545
|
+
const { req, version } = await readRequest(vault, requestId);
|
|
10546
|
+
assertPending(req);
|
|
10547
|
+
const bundle = await buildAccessibleBundle(vault, [...req.collections], opts.reKey);
|
|
10548
|
+
const snapshot = await freezeAndDeleteClosure(vault, req.collections, {
|
|
10549
|
+
disposition: req.disposition,
|
|
10550
|
+
actorUserId: approver,
|
|
10551
|
+
withdrawalId: `wd-${requestId}`
|
|
10552
|
+
});
|
|
10553
|
+
const decidedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
10554
|
+
await writeRequest(vault, {
|
|
10555
|
+
...req,
|
|
10556
|
+
status: "approved",
|
|
10557
|
+
decidedAt,
|
|
10558
|
+
decidedBy: approver,
|
|
10559
|
+
...snapshot ? { snapshotSha256: snapshot.sha256 } : {}
|
|
10560
|
+
}, version);
|
|
10561
|
+
await vault._getLedgerOrNull()?.append({
|
|
10562
|
+
op: "lifecycle",
|
|
10563
|
+
collection: "",
|
|
10564
|
+
id: "",
|
|
10565
|
+
version: 0,
|
|
10566
|
+
actor: approver,
|
|
10567
|
+
payloadHash: "",
|
|
10568
|
+
reason: `user-withdrawal-approved:${requestId}:${req.requester}:${req.disposition}`
|
|
10569
|
+
});
|
|
10570
|
+
return snapshot ? { bundle, snapshot } : { bundle };
|
|
10571
|
+
}
|
|
10572
|
+
async function rejectWithdrawal(vault, requestId, opts = {}) {
|
|
10573
|
+
const approver = assertApprover(vault);
|
|
10574
|
+
const { req, version } = await readRequest(vault, requestId);
|
|
10575
|
+
assertPending(req);
|
|
10576
|
+
const decidedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
10577
|
+
const updated = {
|
|
10578
|
+
...req,
|
|
10579
|
+
status: "rejected",
|
|
10580
|
+
decidedAt,
|
|
10581
|
+
decidedBy: approver,
|
|
10582
|
+
...opts.reason ? { rejectReason: opts.reason } : {}
|
|
10583
|
+
};
|
|
10584
|
+
await writeRequest(vault, updated, version);
|
|
10585
|
+
await vault._getLedgerOrNull()?.append({
|
|
10586
|
+
op: "lifecycle",
|
|
10587
|
+
collection: "",
|
|
10588
|
+
id: "",
|
|
10589
|
+
version: 0,
|
|
10590
|
+
actor: approver,
|
|
10591
|
+
payloadHash: "",
|
|
10592
|
+
reason: `user-withdrawal-rejected:${requestId}:${req.requester}`
|
|
10593
|
+
});
|
|
10594
|
+
return updated;
|
|
10595
|
+
}
|
|
10596
|
+
|
|
10326
10597
|
// src/index.ts
|
|
10327
10598
|
init_ulid();
|
|
10328
10599
|
|
|
@@ -12482,20 +12753,99 @@ init_public_envelope();
|
|
|
12482
12753
|
|
|
12483
12754
|
// src/meta/user-envelope/api.ts
|
|
12484
12755
|
var UserApi = class {
|
|
12485
|
-
constructor(adapter, vaultName, writerKeyringId, getDek, checkGate2) {
|
|
12756
|
+
constructor(adapter, vaultName, writerKeyringId, getDek, checkGate2, exportAccessible, unilateralWithdraw, requestWithdraw, listWithdrawals, approveWithdraw, rejectWithdraw) {
|
|
12486
12757
|
this.adapter = adapter;
|
|
12487
12758
|
this.vaultName = vaultName;
|
|
12488
12759
|
this.writerKeyringId = writerKeyringId;
|
|
12489
12760
|
this.getDek = getDek;
|
|
12490
12761
|
this.checkGate = checkGate2;
|
|
12762
|
+
this.exportAccessible = exportAccessible;
|
|
12763
|
+
this.unilateralWithdraw = unilateralWithdraw;
|
|
12764
|
+
this.requestWithdraw = requestWithdraw;
|
|
12765
|
+
this.listWithdrawals = listWithdrawals;
|
|
12766
|
+
this.approveWithdraw = approveWithdraw;
|
|
12767
|
+
this.rejectWithdraw = rejectWithdraw;
|
|
12491
12768
|
}
|
|
12492
12769
|
adapter;
|
|
12493
12770
|
vaultName;
|
|
12494
12771
|
writerKeyringId;
|
|
12495
12772
|
getDek;
|
|
12496
12773
|
checkGate;
|
|
12774
|
+
exportAccessible;
|
|
12775
|
+
unilateralWithdraw;
|
|
12776
|
+
requestWithdraw;
|
|
12777
|
+
listWithdrawals;
|
|
12778
|
+
approveWithdraw;
|
|
12779
|
+
rejectWithdraw;
|
|
12497
12780
|
/** keyringId → set of listeners. Wildcard '*' fires on every change. */
|
|
12498
12781
|
listeners = /* @__PURE__ */ new Map();
|
|
12782
|
+
/**
|
|
12783
|
+
* #199 P3 — file a two-party withdrawal request for the caller's accessible
|
|
12784
|
+
* scope. Non-destructive (writes a pending request); an owner later approves
|
|
12785
|
+
* or rejects. This is the path for read-only roles (`client`/`viewer`) that
|
|
12786
|
+
* cannot self-serve a destructive `unilateralWithdrawal`. Gated by
|
|
12787
|
+
* `user-request-withdrawal` (enabled by default).
|
|
12788
|
+
*/
|
|
12789
|
+
async requestWithdrawal(opts = {}) {
|
|
12790
|
+
if (this.checkGate) await this.checkGate("user-request-withdrawal");
|
|
12791
|
+
if (!this.requestWithdraw) {
|
|
12792
|
+
throw new Error("requestWithdrawal requires a Noydb-backed vault (not a bare UserApi)");
|
|
12793
|
+
}
|
|
12794
|
+
return this.requestWithdraw(opts);
|
|
12795
|
+
}
|
|
12796
|
+
/** #199 P3 — owner side: list filed withdrawal requests (optionally by status). */
|
|
12797
|
+
async listWithdrawalRequests(opts = {}) {
|
|
12798
|
+
if (!this.listWithdrawals) {
|
|
12799
|
+
throw new Error("listWithdrawalRequests requires a Noydb-backed vault (not a bare UserApi)");
|
|
12800
|
+
}
|
|
12801
|
+
return this.listWithdrawals(opts);
|
|
12802
|
+
}
|
|
12803
|
+
/**
|
|
12804
|
+
* #199 P3 — owner side: approve a pending request. Extracts the requester's
|
|
12805
|
+
* recorded scope under firm authority, disposes of the source per the
|
|
12806
|
+
* request's disposition, and returns the re-keyed bundle to hand back. Gated
|
|
12807
|
+
* by `approve-user-withdrawal` (tier-2 default) + owner/admin role.
|
|
12808
|
+
*/
|
|
12809
|
+
async approveWithdrawal(requestId, opts = {}) {
|
|
12810
|
+
if (this.checkGate) await this.checkGate("approve-user-withdrawal");
|
|
12811
|
+
if (!this.approveWithdraw) {
|
|
12812
|
+
throw new Error("approveWithdrawal requires a Noydb-backed vault (not a bare UserApi)");
|
|
12813
|
+
}
|
|
12814
|
+
return this.approveWithdraw(requestId, opts);
|
|
12815
|
+
}
|
|
12816
|
+
/** #199 P3 — owner side: reject a pending request (no data is touched). */
|
|
12817
|
+
async rejectWithdrawal(requestId, opts = {}) {
|
|
12818
|
+
if (this.checkGate) await this.checkGate("approve-user-withdrawal");
|
|
12819
|
+
if (!this.rejectWithdraw) {
|
|
12820
|
+
throw new Error("rejectWithdrawal requires a Noydb-backed vault (not a bare UserApi)");
|
|
12821
|
+
}
|
|
12822
|
+
return this.rejectWithdraw(requestId, opts);
|
|
12823
|
+
}
|
|
12824
|
+
/**
|
|
12825
|
+
* #199 P2 — single-party withdrawal: export the caller's accessible scope
|
|
12826
|
+
* (re-keyed) and dispose of the source (`delete` or `freeze`). Gated by the
|
|
12827
|
+
* fail-closed built-in `client-unilateral-withdraw` policy — undefined or
|
|
12828
|
+
* disabled → throws (use `requestWithdrawal`). The firm enables it at vault
|
|
12829
|
+
* creation.
|
|
12830
|
+
*/
|
|
12831
|
+
async unilateralWithdrawal(opts) {
|
|
12832
|
+
if (this.checkGate) await this.checkGate("client-unilateral-withdraw");
|
|
12833
|
+
if (!this.unilateralWithdraw) {
|
|
12834
|
+
throw new Error("unilateralWithdrawal requires a Noydb-backed vault (not a bare UserApi)");
|
|
12835
|
+
}
|
|
12836
|
+
return this.unilateralWithdraw(opts);
|
|
12837
|
+
}
|
|
12838
|
+
/**
|
|
12839
|
+
* #199 — export the calling user's accessible scope as a portable, re-keyed
|
|
12840
|
+
* `.noydb` bundle. Non-destructive and **always allowed** (data sovereignty
|
|
12841
|
+
* by construction, §11.11) but audited. Scope = the caller's DEK access set.
|
|
12842
|
+
*/
|
|
12843
|
+
async exportMyAccessibleData(opts = {}) {
|
|
12844
|
+
if (!this.exportAccessible) {
|
|
12845
|
+
throw new Error("exportMyAccessibleData requires a Noydb-backed vault (not a bare UserApi)");
|
|
12846
|
+
}
|
|
12847
|
+
return this.exportAccessible(opts);
|
|
12848
|
+
}
|
|
12499
12849
|
// ─── Write-self ──────────────────────────────────────────────────────
|
|
12500
12850
|
/** Read the writer's own envelope. Returns null if never written. */
|
|
12501
12851
|
async me() {
|
|
@@ -21932,7 +22282,13 @@ var Vault = class {
|
|
|
21932
22282
|
this.name,
|
|
21933
22283
|
this.keyring.userId,
|
|
21934
22284
|
() => this.getDEK(USER_ENVELOPE_COLLECTION),
|
|
21935
|
-
(gate, presented) => this.noydb.checkGate(this.name, gate, presented)
|
|
22285
|
+
(gate, presented) => this.noydb.checkGate(this.name, gate, presented),
|
|
22286
|
+
(opts2) => exportAccessibleData(this, opts2),
|
|
22287
|
+
(opts2) => withdrawAccessibleData(this, opts2),
|
|
22288
|
+
(opts2) => requestWithdrawal(this, opts2),
|
|
22289
|
+
(opts2) => listWithdrawalRequests(this, opts2),
|
|
22290
|
+
(requestId, opts2) => approveWithdrawal(this, requestId, opts2),
|
|
22291
|
+
(requestId, opts2) => rejectWithdrawal(this, requestId, opts2)
|
|
21936
22292
|
);
|
|
21937
22293
|
}
|
|
21938
22294
|
/**
|
|
@@ -24323,6 +24679,7 @@ var Vault = class {
|
|
|
24323
24679
|
collectionCache: this.collectionCache,
|
|
24324
24680
|
refRegistry: this.refRegistry,
|
|
24325
24681
|
getDEK: this.getDEK,
|
|
24682
|
+
keyring: this.keyring,
|
|
24326
24683
|
subsystems: {
|
|
24327
24684
|
guards: this.guardRegistry !== null,
|
|
24328
24685
|
derivations: this.derivationRegistry !== null,
|
|
@@ -25675,7 +26032,18 @@ var PERSONAL_POLICY = Object.freeze({
|
|
|
25675
26032
|
// Setting `enabled: false` makes vault.user.list() return only
|
|
25676
26033
|
// self (privacy-strict opt-out).
|
|
25677
26034
|
"edit-own-profile": { minTier: 3 },
|
|
25678
|
-
"view-team-profiles": { minTier: 2 }
|
|
26035
|
+
"view-team-profiles": { minTier: 2 },
|
|
26036
|
+
// client-unilateral-withdraw: a non-owner's self-service DESTRUCTIVE
|
|
26037
|
+
// withdrawal (export-and-delete/freeze, #199). Fail-closed by default —
|
|
26038
|
+
// the firm opts in per jurisdiction/contract (e.g. GDPR Art. 17).
|
|
26039
|
+
// Listed explicitly (not just relying on the built-in default) so it is
|
|
26040
|
+
// discoverable in describeGate / policy dumps.
|
|
26041
|
+
"client-unilateral-withdraw": { minTier: 1, enabled: false },
|
|
26042
|
+
// Two-party withdrawal (#199 P3): filing a request is non-destructive
|
|
26043
|
+
// (tier-1, enabled so a read-only client can ask); deciding it is the
|
|
26044
|
+
// destructive step (tier-2 floor + owner/admin role, enforced in code).
|
|
26045
|
+
"user-request-withdrawal": { minTier: 1 },
|
|
26046
|
+
"approve-user-withdrawal": { minTier: 2 }
|
|
25679
26047
|
}
|
|
25680
26048
|
});
|
|
25681
26049
|
var STRICT_POLICY = Object.freeze({
|
|
@@ -25774,7 +26142,24 @@ var STRICT_POLICY = Object.freeze({
|
|
|
25774
26142
|
minTier: 2,
|
|
25775
26143
|
factors: [{ anyOf: ["totp", "email-otp"] }]
|
|
25776
26144
|
},
|
|
25777
|
-
"view-team-profiles": { minTier: 2 }
|
|
26145
|
+
"view-team-profiles": { minTier: 2 },
|
|
26146
|
+
// STRICT: still fail-closed, but if a regulated firm flips enabled:true
|
|
26147
|
+
// they inherit a two-factor proof + shared-device block for the
|
|
26148
|
+
// destructive withdrawal (mirrors export-plaintext's hardening).
|
|
26149
|
+
"client-unilateral-withdraw": {
|
|
26150
|
+
minTier: 1,
|
|
26151
|
+
enabled: false,
|
|
26152
|
+
factors: [{ anyOf: ["totp", "email-otp"], count: 2 }],
|
|
26153
|
+
warn: { sharedDevice: "block" }
|
|
26154
|
+
},
|
|
26155
|
+
// STRICT: filing stays tier-1; the destructive APPROVE demands an
|
|
26156
|
+
// off-device factor + shared-device block (mirrors export-bundle).
|
|
26157
|
+
"user-request-withdrawal": { minTier: 1 },
|
|
26158
|
+
"approve-user-withdrawal": {
|
|
26159
|
+
minTier: 2,
|
|
26160
|
+
factors: [{ anyOf: ["totp", "email-otp"] }],
|
|
26161
|
+
warn: { sharedDevice: "block" }
|
|
26162
|
+
}
|
|
25778
26163
|
}
|
|
25779
26164
|
});
|
|
25780
26165
|
function mergePolicy(base, override) {
|
|
@@ -30254,6 +30639,7 @@ function shortJSON(value) {
|
|
|
30254
30639
|
VaultInstant,
|
|
30255
30640
|
VaultTemplateNotFoundError,
|
|
30256
30641
|
WeakPassphraseError,
|
|
30642
|
+
WithdrawalRequestError,
|
|
30257
30643
|
activeSessionCount,
|
|
30258
30644
|
additiveOnly,
|
|
30259
30645
|
aesGcmOpen,
|
|
@@ -30261,6 +30647,7 @@ function shortJSON(value) {
|
|
|
30261
30647
|
applyI18nLocale,
|
|
30262
30648
|
applyJoins,
|
|
30263
30649
|
applyPatch,
|
|
30650
|
+
approveWithdrawal,
|
|
30264
30651
|
asMoney,
|
|
30265
30652
|
assertStrongPassphrase,
|
|
30266
30653
|
assertTierAccess,
|
|
@@ -30319,6 +30706,7 @@ function shortJSON(value) {
|
|
|
30319
30706
|
evaluateFieldClause,
|
|
30320
30707
|
evaluateImportCapability,
|
|
30321
30708
|
executePlan,
|
|
30709
|
+
exportAccessibleData,
|
|
30322
30710
|
findAuthenticator,
|
|
30323
30711
|
formatDiff,
|
|
30324
30712
|
generateULID,
|
|
@@ -30357,6 +30745,7 @@ function shortJSON(value) {
|
|
|
30357
30745
|
listUserEnvelopeIds,
|
|
30358
30746
|
listUsers,
|
|
30359
30747
|
listUsersWithEnvelopes,
|
|
30748
|
+
listWithdrawalRequests,
|
|
30360
30749
|
loadActiveDelegations,
|
|
30361
30750
|
loadDevUnlock,
|
|
30362
30751
|
loadPaperRecoveryEntries,
|
|
@@ -30401,7 +30790,9 @@ function shortJSON(value) {
|
|
|
30401
30790
|
reduceRecords,
|
|
30402
30791
|
ref,
|
|
30403
30792
|
refArray,
|
|
30793
|
+
rejectWithdrawal,
|
|
30404
30794
|
removeAuthenticator,
|
|
30795
|
+
requestWithdrawal,
|
|
30405
30796
|
resetBrotliSupportCache,
|
|
30406
30797
|
resetJoinWarnings,
|
|
30407
30798
|
resolveCrdtSnapshot,
|
|
@@ -30454,6 +30845,7 @@ function shortJSON(value) {
|
|
|
30454
30845
|
withOverlayedView,
|
|
30455
30846
|
withRetry,
|
|
30456
30847
|
withRollup,
|
|
30848
|
+
withdrawAccessibleData,
|
|
30457
30849
|
wrapBundleStore,
|
|
30458
30850
|
wrapStore,
|
|
30459
30851
|
writeMagicLinkGrant,
|