mindoodb 0.0.51 → 0.0.54

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 (249) hide show
  1. package/README-server.md +136 -71
  2. package/dist/appendonlystores/network/ClientNetworkContentAddressedStore.d.ts +30 -1
  3. package/dist/appendonlystores/network/ClientNetworkContentAddressedStore.d.ts.map +1 -1
  4. package/dist/appendonlystores/network/ClientNetworkContentAddressedStore.js +67 -20
  5. package/dist/appendonlystores/network/ClientNetworkContentAddressedStore.js.map +1 -1
  6. package/dist/appendonlystores/network/HttpTransport.d.ts +2 -0
  7. package/dist/appendonlystores/network/HttpTransport.d.ts.map +1 -1
  8. package/dist/appendonlystores/network/HttpTransport.js +6 -62
  9. package/dist/appendonlystores/network/HttpTransport.js.map +1 -1
  10. package/dist/appendonlystores/network/ServerNetworkContentAddressedStore.d.ts +82 -0
  11. package/dist/appendonlystores/network/ServerNetworkContentAddressedStore.d.ts.map +1 -1
  12. package/dist/appendonlystores/network/ServerNetworkContentAddressedStore.js +229 -25
  13. package/dist/appendonlystores/network/ServerNetworkContentAddressedStore.js.map +1 -1
  14. package/dist/browser/index.mjs +59 -0
  15. package/dist/core/BaseMindooDB.d.ts +148 -80
  16. package/dist/core/BaseMindooDB.d.ts.map +1 -1
  17. package/dist/core/BaseMindooDB.js +890 -692
  18. package/dist/core/BaseMindooDB.js.map +1 -1
  19. package/dist/core/BaseMindooTenant.d.ts +59 -7
  20. package/dist/core/BaseMindooTenant.d.ts.map +1 -1
  21. package/dist/core/BaseMindooTenant.js +407 -86
  22. package/dist/core/BaseMindooTenant.js.map +1 -1
  23. package/dist/core/BaseMindooTenantDirectory.d.ts +61 -9
  24. package/dist/core/BaseMindooTenantDirectory.d.ts.map +1 -1
  25. package/dist/core/BaseMindooTenantDirectory.js +363 -35
  26. package/dist/core/BaseMindooTenantDirectory.js.map +1 -1
  27. package/dist/core/BaseMindooTenantFactory.d.ts +17 -1
  28. package/dist/core/BaseMindooTenantFactory.d.ts.map +1 -1
  29. package/dist/core/BaseMindooTenantFactory.js +286 -17
  30. package/dist/core/BaseMindooTenantFactory.js.map +1 -1
  31. package/dist/core/MindooDBServerAdmin.d.ts +44 -22
  32. package/dist/core/MindooDBServerAdmin.d.ts.map +1 -1
  33. package/dist/core/MindooDBServerAdmin.js +45 -21
  34. package/dist/core/MindooDBServerAdmin.js.map +1 -1
  35. package/dist/core/accesscontrol/DirectoryStateNode.d.ts +68 -1
  36. package/dist/core/accesscontrol/DirectoryStateNode.d.ts.map +1 -1
  37. package/dist/core/accesscontrol/DirectoryStateNode.js +157 -4
  38. package/dist/core/accesscontrol/DirectoryStateNode.js.map +1 -1
  39. package/dist/core/accesscontrol/directoryProjection.d.ts.map +1 -1
  40. package/dist/core/accesscontrol/directoryProjection.js +14 -5
  41. package/dist/core/accesscontrol/directoryProjection.js.map +1 -1
  42. package/dist/core/accesscontrol/grantKeys.d.ts +17 -14
  43. package/dist/core/accesscontrol/grantKeys.d.ts.map +1 -1
  44. package/dist/core/accesscontrol/grantKeys.js +51 -21
  45. package/dist/core/accesscontrol/grantKeys.js.map +1 -1
  46. package/dist/core/accesscontrol/types.d.ts +2 -1
  47. package/dist/core/accesscontrol/types.d.ts.map +1 -1
  48. package/dist/core/accesscontrol/types.js +3 -2
  49. package/dist/core/accesscontrol/types.js.map +1 -1
  50. package/dist/core/appendonlystores/network/AuthenticationService.d.ts +7 -0
  51. package/dist/core/appendonlystores/network/AuthenticationService.d.ts.map +1 -1
  52. package/dist/core/appendonlystores/network/AuthenticationService.js +70 -14
  53. package/dist/core/appendonlystores/network/AuthenticationService.js.map +1 -1
  54. package/dist/core/appendonlystores/network/entryWireCodec.d.ts +51 -0
  55. package/dist/core/appendonlystores/network/entryWireCodec.d.ts.map +1 -0
  56. package/dist/core/appendonlystores/network/entryWireCodec.js +93 -0
  57. package/dist/core/appendonlystores/network/entryWireCodec.js.map +1 -0
  58. package/dist/core/appendonlystores/syncStores.d.ts +147 -0
  59. package/dist/core/appendonlystores/syncStores.d.ts.map +1 -0
  60. package/dist/core/appendonlystores/syncStores.js +612 -0
  61. package/dist/core/appendonlystores/syncStores.js.map +1 -0
  62. package/dist/core/builtinDbInvariants.d.ts +83 -0
  63. package/dist/core/builtinDbInvariants.d.ts.map +1 -0
  64. package/dist/core/builtinDbInvariants.js +244 -0
  65. package/dist/core/builtinDbInvariants.js.map +1 -0
  66. package/dist/core/copy/feasibility.d.ts.map +1 -1
  67. package/dist/core/copy/feasibility.js +3 -0
  68. package/dist/core/copy/feasibility.js.map +1 -1
  69. package/dist/core/copy/flatten.d.ts.map +1 -1
  70. package/dist/core/copy/flatten.js +6 -1
  71. package/dist/core/copy/flatten.js.map +1 -1
  72. package/dist/core/copy/host.d.ts +5 -0
  73. package/dist/core/copy/host.d.ts.map +1 -1
  74. package/dist/core/copy/preflight.d.ts.map +1 -1
  75. package/dist/core/copy/preflight.js +7 -1
  76. package/dist/core/copy/preflight.js.map +1 -1
  77. package/dist/core/copy/types.d.ts +7 -0
  78. package/dist/core/copy/types.d.ts.map +1 -1
  79. package/dist/core/crypto/EntrySignature.d.ts +8 -2
  80. package/dist/core/crypto/EntrySignature.d.ts.map +1 -1
  81. package/dist/core/crypto/EntrySignature.js +14 -5
  82. package/dist/core/crypto/EntrySignature.js.map +1 -1
  83. package/dist/core/crypto/MindooDocSigner.d.ts.map +1 -1
  84. package/dist/core/crypto/MindooDocSigner.js +11 -2
  85. package/dist/core/crypto/MindooDocSigner.js.map +1 -1
  86. package/dist/core/crypto/RSAEncryption.d.ts +19 -0
  87. package/dist/core/crypto/RSAEncryption.d.ts.map +1 -1
  88. package/dist/core/crypto/RSAEncryption.js +36 -3
  89. package/dist/core/crypto/RSAEncryption.js.map +1 -1
  90. package/dist/core/crypto/privateKeyEncryption.js +1 -1
  91. package/dist/core/crypto/privateKeyEncryption.js.map +1 -1
  92. package/dist/core/crypto/rsaOaep3072.d.ts +12 -0
  93. package/dist/core/crypto/rsaOaep3072.d.ts.map +1 -0
  94. package/dist/core/crypto/rsaOaep3072.js +30 -0
  95. package/dist/core/crypto/rsaOaep3072.js.map +1 -0
  96. package/dist/core/index.d.ts +7 -3
  97. package/dist/core/index.d.ts.map +1 -1
  98. package/dist/core/index.js +33 -4
  99. package/dist/core/index.js.map +1 -1
  100. package/dist/core/join/deviceJoinBootstrap.d.ts +23 -0
  101. package/dist/core/join/deviceJoinBootstrap.d.ts.map +1 -0
  102. package/dist/core/join/deviceJoinBootstrap.js +37 -0
  103. package/dist/core/join/deviceJoinBootstrap.js.map +1 -0
  104. package/dist/core/keys/KeyBag.d.ts +20 -2
  105. package/dist/core/keys/KeyBag.d.ts.map +1 -1
  106. package/dist/core/keys/KeyBag.js +22 -10
  107. package/dist/core/keys/KeyBag.js.map +1 -1
  108. package/dist/core/logging/MindooLogger.d.ts.map +1 -1
  109. package/dist/core/logging/MindooLogger.js +65 -40
  110. package/dist/core/logging/MindooLogger.js.map +1 -1
  111. package/dist/core/tenantIdValidation.d.ts.map +1 -1
  112. package/dist/core/tenantIdValidation.js +1 -1
  113. package/dist/core/tenantIdValidation.js.map +1 -1
  114. package/dist/core/tenantSetup.d.ts +94 -9
  115. package/dist/core/tenantSetup.d.ts.map +1 -1
  116. package/dist/core/tenantSetup.js +208 -20
  117. package/dist/core/tenantSetup.js.map +1 -1
  118. package/dist/core/types.d.ts +313 -33
  119. package/dist/core/types.d.ts.map +1 -1
  120. package/dist/core/types.js +14 -1
  121. package/dist/core/types.js.map +1 -1
  122. package/dist/core/uri/joinRequestUri.d.ts.map +1 -1
  123. package/dist/core/uri/joinRequestUri.js +12 -3
  124. package/dist/core/uri/joinRequestUri.js.map +1 -1
  125. package/dist/core/userid/canonicalUsername.d.ts +66 -0
  126. package/dist/core/userid/canonicalUsername.d.ts.map +1 -0
  127. package/dist/core/userid/canonicalUsername.js +235 -0
  128. package/dist/core/userid/canonicalUsername.js.map +1 -0
  129. package/dist/core/userid/index.d.ts +9 -0
  130. package/dist/core/userid/index.d.ts.map +1 -1
  131. package/dist/core/userid/index.js +14 -0
  132. package/dist/core/userid/index.js.map +1 -1
  133. package/dist/core/userkeys/UserKeyDocument.d.ts +50 -0
  134. package/dist/core/userkeys/UserKeyDocument.d.ts.map +1 -0
  135. package/dist/core/userkeys/UserKeyDocument.js +145 -0
  136. package/dist/core/userkeys/UserKeyDocument.js.map +1 -0
  137. package/dist/core/userkeys/UserKeyManager.d.ts +170 -0
  138. package/dist/core/userkeys/UserKeyManager.d.ts.map +1 -0
  139. package/dist/core/userkeys/UserKeyManager.js +1277 -0
  140. package/dist/core/userkeys/UserKeyManager.js.map +1 -0
  141. package/dist/core/userkeys/fingerprint.d.ts +17 -0
  142. package/dist/core/userkeys/fingerprint.d.ts.map +1 -0
  143. package/dist/core/userkeys/fingerprint.js +59 -0
  144. package/dist/core/userkeys/fingerprint.js.map +1 -0
  145. package/dist/core/userkeys/index.d.ts +11 -0
  146. package/dist/core/userkeys/index.d.ts.map +1 -0
  147. package/dist/core/userkeys/index.js +49 -0
  148. package/dist/core/userkeys/index.js.map +1 -0
  149. package/dist/core/userkeys/recipients.d.ts +51 -0
  150. package/dist/core/userkeys/recipients.d.ts.map +1 -0
  151. package/dist/core/userkeys/recipients.js +292 -0
  152. package/dist/core/userkeys/recipients.js.map +1 -0
  153. package/dist/core/userkeys/resolveUserKeyDocument.d.ts +21 -0
  154. package/dist/core/userkeys/resolveUserKeyDocument.d.ts.map +1 -0
  155. package/dist/core/userkeys/resolveUserKeyDocument.js +98 -0
  156. package/dist/core/userkeys/resolveUserKeyDocument.js.map +1 -0
  157. package/dist/core/userkeys/sealedCrypto.d.ts +21 -0
  158. package/dist/core/userkeys/sealedCrypto.d.ts.map +1 -0
  159. package/dist/core/userkeys/sealedCrypto.js +79 -0
  160. package/dist/core/userkeys/sealedCrypto.js.map +1 -0
  161. package/dist/core/userkeys/sealedTypes.d.ts +72 -0
  162. package/dist/core/userkeys/sealedTypes.d.ts.map +1 -0
  163. package/dist/core/userkeys/sealedTypes.js +14 -0
  164. package/dist/core/userkeys/sealedTypes.js.map +1 -0
  165. package/dist/core/userkeys/types.d.ts +70 -0
  166. package/dist/core/userkeys/types.d.ts.map +1 -0
  167. package/dist/core/userkeys/types.js +48 -0
  168. package/dist/core/userkeys/types.js.map +1 -0
  169. package/dist/core/userkeys/validateUserKeyDocument.d.ts +19 -0
  170. package/dist/core/userkeys/validateUserKeyDocument.d.ts.map +1 -0
  171. package/dist/core/userkeys/validateUserKeyDocument.js +42 -0
  172. package/dist/core/userkeys/validateUserKeyDocument.js.map +1 -0
  173. package/dist/node/server/CapabilityMatcher.d.ts +7 -2
  174. package/dist/node/server/CapabilityMatcher.d.ts.map +1 -1
  175. package/dist/node/server/CapabilityMatcher.js +12 -3
  176. package/dist/node/server/CapabilityMatcher.js.map +1 -1
  177. package/dist/node/server/DeviceDiscoveryService.d.ts +36 -0
  178. package/dist/node/server/DeviceDiscoveryService.d.ts.map +1 -0
  179. package/dist/node/server/DeviceDiscoveryService.js +165 -0
  180. package/dist/node/server/DeviceDiscoveryService.js.map +1 -0
  181. package/dist/node/server/MindooDBServer.d.ts +27 -1
  182. package/dist/node/server/MindooDBServer.d.ts.map +1 -1
  183. package/dist/node/server/MindooDBServer.js +265 -220
  184. package/dist/node/server/MindooDBServer.js.map +1 -1
  185. package/dist/node/server/SyncEventBus.d.ts +10 -0
  186. package/dist/node/server/SyncEventBus.d.ts.map +1 -1
  187. package/dist/node/server/SyncEventBus.js.map +1 -1
  188. package/dist/node/server/SystemAdminAuth.d.ts +7 -0
  189. package/dist/node/server/SystemAdminAuth.d.ts.map +1 -1
  190. package/dist/node/server/SystemAdminAuth.js +4 -1
  191. package/dist/node/server/SystemAdminAuth.js.map +1 -1
  192. package/dist/node/server/TenantManager.d.ts +86 -5
  193. package/dist/node/server/TenantManager.d.ts.map +1 -1
  194. package/dist/node/server/TenantManager.js +235 -71
  195. package/dist/node/server/TenantManager.js.map +1 -1
  196. package/dist/node/server/add-to-network.d.ts.map +1 -1
  197. package/dist/node/server/add-to-network.js +33 -15
  198. package/dist/node/server/add-to-network.js.map +1 -1
  199. package/dist/node/server/canonicalSetupName.d.ts +22 -0
  200. package/dist/node/server/canonicalSetupName.d.ts.map +1 -0
  201. package/dist/node/server/canonicalSetupName.js +38 -0
  202. package/dist/node/server/canonicalSetupName.js.map +1 -0
  203. package/dist/node/server/config.d.ts.map +1 -1
  204. package/dist/node/server/config.js +33 -1
  205. package/dist/node/server/config.js.map +1 -1
  206. package/dist/node/server/peer/ClusterAuditLog.d.ts +64 -0
  207. package/dist/node/server/peer/ClusterAuditLog.d.ts.map +1 -0
  208. package/dist/node/server/peer/ClusterAuditLog.js +99 -0
  209. package/dist/node/server/peer/ClusterAuditLog.js.map +1 -0
  210. package/dist/node/server/peer/ClusterJobRunner.d.ts +43 -0
  211. package/dist/node/server/peer/ClusterJobRunner.d.ts.map +1 -0
  212. package/dist/node/server/peer/ClusterJobRunner.js +119 -0
  213. package/dist/node/server/peer/ClusterJobRunner.js.map +1 -0
  214. package/dist/node/server/peer/ClusterManager.d.ts +116 -0
  215. package/dist/node/server/peer/ClusterManager.d.ts.map +1 -0
  216. package/dist/node/server/peer/ClusterManager.js +274 -0
  217. package/dist/node/server/peer/ClusterManager.js.map +1 -0
  218. package/dist/node/server/peer/PeerAuthService.d.ts +69 -0
  219. package/dist/node/server/peer/PeerAuthService.d.ts.map +1 -0
  220. package/dist/node/server/peer/PeerAuthService.js +208 -0
  221. package/dist/node/server/peer/PeerAuthService.js.map +1 -0
  222. package/dist/node/server/peer/PeerReplicator.d.ts +232 -0
  223. package/dist/node/server/peer/PeerReplicator.d.ts.map +1 -0
  224. package/dist/node/server/peer/PeerReplicator.js +879 -0
  225. package/dist/node/server/peer/PeerReplicator.js.map +1 -0
  226. package/dist/node/server/peer/routes.d.ts +66 -0
  227. package/dist/node/server/peer/routes.d.ts.map +1 -0
  228. package/dist/node/server/peer/routes.js +322 -0
  229. package/dist/node/server/peer/routes.js.map +1 -0
  230. package/dist/node/server/peer/types.d.ts +244 -0
  231. package/dist/node/server/peer/types.d.ts.map +1 -0
  232. package/dist/node/server/peer/types.js +189 -0
  233. package/dist/node/server/peer/types.js.map +1 -0
  234. package/dist/node/server/server.d.ts +1 -1
  235. package/dist/node/server/server.js +18 -37
  236. package/dist/node/server/server.js.map +1 -1
  237. package/dist/node/server/serverinit.d.ts +2 -1
  238. package/dist/node/server/serverinit.d.ts.map +1 -1
  239. package/dist/node/server/serverinit.js +34 -12
  240. package/dist/node/server/serverinit.js.map +1 -1
  241. package/dist/node/server/types.d.ts +42 -30
  242. package/dist/node/server/types.d.ts.map +1 -1
  243. package/dist/node/server/types.js +1 -7
  244. package/dist/node/server/types.js.map +1 -1
  245. package/dist/node/server/validation.d.ts +0 -6
  246. package/dist/node/server/validation.d.ts.map +1 -1
  247. package/dist/node/server/validation.js +1 -7
  248. package/dist/node/server/validation.js.map +1 -1
  249. package/package.json +5 -5
