c5b4703687
Aucun parcours n'avait jamais marché le chemin d'un nouvel arrivant : tous pré-injectaient l'identifiant dans l'URL, ce qui fait résoudre l'identité sans jamais afficher la barrière. La suite était verte par-dessus un lien de téléchargement pointant sur un fichier que personne ne servait — et le serveur de test répondait la page HTML de l'application pour tout chemin inconnu, donc un fichier manquant ne POUVAIT pas échouer. Le nouveau parcours part d'un profil vide : barrière, téléchargement réel, import dans l'application portefeuille, saisie de l'identifiant, remise au broker, retour dans l'iframe. Il vérifie neuf points, dont celui qui compte — l'identifiant a survécu et l'identité rapportée est celle qui a été saisie. Le harnais, lui, se pendait au lieu d'échouer. Cause observée : le tuyau devtools de Chromium lâche et Playwright n'émet ni close ni disconnected, si bien que la suite bloquait dans son propre nettoyage sans imprimer ni résumé ni l'échec déjà en route. Toutes les attentes sont désormais bornées et nomment ce qu'elles attendaient ; vérifié en cassant délibérément une attente, et observé en conditions réelles — trois minutes et « gave up waiting for: alice to sign in » là où j'ai tué trois exécutions d'une heure ce matin. Deux exécutions simultanées ne se détruisent plus : verrou atomique sur le profil, et récupération d'un navigateur laissé par une exécution tuée. Le marqueur devient .user-consumed — il n'a jamais attesté d'une disponibilité, seulement qu'un lot avait déjà pris l'utilisateur de ce profil. Au passage, la destruction du profil dépendait du marqueur, écrit en FIN de lot : une exécution tuée avant laissait un profil que la suivante réutilisait, et héritait de sa casse. Elle dépend maintenant du profil. La suite applicative reste non mesurée sur cette machine : un conteneur en boucle de redémarrage recycle son interface réseau, et sept exécutions sur dix échouent sur le transport. Trois sont passées 21/21.
40 lines
3.1 KiB
Markdown
40 lines
3.1 KiB
Markdown
---
|
|
type: knowledge
|
|
summary: Deciding which identity acts needs no session; connecting does — conflating them deadlocks one way and silently breaks sharing the other
|
|
---
|
|
|
|
# Settling is not connecting
|
|
|
|
Two acts of different nature hide behind "sign in":
|
|
|
|
- **`settle`** — decide which identity is acting, from the URL, from storage, or by asking at the `barrier`, then persist it. Pure DOM and storage. **No session required.**
|
|
- **connect** — restore what was shared with that identity and drain its inbox. **Requires a live session.**
|
|
|
|
They must stay apart, and the reason is not tidiness.
|
|
|
|
## Why they cannot be one call
|
|
|
|
`init()` starts the broker redirect as its first statement when the page is top-level, so anything the barrier needs to show must already be in place. The `barrier` must therefore appear **before** `init()`.
|
|
|
|
But an identity cannot connect before `init()` either: connecting reaches the application's session thunk, which only `init()`'s own callback resolves. Await the whole of sign-in before `init()` and it deadlocks; call it after and the `barrier` never appears.
|
|
|
|
The way out is that only *half* of it has that dependency. Settling is awaited by this package's `init()` wrapper before it delegates; connecting stays in the published call, awaited where a session exists.
|
|
|
|
**The invariant is carried by the composition from `init()` onwards** — which is why an application must call this package's `init`, not the one it injected: calling it settles the identity whether or not the published call has run.
|
|
|
|
It does **not** cover calling the published sign-in strictly first and awaiting it: the connection work it adds waits for a session only `init()` resolves, so it deadlocks in silence. Half an invariant carried by a mechanism, half still owed to the caller — and the half still owed fails silently, which is the worst kind.
|
|
|
|
## The failure this cost
|
|
|
|
Settling once reached the session, because recording who is acting also fired the connection. In the reference application `init()` is called *from inside the executor that builds the session promise*, so the thunk could not answer by construction. It threw, the account lookup answered null, and the connection run abandoned **without restoring or draining** — having already registered itself as in flight. The published call then joined that dead run and resolved having done nothing.
|
|
|
|
Symptom: a document shared with someone did not open for them. No error, just unreadable content.
|
|
|
|
Nothing had changed in the connection logic. What changed was *when* the identity was recorded. Before the split, nothing recorded an identity during module evaluation: a session existed, the run was healthy, and joining it was harmless.
|
|
|
|
Hence the shape of the fix — recording who acts and starting to connect are separate operations, and the session-free half only records. To validate: the session thunk must never be called while settling.
|
|
|
|
## The lesson worth keeping
|
|
|
|
A "session-free" half that calls something which fires a session lookup is not session-free. When splitting on a dependency, check what the *side effects* of each remaining call reach, not only what the call itself does.
|