diff --git a/docs/simulation.md b/docs/simulation.md index 0ab2e96..94b0958 100644 --- a/docs/simulation.md +++ b/docs/simulation.md @@ -96,9 +96,15 @@ store-id: blocker, [`migration-guide.md`](./migration-guide.md)). At migration each scope resolves to the user's REAL per-scope store — the change is in this function, the consumer is unchanged. -- **`resolveInboxAnchor()`** — the anchor where emulated inbox deposits land - (today the shared wallet's private store — a real repo NURI, required because - the broker rejects a `urn:` anchor). At migration it becomes the host's native +- **`resolveInboxAnchor()`** — the anchor where emulated inbox deposits land: a + **DEDICATED inbox document** (a reserved account's public scope document, from + `docCreate` — a real repo NURI, stable across clients), **not** the shared + wallet's private-store root. Why dedicated: the shim (the account→document trust + root) lives in the private-store graph and is scanned on every `loadShim`; + routing every inbox deposit into that SAME graph bloats it without bound + (thousands of deposit triples across sessions), turning `loadShim` into a + multi-second full-graph scan. A separate inbox document keeps the shim graph + small and the deposits isolated. At migration it becomes the host's native inbox NURI. Both resolve the native store ids from the **injected session** diff --git a/packages/client/src/store-registry.ts b/packages/client/src/store-registry.ts index 3b0ec4b..9155a69 100644 --- a/packages/client/src/store-registry.ts +++ b/packages/client/src/store-registry.ts @@ -315,15 +315,31 @@ export async function resolveScopeGraph(scope: Scope): Promise { return scopeStoreNuri(scope); } +/** + * The reserved account that OWNS the shared registration-inbox document. Like the + * discovery index's special account, it lives in the reserved namespace (no user + * can produce this key) and only HOSTS a document — its `public` scope document is + * the inbox anchor. Disappears at migration (native per-document inboxes). + */ +const INBOX_ANCHOR_ACCOUNT = reservedAccount("inbox"); + /** * The inbox anchor NURI for the current session (where emulated inbox deposits - * physically land). SDK-shaped: the consumer never resolves the private store - * itself. In the polyfill this is the shared wallet's private store (a real - * repo NURI — required because the broker rejects a `urn:` anchor); at - * migration it becomes the host's native inbox and this resolution moves here. + * physically land). SDK-shaped: the consumer never resolves a store itself. + * + * This is a DEDICATED inbox DOCUMENT (a reserved account's public scope document — + * a real repo NURI from `docCreate`, stable across clients via the shim), NOT the + * shared wallet's private-store root. Reason (perf + hygiene): the shim (the + * account→document trust root) lives in the private-store graph and is scanned on + * every `loadShim`; routing every inbox deposit into that SAME graph bloats it + * without bound (thousands of deposit triples across sessions), turning `loadShim` + * into a multi-second full-graph scan. A separate inbox document keeps the shim + * graph small and the deposits isolated. At migration this becomes the host's + * native per-document inbox and the resolution moves here. */ export async function resolveInboxAnchor(): Promise { - return scopeStoreNuri("private"); + const record = await ensureAccount(INBOX_ANCHOR_ACCOUNT); + return record.docPublic; } // --- per-entity documents + per-scope index ------------------------------- diff --git a/packages/client/test/store-registry.test.ts b/packages/client/test/store-registry.test.ts index 721fad5..11ab647 100644 --- a/packages/client/test/store-registry.test.ts +++ b/packages/client/test/store-registry.test.ts @@ -196,7 +196,14 @@ test("resolveScopeGraph maps scopes to native store NURIs (no store-id leaks to expect(await resolveScopeGraph("private")).toBe("did:ng:PRIV"); expect(await resolveScopeGraph("protected")).toBe("did:ng:PROT"); expect(await resolveScopeGraph("public")).toBe("did:ng:PROT"); // co-located - expect(await resolveInboxAnchor()).toBe("did:ng:PRIV"); + // The inbox anchor is now a DEDICATED inbox DOCUMENT (a reserved account's + // public scope doc, from docCreate) — NOT the private-store root — so inbox + // deposits don't bloat the shim graph. It is a real repo NURI and STABLE + // across calls (same reserved account → same document). + const anchor = await resolveInboxAnchor(); + expect(anchor).toMatch(/^did:ng:o:doc/); + expect(anchor).not.toBe("did:ng:PRIV"); + expect(await resolveInboxAnchor()).toBe(anchor); // stable }); test("resolveScopeGraph falls back to the private store when no protected id is injected", async () => {