a33fb8a214
Deux affirmations à corriger, toutes deux écrites par moi et démenties par le bug d'aujourd'hui. La feuille disait que se connecter « restaure ce qui vous a été partagé ». C'est la moitié : le portefeuille garde vos propres clés ailleurs, et ne rejouer que le registre des partages refusait à une application son PROPRE document après un rechargement. La règle qui reste : rejouer TOUS les registres durables, pas celui qui se trouve sur le chemin qu'on teste. Et la règle « tout échec remonte » était trop grossière. Elle était juste pour la restauration ; appliquée à chaque étape, elle verrouillait. Un dépôt inapplicable n'est pas consommé par son échec, donc il refusait la session à chaque tentative — une personne qui ne peut plus entrer, là où il n'y avait qu'un partage manquant. La distinction consignée : ATTEINDRE les files et restaurer sont de l'infrastructure, et refuser la session y est juste ; APPLIQUER un élément est de la donnée, et ça se signale sans priver personne de sa session. Plus la forme générale, parce qu'elle se reproduira : devant un « tout échec remonte », demander lesquels se rattrapent en réessayant plus tard et lesquels sont définitifs pour l'acteur. Les définitifs ne doivent jamais garder une porte qu'il n'a pas d'autre moyen de franchir.
52 lines
4.9 KiB
Markdown
52 lines
4.9 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** — put back in that identity's hands everything it can already open: what it OWNS as well as what was shared with it, then drain its queues. **Requires a live session.**
|
|
|
|
The wallet keeps those two in different places, and connecting once replayed only the second — so an application that reloaded and went straight to a document it had made itself was refused its own document. Whatever else changes here, connecting must replay **every** durable register, not the one that happens to be read on the path being tested.
|
|
|
|
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 needs the session, which arrives only through `init()`'s callback. 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. At the time the session was assembled by the application: it built a promise around `init()`'s callback and handed the package a thunk reading it. The application called `init()` *from inside the executor building that 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. The session is now the package's own — its `init()` wrapper captures the event — so no application can wire this wrong again. To validate: nothing reachable from settling asks for a session.
|
|
|
|
## 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.
|
|
|
|
## Failing to reach a queue is not failing to apply one item in it
|
|
|
|
Making connection failures surface was right — a restore that did not happen makes shared documents silently invisible, and resolving as though it had is the defect family this project keeps finding. But applied to *every* step alike, that rule locked people out.
|
|
|
|
A deposit that cannot be applied is not consumed by failing. So a single unusable item denied the session, and denied it again at every future connection: not a missing share, a person who can never sign in. Strictly worse than the silence it replaced.
|
|
|
|
The distinction to keep: **reaching** the queues, and the restore itself, are infrastructure — if they fail, the session genuinely cannot proceed, and rejecting is right. **Applying one item** is data — it must be reported loudly, the remaining queues must still be drained, and the session must still be granted.
|
|
|
|
The general shape, worth carrying to any similar rule: when a blanket "every failure surfaces" is imposed, ask which failures are *retried by simply trying again later* and which are *permanent for the actor*. The permanent ones must never gate something the actor cannot otherwise obtain.
|