refactor(api): partager nomme le document, détenir répond par oui ou non
`shareCap(cap, toUser)` faisait tenir une clé à l'appelant. En amont il n'en
tient aucune : c'est le verifier qui remplit `ContactDetails.read_cap`, et une
inbox se résout depuis un profil. Cette signature a déjà changé deux fois
aujourd'hui — `(cap, toInbox)` puis `(cap, toUser)` — et les deux laissaient à
l'app quelque chose qu'elle ne tiendra pas plus tard.
- `inbox.share(doc, toUser)` : les deux choses qu'une application a, un document
et une personne. Ni la clé ni l'adresse n'apparaissent.
- `hasCap(doc)` remplace `capFor(doc)` et rend un BOOLÉEN. C'est la seule
question que le modèle admette, et l'unique appelant qui utilisait la valeur
s'en servait pour la passer à `shareCap`.
Les tests ont fait apparaître un besoin que ces retraits allaient casser :
obtenir le lien PARTAGEABLE d'un document publié, pour le faire circuler. C'est
distinct du partage dirigé et ça existe en amont — un `RepoLinkV0 { read_cap }`
est ce qu'on passe, `ContactDetails.read_cap` est la remise à quelqu'un. D'où
`linkTo(doc)`, seul endroit où une app tient légitimement une clé : on ne peut
pas faire circuler ce qu'on n'a pas le droit de toucher. La clé d'un document
protégé, elle, ne sort jamais par là — elle passe par `share`.
171 tests unitaires, e2e 42/42 en 3,5 min (synchro à froid 29s, stable contre
30s au run précédent — le wallet par batterie tient).
This commit is contained in:
@@ -18,9 +18,10 @@
|
||||
import { getCaps } from "../src/shared-wallet/bootstrap";
|
||||
import { test, expect, mock, afterAll } from "bun:test";
|
||||
import { createEntityDoc, resetRegistryCache, userInbox, listMyEntityDocs } from "../src/shared-wallet/account-registry";
|
||||
import { linkTo } from "../src/surface/placement";
|
||||
import type { RegistrySession } from "../src/shared-wallet/account-registry";
|
||||
import type { ReadCap } from "../src/model/types";
|
||||
import {configure,configureStoreRegistry,resetStoreRegistry,resetConfig,capFor,resetCaps,setCurrentUser,shareCap} from "../src/polyfill";
|
||||
import {configure,configureStoreRegistry,resetStoreRegistry,resetConfig,hasCap,resetCaps,setCurrentUser,share} from "../src/polyfill";
|
||||
import { read as readInbox } from "../src/surface/inbox";
|
||||
import { filterReadable } from "../src/emulated-verifier/read-filter";
|
||||
|
||||
@@ -221,7 +222,7 @@ test("(a) sharing one document's cap to ONE inbox reveals it there, and only the
|
||||
// bob's OWN inbox — the only cross-wallet act there is.
|
||||
const bobInbox = await userInbox("bob", "protected");
|
||||
setCurrentUser("alice");
|
||||
await shareCap(capFor(shared)!, "bob");
|
||||
await share(shared, "bob");
|
||||
|
||||
// bob processes his inbox — no dedicated "receive" operation exists.
|
||||
setCurrentUser("bob");
|
||||
@@ -239,12 +240,12 @@ test("a cap deposit is absorbed, not surfaced as a consumer deposit", async () =
|
||||
setCurrentUser("alice");
|
||||
const doc = await createEntityDoc("alice", "protected");
|
||||
const bobInbox = await userInbox("bob", "protected");
|
||||
await shareCap(capFor(doc)!, "bob");
|
||||
await share(doc, "bob");
|
||||
|
||||
setCurrentUser("bob");
|
||||
const deposits = await readInbox(bobInbox);
|
||||
expect(deposits).toEqual([]); // infrastructure, not consumer data
|
||||
expect(capFor(doc)).toBeDefined(); // …but it landed in bob's held caps
|
||||
expect(hasCap(doc)).toBe(true); // …but it landed in bob's held caps
|
||||
});
|
||||
|
||||
// (b) A bare reference grants nothing; the repo link of a published document does.
|
||||
@@ -254,7 +255,7 @@ test("(b) a bare reference reads nothing; the repo link of a published document
|
||||
const pub = await createEntityDoc("alice", "public");
|
||||
const items = [item(pub, "u1")];
|
||||
expect(getCaps().isPublished(pub)).toBe(true);
|
||||
const link = capFor(pub)!;
|
||||
const link = linkTo(pub);
|
||||
|
||||
// bob HAS the document's bare NURI (it is right there in `items`) and reads nothing.
|
||||
setCurrentUser("bob");
|
||||
@@ -270,14 +271,13 @@ test("(c) switching identity switches heldByHolder — a returning identity keep
|
||||
inject();
|
||||
setCurrentUser("alice");
|
||||
const doc = await createEntityDoc("alice", "protected");
|
||||
const cap = capFor(doc);
|
||||
expect(cap).toBeDefined();
|
||||
expect(hasCap(doc)).toBe(true);
|
||||
|
||||
setCurrentUser("bob");
|
||||
expect(capFor(doc)).toBeUndefined();
|
||||
expect(hasCap(doc)).toBe(false);
|
||||
|
||||
setCurrentUser("alice");
|
||||
expect(capFor(doc)).toBe(cap!); // durable across the switch — nothing re-declared
|
||||
expect(hasCap(doc)).toBe(true); // durable across the switch — nothing re-declared
|
||||
});
|
||||
|
||||
// A virtual user IS a shim account, and the shim keys accounts through the
|
||||
@@ -289,18 +289,17 @@ test("one held caps per virtual WALLET, not per spelling of its id", async () =>
|
||||
|
||||
setCurrentUser("@Alice");
|
||||
const doc = await createEntityDoc("@Alice", "protected");
|
||||
const cap = capFor(doc);
|
||||
expect(cap).toBeDefined();
|
||||
expect(hasCap(doc)).toBe(true);
|
||||
|
||||
// Same account, spelled differently — same shim account, so the same held caps.
|
||||
setCurrentUser("alice");
|
||||
expect(capFor(doc)).toBe(cap!);
|
||||
expect(hasCap(doc)).toBe(true);
|
||||
setCurrentUser(" ALICE ");
|
||||
expect(capFor(doc)).toBe(cap!);
|
||||
expect(hasCap(doc)).toBe(true);
|
||||
|
||||
// A genuinely different account still holds nothing.
|
||||
setCurrentUser("bob");
|
||||
expect(capFor(doc)).toBeUndefined();
|
||||
expect(hasCap(doc)).toBe(false);
|
||||
});
|
||||
|
||||
// THE BREACH P1a OPENED. Caps travel as inbox deposits, so an unguarded inbox read
|
||||
@@ -314,14 +313,14 @@ test("an inbox may be DEPOSITED into by anyone, and READ only by its owner", asy
|
||||
const bobInbox = await userInbox("bob", "protected");
|
||||
|
||||
// Alice deposits into bob's inbox — allowed, and it grants her nothing back.
|
||||
await shareCap(capFor(secret)!, "bob");
|
||||
await share(secret, "bob");
|
||||
await expect(readInbox(bobInbox)).rejects.toThrow(/does not belong to the connected wallet/i);
|
||||
expect(capFor(secret)).toBeDefined(); // still hers, obviously
|
||||
expect(hasCap(secret)).toBe(true); // still hers, obviously
|
||||
|
||||
// Mallory knows the NURI of bob's inbox and tries to pocket what is in it.
|
||||
setCurrentUser("mallory");
|
||||
await expect(readInbox(bobInbox)).rejects.toThrow(/does not belong to the connected wallet/i);
|
||||
expect(capFor(secret)).toBeUndefined(); // nothing was absorbed
|
||||
expect(hasCap(secret)).toBe(false); // nothing was absorbed
|
||||
|
||||
// Anonymous owns no inbox at all.
|
||||
setCurrentUser(null);
|
||||
@@ -330,7 +329,7 @@ test("an inbox may be DEPOSITED into by anyone, and READ only by its owner", asy
|
||||
// Bob reads his own, and only then does the cap land.
|
||||
setCurrentUser("bob");
|
||||
await readInbox(bobInbox);
|
||||
expect(capFor(secret)).toBeDefined();
|
||||
expect(hasCap(secret)).toBe(true);
|
||||
});
|
||||
|
||||
test("a fresh session rebuilds the held caps from the scope index (the emulated AddRepo)", async () => {
|
||||
@@ -373,7 +372,7 @@ test("a document's cap is READ from the Store branch, never recomputed", async (
|
||||
|
||||
expect(await listMyEntityDocs("alice", "protected")).toEqual([doc]);
|
||||
// Recomputing would have produced `:r:OK`; this is what was stored.
|
||||
expect(capFor(doc)).toBe(`${doc}:r:FROM-THE-STORE` as ReadCap);
|
||||
expect(getCaps().capFor(doc)).toBe(`${doc}:r:FROM-THE-STORE` as ReadCap);
|
||||
});
|
||||
|
||||
// The listing and the keys are separate upstream (Main vs Store branch), and the
|
||||
@@ -398,5 +397,5 @@ test("creation mints the cap ONCE — the stored value is the one held", async (
|
||||
const doc = await createEntityDoc("alice", "protected");
|
||||
|
||||
const stored = ng._quads.find((q) => q.p === "urn:ng-eventually:shim:readCap")!;
|
||||
expect(capFor(doc)).toBe(stored.o as ReadCap); // same value, not two mints that agree by luck
|
||||
expect(getCaps().capFor(doc)).toBe(stored.o as ReadCap); // same value, not two mints that agree by luck
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user