feat(inbox): un utilisateur a DEUX inboxes, comme en amont
Tranché par la cascade plutôt qu'en attendant une réponse : le niveau 3 (ORM) ne dit rien des inbox, le niveau 2 non plus — `@ng-org/web` n'expose aucune méthode contenant « inbox » et la session n'en publie aucune. C'est donc le modèle du moteur qui décide, et il dit DEUX : un site porte une inbox sur son repo de store public et une autre sur son protégé (`engine/verifier/src/site.rs:127-152`), les seuls `AddInboxCap` du moteur, `new_store_default` n'en posant une que `if !private`. Elles sont adressées séparément jusque dans les enregistrements de contact, qui choisissent leur prédicat selon le profil visé — `ng:site_inbox` pour un profil public, `ng:protected_inbox` sinon (`engine/verifier/src/inbox_processor.rs:787,823-824`). `userInbox(id)` en exposait une : une cardinalité que cette bibliothèque avait inventée, et que le nom `walletInbox` avait contribué à masquer. Elle prend désormais le scope, et le store PRIVÉ n'en a pas — d'où `InboxScope` plutôt que `Scope` : demander l'inbox privée n'est pas une recherche qui ne rend rien, c'est une question sans référent dans le modèle, et le type l'interdit. `myInboxes` énumère les deux, `isOwnInbox` reconnaît les deux. Le shim garde un triple par (user, scope). 160 tests unitaires, typecheck src/test/e2e vert, e2e 40/40 contre le broker.
This commit is contained in:
@@ -262,7 +262,7 @@ test("Bob: reads the public document, sees the reference, and cannot read throug
|
||||
test("Charlie: same public document, same reference — and he reads through it", async () => {
|
||||
inject();
|
||||
const { protDoc, pubDoc, pubLink, protCap } = await aliceSetsUpHerDocuments();
|
||||
const CHARLIE_INBOX = await userInbox("charlie");
|
||||
const CHARLIE_INBOX = await userInbox("charlie", "protected");
|
||||
|
||||
// Alice decides Charlie may read that ONE document, and delivers its cap to his
|
||||
// inbox. She names no principal to the registry; she addresses an inbox.
|
||||
@@ -282,7 +282,7 @@ test("Charlie: same public document, same reference — and he reads through it"
|
||||
test("the ONLY difference between Bob and Charlie is each of them holds", async () => {
|
||||
inject();
|
||||
const { protDoc, pubLink, protCap } = await aliceSetsUpHerDocuments();
|
||||
const CHARLIE_INBOX = await userInbox("charlie");
|
||||
const CHARLIE_INBOX = await userInbox("charlie", "protected");
|
||||
|
||||
setCurrentUser("alice");
|
||||
await shareCap(protCap, CHARLIE_INBOX);
|
||||
@@ -305,7 +305,7 @@ test("the ONLY difference between Bob and Charlie is each of them holds", async
|
||||
test("dynamic: a cap delivered to Bob's inbox makes the refused document readable, and signals it", async () => {
|
||||
inject();
|
||||
const { pubDoc, pubLink, protCap } = await aliceSetsUpHerDocuments();
|
||||
const BOB_INBOX = await userInbox("bob");
|
||||
const BOB_INBOX = await userInbox("bob", "protected");
|
||||
|
||||
setCurrentUser("bob");
|
||||
getCaps().learn(pubLink);
|
||||
@@ -361,7 +361,7 @@ test("a bare reference to the PUBLIC document is not enough either — the link
|
||||
test("a Link is APPLIED durably: the cap survives with the inbox emptied", async () => {
|
||||
const ng = inject();
|
||||
const { protDoc, protCap } = await aliceSetsUpHerDocuments();
|
||||
const bobInbox = await userInbox("bob");
|
||||
const bobInbox = await userInbox("bob", "protected");
|
||||
|
||||
setCurrentUser("alice");
|
||||
await shareCap(protCap, bobInbox);
|
||||
@@ -406,7 +406,7 @@ test("a document has its own inbox: anyone deposits, only the owner reads", asyn
|
||||
setCurrentUser("alice");
|
||||
const doc = await createEntityDoc("alice", "public");
|
||||
const aliceInbox = await openDocumentInbox(doc);
|
||||
expect(aliceInbox).not.toBe(await userInbox("alice"));
|
||||
expect(aliceInbox).not.toBe(await userInbox("alice", "protected"));
|
||||
const link = capFor(doc)!; // the repo link alice circulates — links DO travel
|
||||
|
||||
// Bob RESOLVES the address himself, from the document. The only thing he is handed
|
||||
@@ -497,7 +497,7 @@ test("connecting drains BOTH levels: the user's inbox and its documents'", async
|
||||
const protDoc = await createEntityDoc("alice", "protected");
|
||||
const pubDoc = await createEntityDoc("alice", "public");
|
||||
const docInbox = await openDocumentInbox(pubDoc);
|
||||
const aliceInbox = await userInbox("alice");
|
||||
const aliceInbox = await userInbox("alice", "protected");
|
||||
|
||||
// Two deposits, one at each level, both made by someone else.
|
||||
setCurrentUser("carol");
|
||||
@@ -519,8 +519,8 @@ test("connecting drains BOTH levels: the user's inbox and its documents'", async
|
||||
test("a third party resolves another user's inbox (the wallet level)", async () => {
|
||||
inject();
|
||||
setCurrentUser("alice");
|
||||
const aliceView = await userInbox("alice");
|
||||
const aliceView = await userInbox("alice", "protected");
|
||||
setCurrentUser("bob");
|
||||
const bobView = await userInbox("alice");
|
||||
const bobView = await userInbox("alice", "protected");
|
||||
expect(bobView).toBe(aliceView);
|
||||
});
|
||||
|
||||
@@ -165,7 +165,7 @@ beforeEach(async () => {
|
||||
fake = inject();
|
||||
resetRegistryCache();
|
||||
setCurrentUser("alice");
|
||||
TARGET = await userInbox("alice");
|
||||
TARGET = await userInbox("alice", "protected");
|
||||
});
|
||||
|
||||
test("post writes via the real injected ng.sparql_update (not makeNg), scoped to the inbox", async () => {
|
||||
|
||||
@@ -228,7 +228,7 @@ test("(a) sharing one document's cap to ONE inbox reveals it there, and only the
|
||||
|
||||
// The app decides alice↔bob are related: alice shares ONE document's cap into
|
||||
// bob's OWN inbox — the only cross-wallet act there is.
|
||||
const bobInbox = await userInbox("bob");
|
||||
const bobInbox = await userInbox("bob", "protected");
|
||||
setCurrentUser("alice");
|
||||
await shareCap(capFor(shared)!, bobInbox);
|
||||
|
||||
@@ -239,7 +239,7 @@ test("(a) sharing one document's cap to ONE inbox reveals it there, and only the
|
||||
|
||||
// carol, who was not shared with, still reads nothing.
|
||||
setCurrentUser("carol");
|
||||
await readInbox(await userInbox("carol"));
|
||||
await readInbox(await userInbox("carol", "protected"));
|
||||
expect(view(items)).toEqual([]);
|
||||
});
|
||||
|
||||
@@ -247,7 +247,7 @@ test("a cap deposit is absorbed, not surfaced as a consumer deposit", async () =
|
||||
inject();
|
||||
setCurrentUser("alice");
|
||||
const doc = await createEntityDoc("alice", "protected");
|
||||
const bobInbox = await userInbox("bob");
|
||||
const bobInbox = await userInbox("bob", "protected");
|
||||
await shareCap(capFor(doc)!, bobInbox);
|
||||
|
||||
setCurrentUser("bob");
|
||||
@@ -320,7 +320,7 @@ test("an inbox may be DEPOSITED into by anyone, and READ only by its owner", asy
|
||||
inject();
|
||||
setCurrentUser("alice");
|
||||
const secret = await createEntityDoc("alice", "protected");
|
||||
const bobInbox = await userInbox("bob");
|
||||
const bobInbox = await userInbox("bob", "protected");
|
||||
|
||||
// Alice deposits into bob's inbox — allowed, and it grants her nothing back.
|
||||
await shareCap(capFor(secret)!, bobInbox);
|
||||
|
||||
@@ -103,7 +103,7 @@ test("a user reaches its OWN stores and inbox — the boundary must not lock it
|
||||
inject();
|
||||
setCurrentUser("alice");
|
||||
await createEntityDoc("alice", "protected"); // provisions alice's account
|
||||
const inbox = await userInbox("alice");
|
||||
const inbox = await userInbox("alice", "protected");
|
||||
|
||||
expect(mayReach(inbox)).toBe(true);
|
||||
await sparqlQuery(SESSION.sessionId, READ, undefined, inbox);
|
||||
@@ -116,7 +116,7 @@ test("a user reaches its OWN stores and inbox — the boundary must not lock it
|
||||
test("DEPOSITING into another user's inbox crosses the boundary, and gives nothing back", async () => {
|
||||
const { ng } = inject();
|
||||
setCurrentUser("bob");
|
||||
const bobInbox = await userInbox("bob");
|
||||
const bobInbox = await userInbox("bob", "protected");
|
||||
|
||||
setCurrentUser("alice");
|
||||
await createEntityDoc("alice", "private"); // alice now holds caps → guard is armed
|
||||
|
||||
@@ -248,11 +248,11 @@ test("resolveScopeGraph maps scopes to native store NURIs (no store-id leaks to
|
||||
// docCreate), not the private-store root, so deposits never bloat the shim graph.
|
||||
// Stable per wallet, and DISJOINT between wallets: reading someone else's inbox
|
||||
// would collect the caps addressed to them (see inbox.ts's read guard).
|
||||
const mine = await userInbox("@alice");
|
||||
const mine = await userInbox("@alice", "protected");
|
||||
expect(mine).toMatch(/^did:ng:o:doc/);
|
||||
expect(mine).not.toBe("did:ng:PRIV");
|
||||
expect(await userInbox("@alice")).toBe(mine); // stable
|
||||
expect(await userInbox("@bob")).not.toBe(mine); // another wallet, another inbox
|
||||
expect(await userInbox("@alice", "protected")).toBe(mine); // stable
|
||||
expect(await userInbox("@bob", "protected")).not.toBe(mine); // another wallet, another inbox
|
||||
});
|
||||
|
||||
test("resolveScopeGraph falls back to the private store when no protected id is injected", async () => {
|
||||
@@ -379,3 +379,18 @@ test("normalizeId defaults to trim when not provided", async () => {
|
||||
expect(b).toEqual(a);
|
||||
expect(ng.doc_create).toHaveBeenCalledTimes(4); // 1 doc-shim + 3 scope docs
|
||||
});
|
||||
|
||||
test("a user has TWO inboxes — public and protected — and they are distinct documents", async () => {
|
||||
// Upstream a site carries an inbox on its public store repo and another on its
|
||||
// protected one (`engine/verifier/src/site.rs:127-152`), addressed separately down to
|
||||
// the contact predicates (`ng:site_inbox` vs `ng:protected_inbox`). Exposing one was a
|
||||
// cardinality this library invented; neither the ORM nor the wasm binding says
|
||||
// anything about inboxes, so the engine's model is what decides.
|
||||
resetRegistryCache();
|
||||
const pub = await userInbox("@dana", "public");
|
||||
const prot = await userInbox("@dana", "protected");
|
||||
expect(pub).not.toBe(prot);
|
||||
// …and each is stable for its own scope.
|
||||
expect(await userInbox("@dana", "public")).toBe(pub);
|
||||
expect(await userInbox("@dana", "protected")).toBe(prot);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user