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
+3 -4
View File
@@ -614,13 +614,12 @@ Exported, but not SDK surface. Coding against these builds knowledge that migrat
```text ```text
direct: BaseType, DeepSignalSet, DocChange, DocChangeType, InboxScope, NG, NgLike, Nuri, PrincipalId, ReadCap, Schema, Scope, ShapeObservable, ShapeQuery, ShapeType, SharedWalletConfig, UnionSubject, Unsubscribe, UseShapeLike, assertNuri, docChangeType, ensureIdentity, escapeIri, escapeLiteral, hasReadCap, init, initNg, isNuri, ng, readUnion, subscribeDoc, subscribeDocs, useShape, watchShape direct: BaseType, DeepSignalSet, DocChange, DocChangeType, InboxScope, NG, NgLike, Nuri, PrincipalId, ReadCap, Schema, Scope, ShapeObservable, ShapeQuery, ShapeType, SharedWalletConfig, UnionSubject, Unsubscribe, UseShapeLike, assertNuri, docChangeType, ensureIdentity, escapeIri, escapeLiteral, hasReadCap, init, initNg, isNuri, ng, readUnion, subscribeDoc, subscribeDocs, useShape, watchShape
docs: depositInto, docCreate, sparqlQuery, sparqlUpdate docs: depositInto, docCreate, sparqlQuery, sparqlUpdate
inbox: Deposit, PostOptions, materialize, post, postToDocument, processInbox, read, readSynced, shareCap, watch inbox: Deposit, PostOptions, materialize, post, postToDocument, processInbox, read, readForDocument, readSynced, shareCap, watch
storeRegistry: createEntityDoc, documentInboxAddress, listMyEntityDocs, openDocumentInbox, resolveScopeGraph, resolveWriteGraph, userInbox storeRegistry: createEntityDoc, listMyEntityDocs, openDocumentInbox, resolveScopeGraph, resolveWriteGraph
``` ```
### `@ng-eventually/client/polyfill` — `src/polyfill.ts` ### `@ng-eventually/client/polyfill` — `src/polyfill.ts`
```text ```text
direct: CapRegistry, EventuallyConfig, RegistrySession, StoreRegistryDeps, VirtualUserRecord, VirtualUserStorage, capFor, configure, configureStoreRegistry, connectedUser, getCaps, getConfig, getCurrentUser, getStoreRegistryDeps, resetCaps, resetConfig, resetStoreRegistry, setCurrentUser, shareCap direct: EventuallyConfig, RegistrySession, StoreRegistryDeps, VirtualUserRecord, capFor, configure, configureStoreRegistry, connectedUser, getConfig, getStoreRegistryDeps, resetCaps, resetConfig, resetStoreRegistry, setCurrentUser, shareCap
virtualUsers: ACCOUNT_STORAGE_KEY, IdentityStore, VirtualUserStorage, browserIdentityStore
``` ```
+10 -7
View File
@@ -134,12 +134,17 @@ async function readSharedNote(link: string): Promise<Note | null> {
}; };
} }
/** Hand a reader the key to one of my notes, addressed to their inbox. */ /**
* Hand a reader the key to one of my notes.
*
* Names the PERSON. Where their inbox is, and whether they have one yet, is the
* library's business — an application will never handle an inbox address once this is
* native, so it does not handle one now.
*/
async function shareNote(doc: Nuri, withUser: string): Promise<void> { async function shareNote(doc: Nuri, withUser: string): Promise<void> {
const cap = capFor(doc); const cap = capFor(doc);
if (!cap) throw new Error("this note is not mine to share"); if (!cap) throw new Error("this note is not mine to share");
const theirInbox = await storeRegistry.userInbox(withUser, "protected"); await inbox.shareCap(cap, withUser);
await inbox.shareCap(cap, theirInbox);
} }
/** Open a note for messages — only its owner can, and only they will read them. */ /** Open a note for messages — only its owner can, and only they will read them. */
@@ -152,11 +157,9 @@ async function leaveMessage(doc: Nuri, text: string): Promise<void> {
await inbox.postToDocument(doc, { payload: { text } }); await inbox.postToDocument(doc, { payload: { text } });
} }
/** The messages left on one of my notes. */ /** The messages left on one of my notes — named by the note, like leaving one. */
async function messagesOn(doc: Nuri): Promise<string[]> { async function messagesOn(doc: Nuri): Promise<string[]> {
const address = await storeRegistry.documentInboxAddress(doc); const deposits = await inbox.readForDocument(doc);
if (!address) return [];
const deposits = await inbox.read(address);
return deposits.map((d) => String((d.payload as { text?: string })?.text ?? "")); return deposits.map((d) => String((d.payload as { text?: string })?.text ?? ""));
} }
+7 -7
View File
@@ -19,9 +19,7 @@ import {
configure, configure,
configureStoreRegistry, configureStoreRegistry,
setCurrentUser, setCurrentUser,
getCurrentUser,
capFor, capFor,
getCaps,
resetCaps, resetCaps,
shareCap, shareCap,
connectedUser, connectedUser,
@@ -40,6 +38,8 @@ import {
// application must not — but through the internal path, never the published entry. // application must not — but through the internal path, never the published entry.
// `storeRegistry` above is the app-facing slice; these are the shim internals. // `storeRegistry` above is the app-facing slice; these are the shim internals.
import * as registryInternals from "../src/shared-wallet/account-registry"; import * as registryInternals from "../src/shared-wallet/account-registry";
import { getCaps, getCurrentUser } from "../src/shared-wallet/bootstrap";
import { documentInboxAddress } from "../src/emulated-verifier/branch-registers";
import * as virtualUsers from "../src/shared-wallet/virtual-users"; import * as virtualUsers from "../src/shared-wallet/virtual-users";
import { isNuri, ensureIdentity } from "@ng-eventually/client"; import { isNuri, ensureIdentity } from "@ng-eventually/client";
import type { Nuri, ShapeObservable, ShapeQuery } from "@ng-eventually/client"; import type { Nuri, ShapeObservable, ShapeQuery } from "@ng-eventually/client";
@@ -433,7 +433,7 @@ const identity = new IdentityStore(
// deposit into anyone's, you may only read your own. Establishing the identity // deposit into anyone's, you may only read your own. Establishing the identity
// FIRST is what makes `userInbox` resolve (and file) that user's inbox. // FIRST is what makes `userInbox` resolve (and file) that user's inbox.
setCurrentUser(id); setCurrentUser(id);
const target = await storeRegistry.userInbox(id, "protected"); const target = await registryInternals.userInbox(id, "protected");
await inbox.post(target, { payload: payloadA, from: null, ts: 1000 }); await inbox.post(target, { payload: payloadA, from: null, ts: 1000 });
await inbox.post(target, { payload: payloadB, from: null, ts: 2000 }); await inbox.post(target, { payload: payloadB, from: null, ts: 2000 });
const deposits = await inbox.read(target); const deposits = await inbox.read(target);
@@ -447,7 +447,7 @@ const identity = new IdentityStore(
// Watching an inbox is READING it continuously, so the watcher stays connected // Watching an inbox is READING it continuously, so the watcher stays connected
// for the whole probe — including across `inboxWatchDeposit`. // for the whole probe — including across `inboxWatchDeposit`.
setCurrentUser(id); setCurrentUser(id);
const target = await storeRegistry.userInbox(id, "protected"); const target = await registryInternals.userInbox(id, "protected");
const rec = { fires: 0, lastLen: -1, unsub: () => {}, target }; const rec = { fires: 0, lastLen: -1, unsub: () => {}, target };
(window as any).__sdk._inboxWatch = rec; (window as any).__sdk._inboxWatch = rec;
rec.unsub = inbox.watch(target, (deposits) => { rec.unsub = inbox.watch(target, (deposits) => {
@@ -874,7 +874,7 @@ const identity = new IdentityStore(
setCurrentUser(depositorId); setCurrentUser(depositorId);
getCaps().learn(link); getCaps().learn(link);
const resolved = await storeRegistry.documentInboxAddress(doc); const resolved = await documentInboxAddress(doc);
// The one-call form an app actually uses: it names the DOCUMENT, never an inbox. // The one-call form an app actually uses: it names the DOCUMENT, never an inbox.
await inbox.postToDocument(doc, { payload: { viaPostToDocument: true }, ts: 900 }); await inbox.postToDocument(doc, { payload: { viaPostToDocument: true }, ts: 900 });
// Opening one on someone else's document must be refused, not silently forked. // Opening one on someone else's document must be refused, not silently forked.
@@ -908,7 +908,7 @@ const identity = new IdentityStore(
// the recipient's durable Links would grow run after run on a persistent wallet, // the recipient's durable Links would grow run after run on a persistent wallet,
// making every later `connectedUser()` re-apply a longer and longer history. // making every later `connectedUser()` re-apply a longer and longer history.
setCurrentUser(friendId); setCurrentUser(friendId);
const friendInbox = await storeRegistry.userInbox(friendId, "protected"); const friendInbox = await registryInternals.userInbox(friendId, "protected");
setCurrentUser("owner-O"); setCurrentUser("owner-O");
const doc = await docs.docCreate(s.session_id, "Graph", "data:graph", "store", undefined); const doc = await docs.docCreate(s.session_id, "Graph", "data:graph", "store", undefined);
@@ -920,7 +920,7 @@ const identity = new IdentityStore(
const before = [...(libUseShape(null, null) as Iterable<any>)].length; const before = [...(libUseShape(null, null) as Iterable<any>)].length;
setCurrentUser("owner-O"); setCurrentUser("owner-O");
await shareCap(cap, friendInbox); await shareCap(cap, friendId);
setCurrentUser(friendId); setCurrentUser(friendId);
const absorbed = await inbox.read(friendInbox); // processing it applies the cap const absorbed = await inbox.read(friendInbox); // processing it applies the cap
+17 -5
View File
@@ -22,8 +22,6 @@ export {
getStoreRegistryDeps, getStoreRegistryDeps,
resetStoreRegistry, resetStoreRegistry,
setCurrentUser, setCurrentUser,
getCurrentUser,
getCaps,
capFor, capFor,
resetCaps, resetCaps,
} from "./shared-wallet/bootstrap"; } from "./shared-wallet/bootstrap";
@@ -33,10 +31,26 @@ export {
// lives in `inbox.ts` because sharing IS an inbox deposit (upstream: a sealed // lives in `inbox.ts` because sharing IS an inbox deposit (upstream: a sealed
// message carrying the cap), but it is surfaced here so the cap vocabulary stays // message carrying the cap), but it is surfaced here so the cap vocabulary stays
// on the polyfill side of the boundary rather than in the SDK-identical entry. // on the polyfill side of the boundary rather than in the SDK-identical entry.
export { CapRegistry } from "./emulated-verifier/caps";
export { shareCap } from "./surface/inbox"; export { shareCap } from "./surface/inbox";
export { connectedUser } from "./emulated-verifier/connect"; export { connectedUser } from "./emulated-verifier/connect";
// --- what is deliberately NOT published --------------------------------------
//
// Removed 2026-08-05, each because an application coding against it learns something it
// must unlearn — the one failure this library exists to prevent:
//
// - `getCaps` / `CapRegistry` — the emulation's engine room. The consumer question is
// `capFor(doc)`: do I hold this? The registry object has neither a successor nor an
// inert form, so anything built on it must be rewritten rather than left alone.
// - `getCurrentUser` — an application knows who it signed in; asking the library back
// is a convenience of the shared wallet, not a brick of the model.
// - `virtualUsers` / `IdentityStore` — remembering an identity between sessions is the
// application's job upstream too. The gate persists what IT needs
// (`shared-wallet/access-gate.ts`); nothing else has to be exposed.
//
// What remains here is the whole polyfill-era surface: `configure`, `setCurrentUser`,
// `capFor`, `shareCap` and the test resets. Two of them are what an application calls.
// --- identity persistence (polyfill-era, no SDK counterpart) ---------------- // --- identity persistence (polyfill-era, no SDK counterpart) ----------------
// //
// Moved here from the SDK-identical entry on 2026-08-03. `accounts` persists WHICH // Moved here from the SDK-identical entry on 2026-08-03. `accounts` persists WHICH
@@ -44,7 +58,5 @@ export { connectedUser } from "./emulated-verifier/connect";
// one shared wallet hosts several identities. The real SDK has no counterpart: there // one shared wallet hosts several identities. The real SDK has no counterpart: there
// each user opens their own wallet, and "who am I" is the session. Shipping it from // each user opens their own wallet, and "who am I" is the session. Shipping it from
// the SDK entry advertised as durable something that disappears at migration. // the SDK entry advertised as durable something that disappears at migration.
export * as virtualUsers from "./shared-wallet/virtual-users";
export type { VirtualUserStorage } from "./shared-wallet/virtual-users";
// Config-shaped types the bootstrap needs; both describe the shim, not the SDK. // Config-shaped types the bootstrap needs; both describe the shim, not the SDK.
export type { VirtualUserRecord, RegistrySession } from "./shared-wallet/account-registry"; export type { VirtualUserRecord, RegistrySession } from "./shared-wallet/account-registry";
+20 -2
View File
@@ -31,6 +31,7 @@ import { subscribeDoc } from "./subscribe";
import { ensureRepoOpen } from "../emulated-verifier/open-repo"; import { ensureRepoOpen } from "../emulated-verifier/open-repo";
import { getCaps, getCurrentUser, getStoreRegistryDeps } from "../shared-wallet/bootstrap"; import { getCaps, getCurrentUser, getStoreRegistryDeps } from "../shared-wallet/bootstrap";
import { addLink, documentInboxAddress, isOwnInbox } from "../emulated-verifier/branch-registers"; import { addLink, documentInboxAddress, isOwnInbox } from "../emulated-verifier/branch-registers";
import { userInbox } from "../shared-wallet/account-registry";
import { escapeLiteral } from "./sparql"; import { escapeLiteral } from "./sparql";
import { hasReadCap } from "../model/nuri"; import { hasReadCap } from "../model/nuri";
import { import {
@@ -279,14 +280,31 @@ function capOfPayload(payload: unknown): ReadCap | null {
* The shape is right; the implementation is absent at both ends, so we emulate it * The shape is right; the implementation is absent at both ends, so we emulate it
* meanwhile. * meanwhile.
*/ */
export async function shareCap(cap: ReadCap, toInbox: Nuri): Promise<void> { export async function shareCap(cap: ReadCap, toUser: string): Promise<void> {
if (!hasReadCap(cap)) { if (!hasReadCap(cap)) {
throw new Error( throw new Error(
"[ng-eventually] inbox.shareCap: expected a ReadCap (a NURI carrying `:r:`), " + "[ng-eventually] inbox.shareCap: expected a ReadCap (a NURI carrying `:r:`), " +
`got a bare reference — naming is not reading: ${JSON.stringify(cap)}`, `got a bare reference — naming is not reading: ${JSON.stringify(cap)}`,
); );
} }
await post(toInbox, { payload: { kind: LINK_KIND, cap } }); // Takes the RECIPIENT, not their inbox address. Sharing is an act toward someone;
// which inbox carries it is the library's business, and an address is exactly what a
// caller will not have to handle once this is native. It used to take `toInbox`, which
// forced every consumer to resolve an address first — a step it would then have to
// unlearn. Protected, because directed sharing is not a public announcement.
await post(await userInbox(toUser, "protected"), { payload: { kind: LINK_KIND, cap } });
}
/**
* The messages left on a document YOU own — the read side of {@link postToDocument}.
*
* Named by the DOCUMENT, like the deposit side: an owner reading their own messages has
* no more reason to handle an inbox address than a depositor does. Empty when the
* document has no inbox, which is a state and not an error.
*/
export async function readForDocument(doc: Nuri): Promise<Deposit[]> {
const address = await documentInboxAddress(doc);
return address ? read(address) : [];
} }
// --- the read guard ------------------------------------------------------ // --- the read guard ------------------------------------------------------
+9 -3
View File
@@ -17,6 +17,14 @@
* *
* At migration this file disappears: placement becomes the user's real per-scope * At migration this file disappears: placement becomes the user's real per-scope
* stores and the calls below become native SDK ones. * stores and the calls below become native SDK ones.
*
* **No inbox ADDRESS is published here**, deliberately (`userInbox`,
* `documentInboxAddress`, removed 2026-08-05). An application deposits with
* `inbox.postToDocument(doc, …)`, shares with `inbox.shareCap(cap, toUser)` and reads
* its own with `inbox.readForDocument(doc)` — always naming a document or a person,
* never an address. Upstream an address is resolved from a profile and never handled by
* a caller, so exposing one taught a step that has to be unlearned. The example
* application is the check: it must never name an inbox.
*/ */
export { export {
@@ -28,9 +36,7 @@ export {
resolveScopeGraph, resolveScopeGraph,
/** The NURI where GROUPED entities of `scope` are written (no per-entity document). */ /** The NURI where GROUPED entities of `scope` are written (no per-entity document). */
resolveWriteGraph, resolveWriteGraph,
/** A user's own inbox — where caps and messages addressed to THEM arrive. */
userInbox,
/** Open an inbox on a document you OWN, so others can deposit into it. */ /** Open an inbox on a document you OWN, so others can deposit into it. */
/** WHERE to deposit for a document — readable by any holder of it. `undefined` if none. */ /** WHERE to deposit for a document — readable by any holder of it. `undefined` if none. */
} from "../shared-wallet/account-registry"; } from "../shared-wallet/account-registry";
export { documentInboxAddress, openDocumentInbox } from "../emulated-verifier/branch-registers"; export { openDocumentInbox } from "../emulated-verifier/branch-registers";
+2 -8
View File
@@ -5,15 +5,9 @@
* reads an empty identity, provisions a second virtual user, and the returning user * 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. * 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 { test, expect, afterEach } from "bun:test";
import { import {configure,configureStoreRegistry,resetConfig,resetStoreRegistry,setCurrentUser} from "../src/polyfill";
configure,
configureStoreRegistry,
resetConfig,
resetStoreRegistry,
setCurrentUser,
getCurrentUser,
} from "../src/polyfill";
import { ensureIdentity } from "../src/shared-wallet/access-gate"; import { ensureIdentity } from "../src/shared-wallet/access-gate";
const KEY = "ng-eventually:identity"; 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 { describe, it, expect, mock, beforeEach, afterEach, afterAll } from "bun:test";
import { setAccessLog, enabled, shortNuri } from "../src/shared-wallet/access-log"; import { setAccessLog, enabled, shortNuri } from "../src/shared-wallet/access-log";
import { docCreate, sparqlUpdate, sparqlQuery } from "../src/surface/docs"; import { docCreate, sparqlUpdate, sparqlQuery } from "../src/surface/docs";
import { import {configure,configureStoreRegistry,resetStoreRegistry,resetConfig,setCurrentUser,resetCaps,connectedUser} from "../src/polyfill";
configure,
configureStoreRegistry,
resetStoreRegistry,
resetConfig,
setCurrentUser,
getCurrentUser,
resetCaps,
connectedUser,
} from "../src/polyfill";
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Helpers // Helpers
+1 -6
View File
@@ -26,12 +26,7 @@ import {
resetRegistryCache, resetRegistryCache,
} from "../src/shared-wallet/account-registry"; } from "../src/shared-wallet/account-registry";
import type { RegistrySession } from "../src/shared-wallet/account-registry"; import type { RegistrySession } from "../src/shared-wallet/account-registry";
import { import {configure,configureStoreRegistry,resetStoreRegistry,resetConfig} from "../src/polyfill";
configure,
configureStoreRegistry,
resetStoreRegistry,
resetConfig,
} from "../src/polyfill";
import { resetOpenedRepos } from "../src/emulated-verifier/open-repo"; import { resetOpenedRepos } from "../src/emulated-verifier/open-repo";
afterAll(() => { afterAll(() => {
@@ -23,14 +23,7 @@
import { describe, it, expect, mock, afterAll, beforeEach } from "bun:test"; import { describe, it, expect, mock, afterAll, beforeEach } from "bun:test";
import { ensureAccount, resolveWriteGraph, resetRegistryCache } from "../src/shared-wallet/account-registry"; import { ensureAccount, resolveWriteGraph, resetRegistryCache } from "../src/shared-wallet/account-registry";
import { resetOpenedRepos } from "../src/emulated-verifier/open-repo"; import { resetOpenedRepos } from "../src/emulated-verifier/open-repo";
import { import {configure,configureStoreRegistry,resetStoreRegistry,resetConfig,resetCaps,setCurrentUser} from "../src/polyfill";
configure,
configureStoreRegistry,
resetStoreRegistry,
resetConfig,
resetCaps,
setCurrentUser,
} from "../src/polyfill";
import { resetInfrastructure } from "../src/emulated-verifier/reach"; import { resetInfrastructure } from "../src/emulated-verifier/reach";
const SESSION = { sessionId: "sid-cold", privateStoreId: "PRIV-COLD" }; 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 * 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. * 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 { test, expect, mock, afterAll } from "bun:test";
import { import {
createEntityDoc, createEntityDoc,
@@ -25,18 +26,7 @@ import {
} from "../src/shared-wallet/account-registry"; } from "../src/shared-wallet/account-registry";
import { documentInboxAddress, openDocumentInbox } from "../src/emulated-verifier/branch-registers"; import { documentInboxAddress, openDocumentInbox } from "../src/emulated-verifier/branch-registers";
import type { RegistrySession } from "../src/shared-wallet/account-registry"; import type { RegistrySession } from "../src/shared-wallet/account-registry";
import { import {configure,configureStoreRegistry,resetStoreRegistry,resetConfig,capFor,resetCaps,setCurrentUser,shareCap,connectedUser} from "../src/polyfill";
configure,
configureStoreRegistry,
resetStoreRegistry,
resetConfig,
capFor,
getCaps,
resetCaps,
setCurrentUser,
shareCap,
connectedUser,
} from "../src/polyfill";
import { post, postToDocument, read as readInbox } from "../src/surface/inbox"; import { post, postToDocument, read as readInbox } from "../src/surface/inbox";
import { readUnion } from "../src/surface/read-model"; import { readUnion } from "../src/surface/read-model";
import { sparqlUpdate } from "../src/surface/docs"; 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 // 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. // inbox. She names no principal to the registry; she addresses an inbox.
setCurrentUser("alice"); setCurrentUser("alice");
await shareCap(protCap, CHARLIE_INBOX); await shareCap(protCap, "charlie");
setCurrentUser("charlie"); setCurrentUser("charlie");
getCaps().learn(pubLink); 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"); const CHARLIE_INBOX = await userInbox("charlie", "protected");
setCurrentUser("alice"); setCurrentUser("alice");
await shareCap(protCap, CHARLIE_INBOX); await shareCap(protCap, "charlie");
setCurrentUser("bob"); setCurrentUser("bob");
getCaps().learn(pubLink); 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 // Alice delivers the cap. Bob's client processes his inbox — the only thing that
// happens; no "receive" call exists. // happens; no "receive" call exists.
setCurrentUser("alice"); setCurrentUser("alice");
await shareCap(protCap, BOB_INBOX); await shareCap(protCap, "bob");
setCurrentUser("bob"); setCurrentUser("bob");
await readInbox(BOB_INBOX); 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"); const bobInbox = await userInbox("bob", "protected");
setCurrentUser("alice"); setCurrentUser("alice");
await shareCap(protCap, bobInbox); await shareCap(protCap, "bob");
// Bob connects: the library restores + drains, with nothing asked of the app. // Bob connects: the library restores + drains, with nothing asked of the app.
setCurrentUser("bob"); 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. // Two deposits, one at each level, both made by someone else.
setCurrentUser("carol"); setCurrentUser("carol");
const carolDoc = await createEntityDoc("carol", "protected"); 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 }); await post(docInbox, { payload: { onTheDocument: true }, ts: 2 });
// Alice connects: one call, both queues. // 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(). // 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() { function fakeNg() {
return { 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 { post, read, materialize, watch } from "../src/surface/inbox";
import { userInbox, resetRegistryCache } from "../src/shared-wallet/account-registry"; import { userInbox, resetRegistryCache } from "../src/shared-wallet/account-registry";
import type { Deposit } from "../src/surface/inbox"; import type { Deposit } from "../src/surface/inbox";
import { import {configure,configureStoreRegistry,resetStoreRegistry,resetConfig,setCurrentUser} from "../src/polyfill";
configure,
configureStoreRegistry,
resetStoreRegistry,
resetConfig,
setCurrentUser,
} from "../src/polyfill";
import type { RegistrySession } from "../src/shared-wallet/account-registry"; import type { RegistrySession } from "../src/shared-wallet/account-registry";
// This suite injects a fake `ng` via configure() and reuses the storeRegistry's // 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; * link of a published document opens it for whoever receives it;
* (c) switching identity SWITCHES heldByHolder — it never wipes one. * (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 { test, expect, mock, afterAll } from "bun:test";
import { createEntityDoc, resetRegistryCache, userInbox, listMyEntityDocs } from "../src/shared-wallet/account-registry"; import { createEntityDoc, resetRegistryCache, userInbox, listMyEntityDocs } from "../src/shared-wallet/account-registry";
import type { RegistrySession } from "../src/shared-wallet/account-registry"; import type { RegistrySession } from "../src/shared-wallet/account-registry";
import type { ReadCap } from "../src/model/types"; import type { ReadCap } from "../src/model/types";
import { import {configure,configureStoreRegistry,resetStoreRegistry,resetConfig,capFor,resetCaps,setCurrentUser,shareCap} from "../src/polyfill";
configure,
configureStoreRegistry,
resetStoreRegistry,
resetConfig,
capFor,
getCaps,
resetCaps,
setCurrentUser,
shareCap,
} from "../src/polyfill";
import { read as readInbox } from "../src/surface/inbox"; import { read as readInbox } from "../src/surface/inbox";
import { filterReadable } from "../src/emulated-verifier/read-filter"; 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. // bob's OWN inbox — the only cross-wallet act there is.
const bobInbox = await userInbox("bob", "protected"); const bobInbox = await userInbox("bob", "protected");
setCurrentUser("alice"); setCurrentUser("alice");
await shareCap(capFor(shared)!, bobInbox); await shareCap(capFor(shared)!, "bob");
// bob processes his inbox — no dedicated "receive" operation exists. // bob processes his inbox — no dedicated "receive" operation exists.
setCurrentUser("bob"); setCurrentUser("bob");
@@ -248,7 +239,7 @@ test("a cap deposit is absorbed, not surfaced as a consumer deposit", async () =
setCurrentUser("alice"); setCurrentUser("alice");
const doc = await createEntityDoc("alice", "protected"); const doc = await createEntityDoc("alice", "protected");
const bobInbox = await userInbox("bob", "protected"); const bobInbox = await userInbox("bob", "protected");
await shareCap(capFor(doc)!, bobInbox); await shareCap(capFor(doc)!, "bob");
setCurrentUser("bob"); setCurrentUser("bob");
const deposits = await readInbox(bobInbox); 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"); const bobInbox = await userInbox("bob", "protected");
// Alice deposits into bob's inbox — allowed, and it grants her nothing back. // 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); await expect(readInbox(bobInbox)).rejects.toThrow(/does not belong to the connected wallet/i);
expect(capFor(secret)).toBeDefined(); // still hers, obviously 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 { test, expect, mock, afterEach } from "bun:test";
import { makeNg } from "../src/surface/ng-proxy"; import { makeNg } from "../src/surface/ng-proxy";
import { import {configure,resetConfig,resetCaps,setCurrentUser} from "../src/polyfill";
configure,
resetConfig,
getCaps,
resetCaps,
setCurrentUser,
} from "../src/polyfill";
// This suite injects a fake `ng` via configure() and declares WRITE caps — // This suite injects a fake `ng` via configure() and declares WRITE caps —
// which stay an authorization list on purpose: only READING is key possession // 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 { describe, it, expect, mock, beforeEach, afterAll } from "bun:test";
import { ensureRepoOpen, ensureReposOpen, resetOpenedRepos } from "../src/emulated-verifier/open-repo"; import { ensureRepoOpen, ensureReposOpen, resetOpenedRepos } from "../src/emulated-verifier/open-repo";
import { readUnion } from "../src/surface/read-model"; import { readUnion } from "../src/surface/read-model";
import { import {configure,configureStoreRegistry,resetStoreRegistry,resetConfig,resetCaps,setCurrentUser} from "../src/polyfill";
configure,
configureStoreRegistry,
resetStoreRegistry,
resetConfig,
resetCaps,
setCurrentUser,
} from "../src/polyfill";
import { resetInfrastructure } from "../src/emulated-verifier/reach"; import { resetInfrastructure } from "../src/emulated-verifier/reach";
import { resetRegistryCache } from "../src/shared-wallet/account-registry"; 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 { sparqlQuery, sparqlUpdate, depositInto } from "../src/surface/docs";
import { createEntityDoc, resetRegistryCache, userInbox } from "../src/shared-wallet/account-registry"; import { createEntityDoc, resetRegistryCache, userInbox } from "../src/shared-wallet/account-registry";
import type { RegistrySession } from "../src/shared-wallet/account-registry"; import type { RegistrySession } from "../src/shared-wallet/account-registry";
import { import {configure,configureStoreRegistry,resetStoreRegistry,resetConfig,resetCaps,setCurrentUser} from "../src/polyfill";
configure,
configureStoreRegistry,
resetStoreRegistry,
resetConfig,
resetCaps,
setCurrentUser,
} from "../src/polyfill";
import { mayReach, mustNotAttempt } from "../src/emulated-verifier/reach"; import { mayReach, mustNotAttempt } from "../src/emulated-verifier/reach";
import { hasReadCap } from "../src/model/nuri"; 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 { test, expect, mock, afterAll } from "bun:test";
import { readUnion } from "../src/surface/read-model"; import { readUnion } from "../src/surface/read-model";
import type { Nuri } from "../src/model/types"; import type { Nuri } from "../src/model/types";
import { import {configure,configureStoreRegistry,resetCaps,setCurrentUser} from "../src/polyfill";
configure,
configureStoreRegistry,
getCaps,
resetCaps,
setCurrentUser,
} from "../src/polyfill";
// The cap registry is process-wide, so each inject() starts from an empty one: // 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 // 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, resetRegistryCache,
} from "../src/shared-wallet/account-registry"; } from "../src/shared-wallet/account-registry";
import type { RegistrySession } from "../src/shared-wallet/account-registry"; import type { RegistrySession } from "../src/shared-wallet/account-registry";
import { import {configure,configureStoreRegistry,resetStoreRegistry,resetConfig} from "../src/polyfill";
configure,
configureStoreRegistry,
resetStoreRegistry,
resetConfig,
} from "../src/polyfill";
// This suite injects a fake `ng` via configure(); bun runs test files in a // 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 // 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 { test, expect, mock, afterAll } from "bun:test";
import { subscribeDoc, subscribeDocs } from "../src/surface/subscribe"; import { subscribeDoc, subscribeDocs } from "../src/surface/subscribe";
import { import {configure,configureStoreRegistry,resetConfig,resetStoreRegistry} from "../src/polyfill";
configure,
configureStoreRegistry,
resetConfig,
resetStoreRegistry,
} from "../src/polyfill";
import type { RegistrySession } from "../src/shared-wallet/account-registry"; import type { RegistrySession } from "../src/shared-wallet/account-registry";
// subscribeDoc/subscribeDocs wrap the REAL injected `ng.doc_subscribe`. This // 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 { describe, it, expect, mock, beforeEach, afterEach, afterAll } from "bun:test";
import { watchShape } from "../src/surface/watch-shape"; import { watchShape } from "../src/surface/watch-shape";
import { import {configure,configureStoreRegistry,resetStoreRegistry,resetConfig,resetCaps,setCurrentUser} from "../src/polyfill";
configure,
configureStoreRegistry,
resetStoreRegistry,
resetConfig,
resetCaps,
setCurrentUser,
} from "../src/polyfill";
import { resetRegistryCache, createEntityDoc } from "../src/shared-wallet/account-registry"; import { resetRegistryCache, createEntityDoc } from "../src/shared-wallet/account-registry";
import { resetOpenedRepos, setOpenTimeoutForTests, getSyncState } from "../src/emulated-verifier/open-repo"; import { resetOpenedRepos, setOpenTimeoutForTests, getSyncState } from "../src/emulated-verifier/open-repo";