/** * ReadCap ACTIVE — end-to-end proof that isolation is enforced by the emulated * cap registry (not merely by the app's social isolation filter). * * This mirrors exactly what the app's storeRegistry wrapper does: create an * entity document through the REAL registry (`createEntityDoc`), then declare * its cap policy via `getCaps().open(doc, scope, owner)`. The read filter then * hides one owner's private document from another principal — the faithful * per-DOCUMENT NextGraph behavior. */ import { test, expect, mock, afterAll } from "bun:test"; import { createEntityDoc, resetRegistryCache } from "../src/store-registry"; import type { RegistrySession } from "../src/store-registry"; import { configure, configureStoreRegistry, resetStoreRegistry, resetConfig, getCaps, resetCaps, } from "../src/polyfill"; import { filterReadable } from "../src/read-filter"; afterAll(() => { resetConfig(); resetStoreRegistry(); resetCaps(); }); const SESSION: RegistrySession = { sessionId: "sid", privateStoreId: "PRIV" }; function inject() { let n = 0; const ng = { doc_create: mock(async () => `did:ng:o:doc${++n}`), sparql_update: mock(async () => undefined), sparql_query: mock(async () => ({ results: { bindings: [] } })), }; configure({ ng: ng as any, useShape: (() => {}) as any }); configureStoreRegistry({ getSession: async () => SESSION, normalizeUser: (u) => u.trim() }); resetRegistryCache(); resetCaps(); return ng; } test("ReadCap active: a private entity doc created via the real registry is hidden from another principal", async () => { inject(); // Alice creates a PRIVATE entity document via the REAL store-registry, then // (as the app wrapper does) declares its cap policy: owner-only read. const aliceDoc = await createEntityDoc("alice", "private"); getCaps().open(aliceDoc, "private", "alice"); // Bob creates a PUBLIC entity document (world-readable). const bobDoc = await createEntityDoc("bob", "public"); getCaps().open(bobDoc, "public", "bob"); // The reactive set as the broker would deliver it (mono-store: items carry // their @graph = the document they live in). const items = [ { "@graph": aliceDoc, "@id": "a1", label: "alice-private" }, { "@graph": bobDoc, "@id": "b1", label: "bob-public" }, ]; // Bob cannot read alice's private doc; he CAN read the public one and, since // the read filter is now under a policy, alice's private item is filtered out. const bobView = filterReadable(items, getCaps(), "bob").map((i) => i["@id"]); expect(bobView).toEqual(["b1"]); // Alice reads her own private doc AND the public one. const aliceView = filterReadable(items, getCaps(), "alice").map((i) => i["@id"]); expect(aliceView.sort()).toEqual(["a1", "b1"]); // Anonymous sees only the public doc. const anonView = filterReadable(items, getCaps(), null).map((i) => i["@id"]); expect(anonView).toEqual(["b1"]); // Sanity: this is the REGISTRY talking, not app-level isolation — the caps // registry has an active read policy. expect(getCaps().hasReadPolicy()).toBe(true); });