mindoodb 0.0.86 → 0.0.89

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 (127) hide show
  1. package/README-server.md +5 -2
  2. package/dist/appendonlystores/network/ClientNetworkContentAddressedStore.d.ts.map +1 -1
  3. package/dist/appendonlystores/network/ClientNetworkContentAddressedStore.js +2 -1
  4. package/dist/appendonlystores/network/ClientNetworkContentAddressedStore.js.map +1 -1
  5. package/dist/appendonlystores/network/ServerNetworkContentAddressedStore.d.ts +154 -12
  6. package/dist/appendonlystores/network/ServerNetworkContentAddressedStore.d.ts.map +1 -1
  7. package/dist/appendonlystores/network/ServerNetworkContentAddressedStore.js +348 -24
  8. package/dist/appendonlystores/network/ServerNetworkContentAddressedStore.js.map +1 -1
  9. package/dist/browser/index.mjs +16 -0
  10. package/dist/core/BaseMindooDB.d.ts +67 -9
  11. package/dist/core/BaseMindooDB.d.ts.map +1 -1
  12. package/dist/core/BaseMindooDB.js +261 -12
  13. package/dist/core/BaseMindooDB.js.map +1 -1
  14. package/dist/core/BaseMindooTenant.d.ts +29 -0
  15. package/dist/core/BaseMindooTenant.d.ts.map +1 -1
  16. package/dist/core/BaseMindooTenant.js +111 -6
  17. package/dist/core/BaseMindooTenant.js.map +1 -1
  18. package/dist/core/BaseMindooTenantDirectory.d.ts +97 -0
  19. package/dist/core/BaseMindooTenantDirectory.d.ts.map +1 -1
  20. package/dist/core/BaseMindooTenantDirectory.js +312 -0
  21. package/dist/core/BaseMindooTenantDirectory.js.map +1 -1
  22. package/dist/core/BaseMindooTenantFactory.d.ts.map +1 -1
  23. package/dist/core/BaseMindooTenantFactory.js +2 -1
  24. package/dist/core/BaseMindooTenantFactory.js.map +1 -1
  25. package/dist/core/MindooDBServerAdmin.d.ts.map +1 -1
  26. package/dist/core/MindooDBServerAdmin.js +2 -1
  27. package/dist/core/MindooDBServerAdmin.js.map +1 -1
  28. package/dist/core/accesscontrol/directoryProjection.d.ts.map +1 -1
  29. package/dist/core/accesscontrol/directoryProjection.js +23 -0
  30. package/dist/core/accesscontrol/directoryProjection.js.map +1 -1
  31. package/dist/core/accesscontrol/evaluate.d.ts +14 -0
  32. package/dist/core/accesscontrol/evaluate.d.ts.map +1 -1
  33. package/dist/core/accesscontrol/evaluate.js +42 -1
  34. package/dist/core/accesscontrol/evaluate.js.map +1 -1
  35. package/dist/core/accesscontrol/types.d.ts +110 -4
  36. package/dist/core/accesscontrol/types.d.ts.map +1 -1
  37. package/dist/core/accesscontrol/types.js +135 -1
  38. package/dist/core/accesscontrol/types.js.map +1 -1
  39. package/dist/core/appendonlystores/network/AuthenticationService.d.ts +6 -0
  40. package/dist/core/appendonlystores/network/AuthenticationService.d.ts.map +1 -1
  41. package/dist/core/appendonlystores/network/AuthenticationService.js +8 -7
  42. package/dist/core/appendonlystores/network/AuthenticationService.js.map +1 -1
  43. package/dist/core/appendonlystores/network/IrohNetworkTransport.d.ts.map +1 -1
  44. package/dist/core/appendonlystores/network/IrohNetworkTransport.js +12 -0
  45. package/dist/core/appendonlystores/network/IrohNetworkTransport.js.map +1 -1
  46. package/dist/core/appendonlystores/rpc/RpcContentAddressedStore.d.ts +6 -0
  47. package/dist/core/appendonlystores/rpc/RpcContentAddressedStore.d.ts.map +1 -1
  48. package/dist/core/appendonlystores/rpc/RpcContentAddressedStore.js +20 -1
  49. package/dist/core/appendonlystores/rpc/RpcContentAddressedStore.js.map +1 -1
  50. package/dist/core/appendonlystores/sqlite/SqliteContentAddressedStore.d.ts +7 -0
  51. package/dist/core/appendonlystores/sqlite/SqliteContentAddressedStore.d.ts.map +1 -1
  52. package/dist/core/appendonlystores/sqlite/SqliteContentAddressedStore.js +18 -0
  53. package/dist/core/appendonlystores/sqlite/SqliteContentAddressedStore.js.map +1 -1
  54. package/dist/core/appendonlystores/syncStores.d.ts.map +1 -1
  55. package/dist/core/appendonlystores/syncStores.js +25 -5
  56. package/dist/core/appendonlystores/syncStores.js.map +1 -1
  57. package/dist/core/appendonlystores/types.d.ts +7 -1
  58. package/dist/core/appendonlystores/types.d.ts.map +1 -1
  59. package/dist/core/appendonlystores/types.js +1 -0
  60. package/dist/core/appendonlystores/types.js.map +1 -1
  61. package/dist/core/builtinDbInvariants.d.ts +53 -0
  62. package/dist/core/builtinDbInvariants.d.ts.map +1 -1
  63. package/dist/core/builtinDbInvariants.js +90 -1
  64. package/dist/core/builtinDbInvariants.js.map +1 -1
  65. package/dist/core/crypto/AuthChallenge.d.ts +68 -0
  66. package/dist/core/crypto/AuthChallenge.d.ts.map +1 -0
  67. package/dist/core/crypto/AuthChallenge.js +118 -0
  68. package/dist/core/crypto/AuthChallenge.js.map +1 -0
  69. package/dist/core/crypto/DomainStatement.d.ts +5 -3
  70. package/dist/core/crypto/DomainStatement.d.ts.map +1 -1
  71. package/dist/core/crypto/DomainStatement.js +5 -3
  72. package/dist/core/crypto/DomainStatement.js.map +1 -1
  73. package/dist/core/crypto/encryptedFields.d.ts +11 -0
  74. package/dist/core/crypto/encryptedFields.d.ts.map +1 -1
  75. package/dist/core/crypto/encryptedFields.js +18 -0
  76. package/dist/core/crypto/encryptedFields.js.map +1 -1
  77. package/dist/core/index.d.ts +3 -3
  78. package/dist/core/index.d.ts.map +1 -1
  79. package/dist/core/index.js +19 -6
  80. package/dist/core/index.js.map +1 -1
  81. package/dist/core/query/executeQuery.d.ts.map +1 -1
  82. package/dist/core/query/executeQuery.js +5 -1
  83. package/dist/core/query/executeQuery.js.map +1 -1
  84. package/dist/core/query/executeQueryInclude.d.ts.map +1 -1
  85. package/dist/core/query/executeQueryInclude.js +2 -0
  86. package/dist/core/query/executeQueryInclude.js.map +1 -1
  87. package/dist/core/query/queryView.d.ts.map +1 -1
  88. package/dist/core/query/queryView.js +5 -1
  89. package/dist/core/query/queryView.js.map +1 -1
  90. package/dist/core/types.d.ts +143 -8
  91. package/dist/core/types.d.ts.map +1 -1
  92. package/dist/core/types.js.map +1 -1
  93. package/dist/core/userkeys/index.d.ts +1 -0
  94. package/dist/core/userkeys/index.d.ts.map +1 -1
  95. package/dist/core/userkeys/index.js +5 -1
  96. package/dist/core/userkeys/index.js.map +1 -1
  97. package/dist/core/userkeys/sealedValues.d.ts +54 -0
  98. package/dist/core/userkeys/sealedValues.d.ts.map +1 -0
  99. package/dist/core/userkeys/sealedValues.js +67 -0
  100. package/dist/core/userkeys/sealedValues.js.map +1 -0
  101. package/dist/core/utils/dynamicImportInterop.d.ts +10 -0
  102. package/dist/core/utils/dynamicImportInterop.d.ts.map +1 -0
  103. package/dist/core/utils/dynamicImportInterop.js +22 -0
  104. package/dist/core/utils/dynamicImportInterop.js.map +1 -0
  105. package/dist/node/appendonlystores/BasicOnDiskContentAddressedStore.d.ts.map +1 -1
  106. package/dist/node/appendonlystores/BasicOnDiskContentAddressedStore.js +7 -0
  107. package/dist/node/appendonlystores/BasicOnDiskContentAddressedStore.js.map +1 -1
  108. package/dist/server/DeviceDiscoveryService.d.ts.map +1 -1
  109. package/dist/server/DeviceDiscoveryService.js +3 -4
  110. package/dist/server/DeviceDiscoveryService.js.map +1 -1
  111. package/dist/server/SystemAdminAuth.d.ts.map +1 -1
  112. package/dist/server/SystemAdminAuth.js +3 -4
  113. package/dist/server/SystemAdminAuth.js.map +1 -1
  114. package/dist/server/TenantManager.d.ts +36 -0
  115. package/dist/server/TenantManager.d.ts.map +1 -1
  116. package/dist/server/TenantManager.js +234 -4
  117. package/dist/server/TenantManager.js.map +1 -1
  118. package/dist/server/http/MindooDBServerCore.d.ts.map +1 -1
  119. package/dist/server/http/MindooDBServerCore.js +7 -0
  120. package/dist/server/http/MindooDBServerCore.js.map +1 -1
  121. package/dist/server/peer/PeerAuthService.d.ts.map +1 -1
  122. package/dist/server/peer/PeerAuthService.js +3 -4
  123. package/dist/server/peer/PeerAuthService.js.map +1 -1
  124. package/dist/server/peer/PeerReplicator.d.ts.map +1 -1
  125. package/dist/server/peer/PeerReplicator.js +2 -1
  126. package/dist/server/peer/PeerReplicator.js.map +1 -1
  127. package/package.json +1 -1
