refactor(api): l'app nomme une personne ou un document, jamais une adresse d'inbox

L'app d'exemple a servi de juge, et elle a immédiatement montré ce que
l'inventaire ne montrait pas : pour partager une note elle résolvait l'inbox du
destinataire, pour lire ses messages elle résolvait l'adresse de la sienne. Deux
gestes qu'aucune application n'aura à faire une fois la chose native — donc deux
gestes qu'elle ne doit pas apprendre.

- `shareCap(cap, toUser)` remplace `shareCap(cap, toInbox)`. Partager est un acte
  envers quelqu'un ; où est son inbox regarde la bibliothèque.
- `inbox.readForDocument(doc)` : le propriétaire lit ses messages en nommant la
  note, comme le déposant la nomme pour en laisser un.
- `storeRegistry.userInbox` et `documentInboxAddress` sortent de la surface
  publiée. Ils restent joignables en interne, où le shim en a besoin.

Sortent aussi de `/polyfill`, chacun parce qu'une app qui code contre apprend ce
qu'il faudra désapprendre :

- `getCaps` / `CapRegistry` — la salle des machines. La question du consommateur
  est `capFor(doc)` : est-ce que je le détiens ? Le registre n'a ni successeur ni
  forme inerte ; ce qui s'appuie dessus sera à réécrire, pas à laisser en place.
- `getCurrentUser` — une app sait qui elle a connecté ; le redemander à la
  bibliothèque est une commodité du wallet partagé.
- `virtualUsers` / `IdentityStore` — se souvenir d'une identité entre deux
  sessions est aussi le travail de l'app en amont. L'écran d'accès persiste ce
  dont IL a besoin ; rien d'autre n'a à être exposé.

Reste sur `/polyfill` ce qu'une app appelle vraiment : `configure` et
`setCurrentUser`. Le reste y est du test ou de l'injection interne.

