Merge branch 'main' into claude/m3-implementation-57f9d7
ci / build and test (push) Failing after 2s

Three files conflicted, and two of the resolutions are more than a choice of
side.

QuickConnectTests had both branches fixing the same build break — main's M2
merge left the shell's constructor with an ISftpSessionFactory nobody passed.
Main's version wins because it carries a comment saying why the palette never
needs a session.

VaultSession's conflict is adjacent edits: main added the remembered sign-in
members and this branch changed SyncAsync's summary from "the active vault" to
"one vault". Both kept.

VaultViewModel is the one that matters. Main taught the background pass to
report a sync that had to start over, on the grounds that a machine which
silently re-read a whole vault has had something happen to it; this branch
turned a pass into one report per readable vault. Taking either side alone
would have lost the other, so ResyncedFromStart is now one of the conditions
IsWorthReporting checks, per vault.

Merging also broke something neither branch could have caught alone, and the
build would not have said a word. SyncOnceAsync cleared LastSyncFailed
unconditionally, which was right while a pass was one vault and a failure was
an exception that never reached that line. A failure is now a report — one
unreachable team vault must not stop the others syncing — so the flag was being
cleared over a vault that had just failed, lighting the titlebar SYNCED. It is
computed from the report instead, in the one place both callers go through, so
the manual command gets it as well as the loop. The background pass still
swallows the message and keeps the fact, which is what
AnAutomaticPassThatFails_LeavesTheStatusAlone is there to hold it to.

Two comments the auto-merge left describing a world with one vault in it: the
SCOPES rail's, which said team vaults are refused by the access service, and
the host sidebar's "One heading, for one vault".
This commit is contained in:
2026-07-31 12:26:59 +02:00
42 changed files with 3708 additions and 155 deletions
+10
View File
@@ -193,6 +193,16 @@ moment to discard it.
The label is versioned, so a client holding a v1 cache fails to open it and re-pulls rather than
decrypting to nonsense. That is the whole reason for bumping rather than reusing the label.
**What it seals, and the one entry that is not vault content.** Three kinds of record: the plaintext
columns the server needs, the values a merge overrode, and — added 2026-07-31 — the OIDC **refresh
token** this machine may resume its sign-in with, bound as `LocalCache(User, userId)`. The third is
different in kind from the other two: it is a credential for the *account*, not for the vault, and
sealing it here is a deliberate choice about what a stolen cache file is worth. A refresh token kept in
the clear beside the ciphertext would let a copied profile reach the server as its owner without the
passphrase ever being guessed; under this key it can only be read by a process that has already opened
the vault. The cost is stated rather than worked around: **a locked client cannot reach the server at
all**, because the token it would present is behind the same lock as everything else.
### Why the bundle is wrapped many ways
This is the load-bearing structural choice. Because every wrap protects the *same* bundle:
+8 -2
View File
@@ -193,8 +193,14 @@ What the row did not anticipate is that the interesting half is not the endpoint
## Preferences
The screen ships with what is real — this machine's device key, locking, and syncing and lists the rest
as absent rather than omitting it silently.
The screen ships with what is real — this machine's device key, locking, syncing, and signing out — and
lists the rest as absent rather than omitting it silently.
> **Signing out is not on the design and is here anyway.** The design has no way to leave a machine, and
> without one there is no way to hand a laptop on, to enrol a second account, or to get past a forgotten
> passphrase — which is unrecoverable by construction, so the unlock screen would otherwise be a dead end.
> It empties the local cache and withdraws the device key from the account; it cannot end the session at the
> identity provider, and says so.
| Design element | Layer | What it would take |
| --- | --- | --- |