@@ -19,13 +19,14 @@
19
19
  import { NodeCryptoAdapter } from "../crypto/NodeCryptoAdapter";
20
20
  import { AuthenticationService } from "../../core/appendonlystores/network/AuthenticationService";
21
21
  import { ServerNetworkContentAddressedStore } from "../../appendonlystores/network/ServerNetworkContentAddressedStore";
22
+ import type { ServerPeerTokenResolver, ServerTrustedWitnessResolver } from "../../appendonlystores/network/ServerNetworkContentAddressedStore";
22
23
  import type { WitnessSigner } from "../../core/crypto/WitnessReceipt";
23
24
  import type { TimestampProvider } from "../../core/accesscontrol/timestamp/TimestampProvider";
24
25
  import type { MindooTenant, MindooTenantDirectory, ContentAddressedStore, DirectoryUserLookup } from "../../core/types";
25
26
  import { StoreKind } from "../../core/types";
26
27
  import type { PrivateUserId } from "../../core/userid";
27
28
  import { StoreFactory } from "./StoreFactory";
28
- import type { TenantConfig, TenantContext, RegisterTenantRequest, TrustedServer, NamedRemoteServerConfig } from "./types";
29
+ import type { TenantConfig, TenantContext, RegisterTenantRequest, TrustedServer } from "./types";
29
30
  interface LoadedTenant {
30
31
  context: TenantContext;
31
32
  storeFactory: StoreFactory;
@@ -55,6 +56,7 @@ export declare class SimpleMindooDirectory implements Pick<MindooTenantDirectory
55
56
  encryptionPublicKey: string;
56
57
  } | null>;