@@ -8,6 +8,7 @@ const singleFlight_1 = require("./utils/singleFlight");
8
8
  const logging_1 = require("./logging");
9
9
  const grantKeys_1 = require("./accesscontrol/grantKeys");
10
10
  const types_2 = require("./accesscontrol/types");
11
+ const builtinDbInvariants_1 = require("./builtinDbInvariants");
11
12
  const DirectoryStateNode_1 = require("./accesscontrol/DirectoryStateNode");
12
13
  const DirectoryTimeTravelIndex_1 = require("./accesscontrol/DirectoryTimeTravelIndex");
13
14
  const directoryProjection_1 = require("./accesscontrol/directoryProjection");
@@ -1321,6 +1322,10 @@ class BaseMindooTenantDirectory {
1321
1322
  * server to gate all sync operations for a database id.
1322
1323
  */
1323
1324
  async evaluateDbAccessForSigningKey(input) {
1325
+ return (await this.evaluateDbAccessModeForSigningKey(input)).mode === "read";
1326
+ }
1327
+ /** The configured gates (§6.7 id allowlist, §6.8 read gate), without the personal-database invariant. */
1328
+ async evaluateConfiguredReadGate(input) {
1324
1329
  // Gate 1 — tenant-wide database-id allowlist (`databaseCreationPolicy`).
1325
1330
  // Orthogonal to read access: a database must clear BOTH gates.
1326
1331
  if (!(await this.isDatabaseAllowed(input.dbid, { signingKey: input.signingKey }))) {
@@ -1348,6 +1353,281 @@ class BaseMindooTenantDirectory {
1348
1353
  });
1349
1354
  return decision.allowed;
1350
1355
  }
1356
+ /**
1357
+ * The read scope of a principal in `dbid` (docs/accesscontrol.md §6.12):
1358
+ * `"all"` when it reads every document, `"own"` when the database's
1359
+ * `docReadScope` is `"own"` and the principal is neither the tenant admin nor
1360
+ * matched by a `doc_read_all` allow rule. The directory databases are never
1361
+ * scoped. Evaluated at the directory head, like the read gate.
1362
+ */
1363
+ async evaluateReadScopeForSigningKey(input) {
1364
+ if (input.dbid === types_1.DIRECTORY_DB_ID || input.dbid === types_1.USER_DIRECTORY_DB_ID)
1365
+ return "all";
1366
+ const adminKey = this.tenant.getAdministrationPublicKey();
1367
+ if (input.signingKey === adminKey)
1368
+ return "all";
1369
+ const decision = await this.evaluateAccessForSigningKey({
1370
+ op: "doc_read_all",
1371
+ dbid: input.dbid,
1372
+ signingKey: input.signingKey,
1373
+ trustedTime: Number.MAX_SAFE_INTEGER,
1374
+ });
1375
+ return decision.allowed ? "all" : "own";
1376
+ }
1377
+ /**
1378
+ * The current user's read scope in `dbid` (§6.12), the client-side
1379
+ * counterpart of {@link evaluateReadScopeForSigningKey}, e.g. to tell the
1380
+ * user that a list only shows their own documents.
1381
+ */
1382
+ async getReadScope(dbid) {
1383
+ const currentUser = await this.tenant.getCurrentUserId();
1384
+ return this.evaluateReadScopeForSigningKey({ dbid, signingKey: currentUser.userSigningPublicKey });
1385
+ }
1386
+ /**
1387
+ * Like {@link evaluateDbAccessForSigningKey}, but distinguishes the deposit
1388
+ * right (docs/accesscontrol.md §6.10): `"read"` passes the read gate (all
1389
+ * sync), `"deposit"` fails it but holds a `doc_deposit` allow (push of
1390
+ * sealed creates only), `"none"` neither. `maxRecipients` is the bound from
1391
+ * the matching deposit rule.
1392
+ */
1393
+ async evaluateDbAccessModeForSigningKey(input) {
1394
+ // Built-in personal-database invariant (§6.11): decides first, and a
1395
+ // configured policy can only take away what it grants.
1396
+ const personal = await this.evaluatePersonalDatabaseAccess(input);
1397
+ if (personal) {
1398
+ if (personal.mode === "none")
1399
+ return personal;
1400
+ if ((0, DirectoryStateNode_1.signingKeysEqual)(input.signingKey, this.tenant.getAdministrationPublicKey())) {
1401
+ return personal;
1402
+ }
1403
+ if (personal.mode === "read") {
1404
+ return (await this.evaluateConfiguredReadGate(input)) ? personal : { mode: "none", reason: "denied by the configured policy" };
1405
+ }
1406
+ if (!(await this.isDatabaseAllowed(input.dbid, { signingKey: input.signingKey })))
1407
+ return { mode: "none" };
1408
+ const configured = await this.evaluateAccessForSigningKey({
1409
+ op: "doc_deposit",
1410
+ dbid: input.dbid,
1411
+ signingKey: input.signingKey,
1412
+ trustedTime: Number.MAX_SAFE_INTEGER,
1413
+ });
1414
+ // Only a matching deny rule counts here; the built-in grant replaces the allow rule.
1415
+ return !configured.allowed && configured.matchedRuleId
1416
+ ? { mode: "none", reason: `deposit denied by rule ${configured.matchedRuleId}` }
1417
+ : personal;
1418
+ }
1419
+ if (await this.evaluateConfiguredReadGate(input)) {
1420
+ return { mode: "read" };
1421
+ }
1422
+ // The id allowlist (gate 1) applies to depositors as well.
1423
+ if (!(await this.isDatabaseAllowed(input.dbid, { signingKey: input.signingKey }))) {
1424
+ return { mode: "none" };
1425
+ }
1426
+ const decision = await this.evaluateAccessForSigningKey({
1427
+ op: "doc_deposit",
1428
+ dbid: input.dbid,
1429
+ signingKey: input.signingKey,
1430
+ trustedTime: Number.MAX_SAFE_INTEGER,
1431
+ });
1432
+ if (!decision.allowed)
1433
+ return { mode: "none" };
1434
+ const rule = await this.depositRule(decision.matchedRuleId);
1435
+ return {
1436
+ mode: "deposit",
1437
+ maxRecipients: rule?.maxRecipients ?? types_2.DEFAULT_DEPOSIT_MAX_RECIPIENTS,
1438
+ ...(rule?.docIdPrefixes?.length ? { docIdPrefixes: rule.docIdPrefixes } : {}),
1439
+ };
1440
+ }
1441
+ /**
1442
+ * The built-in rule for personal databases (§6.11), or `null` when `dbid` is
1443
+ * not one. `<prefix><grantDocId>` may be read and synced by the devices on
1444
+ * that grant and by the tenant admin; it may be deposited into by
1445
+ * identities whose grant lists the prefix in `depositPrefixes`; nobody
1446
+ * else gets anything. An unknown grant id leaves everyone but the admin out.
1447
+ * `mbusy_` (free/busy) is the exception: other members read it read-only
1448
+ * (`readOnly`, pushes refused) under the tenant's `freeBusy` setting.
1449
+ */
1450
+ async evaluatePersonalDatabaseAccess(input) {
1451
+ const head = await this.getDirectoryStateHead();
1452
+ const owner = (0, builtinDbInvariants_1.personalDatabaseOwner)(input.dbid, head.defaultPolicy?.personalDatabasePrefixes ?? []);
1453
+ if (!owner)
1454
+ return null;
1455
+ if ((0, DirectoryStateNode_1.signingKeysEqual)(input.signingKey, this.tenant.getAdministrationPublicKey())) {
1456
+ return { mode: "read", reason: "tenant admin" };
1457
+ }
1458
+ const grant = await this.loadGrantDocument(owner.grantDocId);
1459
+ if (grant && (0, grantKeys_1.extractActiveKeyPairs)(grant).some((pair) => (0, DirectoryStateNode_1.signingKeysEqual)(pair.signingPublicKey, input.signingKey))) {
1460
+ return { mode: "read", reason: "a device of the owner" };
1461
+ }
1462
+ const signerGrants = await this.grantsForSigningKey(input.signingKey);
1463
+ const listed = (grant, field) => Array.isArray(grant[field]) && grant[field].includes(owner.prefix);
1464
+ if (signerGrants.some((grant) => listed(grant, "accessPrefixes"))) {
1465
+ return { mode: "read", reason: `service access to ${owner.prefix}* granted` };
1466
+ }
1467
+ // Free/busy (§6.11): other members read the owner's busy times, read-only,
1468
+ // under the tenant's `freeBusy` setting. Nobody but the owner writes.
1469
+ if (owner.prefix === "mbusy_" && grant && signerGrants.length > 0) {
1470
+ const mode = head.defaultPolicy?.freeBusy ?? "members";
1471
+ let allowed = mode === "members";
1472
+ if (mode === "rules") {
1473
+ const decision = await this.evaluateAccessForSigningKey({
1474
+ op: "doc_read",
1475
+ dbid: input.dbid,
1476
+ signingKey: input.signingKey,
1477
+ trustedTime: Number.MAX_SAFE_INTEGER,
1478
+ });
1479
+ allowed = decision.allowed && decision.matchedRuleId !== undefined;
1480
+ }
1481
+ if (allowed)
1482
+ return { mode: "read", readOnly: true, reason: `free/busy (${mode})` };
1483
+ return { mode: "none", reason: `free/busy of another person (${mode})` };
1484
+ }
1485
+ const depositors = signerGrants.filter((grant) => listed(grant, "depositPrefixes"));
1486
+ if (depositors.length > 0) {
1487
+ // Document ids a deposit may use: the grant's `depositDocIdPrefixes`
1488
+ // (per database prefix, e.g. {"mmail_": ["memo_"]}), else the built-in
1489
+ // default for the prefix (mmail_: "memo_", MindooMail messages).
1490
+ const configured = depositors
1491
+ .map((grant) => grant.depositDocIdPrefixes)
1492
+ .map((byPrefix) => (byPrefix && Array.isArray(byPrefix[owner.prefix]) ? byPrefix[owner.prefix] : null))
1493
+ .find((list) => list !== null);
1494
+ const docIdPrefixes = configured
1495
+ ? configured.map(String).filter((p) => p.length > 0)
1496
+ : [...(builtinDbInvariants_1.BUILTIN_PERSONAL_DEPOSIT_DOC_ID_PREFIXES[owner.prefix] ?? [])];
1497
+ return {
1498
+ mode: "deposit",
1499
+ maxRecipients: 1,
1500
+ ...(docIdPrefixes.length ? { docIdPrefixes } : {}),
1501
+ reason: `deposit into ${owner.prefix}* granted`,
1502
+ };
1503
+ }
1504
+ // Internal mail (§6.11): members deposit MindooMail messages into each
1505
+ // other's mailbox, under the tenant's `internalMail` setting.
1506
+ if (owner.prefix === "mmail_" && grant && signerGrants.length > 0) {
1507
+ const mode = head.defaultPolicy?.internalMail ?? "members";
1508
+ let allowed = mode === "members";
1509
+ if (mode === "rules") {
1510
+ const decision = await this.evaluateAccessForSigningKey({
1511
+ op: "doc_deposit",
1512
+ dbid: input.dbid,
1513
+ signingKey: input.signingKey,
1514
+ trustedTime: Number.MAX_SAFE_INTEGER,
1515
+ });
1516
+ allowed = decision.allowed && decision.matchedRuleId !== undefined;
1517
+ }
1518
+ if (allowed) {
1519
+ const limits = head.defaultPolicy?.internalMailLimits ?? {};
1520
+ return {
1521
+ mode: "deposit",
1522
+ maxRecipients: 1,
1523
+ docIdPrefixes: [...(builtinDbInvariants_1.BUILTIN_PERSONAL_DEPOSIT_DOC_ID_PREFIXES.mmail_ ?? [])],
1524
+ internalMail: {
1525
+ maxBytesPerMail: limits.maxBytesPerMail ?? types_2.DEFAULT_INTERNAL_MAIL_LIMITS.maxBytesPerMail,
1526
+ maxMailsPerHour: limits.maxMailsPerHour ?? types_2.DEFAULT_INTERNAL_MAIL_LIMITS.maxMailsPerHour,
1527
+ },
1528
+ reason: `internal mail (${mode})`,
1529
+ };
1530
+ }
1531
+ }
1532
+ return { mode: "none", reason: grant ? "personal database of another person" : "personal database without a matching grant" };
1533
+ }
1534
+ /** A `grantaccess` document by id, or null (missing, deleted, or another kind of document). */
1535
+ async loadGrantDocument(grantDocId) {
1536
+ const directoryDB = await this.getDirectoryDB();
1537
+ try {
1538
+ const doc = await directoryDB.getDocument(grantDocId);
1539
+ if (doc.isDeleted())
1540
+ return null;
1541
+ const data = doc.getData();
1542
+ return data.form === "useroperation" && data.type === "grantaccess" ? data : null;
1543
+ }
1544
+ catch {
1545
+ return null;
1546
+ }
1547
+ }
1548
+ /** The active grants that list `signingKey` as a device. */
1549
+ async grantsForSigningKey(signingKey) {
1550
+ const lookup = await this.getUserBySigningPublicKey(signingKey);
1551
+ if (!lookup)
1552
+ return [];
1553
+ const grants = await this.findGrantAccessDocuments(lookup.username);
1554
+ return grants
1555
+ .filter((doc) => !doc.isDeleted())
1556
+ .map((doc) => doc.getData())
1557
+ .filter((data) => (0, grantKeys_1.extractActiveKeyPairs)(data).some((pair) => (0, DirectoryStateNode_1.signingKeysEqual)(pair.signingPublicKey, signingKey)));
1558
+ }
1559
+ /**
1560
+ * Let an identity deposit into personal databases with these prefixes
1561
+ * (§6.11), e.g. `["mmail_"]` for a mail gateway. Written on the person's
1562
+ * grant, admin-signed; an empty list takes the right away again.
1563
+ */
1564
+ async setGrantDepositPrefixes(username, prefixes, administrationPrivateKey, administrationPrivateKeyPassword) {
1565
+ await this.setGrantPersonalDatabaseRights(username, { depositPrefixes: prefixes }, administrationPrivateKey, administrationPrivateKeyPassword);
1566
+ }
1567
+ /**
1568
+ * The rights an identity has in other people's personal databases (§6.11):
1569
+ * `depositPrefixes` (push sealed creates only) and `accessPrefixes` (read
1570
+ * and sync, for a service such as the mail gateway serving every outbox).
1571
+ * Written on the identity's grant, admin-signed; a field left out stays as
1572
+ * it is, an empty list removes the right. `depositDocIdPrefixes` sets the
1573
+ * document ids a deposit may use per database prefix (e.g.
1574
+ * `{ mmail_: ["memo_"] }`, the built-in default); an empty list allows any id.
1575
+ */
1576
+ async setGrantPersonalDatabaseRights(username, rights, administrationPrivateKey, administrationPrivateKeyPassword) {
1577
+ for (const prefix of [...(rights.depositPrefixes ?? []), ...(rights.accessPrefixes ?? [])]) {
1578
+ if (!builtinDbInvariants_1.PERSONAL_DATABASE_PREFIX_REGEX.test(prefix)) {
1579
+ throw new Error(`depositPrefixes: "${prefix}" is not a personal-database prefix like "mmail_"`);
1580
+ }
1581
+ }
1582
+ for (const [prefix, ids] of Object.entries(rights.depositDocIdPrefixes ?? {})) {
1583
+ if (!builtinDbInvariants_1.PERSONAL_DATABASE_PREFIX_REGEX.test(prefix)) {
1584
+ throw new Error(`depositDocIdPrefixes: "${prefix}" is not a personal-database prefix like "mmail_"`);
1585
+ }
1586
+ (0, types_2.validateDocIdPrefixes)(ids, `depositDocIdPrefixes.${prefix}`);
1587
+ }
1588
+ const grants = (await this.findGrantAccessDocuments(username)).filter((doc) => !doc.isDeleted());
1589
+ if (grants.length === 0)
1590
+ throw new Error(`No grant for ${username}`);
1591
+ const directoryDB = await this.getDirectoryDB();
1592
+ const signingKeyPair = {
1593
+ publicKey: this.tenant.getAdministrationPublicKey(),
1594
+ privateKey: administrationPrivateKey,
1595
+ };
1596
+ for (const grant of grants) {
1597
+ await directoryDB.changeDoc(grant, (d) => {
1598
+ const data = d.getData();
1599
+ if (rights.depositPrefixes)
1600
+ data.depositPrefixes = [...new Set(rights.depositPrefixes)];
1601
+ if (rights.accessPrefixes)
1602
+ data.accessPrefixes = [...new Set(rights.accessPrefixes)];
1603
+ if (rights.depositDocIdPrefixes) {
1604
+ const merged = { ...(data.depositDocIdPrefixes ?? {}) };
1605
+ for (const [prefix, ids] of Object.entries(rights.depositDocIdPrefixes))
1606
+ merged[prefix] = [...new Set(ids)];
1607
+ data.depositDocIdPrefixes = merged;
1608
+ }
1609
+ }, { signingKeyPair, signingKeyPassword: administrationPrivateKeyPassword });
1610
+ }
1611
+ await this.updateUnifiedCache();
1612
+ }
1613
+ /**
1614
+ * Whether the current user holds the deposit right on `dbid` (§6.10): a
1615
+ * `doc_deposit` allow rule matches and no deny does. Says nothing about the
1616
+ * read gate; callers check {@link canReadDatabase} first.
1617
+ */
1618
+ async canDepositDatabase(dbid) {
1619
+ if (dbid === types_1.DIRECTORY_DB_ID || dbid === types_1.USER_DIRECTORY_DB_ID)
1620
+ return false;
1621
+ const currentUser = await this.tenant.getCurrentUserId();
1622
+ const access = await this.evaluateDbAccessModeForSigningKey({ dbid, signingKey: currentUser.userSigningPublicKey });
1623
+ return access.mode === "deposit";
1624
+ }
1625
+ async depositRule(ruleId) {
1626
+ if (!ruleId)
1627
+ return null;
1628
+ const head = await this.getDirectoryStateHead();
1629
+ return (head.rulesByType.get("doc_deposit") ?? []).find((r) => r.ruleId === ruleId) ?? null;
1630
+ }
1351
1631
  /**
1352
1632
  * Whether the current user may open and sync `dbid` under the database read
1353
1633
  * gate (`doc_read`, §6.6). The client-side counterpart of
@@ -1364,6 +1644,10 @@ class BaseMindooTenantDirectory {
1364
1644
  const adminKey = this.tenant.getAdministrationPublicKey();
1365
1645
  if (currentUser.userSigningPublicKey === adminKey)
1366
1646
  return true;
1647
+ // The personal-database invariant (§6.11) decides first, policy on top.
1648
+ const personal = await this.evaluatePersonalDatabaseAccess({ dbid, signingKey: currentUser.userSigningPublicKey });
1649
+ if (personal && personal.mode !== "read")
1650
+ return false;
1367
1651
  const decision = await this.canDo("doc_read", dbid);
1368
1652
  return decision.allowed;
1369
1653
  }
@@ -1382,12 +1666,30 @@ class BaseMindooTenantDirectory {
1382
1666
  (0, types_3.validateAccessPolicy)(policy);
1383
1667
  await this.writePolicyDoc(types_3.ACL_DEFAULT_POLICY_DOC_ID, policy, administrationPrivateKey, administrationPrivateKeyPassword);
1384
1668
  }
1669
+ async requireV2EntrySignatures(administrationPrivateKey, administrationPrivateKeyPassword) {
1670
+ const head = await this.getDirectoryStateHead();
1671
+ const existing = head.defaultPolicy?.requireMetadataSignatureSince;
1672
+ if (typeof existing === "number" && Number.isFinite(existing)) {
1673
+ return { since: existing, changed: false };
1674
+ }
1675
+ const since = (0, timeSource_1.semanticNow)();
1676
+ await this.setDefaultAccessPolicy(head.defaultPolicy
1677
+ ? { requireMetadataSignatureSince: since }
1678
+ : // No policy document yet: writing one is what activates access
1679
+ // control, so it carries the master switch, exactly as a new tenant
1680
+ // gets it from the factory.
1681
+ { disableAllAccessChecksAndPolicies: true, requireMetadataSignatureSince: since }, administrationPrivateKey, administrationPrivateKeyPassword);
1682
+ return { since, changed: true };
1683
+ }
1385
1684
  /**
1386
1685
  * Create or update a per-database policy override (§6.2). Fields set here
1387
1686
  * override the tenant default for `dbid`; unset fields inherit it.
1388
1687
  */
1389
1688
  async setDatabaseAccessPolicy(dbid, policy, administrationPrivateKey, administrationPrivateKeyPassword) {
1390
1689
  (0, types_3.validateAccessPolicy)(policy);
1690
+ if (policy.personalDatabasePrefixes !== undefined) {
1691
+ throw new Error("personalDatabasePrefixes is a tenant-level setting; set it on the default policy");
1692
+ }
1391
1693
  if (policy.llm !== undefined) {
1392
1694
  throw new Error("llm is a tenant-level setting; set it on the default policy");
1393
1695
  }
@@ -1439,6 +1741,8 @@ class BaseMindooTenantDirectory {
1439
1741
  groups: rule.groups ?? [],
1440
1742
  }),
1441
1743
  action: rule.action ?? "allow",
1744
+ ...(rule.maxRecipients !== undefined ? { maxRecipients: rule.maxRecipients } : {}),
1745
+ ...(rule.docIdPrefixes !== undefined ? { docIdPrefixes: [...new Set(rule.docIdPrefixes)] } : {}),
1442
1746
  };
1443
1747
  // Fail fast on malformed clauses / operators (§6.3 closed set).
1444
1748
  (0, types_3.validateAclRule)(ruleDoc);
@@ -1462,6 +1766,14 @@ class BaseMindooTenantDirectory {
1462
1766
  data.description = ruleDoc.description;
1463
1767
  if (ruleDoc.withfields !== undefined)
1464
1768
  data.withfields = ruleDoc.withfields;
1769
+ if (ruleDoc.maxRecipients !== undefined)
1770
+ data.maxRecipients = ruleDoc.maxRecipients;
1771
+ else if (data.maxRecipients !== undefined)
1772
+ delete data.maxRecipients;
1773
+ if (ruleDoc.docIdPrefixes?.length)
1774
+ data.docIdPrefixes = ruleDoc.docIdPrefixes;
1775
+ else if (data.docIdPrefixes !== undefined)
1776
+ delete data.docIdPrefixes;
1465
1777
  }, {
1466
1778
  signingKeyPair: adminSigningKeyPair,
1467
1779
  signingKeyPassword: administrationPrivateKeyPassword,