feat(base,app,keys): keys of shared spaces, sealed to each member

Step 3 of ADR 0044 (D3, D18, D19). A shared space's data keys are
sealed on a device to each member's public keys; the instance keeps
what devices sealed and never opens it.

- halkyon_keys: the safety code of two accounts' public keys
  (`safetyCode`), 60 digits, the same on both sides.
- halkyon_base: the register hands out accounts' public keys
  (`public-keys-of`, local and remote); the table `shared_space_key`
  and the endpoint `sharedSpaceKeys` (memberKeys, keysNeeded, seal,
  mine). Only the Owner and Editors seal, the first key sealed to a
  member stays, and a member's keys go with their membership. Every
  host's migration creates the table.
- halkyon_app: `ContentCipher` seals a key to another account and
  opens one sealed by another; `SharedKeys` shares a space's key with
  its members, seals keys to newcomers and opens the person's own,
  holding each member to the public keys `KeyPins` pinned on first
  sight (`key_pins.json` in the person's folder), a change refused
  until trusted anew.

Learn takes them into its `Privacy` in step 4.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
Tizian 「ティツィアーン」 2026-11-11 11:28:13 +01:00
commit b72cea1fe0
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64

View file

@ -95,6 +95,16 @@ member reads everything in the space.
2026, the agent's recommendation, reading D13): someone without an `AuthUser` row on
the service gets a waiting invitation, made a membership the first time they sign in
there; on the register, where everyone has one, the invited are members at once.
- **D18.** **A member's public keys are pinned, a change refused, a safety code
compared** (decided 11 Nov 2026, the agent's recommendation): only the instance hands
a device a member's keys, and one that swapped in its own would read every studyset
shared with it. So each device keeps the keys it first saw for each person (as Signal
and SSH do), seals nothing to keys that changed and says so, and shows a safety code
made of both people's keys, which two members compare in person or by call; a swap
after the first sight is caught, one before it by comparing.
- **D19.** **Step 3 builds the module's wrapped keys and halkyon_app's device side**
(decided 11 Nov 2026, the agent's recommendation), each tested; Learn takes them into
its `Privacy` with the copy in step 4.
## Steps
@ -105,7 +115,12 @@ member reads everything in the space.
the engine's grants kept in step, the hand-on as an account is deleted. Built 11 Nov
2026: the module's `sharedSpace` endpoint, Learn's `SyncSpaceGrants`; Todo hands over
no engine yet, so its server refuses (`unavailable`) until its projects are shared.
3. **Keys** (D3): a private studyset's key wrapped to each member's key pair.
3. **Keys** (D3, D18, D19): a private studyset's key wrapped to each member's key pair.
Built 11 Nov 2026: the register hands out members' public keys (`public-keys-of`),
the module keeps the keys devices sealed (`shared_space_key`, its `sharedSpaceKeys`
endpoint, a Viewer sealing none, a member's keys going with their membership);
halkyon_app seals and opens them (`SharedKeys`), pinning each member's public keys
on first sight (`KeyPins`), with the safety code of halkyon_keys (`safetyCode`).
4. **Learn shares a studyset** (D14): the copy, *Manage collaborators* (invite by
@handle, the members, their roles, leaving), each member's own reviews.
5. **Invitations by email and by link** (D4, D5), the Inbox.