170 tests unitaires, e2e 42/42 contre le broker, typecheck vert sur la
bibliothèque, l'exemple et le harnais.
This commit is contained in:
Sylvain Duchesne
2026-08-05 18:55:30 +02:00
parent d35e735c8b
commit 54f8389e9e
21 changed files with 94 additions and 149 deletions
+2 -8
View File
@@ -5,15 +5,9 @@
* reads an empty identity, provisions a second virtual user, and the returning user
* lands in an empty space with no error anywhere. So the order is pinned, not trusted.
*/
import { getCurrentUser } from "../src/shared-wallet/bootstrap";
import { test, expect, afterEach } from "bun:test";
import {
configure,
configureStoreRegistry,
resetConfig,
resetStoreRegistry,
setCurrentUser,
getCurrentUser,
} from "../src/polyfill";
import {configure,configureStoreRegistry,resetConfig,resetStoreRegistry,setCurrentUser} from "../src/polyfill";
import { ensureIdentity } from "../src/shared-wallet/access-gate";
const KEY = "ng-eventually:identity";
+1 -10
View File
@@ -21,16 +21,7 @@
import { describe, it, expect, mock, beforeEach, afterEach, afterAll } from "bun:test";
import { setAccessLog, enabled, shortNuri } from "../src/shared-wallet/access-log";
import { docCreate, sparqlUpdate, sparqlQuery } from "../src/surface/docs";
import {
configure,
configureStoreRegistry,
resetStoreRegistry,
resetConfig,
setCurrentUser,
getCurrentUser,
resetCaps,
connectedUser,
} from "../src/polyfill";
import {configure,configureStoreRegistry,resetStoreRegistry,resetConfig,setCurrentUser,resetCaps,connectedUser} from "../src/polyfill";
// ---------------------------------------------------------------------------
// Helpers
+1 -6
View File
@@ -26,12 +26,7 @@ import {
resetRegistryCache,
} from "../src/shared-wallet/account-registry";
import type { RegistrySession } from "../src/shared-wallet/account-registry";
import {
configure,
configureStoreRegistry,
resetStoreRegistry,
resetConfig,
} from "../src/polyfill";
import {configure,configureStoreRegistry,resetStoreRegistry,resetConfig} from "../src/polyfill";
import { resetOpenedRepos } from "../src/emulated-verifier/open-repo";
afterAll(() => {
@@ -23,14 +23,7 @@
import { describe, it, expect, mock, afterAll, beforeEach } from "bun:test";
import { ensureAccount, resolveWriteGraph, resetRegistryCache } from "../src/shared-wallet/account-registry";
import { resetOpenedRepos } from "../src/emulated-verifier/open-repo";
import {
configure,
configureStoreRegistry,
resetStoreRegistry,
resetConfig,
resetCaps,
setCurrentUser,
} from "../src/polyfill";
import {configure,configureStoreRegistry,resetStoreRegistry,resetConfig,resetCaps,setCurrentUser} from "../src/polyfill";
import { resetInfrastructure } from "../src/emulated-verifier/reach";
const SESSION = { sessionId: "sid-cold", privateStoreId: "PRIV-COLD" };
+7 -17
View File
@@ -17,6 +17,7 @@
* The difference between Bob and Charlie is ONLY each of them holds. There is
* no authorization list anywhere, and nobody was named to the registry.
*/
import { getCaps } from "../src/shared-wallet/bootstrap";
import { test, expect, mock, afterAll } from "bun:test";
import {
createEntityDoc,
@@ -25,18 +26,7 @@ import {
} from "../src/shared-wallet/account-registry";
import { documentInboxAddress, openDocumentInbox } from "../src/emulated-verifier/branch-registers";
import type { RegistrySession } from "../src/shared-wallet/account-registry";
import {
configure,
configureStoreRegistry,
resetStoreRegistry,
resetConfig,
capFor,
getCaps,
resetCaps,
setCurrentUser,
shareCap,
connectedUser,
} from "../src/polyfill";
import {configure,configureStoreRegistry,resetStoreRegistry,resetConfig,capFor,resetCaps,setCurrentUser,shareCap,connectedUser} from "../src/polyfill";
import { post, postToDocument, read as readInbox } from "../src/surface/inbox";
import { readUnion } from "../src/surface/read-model";
import { sparqlUpdate } from "../src/surface/docs";
@@ -267,7 +257,7 @@ test("Charlie: same public document, same reference — and he reads through it"
// 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.
setCurrentUser("alice");
await shareCap(protCap, CHARLIE_INBOX);
await shareCap(protCap, "charlie");
setCurrentUser("charlie");
getCaps().learn(pubLink);
@@ -285,7 +275,7 @@ test("the ONLY difference between Bob and Charlie is each of them holds", async
const CHARLIE_INBOX = await userInbox("charlie", "protected");
setCurrentUser("alice");
await shareCap(protCap, CHARLIE_INBOX);
await shareCap(protCap, "charlie");
setCurrentUser("bob");
getCaps().learn(pubLink);
@@ -326,7 +316,7 @@ test("dynamic: a cap delivered to Bob's inbox makes the refused document readabl
// Alice delivers the cap. Bob's client processes his inbox — the only thing that
// happens; no "receive" call exists.
setCurrentUser("alice");
await shareCap(protCap, BOB_INBOX);
await shareCap(protCap, "bob");
setCurrentUser("bob");
await readInbox(BOB_INBOX);
@@ -364,7 +354,7 @@ test("a Link is APPLIED durably: the cap survives with the inbox emptied", async
const bobInbox = await userInbox("bob", "protected");
setCurrentUser("alice");
await shareCap(protCap, bobInbox);
await shareCap(protCap, "bob");
// Bob connects: the library restores + drains, with nothing asked of the app.
setCurrentUser("bob");
@@ -502,7 +492,7 @@ test("connecting drains BOTH levels: the user's inbox and its documents'", async
// Two deposits, one at each level, both made by someone else.
setCurrentUser("carol");
const carolDoc = await createEntityDoc("carol", "protected");
await shareCap(capFor(carolDoc)!, aliceInbox); // a Link, to alice herself
await shareCap(capFor(carolDoc)!, "alice"); // a Link, to alice herself
await post(docInbox, { payload: { onTheDocument: true }, ts: 2 });
// Alice connects: one call, both queues.
+1 -1
View File
@@ -25,7 +25,7 @@ test("throws a clear error when configure() was not called", async () => {
});
// From here on, a fake real `ng` is injected via configure().
import { configure, resetCaps, setCurrentUser } from "../src/polyfill";
import {configure,resetCaps,setCurrentUser} from "../src/polyfill";
function fakeNg() {
return {
+1 -7
View File
@@ -2,13 +2,7 @@ import { test, expect, mock, beforeEach, afterAll } from "bun:test";
import { post, read, materialize, watch } from "../src/surface/inbox";
import { userInbox, resetRegistryCache } from "../src/shared-wallet/account-registry";
import type { Deposit } from "../src/surface/inbox";
import {
configure,
configureStoreRegistry,
resetStoreRegistry,
resetConfig,
setCurrentUser,
} from "../src/polyfill";
import {configure,configureStoreRegistry,resetStoreRegistry,resetConfig,setCurrentUser} from "../src/polyfill";
import type { RegistrySession } from "../src/shared-wallet/account-registry";
// This suite injects a fake `ng` via configure() and reuses the storeRegistry's
+5 -14
View File
@@ -15,21 +15,12 @@
* link of a published document opens it for whoever receives it;
* (c) switching identity SWITCHES heldByHolder — it never wipes one.
*/
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 type { RegistrySession } from "../src/shared-wallet/account-registry";
import type { ReadCap } from "../src/model/types";
import {
configure,
configureStoreRegistry,
resetStoreRegistry,
resetConfig,
capFor,
getCaps,
resetCaps,
setCurrentUser,
shareCap,
} from "../src/polyfill";
import {configure,configureStoreRegistry,resetStoreRegistry,resetConfig,capFor,resetCaps,setCurrentUser,shareCap} from "../src/polyfill";
import { read as readInbox } from "../src/surface/inbox";
import { filterReadable } from "../src/emulated-verifier/read-filter";
@@ -230,7 +221,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)!, bobInbox);
await shareCap(capFor(shared)!, "bob");
// bob processes his inbox — no dedicated "receive" operation exists.
setCurrentUser("bob");
@@ -248,7 +239,7 @@ 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)!, bobInbox);
await shareCap(capFor(doc)!, "bob");
setCurrentUser("bob");
const deposits = await readInbox(bobInbox);
@@ -323,7 +314,7 @@ 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)!, bobInbox);
await shareCap(capFor(secret)!, "bob");
await expect(readInbox(bobInbox)).rejects.toThrow(/does not belong to the connected wallet/i);
expect(capFor(secret)).toBeDefined(); // still hers, obviously
+2 -7
View File
@@ -1,12 +1,7 @@
import { getCaps } from "../src/shared-wallet/bootstrap";
import { test, expect, mock, afterEach } from "bun:test";
import { makeNg } from "../src/surface/ng-proxy";
import {
configure,
resetConfig,
getCaps,
resetCaps,
setCurrentUser,
} from "../src/polyfill";
import {configure,resetConfig,resetCaps,setCurrentUser} from "../src/polyfill";
// This suite injects a fake `ng` via configure() and declares WRITE caps —
// which stay an authorization list on purpose: only READING is key possession
+1 -8
View File
@@ -20,14 +20,7 @@
import { describe, it, expect, mock, beforeEach, afterAll } from "bun:test";
import { ensureRepoOpen, ensureReposOpen, resetOpenedRepos } from "../src/emulated-verifier/open-repo";
import { readUnion } from "../src/surface/read-model";
import {
configure,
configureStoreRegistry,
resetStoreRegistry,
resetConfig,
resetCaps,
setCurrentUser,
} from "../src/polyfill";
import {configure,configureStoreRegistry,resetStoreRegistry,resetConfig,resetCaps,setCurrentUser} from "../src/polyfill";
import { resetInfrastructure } from "../src/emulated-verifier/reach";
import { resetRegistryCache } from "../src/shared-wallet/account-registry";
+1 -8
View File
@@ -14,14 +14,7 @@ import { test, expect, mock, afterAll } from "bun:test";
import { sparqlQuery, sparqlUpdate, depositInto } from "../src/surface/docs";
import { createEntityDoc, resetRegistryCache, userInbox } from "../src/shared-wallet/account-registry";
import type { RegistrySession } from "../src/shared-wallet/account-registry";
import {
configure,
configureStoreRegistry,
resetStoreRegistry,
resetConfig,
resetCaps,
setCurrentUser,
} from "../src/polyfill";
import {configure,configureStoreRegistry,resetStoreRegistry,resetConfig,resetCaps,setCurrentUser} from "../src/polyfill";
import { mayReach, mustNotAttempt } from "../src/emulated-verifier/reach";
import { hasReadCap } from "../src/model/nuri";
+2 -7
View File
@@ -1,13 +1,8 @@
import { getCaps } from "../src/shared-wallet/bootstrap";
import { test, expect, mock, afterAll } from "bun:test";
import { readUnion } from "../src/surface/read-model";
import type { Nuri } from "../src/model/types";
import {
configure,
configureStoreRegistry,
getCaps,
resetCaps,
setCurrentUser,
} from "../src/polyfill";
import {configure,configureStoreRegistry,resetCaps,setCurrentUser} from "../src/polyfill";
// The cap registry is process-wide, so each inject() starts from an empty one:
// once ANY cap exists the possession gate is in force for every reader, and a
+1 -6
View File
@@ -10,12 +10,7 @@ import {
resetRegistryCache,
} from "../src/shared-wallet/account-registry";
import type { RegistrySession } from "../src/shared-wallet/account-registry";
import {
configure,
configureStoreRegistry,
resetStoreRegistry,
resetConfig,
} from "../src/polyfill";
import {configure,configureStoreRegistry,resetStoreRegistry,resetConfig} from "../src/polyfill";
// This suite injects a fake `ng` via configure(); bun runs test files in a
// shared process with a single module singleton, and may run this file BEFORE
+1 -6
View File
@@ -1,11 +1,6 @@
import { test, expect, mock, afterAll } from "bun:test";
import { subscribeDoc, subscribeDocs } from "../src/surface/subscribe";
import {
configure,
configureStoreRegistry,
resetConfig,
resetStoreRegistry,
} from "../src/polyfill";
import {configure,configureStoreRegistry,resetConfig,resetStoreRegistry} from "../src/polyfill";
import type { RegistrySession } from "../src/shared-wallet/account-registry";
// subscribeDoc/subscribeDocs wrap the REAL injected `ng.doc_subscribe`. This
+1 -8
View File
@@ -25,14 +25,7 @@
import { describe, it, expect, mock, beforeEach, afterEach, afterAll } from "bun:test";
import { watchShape } from "../src/surface/watch-shape";
import {
configure,
configureStoreRegistry,
resetStoreRegistry,
resetConfig,
resetCaps,
setCurrentUser,
} from "../src/polyfill";
import {configure,configureStoreRegistry,resetStoreRegistry,resetConfig,resetCaps,setCurrentUser} from "../src/polyfill";
import { resetRegistryCache, createEntityDoc } from "../src/shared-wallet/account-registry";
import { resetOpenedRepos, setOpenTimeoutForTests, getSyncState } from "../src/emulated-verifier/open-repo";