57
58
  getUserBySigningPublicKey(publicKey: string): Promise<DirectoryUserLookup | null>;
59
+ private toLookup;
58
60
  isUserRevoked(username: string): Promise<boolean>;
59
61
  validatePublicSigningKey(publicKey: string, _opts?: {
60
62
  forceRefresh?: boolean;
@@ -79,6 +81,10 @@ export declare class TenantManager {
79
81
  private trustedServers;
80
82
  /** Lazily-built, cached witness signer (server's Ed25519 signing identity). */
81
83
  private witnessSigner;
84
+ /** Lazily-imported RSA private key for peer replication (see getServerEncryptionPrivateKey). */
85
+ private serverEncryptionPrivateKey;
86
+ /** Peer wiring handed down to every server store; see setPeerAccessControl. */
87
+ private peerAccessControl;
82
88
  /** Per-tenant persistent registry of executed purges (docs/accesscontrol.md §13). */
83
89
  private purgedDocRegistries;
84
90
  /** In-flight purge executions, keyed by normalized tenant id (serializes runs). */
@@ -86,6 +92,44 @@ export declare class TenantManager {
86
92
  constructor(dataDir: string, serverPassword?: string);
87
93
  getServerIdentity(): PrivateUserId | null;
88
94
  getServerPublicInfo(): TrustedServer | null;
95
+ getDataDir(): string;
96
+ /**
97
+ * Teach every server store how to recognise a trusted peer.
98
+ *
99
+ * `MindooDBServer` calls this once at startup. Two hooks, both required for
100
+ * peer replication to be correct:
101
+ *
102
+ * - `peerTokenResolver` lets a peer's cluster-level JWT through the tenant
103
+ * sync routes. A peer holds no per-tenant grant, so without it the mirror
104
+ * would need a second replication protocol; with it the peer reuses the
105
+ * client one and the store skips the per-user gates that do not apply to a
106
+ * full replica.
107
+ * - `trustedWitnessResolver` decides whose witness receipts may be preserved
108
+ * instead of re-stamped (convergence invariant 3). Without it a replicated
109
+ * entry would get a fresh `receivedAt` on every hop and "trusted time"
110
+ * would mean "time it reached the last server".
111
+ *
112
+ * Must be called before the first {@link getServerStore}, since stores are
113
+ * cached per tenant and database.
114
+ */
115
+ setPeerAccessControl(options: {
116
+ peerTokenResolver?: ServerPeerTokenResolver;
117
+ trustedWitnessResolver?: ServerTrustedWitnessResolver;
118
+ }): void;
119
+ /**
120
+ * Database ids this server holds for a tenant, read from
121
+ * `{dataDir}/{tenantId}/stores`. Used by peer replication to mirror every
122
+ * database rather than a configured subset.
123
+ */
124
+ listDatabases(tenantId: string): string[];
125
+ /**
126
+ * The server's own RSA private key, imported for decryption.
127
+ *
128
+ * Peer replication needs it because the remote wraps transported entries to
129
+ * this server's `encryptionPublicKey` (the one in its `trusted-servers.json`),
130
+ * exactly as it would wrap them for a client.
131
+ */
132
+ getServerEncryptionPrivateKey(): Promise<CryptoKey | undefined>;
89
133
  /**
90
134
  * Build (and cache) the server's witness identity used to stamp receipts on
91
135
  * accepted entries (docs/accesscontrol.md §5.3). Returns undefined when the
@@ -102,6 +146,15 @@ export declare class TenantManager {
102
146
  getTimestampProvider(): Promise<TimestampProvider | undefined>;
103
147
  listTrustedServers(): TrustedServer[];
104
148
  addTrustedServer(server: TrustedServer): void;
149
+ /**
150
+ * Add or replace a trusted server by name.
151
+ *
152
+ * Peer CRUD needs an upsert: `addTrustedServer` throws on an existing name,
153
+ * which is right for `add-to-network` (a silent overwrite of a peer's keys
154
+ * would be a trust change disguised as a config edit) but wrong for the admin
155
+ * API, where changing a peer's url or role is the common case.
156
+ */
157
+ saveTrustedServer(server: TrustedServer): void;
105
158
  removeTrustedServer(name: string): boolean;
106
159
  private persistTrustedServers;
107
160
  private getServerKeyBagPath;
@@ -117,20 +170,38 @@ export declare class TenantManager {
117
170
  private resolveIncomingPublicInfosKey;
118
171
  private getStoredPublicInfosKeys;
119
172
  private persistTenantPublicInfosKey;
173
+ /**
174
+ * All stored `$publicinfos` AES key versions for a tenant from `server.keybag`.
175
+ * Empty when the tenant was never published or the server keybag is locked.
176
+ */
177
+ getPublicInfosKeysForTenant(tenantId: string): Promise<Uint8Array[]>;
120
178
  listTenantPublicInfosFingerprints(tenantId: string): Promise<string[]>;
121
179
  registerTenant(request: RegisterTenantRequest): Promise<RegisterTenantResult>;
122
180
  getTenant(tenantId: string): Promise<LoadedTenant>;
123
181
  tenantExists(tenantId: string): boolean;
182
+ /**
183
+ * Read the tenant's registered admin signing key without fully loading the
184
+ * tenant. Returns `null` if the tenant is missing or the config is unreadable.
185
+ */
186
+ getTenantAdminSigningPublicKey(tenantId: string): string | null;
187
+ /**
188
+ * True when `publicsignkey` is the registered tenant-admin signing key for
189
+ * `tenantId` (PEM whitespace-insensitive).
190
+ */
191
+ isTenantAdminSigningKeyFor(tenantId: string, publicsignkey: string): boolean;
192
+ /**
193
+ * True when `publicsignkey` is the registered admin signing key of any
194
+ * tenant on this server. Used to issue a system JWT so that admin can
195
+ * DELETE their own tenant; other `/system/*` routes stay capability-gated.
196
+ */
197
+ isRegisteredTenantAdminSigningKey(publicsignkey: string): boolean;
124
198
  listTenants(): string[];
125
199
  /**
126
200
  * Update operator-owned fields of an existing tenant configuration.
127
201
  * Only the fields present in `updates` are overwritten.
128
202
  */
129
- updateTenantConfig(tenantId: string, updates: Partial<Pick<TenantConfig, "defaultStoreType" | "remoteServers">>): void;
203
+ updateTenantConfig(tenantId: string, updates: Partial<Pick<TenantConfig, "defaultStoreType">>): void;
130
204
  removeTenant(tenantId: string): Promise<void>;
131
- getTenantSyncServers(tenantId: string): NamedRemoteServerConfig[];
132
- addTenantSyncServer(tenantId: string, server: NamedRemoteServerConfig): void;
133
- removeTenantSyncServer(tenantId: string, serverName: string): boolean;
134
205
  getServerStore(tenantId: string, dbId: string, storeKind: StoreKind): Promise<ServerNetworkContentAddressedStore>;
135
206
  /**
136
207
  * Build a Tier 1 evaluator closure for a server store, or undefined when the
@@ -140,6 +211,16 @@ export declare class TenantManager {
140
211
  * store (metadata only, no decryption) and comparing it to the change author.
141
212
  */
142
213
  private buildTier1Evaluator;
214
+ /**
215
+ * Builtin `userdirectory` write invariant: admin may create/delete, only the
216
+ * owning person may change. `username_hash` is read from the decrypted
217
+ * `doc_create` payload (`$publicinfos`). Personal documents are sealed to
218
+ * their owner and therefore undecryptable here, so they are owned by whoever
219
+ * signed their `doc_create` — `resolveCreatorSigningKey` answers that from
220
+ * the entry chain alone.
221
+ */
222
+ private buildUserdirectoryWriteContext;
223
+ private findCreateEntry;
143
224
  /**
144
225
  * Build a database-open evaluator closure for a server store, or undefined
145
226
  * when the directory cannot evaluate the database-open policy (e.g. a
@@ -1 +1 @@
1
- {"version":3,"file":"TenantManager.d.ts","sourceRoot":"","sources":["../../../src/node/server/TenantManager.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"TenantManager.d.ts","sourceRoot":"","sources":["../../../src/node/server/TenantManager.ts"],"names":[],"mappings":"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"}
@@ -31,9 +31,9 @@ const RSAEncryption_1 = require("../../core/crypto/RSAEncryption");
31
31
  const privateKeyEncryption_1 = require("../../core/crypto/privateKeyEncryption");
32
32
  const KeyBag_1 = require("../../core/keys/KeyBag");
33
33
  const types_1 = require("../../core/types");
34
+ const builtinDbInvariants_1 = require("../../core/builtinDbInvariants");
35
+ const DirectoryStateNode_1 = require("../../core/accesscontrol/DirectoryStateNode");
34
36
  const StoreFactory_1 = require("./StoreFactory");
35
- const types_2 = require("./types");
36
- const urlSafety_1 = require("../../core/utils/urlSafety");
37
37
  // ---------------------------------------------------------------------------
38
38
  // StoreFactoryAdapter
39
39
  // ---------------------------------------------------------------------------
@@ -89,10 +89,21 @@ class SimpleMindooDirectory {
89
89
  };
90
90
  }
91
91
  async getUserBySigningPublicKey(publicKey) {
92
- const user = this.usersByKey.get(publicKey);
93
- if (!user) {
92
+ const exact = this.usersByKey.get(publicKey);
93
+ if (exact) {
94
+ return this.toLookup(exact);
95
+ }
96
+ const needle = publicKey.replace(/\s+/g, "");
97
+ if (!needle)
94
98
  return null;
99
+ for (const [key, user] of this.usersByKey) {
100
+ if (key.replace(/\s+/g, "") === needle) {
101
+ return this.toLookup(user);
102
+ }
95
103
  }
104
+ return null;
105
+ }
106
+ toLookup(user) {
96
107
  if (typeof user.username === "string" &&
97
108
  this.revokedUsers.has(user.username.toLowerCase())) {
98
109
  return null;
@@ -176,7 +187,8 @@ class CompositeMindooDirectory {
176
187
  return this.inner.isUserRevoked(username);
177
188
  }
178
189
  async validatePublicSigningKey(publicKey, opts) {
179
- if (this.adminBootstrapIdentity && this.adminBootstrapIdentity.signingPublicKey === publicKey) {
190
+ if (this.adminBootstrapIdentity &&
191
+ (0, DirectoryStateNode_1.signingKeysEqual)(this.adminBootstrapIdentity.signingPublicKey, publicKey)) {
180
192
  return true;
181
193
  }
182
194
  const innerResult = await this.inner.validatePublicSigningKey(publicKey, opts);
@@ -208,7 +220,8 @@ class CompositeMindooDirectory {
208
220
  // server, mirroring getUserPublicKeys. Without this, key-based identity
209
221
  // resolution (and the validateToken active-key check below) would treat the
210
222
  // admin's own key as unknown the moment the directory has no admin grant.
211
- if (this.adminBootstrapIdentity && this.adminBootstrapIdentity.signingPublicKey === publicKey) {
223
+ if (this.adminBootstrapIdentity &&
224
+ (0, DirectoryStateNode_1.signingKeysEqual)(this.adminBootstrapIdentity.signingPublicKey, publicKey)) {
212
225
  return {
213
226
  username: this.adminBootstrapIdentity.username,
214
227
  signingPublicKey: this.adminBootstrapIdentity.signingPublicKey,
@@ -270,6 +283,19 @@ class CompositeMindooDirectory {
270
283
  // Config-based directories carry no database-open policy -> unrestricted.
271
284
  return true;
272
285
  }
286
+ /**
287
+ * Forwarded so server-side `userdirectory` writes can resolve the signer's
288
+ * grant. Without this, `evaluateUserdirectoryInvariant` sees a missing
289
+ * method, leaves `signerUsernameHash` null, and denies every non-admin
290
+ * change as "signer is not a granted device" — including the founder
291
+ * wrapping a second device of their own User-Key.
292
+ */
293
+ async resolveUsernameHashForSigningKey(signingKey, trustedTime) {
294
+ if (typeof this.inner.resolveUsernameHashForSigningKey === "function") {
295
+ return this.inner.resolveUsernameHashForSigningKey(signingKey, trustedTime);
296
+ }
297
+ return null;
298
+ }
273
299
  }
274
300
  // ---------------------------------------------------------------------------
275
301
  // TenantManager
@@ -289,6 +315,8 @@ class TenantManager {
289
315
  this.loadedTenants = new Map();
290
316
  this.serverIdentity = null;
291
317
  this.trustedServers = [];
318
+ /** Peer wiring handed down to every server store; see setPeerAccessControl. */
319
+ this.peerAccessControl = {};
292
320
  /** Per-tenant persistent registry of executed purges (docs/accesscontrol.md §13). */
293
321
  this.purgedDocRegistries = new Map();
294
322
  /** In-flight purge executions, keyed by normalized tenant id (serializes runs). */
@@ -331,6 +359,66 @@ class TenantManager {
331
359
  encryptionPublicKey: this.serverIdentity.userEncryptionKeyPair.publicKey,
332
360
  };
333
361
  }
362
+ getDataDir() {
363
+ return this.dataDir;
364
+ }
365
+ /**
366
+ * Teach every server store how to recognise a trusted peer.
367
+ *
368
+ * `MindooDBServer` calls this once at startup. Two hooks, both required for
369
+ * peer replication to be correct:
370
+ *
371
+ * - `peerTokenResolver` lets a peer's cluster-level JWT through the tenant
372
+ * sync routes. A peer holds no per-tenant grant, so without it the mirror
373
+ * would need a second replication protocol; with it the peer reuses the
374
+ * client one and the store skips the per-user gates that do not apply to a
375
+ * full replica.
376
+ * - `trustedWitnessResolver` decides whose witness receipts may be preserved
377
+ * instead of re-stamped (convergence invariant 3). Without it a replicated
378
+ * entry would get a fresh `receivedAt` on every hop and "trusted time"
379
+ * would mean "time it reached the last server".
380
+ *
381
+ * Must be called before the first {@link getServerStore}, since stores are
382
+ * cached per tenant and database.
383
+ */
384
+ setPeerAccessControl(options) {
385
+ this.peerAccessControl = options;
386
+ }
387
+ /**
388
+ * Database ids this server holds for a tenant, read from
389
+ * `{dataDir}/{tenantId}/stores`. Used by peer replication to mirror every
390
+ * database rather than a configured subset.
391
+ */
392
+ listDatabases(tenantId) {
393
+ const storesDir = (0, path_1.join)(this.resolveTenantDir(tenantId.toLowerCase()), "stores");
394
+ if (!(0, fs_1.existsSync)(storesDir)) {
395
+ return [];
396
+ }
397
+ try {
398
+ return (0, fs_1.readdirSync)(storesDir).filter((entry) => (0, fs_1.statSync)((0, path_1.join)(storesDir, entry)).isDirectory());
399
+ }
400
+ catch (error) {
401
+ console.warn(`[TenantManager] Could not list databases for ${tenantId}:`, error);
402
+ return [];
403
+ }
404
+ }
405
+ /**
406
+ * The server's own RSA private key, imported for decryption.
407
+ *
408
+ * Peer replication needs it because the remote wraps transported entries to
409
+ * this server's `encryptionPublicKey` (the one in its `trusted-servers.json`),
410
+ * exactly as it would wrap them for a client.
411
+ */
412
+ async getServerEncryptionPrivateKey() {
413
+ if (this.serverEncryptionPrivateKey)
414
+ return this.serverEncryptionPrivateKey;
415
+ if (!this.serverIdentity || !this.serverPassword)
416
+ return undefined;
417
+ const subtle = this.cryptoAdapter.getSubtle();
418
+ const keyBuffer = await (0, privateKeyEncryption_1.decryptPrivateKey)(this.cryptoAdapter, this.serverIdentity.userEncryptionKeyPair.privateKey, this.serverPassword, "encryption");
419
+ this.serverEncryptionPrivateKey = await subtle.importKey("pkcs8", keyBuffer, { name: "RSA-OAEP", hash: "SHA-256" }, false, ["decrypt"]);
420
+ return this.serverEncryptionPrivateKey;
421
+ }
334
422
  /**
335
423
  * Build (and cache) the server's witness identity used to stamp receipts on
336
424
  * accepted entries (docs/accesscontrol.md §5.3). Returns undefined when the
@@ -379,6 +467,25 @@ class TenantManager {
379
467
  this.persistTrustedServers();
380
468
  console.log(`[TenantManager] Added trusted server: ${server.name}`);
381
469
  }
470
+ /**
471
+ * Add or replace a trusted server by name.
472
+ *
473
+ * Peer CRUD needs an upsert: `addTrustedServer` throws on an existing name,
474
+ * which is right for `add-to-network` (a silent overwrite of a peer's keys
475
+ * would be a trust change disguised as a config edit) but wrong for the admin
476
+ * API, where changing a peer's url or role is the common case.
477
+ */
478
+ saveTrustedServer(server) {
479
+ const idx = this.trustedServers.findIndex((s) => s.name.toLowerCase() === server.name.toLowerCase());
480
+ if (idx === -1) {
481
+ this.trustedServers.push(server);
482
+ }
483
+ else {
484
+ this.trustedServers[idx] = server;
485
+ }
486
+ this.persistTrustedServers();
487
+ console.log(`[TenantManager] Saved trusted server: ${server.name}`);
488
+ }
382
489
  removeTrustedServer(name) {
383
490
  const idx = this.trustedServers.findIndex((s) => s.name.toLowerCase() === name.toLowerCase());
384
491
  if (idx === -1)
@@ -493,6 +600,13 @@ class TenantManager {
493
600
  await this.saveServerKeyBag(keyBag);
494
601
  }
495
602
  }
603
+ /**
604
+ * All stored `$publicinfos` AES key versions for a tenant from `server.keybag`.
605
+ * Empty when the tenant was never published or the server keybag is locked.
606
+ */
607
+ async getPublicInfosKeysForTenant(tenantId) {
608
+ return this.getStoredPublicInfosKeys(tenantId.toLowerCase());
609
+ }
496
610
  async listTenantPublicInfosFingerprints(tenantId) {
497
611
  const normalizedId = tenantId.toLowerCase();
498
612
  const configPath = this.resolveTenantConfigPath(normalizedId);
@@ -564,6 +678,52 @@ class TenantManager {
564
678
  const configPath = this.resolveTenantConfigPath(normalizedId);
565
679
  return (0, fs_1.existsSync)(configPath);
566
680
  }
681
+ /**
682
+ * Read the tenant's registered admin signing key without fully loading the
683
+ * tenant. Returns `null` if the tenant is missing or the config is unreadable.
684
+ */
685
+ getTenantAdminSigningPublicKey(tenantId) {
686
+ try {
687
+ const normalizedId = tenantId.toLowerCase();
688
+ const cached = this.loadedTenants.get(normalizedId);
689
+ if (cached?.context.config.adminSigningPublicKey) {
690
+ return cached.context.config.adminSigningPublicKey;
691
+ }
692
+ const configPath = this.resolveTenantConfigPath(normalizedId);
693
+ if (!(0, fs_1.existsSync)(configPath)) {
694
+ return null;
695
+ }
696
+ const config = JSON.parse((0, fs_1.readFileSync)(configPath, "utf-8"));
697
+ return typeof config.adminSigningPublicKey === "string" &&
698
+ config.adminSigningPublicKey.length > 0
699
+ ? config.adminSigningPublicKey
700
+ : null;
701
+ }
702
+ catch {
703
+ return null;
704
+ }
705
+ }
706
+ /**
707
+ * True when `publicsignkey` is the registered tenant-admin signing key for
708
+ * `tenantId` (PEM whitespace-insensitive).
709
+ */
710
+ isTenantAdminSigningKeyFor(tenantId, publicsignkey) {
711
+ const adminKey = this.getTenantAdminSigningPublicKey(tenantId);
712
+ return adminKey !== null && (0, DirectoryStateNode_1.signingKeysEqual)(adminKey, publicsignkey);
713
+ }
714
+ /**
715
+ * True when `publicsignkey` is the registered admin signing key of any
716
+ * tenant on this server. Used to issue a system JWT so that admin can
717
+ * DELETE their own tenant; other `/system/*` routes stay capability-gated.
718
+ */
719
+ isRegisteredTenantAdminSigningKey(publicsignkey) {
720
+ for (const tenantId of this.listTenants()) {
721
+ if (this.isTenantAdminSigningKeyFor(tenantId, publicsignkey)) {
722
+ return true;
723
+ }
724
+ }
725
+ return false;
726
+ }
567
727
  listTenants() {
568
728
  if (!(0, fs_1.existsSync)(this.dataDir)) {
569
729
  return [];
@@ -593,9 +753,6 @@ class TenantManager {
593
753
  if (updates.defaultStoreType !== undefined) {
594
754
  config.defaultStoreType = updates.defaultStoreType;
595
755
  }
596
- if (updates.remoteServers !== undefined) {
597
- config.remoteServers = updates.remoteServers;
598
- }
599
756
  (0, fs_1.writeFileSync)(configPath, JSON.stringify(config, null, 2), "utf-8");
600
757
  this.loadedTenants.delete(normalizedId);
601
758
  console.log(`[TenantManager] Updated tenant config: ${normalizedId}`);
@@ -616,65 +773,6 @@ class TenantManager {
616
773
  console.log(`[TenantManager] Removed tenant: ${normalizedId}`);
617
774
  }
618
775
  // =======================================================================
619
- // Per-tenant sync server management
620
- // =======================================================================
621
- getTenantSyncServers(tenantId) {
622
- const normalizedId = tenantId.toLowerCase();
623
- const configPath = this.resolveTenantConfigPath(normalizedId);
624
- if (!(0, fs_1.existsSync)(configPath)) {
625
- throw new Error(`Tenant ${normalizedId} not found`);
626
- }
627
- const config = JSON.parse((0, fs_1.readFileSync)(configPath, "utf-8"));
628
- return (config.remoteServers || []);
629
- }
630
- addTenantSyncServer(tenantId, server) {
631
- const normalizedId = tenantId.toLowerCase();
632
- // SSRF guard (defense in depth alongside the HTTP route): a configured sync
633
- // URL is fetched server-side, so reject plaintext/internal targets unless
634
- // explicitly allowed for local development.
635
- const allowInsecure = /^(1|true)$/i.test(process.env[types_2.ENV_VARS.ALLOW_INSECURE_SYNC_URLS] ?? "");
636
- (0, urlSafety_1.assertSafeSyncUrl)(server.url, {
637
- requireHttps: !allowInsecure,
638
- allowPrivate: allowInsecure,
639
- });
640
- const configPath = this.resolveTenantConfigPath(normalizedId);
641
- if (!(0, fs_1.existsSync)(configPath)) {
642
- throw new Error(`Tenant ${normalizedId} not found`);
643
- }
644
- const config = JSON.parse((0, fs_1.readFileSync)(configPath, "utf-8"));
645
- const servers = (config.remoteServers || []);
646
- const idx = servers.findIndex((s) => s.name?.toLowerCase() === server.name.toLowerCase());
647
- if (idx >= 0) {
648
- servers[idx] = server;
649
- console.log(`[TenantManager] Updated sync server "${server.name}" for tenant ${normalizedId}`);
650
- }
651
- else {
652
- servers.push(server);
653
- console.log(`[TenantManager] Added sync server "${server.name}" for tenant ${normalizedId}`);
654
- }
655
- config.remoteServers = servers;
656
- (0, fs_1.writeFileSync)(configPath, JSON.stringify(config, null, 2), "utf-8");
657
- this.loadedTenants.delete(normalizedId);
658
- }
659
- removeTenantSyncServer(tenantId, serverName) {
660
- const normalizedId = tenantId.toLowerCase();
661
- const configPath = this.resolveTenantConfigPath(normalizedId);
662
- if (!(0, fs_1.existsSync)(configPath)) {
663
- throw new Error(`Tenant ${normalizedId} not found`);
664
- }
665
- const config = JSON.parse((0, fs_1.readFileSync)(configPath, "utf-8"));
666
- const servers = (config.remoteServers || []);
667
- const idx = servers.findIndex((s) => s.name?.toLowerCase() === serverName.toLowerCase());
668
- if (idx === -1)
669
- return false;
670
- servers.splice(idx, 1);
671
- config.remoteServers = servers;
672
- (0, fs_1.writeFileSync)(configPath, JSON.stringify(config, null, 2), "utf-8");
673
- this.loadedTenants.delete(normalizedId);
674
- console.log(`[TenantManager] Removed sync server "${serverName}" from tenant ${normalizedId}`);
675
- return true;
676
- }
677
- // =======================================================================
678
776
  // Server stores
679
777
  // =======================================================================
680
778
  async getServerStore(tenantId, dbId, storeKind) {
@@ -705,6 +803,9 @@ class TenantManager {
705
803
  const purgedDocResolver = dbId === "directory"
706
804
  ? undefined
707
805
  : () => this.getPurgedDocRegistry(tenantId).getPurgedDocIds(dbId);
806
+ const builtinWriteContext = dbId === types_1.USER_DIRECTORY_DB_ID
807
+ ? this.buildUserdirectoryWriteContext(tenant, localStore)
808
+ : undefined;
708
809
  const serverStore = new ServerNetworkContentAddressedStore_1.ServerNetworkContentAddressedStore(localStore, directory, tenant.authService, this.cryptoAdapter, undefined, {
709
810
  timestampProvider,
710
811
  witnessDbid: dbId,
@@ -713,6 +814,8 @@ class TenantManager {
713
814
  dbAccessEvaluator,
714
815
  revokedKeyResolver,
715
816
  purgedDocResolver,
817
+ builtinWriteContext,
818
+ ...this.peerAccessControl,
716
819
  });
717
820
  tenant.serverStores.set(cacheKey, serverStore);
718
821
  console.log(`[TenantManager] Created server store for ${tenantId}/${dbId}/${storeKind}`);
@@ -734,15 +837,27 @@ class TenantManager {
734
837
  // The witness evaluates Tier 1 at its acceptance time (now), the same time
735
838
  // it will stamp into the receipt (docs/accesscontrol.md §5.3, §7).
736
839
  const trustedTime = Date.now();
737
- // Resolve `$author`: for doc_create the author is the creator; otherwise
738
- // compare the change signer to the document's original doc_create author.
840
+ // Resolve `$author` at grant level: creator at the doc_create trusted
841
+ // time, signer at the witness's acceptance time. Falls back to device-key
842
+ // equality when the directory cannot answer.
739
843
  let isAuthor = entry.entryType === "doc_create";
740
844
  if (!isAuthor) {
741
845
  try {
742
846
  const docEntries = await localStore.findNewEntriesForDoc([], entry.docId);
743
847
  const createEntry = docEntries.find((m) => m.entryType === "doc_create");
744
848
  if (createEntry) {
745
- isAuthor = createEntry.createdByPublicKey === entry.createdByPublicKey;
849
+ const samePerson = directory.isSamePerson;
850
+ if (typeof samePerson === "function") {
851
+ isAuthor = await samePerson({
852
+ creatorSigningKey: createEntry.createdByPublicKey,
853
+ signerSigningKey: entry.createdByPublicKey,
854
+ creatorTrustedTime: createEntry.receivedAt ?? createEntry.createdAt,
855
+ signerTrustedTime: trustedTime,
856
+ });
857
+ }
858
+ else {
859
+ isAuthor = createEntry.createdByPublicKey === entry.createdByPublicKey;
860
+ }
746
861
  }
747
862
  }
748
863
  catch {
@@ -759,6 +874,55 @@ class TenantManager {
759
874
  });
760
875
  };
761
876
  }
877
+ /**
878
+ * Builtin `userdirectory` write invariant: admin may create/delete, only the
879
+ * owning person may change. `username_hash` is read from the decrypted
880
+ * `doc_create` payload (`$publicinfos`). Personal documents are sealed to
881
+ * their owner and therefore undecryptable here, so they are owned by whoever
882
+ * signed their `doc_create` — `resolveCreatorSigningKey` answers that from
883
+ * the entry chain alone.
884
+ */
885
+ buildUserdirectoryWriteContext(tenant, localStore) {
886
+ const adminPublicKey = tenant.context.config.adminSigningPublicKey;
887
+ const mindooTenant = tenant.mindooTenant;
888
+ return {
889
+ adminPublicKey,
890
+ resolveCreatorSigningKey: async (entry) => {
891
+ const createEntry = await this.findCreateEntry(localStore, entry);
892
+ return createEntry?.createdByPublicKey ?? null;
893
+ },
894
+ resolveDocumentUsernameHash: mindooTenant
895
+ ? async (entry) => {
896
+ const createEntry = await this.findCreateEntry(localStore, entry);
897
+ if (!createEntry)
898
+ return null;
899
+ try {
900
+ const plaintext = await mindooTenant.decryptPayload(createEntry.encryptedData, createEntry.decryptionKeyId);
901
+ return (0, builtinDbInvariants_1.usernameHashFromCreateChangeBytes)(plaintext);
902
+ }
903
+ catch {
904
+ return null;
905
+ }
906
+ }
907
+ : undefined,
908
+ };
909
+ }
910
+ async findCreateEntry(localStore, entry) {
911
+ if (entry.entryType === "doc_create") {
912
+ return entry;
913
+ }
914
+ try {
915
+ const metas = await localStore.findNewEntriesForDoc([], entry.docId);
916
+ const createMeta = metas.find((m) => m.entryType === "doc_create");
917
+ if (!createMeta)
918
+ return null;
919
+ const loaded = await localStore.getEntries([createMeta.id]);
920
+ return loaded[0] ?? null;
921
+ }
922
+ catch {
923
+ return null;
924
+ }
925
+ }
762
926
  /**
763
927
  * Build a database-open evaluator closure for a server store, or undefined
764
928
  * when the directory cannot evaluate the database-open policy (e.g. a