mindoodb 0.0.86 → 0.0.88

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 +151 -12
  6. package/dist/appendonlystores/network/ServerNetworkContentAddressedStore.d.ts.map +1 -1
  7. package/dist/appendonlystores/network/ServerNetworkContentAddressedStore.js +340 -24
  8. package/dist/appendonlystores/network/ServerNetworkContentAddressedStore.js.map +1 -1
  9. package/dist/browser/index.mjs +14 -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 +93 -0
  19. package/dist/core/BaseMindooTenantDirectory.d.ts.map +1 -1
  20. package/dist/core/BaseMindooTenantDirectory.js +292 -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 +19 -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 +88 -4
  36. package/dist/core/accesscontrol/types.d.ts.map +1 -1
  37. package/dist/core/accesscontrol/types.js +114 -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 +49 -0
  62. package/dist/core/builtinDbInvariants.d.ts.map +1 -1
  63. package/dist/core/builtinDbInvariants.js +86 -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 +17 -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,261 @@ 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
+ */
1448
+ async evaluatePersonalDatabaseAccess(input) {
1449
+ const head = await this.getDirectoryStateHead();
1450
+ const owner = (0, builtinDbInvariants_1.personalDatabaseOwner)(input.dbid, head.defaultPolicy?.personalDatabasePrefixes ?? []);
1451
+ if (!owner)
1452
+ return null;
1453
+ if ((0, DirectoryStateNode_1.signingKeysEqual)(input.signingKey, this.tenant.getAdministrationPublicKey())) {
1454
+ return { mode: "read", reason: "tenant admin" };
1455
+ }
1456
+ const grant = await this.loadGrantDocument(owner.grantDocId);
1457
+ if (grant && (0, grantKeys_1.extractActiveKeyPairs)(grant).some((pair) => (0, DirectoryStateNode_1.signingKeysEqual)(pair.signingPublicKey, input.signingKey))) {
1458
+ return { mode: "read", reason: "a device of the owner" };
1459
+ }
1460
+ const signerGrants = await this.grantsForSigningKey(input.signingKey);
1461
+ const listed = (grant, field) => Array.isArray(grant[field]) && grant[field].includes(owner.prefix);
1462
+ if (signerGrants.some((grant) => listed(grant, "accessPrefixes"))) {
1463
+ return { mode: "read", reason: `service access to ${owner.prefix}* granted` };
1464
+ }
1465
+ const depositors = signerGrants.filter((grant) => listed(grant, "depositPrefixes"));
1466
+ if (depositors.length > 0) {
1467
+ // Document ids a deposit may use: the grant's `depositDocIdPrefixes`
1468
+ // (per database prefix, e.g. {"mmail_": ["memo_"]}), else the built-in
1469
+ // default for the prefix (mmail_: "memo_", MindooMail messages).
1470
+ const configured = depositors
1471
+ .map((grant) => grant.depositDocIdPrefixes)
1472
+ .map((byPrefix) => (byPrefix && Array.isArray(byPrefix[owner.prefix]) ? byPrefix[owner.prefix] : null))
1473
+ .find((list) => list !== null);
1474
+ const docIdPrefixes = configured
1475
+ ? configured.map(String).filter((p) => p.length > 0)
1476
+ : [...(builtinDbInvariants_1.BUILTIN_PERSONAL_DEPOSIT_DOC_ID_PREFIXES[owner.prefix] ?? [])];
1477
+ return {
1478
+ mode: "deposit",
1479
+ maxRecipients: 1,
1480
+ ...(docIdPrefixes.length ? { docIdPrefixes } : {}),
1481
+ reason: `deposit into ${owner.prefix}* granted`,
1482
+ };
1483
+ }
1484
+ // Internal mail (§6.11): members deposit MindooMail messages into each
1485
+ // other's mailbox, under the tenant's `internalMail` setting.
1486
+ if (owner.prefix === "mmail_" && grant && signerGrants.length > 0) {
1487
+ const mode = head.defaultPolicy?.internalMail ?? "members";
1488
+ let allowed = mode === "members";
1489
+ if (mode === "rules") {
1490
+ const decision = await this.evaluateAccessForSigningKey({
1491
+ op: "doc_deposit",
1492
+ dbid: input.dbid,
1493
+ signingKey: input.signingKey,
1494
+ trustedTime: Number.MAX_SAFE_INTEGER,
1495
+ });
1496
+ allowed = decision.allowed && decision.matchedRuleId !== undefined;
1497
+ }
1498
+ if (allowed) {
1499
+ const limits = head.defaultPolicy?.internalMailLimits ?? {};
1500
+ return {
1501
+ mode: "deposit",
1502
+ maxRecipients: 1,
1503
+ docIdPrefixes: [...(builtinDbInvariants_1.BUILTIN_PERSONAL_DEPOSIT_DOC_ID_PREFIXES.mmail_ ?? [])],
1504
+ internalMail: {
1505
+ maxBytesPerMail: limits.maxBytesPerMail ?? types_2.DEFAULT_INTERNAL_MAIL_LIMITS.maxBytesPerMail,
1506
+ maxMailsPerHour: limits.maxMailsPerHour ?? types_2.DEFAULT_INTERNAL_MAIL_LIMITS.maxMailsPerHour,
1507
+ },
1508
+ reason: `internal mail (${mode})`,
1509
+ };
1510
+ }
1511
+ }
1512
+ return { mode: "none", reason: grant ? "personal database of another person" : "personal database without a matching grant" };
1513
+ }
1514
+ /** A `grantaccess` document by id, or null (missing, deleted, or another kind of document). */
1515
+ async loadGrantDocument(grantDocId) {
1516
+ const directoryDB = await this.getDirectoryDB();
1517
+ try {
1518
+ const doc = await directoryDB.getDocument(grantDocId);
1519
+ if (doc.isDeleted())
1520
+ return null;
1521
+ const data = doc.getData();
1522
+ return data.form === "useroperation" && data.type === "grantaccess" ? data : null;
1523
+ }
1524
+ catch {
1525
+ return null;
1526
+ }
1527
+ }
1528
+ /** The active grants that list `signingKey` as a device. */
1529
+ async grantsForSigningKey(signingKey) {
1530
+ const lookup = await this.getUserBySigningPublicKey(signingKey);
1531
+ if (!lookup)
1532
+ return [];
1533
+ const grants = await this.findGrantAccessDocuments(lookup.username);
1534
+ return grants
1535
+ .filter((doc) => !doc.isDeleted())
1536
+ .map((doc) => doc.getData())
1537
+ .filter((data) => (0, grantKeys_1.extractActiveKeyPairs)(data).some((pair) => (0, DirectoryStateNode_1.signingKeysEqual)(pair.signingPublicKey, signingKey)));
1538
+ }
1539
+ /**
1540
+ * Let an identity deposit into personal databases with these prefixes
1541
+ * (§6.11), e.g. `["mmail_"]` for a mail gateway. Written on the person's
1542
+ * grant, admin-signed; an empty list takes the right away again.
1543
+ */
1544
+ async setGrantDepositPrefixes(username, prefixes, administrationPrivateKey, administrationPrivateKeyPassword) {
1545
+ await this.setGrantPersonalDatabaseRights(username, { depositPrefixes: prefixes }, administrationPrivateKey, administrationPrivateKeyPassword);
1546
+ }
1547
+ /**
1548
+ * The rights an identity has in other people's personal databases (§6.11):
1549
+ * `depositPrefixes` (push sealed creates only) and `accessPrefixes` (read
1550
+ * and sync, for a service such as the mail gateway serving every outbox).
1551
+ * Written on the identity's grant, admin-signed; a field left out stays as
1552
+ * it is, an empty list removes the right. `depositDocIdPrefixes` sets the
1553
+ * document ids a deposit may use per database prefix (e.g.
1554
+ * `{ mmail_: ["memo_"] }`, the built-in default); an empty list allows any id.
1555
+ */
1556
+ async setGrantPersonalDatabaseRights(username, rights, administrationPrivateKey, administrationPrivateKeyPassword) {
1557
+ for (const prefix of [...(rights.depositPrefixes ?? []), ...(rights.accessPrefixes ?? [])]) {
1558
+ if (!builtinDbInvariants_1.PERSONAL_DATABASE_PREFIX_REGEX.test(prefix)) {
1559
+ throw new Error(`depositPrefixes: "${prefix}" is not a personal-database prefix like "mmail_"`);
1560
+ }
1561
+ }
1562
+ for (const [prefix, ids] of Object.entries(rights.depositDocIdPrefixes ?? {})) {
1563
+ if (!builtinDbInvariants_1.PERSONAL_DATABASE_PREFIX_REGEX.test(prefix)) {
1564
+ throw new Error(`depositDocIdPrefixes: "${prefix}" is not a personal-database prefix like "mmail_"`);
1565
+ }
1566
+ (0, types_2.validateDocIdPrefixes)(ids, `depositDocIdPrefixes.${prefix}`);
1567
+ }
1568
+ const grants = (await this.findGrantAccessDocuments(username)).filter((doc) => !doc.isDeleted());
1569
+ if (grants.length === 0)
1570
+ throw new Error(`No grant for ${username}`);
1571
+ const directoryDB = await this.getDirectoryDB();
1572
+ const signingKeyPair = {
1573
+ publicKey: this.tenant.getAdministrationPublicKey(),
1574
+ privateKey: administrationPrivateKey,
1575
+ };
1576
+ for (const grant of grants) {
1577
+ await directoryDB.changeDoc(grant, (d) => {
1578
+ const data = d.getData();
1579
+ if (rights.depositPrefixes)
1580
+ data.depositPrefixes = [...new Set(rights.depositPrefixes)];
1581
+ if (rights.accessPrefixes)
1582
+ data.accessPrefixes = [...new Set(rights.accessPrefixes)];
1583
+ if (rights.depositDocIdPrefixes) {
1584
+ const merged = { ...(data.depositDocIdPrefixes ?? {}) };
1585
+ for (const [prefix, ids] of Object.entries(rights.depositDocIdPrefixes))
1586
+ merged[prefix] = [...new Set(ids)];
1587
+ data.depositDocIdPrefixes = merged;
1588
+ }
1589
+ }, { signingKeyPair, signingKeyPassword: administrationPrivateKeyPassword });
1590
+ }
1591
+ await this.updateUnifiedCache();
1592
+ }
1593
+ /**
1594
+ * Whether the current user holds the deposit right on `dbid` (§6.10): a
1595
+ * `doc_deposit` allow rule matches and no deny does. Says nothing about the
1596
+ * read gate; callers check {@link canReadDatabase} first.
1597
+ */
1598
+ async canDepositDatabase(dbid) {
1599
+ if (dbid === types_1.DIRECTORY_DB_ID || dbid === types_1.USER_DIRECTORY_DB_ID)
1600
+ return false;
1601
+ const currentUser = await this.tenant.getCurrentUserId();
1602
+ const access = await this.evaluateDbAccessModeForSigningKey({ dbid, signingKey: currentUser.userSigningPublicKey });
1603
+ return access.mode === "deposit";
1604
+ }
1605
+ async depositRule(ruleId) {
1606
+ if (!ruleId)
1607
+ return null;
1608
+ const head = await this.getDirectoryStateHead();
1609
+ return (head.rulesByType.get("doc_deposit") ?? []).find((r) => r.ruleId === ruleId) ?? null;
1610
+ }
1351
1611
  /**
1352
1612
  * Whether the current user may open and sync `dbid` under the database read
1353
1613
  * gate (`doc_read`, §6.6). The client-side counterpart of
@@ -1364,6 +1624,10 @@ class BaseMindooTenantDirectory {
1364
1624
  const adminKey = this.tenant.getAdministrationPublicKey();
1365
1625
  if (currentUser.userSigningPublicKey === adminKey)
1366
1626
  return true;
1627
+ // The personal-database invariant (§6.11) decides first, policy on top.
1628
+ const personal = await this.evaluatePersonalDatabaseAccess({ dbid, signingKey: currentUser.userSigningPublicKey });
1629
+ if (personal && personal.mode !== "read")
1630
+ return false;
1367
1631
  const decision = await this.canDo("doc_read", dbid);
1368
1632
  return decision.allowed;
1369
1633
  }
@@ -1382,12 +1646,30 @@ class BaseMindooTenantDirectory {
1382
1646
  (0, types_3.validateAccessPolicy)(policy);
1383
1647
  await this.writePolicyDoc(types_3.ACL_DEFAULT_POLICY_DOC_ID, policy, administrationPrivateKey, administrationPrivateKeyPassword);
1384
1648
  }
1649
+ async requireV2EntrySignatures(administrationPrivateKey, administrationPrivateKeyPassword) {
1650
+ const head = await this.getDirectoryStateHead();
1651
+ const existing = head.defaultPolicy?.requireMetadataSignatureSince;
1652
+ if (typeof existing === "number" && Number.isFinite(existing)) {
1653
+ return { since: existing, changed: false };
1654
+ }
1655
+ const since = (0, timeSource_1.semanticNow)();
1656
+ await this.setDefaultAccessPolicy(head.defaultPolicy
1657
+ ? { requireMetadataSignatureSince: since }
1658
+ : // No policy document yet: writing one is what activates access
1659
+ // control, so it carries the master switch, exactly as a new tenant
1660
+ // gets it from the factory.
1661
+ { disableAllAccessChecksAndPolicies: true, requireMetadataSignatureSince: since }, administrationPrivateKey, administrationPrivateKeyPassword);
1662
+ return { since, changed: true };
1663
+ }
1385
1664
  /**
1386
1665
  * Create or update a per-database policy override (§6.2). Fields set here
1387
1666
  * override the tenant default for `dbid`; unset fields inherit it.
1388
1667
  */
