test(e2e): les parcours applicatifs passent par l'app d'exemple
Une seconde suite e2e, `packages/sdk/e2e/notebook.ts` (`test:e2e:app`), qui pilote `examples/notebook` par le DOM — une page de navigateur par identité — contre le même broker réel. **Pourquoi une seconde suite plutôt qu'un ajout dans la première.** `run.ts` parle à un sac de méthodes sur `window.__sdk` : cela prouve que les fonctions TOURNENT, jamais qu'une application peut s'écrire avec. L'écart a déjà coûté un défaut livré — l'inbox d'un document était verte ici et inutilisable en pratique, parce que le harnais pouvait faire traverser une adresse d'une identité à l'autre par une variable, canal qu'aucune application n'a. Ici, rien ne traverse que ce qui traverse dans la vie : la RÉFÉRENCE d'une note, recopiée d'un écran, et un identifiant tapé dans un champ. Quatre parcours, qui se lisent comme des parcours : - Bob lit la note publique d'Alice depuis sa seule référence — la propriété pour laquelle l'émulation du store public existe, vérifiée bout en bout et sans qu'aucune clé ne circule ; - la note protégée d'Alice reste fermée jusqu'à ce qu'elle la partage — même geste côté Bob, issue opposée, décidée par où la note se trouve ; - Bob laisse un message sur la note d'Alice, et seule Alice le lit — il TROUVE l'adresse depuis la note, personne ne la lui donne ; - la liste de chacun ne contient que ses notes. **Deux étapes quittent `run.ts`** (`documentInboxDeposit`, `capsShareCap`), avec un commentaire disant où elles sont parties : ce sont des parcours, et ils valent plus joués sur deux écrans que sur deux appels d'une même page. Ce qui reste là-bas est ce qu'une application ne fait pas : primitives, caractérisation, régressions de démarrage à froid. **Trois défauts trouvés en écrivant la suite**, tous côté application et invisibles pour le harnais : `connectedUser()` devait être attendu à la connexion (sinon une note qu'on vient de vous partager se lit comme illisible — ce qui ressemble à un problème de droits alors que c'est un problème de moment) ; une réponse périmée restait affichée à côté d'une question fraîche ; et changer de portée ne rafraîchissait pas la liste. L'app affiche désormais la référence de chaque note — ce qu'aucun écran ne montre, aucun utilisateur ne peut le faire circuler. Corrigé au passage : le `.gitignore` pointait encore `packages/client/`, si bien que le commit de renommage a embarqué le profil Playwright de la suite e2e (226 fichiers). Les chemins sont réalignés et le commit précédent a été amendé — rien n'était poussé. 179 tests unitaires, e2e 40/40 (3,2 min) et applicatif 10/10 (0,7 min).
This commit is contained in:
@@ -34,7 +34,6 @@ import {
|
||||
// `storeRegistry` above is the app-facing slice; these are the shim internals.
|
||||
import * as registryInternals from "../src/shared-wallet/account-registry";
|
||||
import { getCaps, getCurrentUser, resetCaps } from "../src/shared-wallet/bootstrap";
|
||||
import { documentInboxAddress } from "../src/emulated-verifier/branch-registers";
|
||||
import * as virtualUsers from "../src/shared-wallet/virtual-users";
|
||||
import { ensureIdentity } from "@ng-eventually/sdk";
|
||||
// The harness narrows for its OWN assertions; a consumer never has to (the entries take
|
||||
@@ -848,88 +847,15 @@ const identity = new IdentityStore(
|
||||
setCurrentUser(null);
|
||||
return { ownerView, strangerView, strangerWithLinkView };
|
||||
},
|
||||
/**
|
||||
* Sharing a cap the way the model does it: the owner deposits it into the
|
||||
* recipient's INBOX, and the recipient processing that inbox absorbs it. No
|
||||
* "receive" operation exists, and no principal is ever named to the registry.
|
||||
* Runs against the REAL broker inbox document, so it exercises the whole path.
|
||||
*/
|
||||
/**
|
||||
* The DEPOSIT side of a document's inbox, end to end against the real broker: the
|
||||
* owner opens it, a third party RESOLVES its address from the document itself and
|
||||
* deposits, the owner reads it back.
|
||||
*
|
||||
* The point of the step is the resolution: the depositor is handed the document's
|
||||
* BARE reference — the only thing an application circulates — and must find where to
|
||||
* deposit on its own. It reads the document at all because the document sits in a
|
||||
* public store, which serves its read cap to whoever asks (`public-store.ts`); no key
|
||||
* crosses the identity boundary, here or in any real application.
|
||||
*/
|
||||
async documentInboxDeposit(ownerId: string, depositorId: string) {
|
||||
registryInternals.resetRegistryCache();
|
||||
setCurrentUser(ownerId);
|
||||
const doc = await storeRegistry.createEntityDoc(ownerId, "public");
|
||||
const ownerInbox = await storeRegistry.openDocumentInbox(doc);
|
||||
|
||||
setCurrentUser(depositorId);
|
||||
const resolved = await documentInboxAddress(doc);
|
||||
// The one-call form an app actually uses: it names the DOCUMENT, never an inbox.
|
||||
await inbox.postToDocument(doc, { payload: { viaPostToDocument: true }, ts: 900 });
|
||||
// Opening one on someone else's document must be refused, not silently forked.
|
||||
let openRefused = false;
|
||||
try {
|
||||
await storeRegistry.openDocumentInbox(doc);
|
||||
} catch {
|
||||
openRefused = true;
|
||||
}
|
||||
if (resolved) await inbox.post(resolved, { payload: { joining: true }, ts: 1000 });
|
||||
|
||||
setCurrentUser(ownerId);
|
||||
const deposits = await inbox.read(ownerInbox);
|
||||
// The address is machinery: it must not surface among the document's properties.
|
||||
const subjects = await readUnion([doc]);
|
||||
const props = Object.keys(subjects[0]?.props ?? {});
|
||||
setCurrentUser(null);
|
||||
return {
|
||||
sameInbox: resolved === ownerInbox,
|
||||
openRefused,
|
||||
deposits: deposits.map((d) => d.payload),
|
||||
props,
|
||||
};
|
||||
},
|
||||
async capsShareCap(friendId: string) {
|
||||
const s = await sessionReady;
|
||||
resetCaps();
|
||||
// The recipient's OWN inbox — the address a cap is delivered to. Resolved while
|
||||
// connected as them, since that is who owns it and who may later read it.
|
||||
// `friendId` is fresh per run: this test's assertions survive accumulated caps, but
|
||||
// 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.
|
||||
setCurrentUser(friendId);
|
||||
const friendInbox = await registryInternals.userInbox(friendId, "protected");
|
||||
|
||||
setCurrentUser("owner-O");
|
||||
const doc = await docs.docCreate(s.session_id, "Graph", "data:graph", "store", undefined);
|
||||
injectedSetItems = [{ "@graph": doc, "@id": "1", v: "shared-item" }];
|
||||
getCaps().open(doc, "protected");
|
||||
|
||||
setCurrentUser(friendId);
|
||||
const before = [...(libUseShape(null, null) as Iterable<any>)].length;
|
||||
|
||||
setCurrentUser("owner-O");
|
||||
await inbox.share(doc, friendId);
|
||||
|
||||
setCurrentUser(friendId);
|
||||
const absorbed = await inbox.read(friendInbox); // processing it applies the cap
|
||||
const after = [...(libUseShape(null, null) as Iterable<any>)].length;
|
||||
|
||||
resetCaps();
|
||||
injectedSetItems = [];
|
||||
setCurrentUser(null);
|
||||
// `absorbed` must be EMPTY: a cap delivery is infrastructure, never surfaced
|
||||
// to the consumer as a deposit.
|
||||
return { before, after, surfacedDeposits: absorbed.length };
|
||||
},
|
||||
// MOVED to the applicative suite, `e2e/notebook.ts` (2026-08-07):
|
||||
// - `documentInboxDeposit` → "Bob leaves a message on Alice's note, and only Alice
|
||||
// reads it". This one is WHY that suite exists: it was green here while the
|
||||
// feature was unusable, because a harness can hand an inbox address across an
|
||||
// identity boundary through a variable and an application cannot.
|
||||
// - `capsShareCap` → "Alice's protected note stays shut until she gives Bob the key".
|
||||
//
|
||||
// What stays here is what an application does not do: primitives, characterisation,
|
||||
// and the cold-start regressions.
|
||||
|
||||
// ── accounts (IdentityStore) ─────────────────────────────────────────────
|
||||
identitySet(id: string) { return identity.set(id); },
|
||||
|
||||
Reference in New Issue
Block a user