@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.
Files changed (69) hide show
  1. package/dist/attestation/index.d.cts +1 -1
  2. package/dist/attestation/index.d.ts +1 -1
  3. package/dist/blobs/index.d.cts +3 -3
  4. package/dist/blobs/index.d.ts +3 -3
  5. package/dist/bundle/index.cjs +21664 -21247
  6. package/dist/bundle/index.cjs.map +1 -1
  7. package/dist/bundle/index.d.cts +3 -3
  8. package/dist/bundle/index.d.ts +3 -3
  9. package/dist/bundle/index.js +1 -1
  10. package/dist/{chunk-7PH4OPBZ.js → chunk-P65YMN5V.js} +388 -5
  11. package/dist/chunk-P65YMN5V.js.map +1 -0
  12. package/dist/consent/index.d.cts +2 -2
  13. package/dist/consent/index.d.ts +2 -2
  14. package/dist/derivations/index.d.cts +3 -3
  15. package/dist/derivations/index.d.ts +3 -3
  16. package/dist/{dev-unlock-CY0HIZA0.d.cts → dev-unlock-Bw7iBD1D.d.cts} +1 -1
  17. package/dist/{dev-unlock-CpKSkl2c.d.ts → dev-unlock-DzDzLTdZ.d.ts} +1 -1
  18. package/dist/guards/index.d.cts +3 -3
  19. package/dist/guards/index.d.ts +3 -3
  20. package/dist/{hash-BSd0-_L8.d.cts → hash-C52X_-m5.d.cts} +1 -1
  21. package/dist/{hash-BnBQx39y.d.ts → hash-DepR-xVc.d.ts} +1 -1
  22. package/dist/history/index.d.cts +3 -3
  23. package/dist/history/index.d.ts +3 -3
  24. package/dist/i18n/index.d.cts +2 -2
  25. package/dist/i18n/index.d.ts +2 -2
  26. package/dist/index.cjs +396 -4
  27. package/dist/index.cjs.map +1 -1
  28. package/dist/index.d.cts +10 -10
  29. package/dist/index.d.ts +10 -10
  30. package/dist/index.js +16 -2
  31. package/dist/index.js.map +1 -1
  32. package/dist/materialized-views/index.d.cts +3 -3
  33. package/dist/materialized-views/index.d.ts +3 -3
  34. package/dist/{mime-magic-BnJCGJzB.d.cts → mime-magic-Cxf9B_Dm.d.cts} +1 -1
  35. package/dist/{mime-magic-CjSyakO4.d.ts → mime-magic-Dejetix_.d.ts} +1 -1
  36. package/dist/{noydb-ZZCRF6TE.js → noydb-VGR2HLDB.js} +3 -2
  37. package/dist/overlay-views/index.d.cts +3 -3
  38. package/dist/overlay-views/index.d.ts +3 -3
  39. package/dist/periods/index.d.cts +2 -2
  40. package/dist/periods/index.d.ts +2 -2
  41. package/dist/session/index.d.cts +3 -3
  42. package/dist/session/index.d.ts +3 -3
  43. package/dist/shadow/index.d.cts +2 -2
  44. package/dist/shadow/index.d.ts +2 -2
  45. package/dist/snapshots/index.d.cts +2 -2
  46. package/dist/snapshots/index.d.ts +2 -2
  47. package/dist/store/index.d.cts +2 -2
  48. package/dist/store/index.d.ts +2 -2
  49. package/dist/sync/index.d.cts +1 -1
  50. package/dist/sync/index.d.ts +1 -1
  51. package/dist/team/index.d.cts +2 -2
  52. package/dist/team/index.d.ts +2 -2
  53. package/dist/{transition-guard-Dmpqzg-_.d.cts → transition-guard-BcLyTGYq.d.cts} +1 -1
  54. package/dist/{transition-guard-D4bfIAiW.d.ts → transition-guard-Ctxapq1b.d.ts} +1 -1
  55. package/dist/tx/index.d.cts +2 -2
  56. package/dist/tx/index.d.ts +2 -2
  57. package/dist/{types-DyOI6XZ_.d.cts → types-Bhs2i_Ll.d.cts} +260 -4
  58. package/dist/{types-DLfWFr6U.d.ts → types-DONgts0n.d.ts} +260 -4
  59. package/dist/{ulid-LaxfH2tK.d.cts → ulid-Bg-IBJyA.d.cts} +1 -1
  60. package/dist/{ulid-B2L_aqVA.d.ts → ulid-Dwt3JEcy.d.ts} +1 -1
  61. package/dist/{with-materialized-view-CeZYGJVf.d.cts → with-materialized-view-BYb3p9wT.d.cts} +1 -1
  62. package/dist/{with-materialized-view-DNULSxoP.d.ts → with-materialized-view-CyVLOr09.d.ts} +1 -1
  63. package/dist/{with-overlayed-view-C9joG7UZ.d.ts → with-overlayed-view-BhLRxqwI.d.ts} +1 -1
  64. package/dist/{with-overlayed-view-kdcPGHih.d.cts → with-overlayed-view-LGrQ984e.d.cts} +1 -1
  65. package/dist/{with-rollup-s58XAeWO.d.cts → with-rollup-Bj8c7ttB.d.cts} +1 -1
  66. package/dist/{with-rollup-DJDbrxjf.d.ts → with-rollup-CO8ibRcK.d.ts} +1 -1
  67. package/package.json +3 -3
  68. package/dist/chunk-7PH4OPBZ.js.map +0 -1
  69. /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,