Merge branch 'claude/team-key-share-rotate-4b2619'
ci / build and test (push) Successful in 1m33s
ci / android head (push) Failing after 5s
ci / api image (push) Successful in 21s

Two conflicts, and the second is worth recording. main's M4 bullet gained the
Android signing decision while this branch rewrote the M5 line either side of it;
both are kept.

The other is an ADR number collision: two sessions each took 0010, one for vault
key rotation and one for Android distribution, and both are now on main. ADR
numbers are identifiers — "see ADR 0010" appears in code comments as well as in
prose — so leaving two would make every such reference ambiguous. The rotation
ADR landed first and is referenced from crypto.md, the gaps document, ADR 0009
and the sync code; the Android one is referenced from README and android-port.md.
So the later and cheaper one moves: 0010-android-distribution.md is now ADR 0011,
with its title and both references updated. Nothing about either decision changes.
This commit is contained in:
2026-08-04 10:23:46 +02:00
14 changed files with 928 additions and 72 deletions
+15 -12
View File
@@ -308,11 +308,13 @@ Four limits, stated rather than discovered:
can open, and hands each new key to the members who are left — so nothing written from that point on is
readable to them. Everything they already pulled is still on their machine. Rotate the SSH credentials
that matter — that is the actual remediation, and it is why there is no button labelled anything stronger.
- **A rotation re-keys the vault, not what is already in it.** Existing items stay sealed under the
generation they were written with, and everybody still in the team keeps those keys as well as the new
one — which is what stops a rotation making the vault's own history unreadable. It also means somebody
who left with a copy of the old key could still open old ciphertext they later got hold of. Re-sealing
stored items under the new key is the remaining half; see [ADR 0010](docs/adr/0010-vault-key-rotation.md).
- **A rotation moves the vault's contents too, and says so when it could not.** The key changes first, in
one server transaction; then every item already stored is re-sealed under it, so the key somebody left
with opens nothing that is still here. Existing items keep working throughout — everybody still in the
team holds the older keys as well as the new one, which is what stops a half-finished rotation making a
vault unreadable, and what makes the pass safe to interrupt and run again. An item somebody else was
editing at that moment is left for the next pass, and the message tells you which of the two you got.
See [ADR 0010](docs/adr/0010-vault-key-rotation.md).
- **Adding a member shares the vaults you can open, including their history.** Membership is still one act
and a key is still another — nothing changed about that — but the client now performs the second one for
you, wrapping every generation it holds so the new member can read the vault back to its first item. A
@@ -617,25 +619,26 @@ keychain plus a terminal — and the spike that gates all of it.
adding machine can open to them — every generation of each, so they can read the vault's history and not
only what happens next. Removing somebody revokes their grants, advances each vault it can open to a
fresh key generation in one server transaction, and wraps that key to the members who remain. What a
rotation buys is exact: everything written from then on is unreadable to the person who left. Items
already stored keep the generation they were sealed under and are not re-encrypted — that half is still
outstanding, and it is safe to add later precisely because a vault at mixed generations stays readable.
rotation buys is exact: everything written from then on is unreadable to the person who left. The items
already stored are then re-sealed under the new key as well, item by item and resumably — which is safe
to do incrementally precisely because a vault at mixed generations stays readable. A change queued before
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).
**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
the team owned twice and demoting first leaves it owned by nobody, and there is nobody left with the
authority to finish a transfer that stopped in the middle. Nothing else is touched: you keep your vault
key grants, because removing the outgoing owner would revoke them and flag every team vault for rekey,
which is a much larger act than the one being asked for.
key grants, because removing the outgoing owner would revoke them and rotate every team vault, which is a
much larger act than the one being asked for.
- **M4 — hardening and ops**, packaging, self-hosting guide. *Decided ahead of the work, because the first
release takes it irreversibly:* who signs the client and where it comes from.
[ADR 0010](docs/adr/0010-android-distribution.md) puts the release key with the project rather than with
[ADR 0011](docs/adr/0011-android-distribution.md) puts the release key with the project rather than with
a store, and rules out the arrangement a self-hosted product reaches for by default — the deployment
serving the client binary, which hands it to the one party the whole trust model is about. An installed
Android app can only ever be updated by a package signed with the same key, so this is the first
release's decision to make and nobody else's afterwards.
- **M5 — multi-provider OIDC**, key rotation, per-item content keys.
- **M5 — multi-provider OIDC**, identity key rotation, per-item content keys.
## Licence