--- 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.