Public Access
Merge branch 'claude/vault-realtime-push-d64c61'
This commit is contained in:
@@ -356,6 +356,42 @@ Both default to your personal vault and neither moves on its own, because an ite
|
||||
visible to everybody holding that vault's key. Choosing a vault in the host editor also decides which groups
|
||||
it can be filed under: a group is an item like any other and lives in exactly one vault.
|
||||
|
||||
### Changes that do not wait
|
||||
|
||||
A client holds a WebSocket open to the server — `GET /api/v1/events`, subprotocol
|
||||
`dodossh.events.v1` — and the server sends a line down it whenever something you can read has moved. The
|
||||
client's answer is the same delta pull it would have run on its timer, only now rather than in up to a
|
||||
minute. Two things you can see: an edit somebody else makes appears while you are looking at the list, and
|
||||
a vault shared with you turns up as soon as they share it.
|
||||
|
||||
**What is on that socket is a notice, not your data.** A frame says which vault changed and how far its
|
||||
change log has got, and nothing else: no item, no ciphertext, not even which item it was. That is the
|
||||
decision the rest of this rests on, and it is deliberate twice over — the server has nothing else it
|
||||
*could* send, and keeping it that way means there is still exactly one path that applies a change to your
|
||||
keychain, so the socket can be wrong or absent without anything being applied incorrectly.
|
||||
|
||||
**Polling is still there and is still what guarantees a pass.** The minute timer is unchanged. A network
|
||||
that eats WebSockets, a server with `Events:Enabled` off, an older server, a proxy that will not upgrade, a
|
||||
notice dropped because your machine was too slow to read it — every one of those leaves you with exactly
|
||||
what this product did before the socket existed. Nothing is reachable only this way, and nothing is
|
||||
supposed to become so.
|
||||
|
||||
Three limits are worth knowing rather than discovering:
|
||||
|
||||
- **One node.** Fan-out is in-process, so a deployment running more than one API replica only pushes for
|
||||
writes that its own replica handled. The rest arrive on the timer. The seam for a PostgreSQL
|
||||
`LISTEN`/`NOTIFY` backplane is in place and is not implemented, because an untested backplane would be
|
||||
worse than a documented gap.
|
||||
- **The socket does not outlive your access token.** It is closed at the token's expiry and the client
|
||||
reconnects with a fresh one, which is a gap you will not see. That, plus re-reading your vault list every
|
||||
few minutes, is what bounds how long a withdrawn grant can keep producing notices — and what it bounds is
|
||||
*metadata*, because reading a vault needs a key the server has never held.
|
||||
- **You are told about your own writes.** Your client pushed, so it has already pulled; the extra pass finds
|
||||
nothing. Notices are coalesced over a quarter of a second so that a burst is one pass rather than a dozen.
|
||||
|
||||
The reasoning, including why this is a WebSocket rather than server-sent events and where a shared terminal
|
||||
session will attach to it, is in [ADR 0012](docs/adr/0012-realtime-push.md).
|
||||
|
||||
### The Android head
|
||||
|
||||
`src/DodoSSH.Client.Android` is a phone-first head that shares every view model with the desktop one — the
|
||||
@@ -624,6 +660,20 @@ keychain plus a terminal — and the spike that gates all of it.
|
||||
directories, an interrupted **upload** starts again rather than resuming (an object cannot be written from
|
||||
the middle), and a rename is a copy and a delete rather than one atomic operation. Downloads do resume — a
|
||||
ranged GET is part of the protocol, which is the one place a bucket beats SFTP.
|
||||
|
||||
*Realtime done:* a WebSocket the client holds open, over which the server says which vault has moved so a
|
||||
pull happens now rather than within the minute. What crosses it is a notice and never an item, which is
|
||||
what keeps one code path applying changes and makes a dropped socket cost latency and nothing else — the
|
||||
timer is unchanged and is still the guarantee. Two limits are stated rather than implied: fan-out is
|
||||
in-process, so a multi-replica deployment falls back to the timer for writes another replica handled, and
|
||||
a socket is closed at its access token's expiry rather than outliving the credential that authorised it.
|
||||
See [Changes that do not wait](#changes-that-do-not-wait) and
|
||||
[ADR 0012](docs/adr/0012-realtime-push.md).
|
||||
|
||||
It is also the transport a **shared terminal session** will use — one person's shell, watched or driven by
|
||||
somebody else. Nothing of that exists yet, and ADR 0012 records the one decision made early so it need not
|
||||
be renegotiated: session data will be binary frames on this same socket, because base64 in a JSON envelope
|
||||
is the wrong shape for the one payload here that is continuous rather than occasional.
|
||||
- **M3 — shared vaults**, sharing, ACLs. *Done.* Membership with roles, a public-key directory, the
|
||||
append-only key log served for clients to verify against, shared vaults, and vault key grants
|
||||
wrapped by a client and stored opaquely by the server. `VaultAccessService` now resolves team
|
||||
@@ -647,10 +697,12 @@ keychain plus a terminal — and the spike that gates all of it.
|
||||
the rotation is re-sealed as it is pushed, so nothing reaches the server under a superseded key at all.
|
||||
See [ADR 0010](docs/adr/0010-vault-key-rotation.md).
|
||||
|
||||
**A vault shared with you arrives on the next synchronisation pass**, within the minute, with no sign-in
|
||||
and nothing to press. There is no push channel, so each pass asks the server which vaults this account can
|
||||
reach before syncing the ones it already knows — which is also how a vault that has been deleted, or one
|
||||
whose grant was withdrawn, stops being listed.
|
||||
**A vault shared with you arrives at once**, with no sign-in and nothing to press. Each pass asks the
|
||||
server which vaults this account can reach before syncing the ones it already knows — which is also how a
|
||||
vault that has been deleted, or one whose grant was withdrawn, stops being listed — and the server now
|
||||
says so the moment somebody wraps a key to you rather than leaving it for the next pass. Without a
|
||||
reachable socket that becomes "within the minute", which is what it always was; see
|
||||
[Changes that do not wait](#changes-that-do-not-wait).
|
||||
|
||||
**Ownership transfer is here, and it is one write rather than two.** The member you name becomes owner
|
||||
and you become an admin, in a single transaction — because ownership is sole, so promoting first leaves
|
||||
|
||||
Reference in New Issue
Block a user