refactor(api): le bootstrap redescend de quatre appels à un

L'objectif acté était deux appels spécifiques au polyfill, voire un. Il en publiait
quatre. Chacun des trois de trop était une raison que la BIBLIOTHÈQUE a, pas un besoin
qu'une application a :

- **`configureStoreRegistry`** existait parce qu'il y a deux internes à câbler — le SDK
  injecté d'un côté, la session de l'autre. Vu de l'appelant, les deux disent « voici ce
  qu'il te faut pour tourner ». Replié dans `configure`, qui prend désormais
  `getSession` / `normalizeId` / `pointerGuard`.
- **`setCurrentUser`** n'a plus lieu d'être publié depuis que le portail d'accès est
  passé dans le polyfill : c'est lui qui pose l'identité. Et une application qui nomme
  sa propre identité est exactement le geste qui inverse le modèle — il ne doit pas
  exister d'appel publié vers lequel se tourner. Le harnais e2e, lui, joue plusieurs
  identités sur une même page ; il y accède par le chemin interne, ce qu'un harnais a
  le droit de faire et une application non.
- **`connectedUser`** est maintenant attendu DANS `ensureIdentity`. Ce n'était pas une
  commodité : la suite applicative avait montré qu'une app devait l'attendre elle-même,
  sinon une note qu'on venait de lui partager se lisait comme illisible. J'avais traité
  le symptôme dans l'app d'exemple ; le défaut était côté bibliothèque. En amont, ouvrir
  la session EST la connexion — aucune application n'attend un second appel.

Reste donc `configure({ … })`, plus `await ensureIdentity()` dont le site d'appel
survit à la migration : une application attendra toujours une session avant de rendre.

Le test étendu hier a fait son travail : les deux contrôles de contrat sont passés au
rouge sur `configureStoreRegistry`, `connectedUser` et `StoreRegistryDeps` dès que la
surface a bougé.

