refactor(api): l'application ne nomme plus son identité — elle l'apprend
Lot C de la revue adverse. Cinq corrections, dont une qui change la forme de la surface. **L'application ne pouvait pas obtenir son identité par l'API.** `ensureIdentity()` rendait `void`, `getCurrentUser` n'est plus publié — et pourtant `createEntityDoc(id, …)` et `listMyEntityDocs(id, …)` l'exigeaient. L'app d'exemple s'en sortait en lisant `localStorage["ng-eventually:identity"]` et le paramètre `?ng-id`, deux constantes PRIVÉES du portail d'accès. Une frontière qu'aucun consommateur ne devrait voir, et encore moins dont il devrait dépendre. Vérifié au niveau 2 avant de trancher : `session_start(wallet_name, user_id)` prend l'identité — donc en amont l'application la DÉTIENT, elle la tient du portefeuille qu'elle a ouvert. Ici c'est le portail qui la choisit, donc c'est au portail de la rendre. Deux changements, tous deux vers la cible : - `ensureIdentity()` rend l'identité qu'il a établie ; - `createEntityDoc(scope)`, `listMyEntityDocs(scope)`, `resolveWriteGraph(scope)` perdent leur paramètre d'identité. En amont `doc_create(session_id, …)` ne porte aucun utilisateur : une session EST celle d'un utilisateur. Passer la sienne à chaque appel de placement était un geste sans successeur. L'application garde l'identité pour l'afficher, et ne la passe plus à rien. **`inbox.share` provisionnait un destinataire inexistant.** Une faute de frappe créait les trois stores et l'inbox de ce nom, et la clé atterrissait où personne ne regarde — sans la moindre erreur. En amont on ne peut pas viser un nom qu'on invente : un dépôt est scellé vers une clé d'inbox qui vous est parvenue par un contact entrant. Refuser est fidèle ; provisionner était l'invention. **`createEntityDoc` avalait l'échec de ses deux écritures** et rendait quand même une référence — le document n'était dans aucun store, donc la session suivante ne le listait pas et sa lecture rendait vide, en silence. Il lève maintenant, comme `doc_create` en amont propage les siennes. **Deux entrées prenaient `Nuri` au lieu de `NuriLike`** (`inbox.watch`, `openDocumentInbox`), ce qui contredisait la raison même pour laquelle aucune garde de type n'est publiée. Et **deux messages d'erreur nommaient des symboles retirés** (`storeRegistry.documentInboxAddress`, `setCurrentUser`) : une erreur qui envoie vers une fonction inexistante est pire qu'une erreur muette. Contrat d'API et feuille `contract_sdk-surface` mis à jour ; `readForDocument` et le refus de `share` obtiennent enfin leur règle en §9. 189 tests unitaires, e2e 40/40 et applicatif 12/12.
This commit is contained in:
+12
-22
@@ -94,8 +94,7 @@ configure({
|
||||
|
||||
/** Write a new note in `scope`. The document is created, then filled. */
|
||||
async function writeNote(scope: Scope, title: string, body: string): Promise<Nuri> {
|
||||
const me = currentIdentity();
|
||||
const doc = await storeRegistry.createEntityDoc(me, scope);
|
||||
const doc = await storeRegistry.createEntityDoc(scope);
|
||||
const s = await sessionReady;
|
||||
await docs.sparqlUpdate(
|
||||
s.session_id,
|
||||
@@ -107,7 +106,7 @@ async function writeNote(scope: Scope, title: string, body: string): Promise<Nur
|
||||
|
||||
/** My notes in `scope`, read the way the library intends: list, then read. */
|
||||
async function myNotes(scope: Scope): Promise<Note[]> {
|
||||
const docsOfScope = await storeRegistry.listMyEntityDocs(currentIdentity(), scope);
|
||||
const docsOfScope = await storeRegistry.listMyEntityDocs(scope);
|
||||
const subjects = await readUnion(docsOfScope);
|
||||
return subjects.map((s) => ({
|
||||
doc: s.subject,
|
||||
@@ -175,32 +174,23 @@ function watchNote(doc: Nuri, onChange: () => void): () => void {
|
||||
// --- identity ---------------------------------------------------------------
|
||||
|
||||
let identity = "";
|
||||
function currentIdentity(): string {
|
||||
if (!identity) throw new Error("not signed in yet");
|
||||
return identity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sign in. The library shows its access barrier when it needs one; the day the wallet
|
||||
* supplies the identity, this resolves silently and nothing here changes.
|
||||
* Sign in, and learn who you are.
|
||||
*
|
||||
* One await, and it covers everything: the gate resolves the identity AND waits for the
|
||||
* One await, and it covers everything: the library settles the identity, waits for the
|
||||
* connection work it fires (restoring what others shared with you, draining your
|
||||
* inboxes). The application used to have to await that second part itself — the
|
||||
* applicative e2e is what found it out, because a note someone had just shared read as
|
||||
* unreadable, which looks like a permission problem and is a timing one. The library
|
||||
* absorbed it: upstream, opening the session IS the connection, and no application
|
||||
* awaits a second call.
|
||||
* inboxes), and **returns the identity**. The application keeps it only to display it —
|
||||
* no call takes it, because a session belongs to one user and the target's own
|
||||
* `doc_create` carries no user at all.
|
||||
*
|
||||
* This used to read the library's private storage key to find out who it was, which is a
|
||||
* boundary no consumer should be able to see. Writing this application is what made that
|
||||
* visible.
|
||||
*/
|
||||
async function signIn(): Promise<void> {
|
||||
await ensureIdentity();
|
||||
identity = await ensureIdentity();
|
||||
await sessionReady;
|
||||
identity = readIdentityBack();
|
||||
}
|
||||
|
||||
/** The library owns the identity; the app asks for it rather than remembering it. */
|
||||
function readIdentityBack(): string {
|
||||
return new URLSearchParams(location.search).get("ng-id") ?? localStorage.getItem("ng-eventually:identity") ?? "";
|
||||
}
|
||||
|
||||
function escape(s: string): string {
|
||||
|
||||
Reference in New Issue
Block a user