1389
1668
  async setDatabaseAccessPolicy(dbid, policy, administrationPrivateKey, administrationPrivateKeyPassword) {
1390
1669
  (0, types_3.validateAccessPolicy)(policy);
1670
+ if (policy.personalDatabasePrefixes !== undefined) {
1671
+ throw new Error("personalDatabasePrefixes is a tenant-level setting; set it on the default policy");
1672
+ }
1391
1673
  if (policy.llm !== undefined) {
1392
1674
  throw new Error("llm is a tenant-level setting; set it on the default policy");
1393
1675
  }
@@ -1439,6 +1721,8 @@ class BaseMindooTenantDirectory {
1439
1721
  groups: rule.groups ?? [],
1440
1722
  }),
1441
1723
  action: rule.action ?? "allow",
1724
+ ...(rule.maxRecipients !== undefined ? { maxRecipients: rule.maxRecipients } : {}),
1725
+ ...(rule.docIdPrefixes !== undefined ? { docIdPrefixes: [...new Set(rule.docIdPrefixes)] } : {}),
1442
1726
  };
1443
1727
  // Fail fast on malformed clauses / operators (§6.3 closed set).
1444
1728
  (0, types_3.validateAclRule)(ruleDoc);
@@ -1462,6 +1746,14 @@ class BaseMindooTenantDirectory {
1462
1746
  data.description = ruleDoc.description;
1463
1747
  if (ruleDoc.withfields !== undefined)
1464
1748
  data.withfields = ruleDoc.withfields;
1749
+ if (ruleDoc.maxRecipients !== undefined)
1750
+ data.maxRecipients = ruleDoc.maxRecipients;
1751
+ else if (data.maxRecipients !== undefined)
1752
+ delete data.maxRecipients;
1753
+ if (ruleDoc.docIdPrefixes?.length)
1754
+ data.docIdPrefixes = ruleDoc.docIdPrefixes;
1755
+ else if (data.docIdPrefixes !== undefined)
1756
+ delete data.docIdPrefixes;
1465
1757
  }, {
1466
1758
  signingKeyPair: adminSigningKeyPair,
1467
1759
  signingKeyPassword: administrationPrivateKeyPassword,