180 tests unitaires, e2e 40/40 (3,4 min) et applicatif 10/10 (0,8 min).
This commit is contained in:
Sylvain Duchesne
2026-08-07 12:06:15 +02:00
parent b98fcaa77d
commit 0455a408b6
26 changed files with 205 additions and 114 deletions
+45 -41
View File
@@ -19,34 +19,36 @@ Per the design principle (`README.md` § *Design principle*): an absent implemen
## 1. Bootstrap and configuration
### Today — `@ng-eventually/sdk` (the POLYFILL-ERA block of `src/index.ts`; everything here is removed at migration)
### Today — `@ng-eventually/sdk`: **one call**
```ts
// all from shared-wallet/bootstrap.ts
// shared-wallet/bootstrap.ts
export interface EventuallyConfig {
ng: NgLike;
useShape: UseShapeLike;
sharedWallet?: SharedWalletConfig; // the gate's, § 2bis
ng: NgLike; // the REAL @ng-org/web ng
useShape: UseShapeLike; // the REAL @ng-org/orm useShape
getSession?: () => Promise<RegistrySession>; // the wallet session (a thunk)
normalizeId?: (id: string) => string;
pointerGuard?: { attempts?: number; baseMs?: number; maxStepMs?: number };
sharedWallet?: SharedWalletConfig; // the gate's, § 2bis
currentUser?: PrincipalId;
debugAccessLog?: boolean;
init?: (...args: any[]) => any;
initNg?: (...args: any[]) => any;
}
export function configure(c: EventuallyConfig): void;
export interface StoreRegistryDeps {
getSession: () => Promise<RegistrySession>;
normalizeId?: (id: string) => string;
pointerGuard?: { attempts?: number; baseMs?: number; maxStepMs?: number };
}
export function configureStoreRegistry(deps: StoreRegistryDeps): void;
// NOT published (2026-08-07, with the entry merge) — merging the two doors made
// publishing these a visible choice rather than an inherited one, and the choice is no:
// getConfig, getStoreRegistryDeps internal wiring; the surface reaches them by import
// resetConfig, resetStoreRegistry test resets; the suite reaches them the same way
```
**The count is the contract here.** The agreed target was two polyfill-era calls, or one; it had drifted to four, and each extra one was a reason the LIBRARY has rather than a need an application has. Four became one on 2026-08-07:
| Was published | Where it went |
|---|---|
| `configureStoreRegistry` + `StoreRegistryDeps` | folded into `configure` — two bootstrap calls existed because the library has two internals, which is not a reason a caller should pay |
| `setCurrentUser` | the access gate sets the identity (§ 2bis). An application naming its own identity is the gesture that INVERTS the model; it must not have a published call to reach for |
| `connectedUser` | `ensureIdentity` awaits it. Upstream, opening the session IS the connection — no application awaits a second call |
| `getConfig`, `getStoreRegistryDeps`, `resetConfig`, `resetStoreRegistry` | internal wiring and test resets, reached by their internal path (2026-08-07, with the entry merge) |
So an application's whole bootstrap is `configure({ … })` plus `await ensureIdentity()` — and the second of those keeps its call site after migration.
### Target
**NO COUNTERPART, by design.** The whole subject is the polyfill bootstrap: it exists to inject the real SDK without a hard import (build-alias safety). At migration the consumer initializes the real SDK directly, with the two calls in § 2, and `configure` / `configureStoreRegistry` are deleted (`docs/migration-guide.md` § 7). Nothing in the target takes an "injected `ng`".
@@ -548,16 +550,19 @@ export async function openDocumentInbox(doc: Nuri): Promise<Nuri>;
### Today
```ts
// PUBLISHED — the POLYFILL-ERA block of `src/index.ts`, both with no counterpart.
export function setCurrentUser(id: PrincipalId | null): void; // shared-wallet/bootstrap.ts
export async function connectedUser(): Promise<void>; // emulated-verifier/connect.ts
// NOT published (removed 2026-08-05) — identity persistence is the application's job
// upstream too, and asking the library who you signed in is a shared-wallet convenience:
// shared-wallet/virtual-users.ts IdentityStore, browserIdentityStore,
// VirtualUserStorage, ACCOUNT_STORAGE_KEY
// shared-wallet/bootstrap.ts getCurrentUser
// The access gate persists what IT needs (§ 2bis); nothing else has to be exposed.
// PUBLISHED: nothing. Identity is established by `ensureIdentity()` (§ 2bis) and the
// connection is awaited inside it.
//
// NOT published, and each removal is a gesture an application no longer performs:
// setCurrentUser (2026-08-07) naming one's own identity — the step that inverts the
// model. The gate does it; the e2e harness, which plays
// several identities on one page, reaches it internally.
// connectedUser (2026-08-07) awaited inside `ensureIdentity`; upstream, opening the
// session IS the connection.
// getCurrentUser (2026-08-05) an application knows who it signed in.
// IdentityStore, browserIdentityStore, VirtualUserStorage, ACCOUNT_STORAGE_KEY
// (2026-08-05) persisting an identity is the application's job
// upstream too; the gate persists what IT needs.
```
### Target
@@ -573,8 +578,8 @@ declare function user_disconnect(user_id: string): Promise<void>;
```
- `IdentityStore` / `browserIdentityStore` (the persisted identity id) — **NO COUNTERPART**; they exist only because every virtual user shares one wallet, and they are no longer published at all. Removed at migration (`docs/migration-guide.md` § 5).
- `setCurrentUser`**NO COUNTERPART**; the relay of an identity the broker cannot see. Disappears with the shared wallet. `getCurrentUser` was its read side and is gone from the surface: an application knows who it signed in.
- `connectedUser()` — the awaitable form of what the target does **automatically**: the recipient's verifier processes its inbox as messages arrive/at connection (`Verifier::inbox`, `engine/verifier/src/verifier.rs:1674`). VERIFIED at level 1 that no consumer call is needed upstream; the polyfill fires it from `setCurrentUser` for the same reason. A consumer should treat it as "await a deterministic start" (tests), not as an operation the future SDK will name.
- `setCurrentUser`**NO COUNTERPART**; the relay of an identity the broker cannot see. Disappears with the shared wallet, and is no longer published: the gate is the only caller an application needs.
- `connectedUser()` (internal since 2026-08-07) — the awaitable form of what the target does **automatically**: the recipient's verifier processes its inbox as messages arrive/at connection (`Verifier::inbox`, `engine/verifier/src/verifier.rs:1674`). VERIFIED at level 1 that no consumer call is needed upstream; the polyfill fires it from `setCurrentUser` for the same reason. A consumer should treat it as "await a deterministic start" (tests), not as an operation the future SDK will name.
---
@@ -625,21 +630,20 @@ Exported, but not SDK surface. Coding against these builds knowledge that migrat
### `@ng-eventually/sdk` — `src/index.ts` (the only entry since 2026-08-07)
```text
direct: BaseType, DeepSignalSet, DocChange, DocChangeType, EventuallyConfig, InboxScope, NG, NgLike, Nuri, NuriLike, PrincipalId, ReadCap, RegistrySession, Schema, Scope, ShapeObservable, ShapeQuery, ShapeType, SharedWalletConfig, StoreRegistryDeps, UnionSubject, Unsubscribe, UseShapeLike, configure, configureStoreRegistry, connectedUser, docChangeType, ensureIdentity, init, initNg, ng, readUnion, setCurrentUser, subscribeDoc, subscribeDocs, useShape, watchShape
direct: BaseType, DeepSignalSet, DocChange, DocChangeType, EventuallyConfig, InboxScope, NG, NgLike, Nuri, NuriLike, PrincipalId, ReadCap, RegistrySession, Schema, Scope, ShapeObservable, ShapeQuery, ShapeType, SharedWalletConfig, UnionSubject, Unsubscribe, UseShapeLike, configure, docChangeType, ensureIdentity, init, initNg, ng, readUnion, subscribeDoc, subscribeDocs, useShape, watchShape
docs: depositInto, docCreate, sparqlQuery, sparqlUpdate
inbox: Deposit, PostOptions, materialize, post, postToDocument, processInbox, read, readForDocument, readSynced, share, watch
storeRegistry: createEntityDoc, listMyEntityDocs, openDocumentInbox, resolveScopeGraph, resolveWriteGraph
```
**Of these, four are POLYFILL-ERA and have no target counterpart**`configure`,
`configureStoreRegistry`, `setCurrentUser`, `connectedUser` (plus the types
`EventuallyConfig`, `StoreRegistryDeps`, `RegistrySession`). They are the deletion list,
and `src/index.ts` groups them under a heading that says so. `ensureIdentity` is a fifth
in substance — the shared-wallet gate — but the *call site* survives (§ 2bis).
**Of these, exactly ONE is polyfill-era with no target counterpart**`configure` (plus
the types `EventuallyConfig`, `RegistrySession`). It is the deletion list, and
`src/index.ts` groups it under a heading that says so. `ensureIdentity` is a second in
substance — the shared-wallet gate — but its *call site* survives (§ 2bis).
Six symbols the previous `/polyfill` entry published are gone from the surface entirely:
`getConfig` and `getStoreRegistryDeps` (internal wiring, reached through
`shared-wallet/bootstrap`), `resetConfig` / `resetStoreRegistry` / `resetCaps` (test
resets, reached by their internal path), and the direct `share` re-export — `inbox.share`
was always the same function, and publishing it twice blurred the boundary it was meant
to mark.
Nine symbols published before 2026-08-07 are gone from the surface: `configureStoreRegistry`
and `StoreRegistryDeps` (folded into `configure`), `setCurrentUser` and `connectedUser`
(§ 1), `getConfig` / `getStoreRegistryDeps` (internal wiring), `resetConfig` /
`resetStoreRegistry` / `resetCaps` (test resets), and the direct `share` re-export —
`inbox.share` was always the same function, and publishing it twice blurred the boundary
it was meant to mark.