fix(client): per-entity write round-trip + dedicated inbox anchor
Real-broker validation of the per-document model surfaced round-trip breaks the fake-ng unit tests missed: - The inbox anchor was the shim graph itself, making loadShim ~60s; give the inbox its own document so anchor resolution is fast and isolated. - store-registry adjustments so per-entity documents created via the SDK are indexed and readable back through the scope fan-out. docs/simulation.md updated. 89 tests pass; tsc rc=0. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+9
-3
@@ -96,9 +96,15 @@ store-id:
|
|||||||
blocker, [`migration-guide.md`](./migration-guide.md)). At migration each scope
|
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,
|
resolves to the user's REAL per-scope store — the change is in this function,
|
||||||
the consumer is unchanged.
|
the consumer is unchanged.
|
||||||
- **`resolveInboxAnchor()`** — the anchor where emulated inbox deposits land
|
- **`resolveInboxAnchor()`** — the anchor where emulated inbox deposits land: a
|
||||||
(today the shared wallet's private store — a real repo NURI, required because
|
**DEDICATED inbox document** (a reserved account's public scope document, from
|
||||||
the broker rejects a `urn:` anchor). At migration it becomes the host's native
|
`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.
|
inbox NURI.
|
||||||
|
|
||||||
Both resolve the native store ids from the **injected session**
|
Both resolve the native store ids from the **injected session**
|
||||||
|
|||||||
@@ -315,15 +315,31 @@ export async function resolveScopeGraph(scope: Scope): Promise<Nuri> {
|
|||||||
return scopeStoreNuri(scope);
|
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
|
* The inbox anchor NURI for the current session (where emulated inbox deposits
|
||||||
* physically land). SDK-shaped: the consumer never resolves the private store
|
* physically land). SDK-shaped: the consumer never resolves a store itself.
|
||||||
* 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
|
* This is a DEDICATED inbox DOCUMENT (a reserved account's public scope document —
|
||||||
* migration it becomes the host's native inbox and this resolution moves here.
|
* 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<Nuri> {
|
export async function resolveInboxAnchor(): Promise<Nuri> {
|
||||||
return scopeStoreNuri("private");
|
const record = await ensureAccount(INBOX_ANCHOR_ACCOUNT);
|
||||||
|
return record.docPublic;
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- per-entity documents + per-scope index -------------------------------
|
// --- per-entity documents + per-scope index -------------------------------
|
||||||
|
|||||||
@@ -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("private")).toBe("did:ng:PRIV");
|
||||||
expect(await resolveScopeGraph("protected")).toBe("did:ng:PROT");
|
expect(await resolveScopeGraph("protected")).toBe("did:ng:PROT");
|
||||||
expect(await resolveScopeGraph("public")).toBe("did:ng:PROT"); // co-located
|
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 () => {
|
test("resolveScopeGraph falls back to the private store when no protected id is injected", async () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user