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>
ADR 0044, step 2 (D1, D2, D11, D13, and D15 to D17, decided today).
halkyon_base keeps the spaces several people share, for every app:
shared_space, its members with their roles (Owner, Editor, Viewer), and
invitations by handle waiting for someone with no account on the
service yet. The sharedSpace endpoint creates a space, lists its
members, invites by handle, changes roles, removes members, hands the
space on, leaves and deletes it.
Who may do what:
- Only people with an identity reach the endpoint.
- A space the caller isn't in can't be told from one that doesn't
exist.
- Members are named by their membership, never by their account's ID.
- Only the Owner manages the members, unless the space lets members
invite.
- The Owner's role passes only by handing the space on, and the Owner
leaves only naming who takes it on.
- An invitation counts as an answer about a handle, as it tells whether
one exists.
Every rule has a test asserting the refusal.
The module stays free of the sync engine: a host hands it a SpaceGrants,
and every call runs in the module's transaction, so the engine's grants
follow the roles or nothing changes. The Owner and an Editor write; a
Viewer reads. Learn hands over SyncSpaceGrants on OfflineSyncSpaces. Todo
hands over none yet, so its server refuses until its projects are
shared.
Deleting an account hands each space the person owns to the Editor who
joined first, else the Viewer who joined first. A space with no other
member goes. This runs before the host's own deletions. A person's first
sign-in on a service makes their waiting invitations memberships, in the
same transaction that makes them a person there.
Migrations: the module's and every host's (Learn, Todo, Train, Meditate)
create the three tables, their indexes and the two cascading foreign
keys to shared_space, and nothing else. The old server keeps working.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
ADR 0044 records the answers to its open questions: a shared studyset
passes on when its owner leaves or deletes their account (D11); an
agent's token lapses after 90 days unused (D12); an invitation by handle
waits for someone who never used the app (D13); and sharing copies the
studyset under new IDs into the shared space (D14), since the sync
engine's delete is soft and a row's ID can't be inserted into another
space. Its Steps section orders the work, starting with the community's
confirmation (D8, D9).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
The developer's decisions of 10 Nov 2026 on their evening's requests:
- ADR 0042: every app holds its sync back on a metered connection, as the
OS says, and asks in a quiet banner (Sync now, Without files, Not now);
an answer holds until the connection changes; Settings › Sync chooses
Ask, Without files or Everything.
- ADR 0043: Learn's file store moves into halkyon_base, room counted per
service; Todo gets sealed attachments in descriptions and comments, any
file up to 100 MB, fetched when shown.
- ADR 0044: shared spaces in halkyon_base for every app, Learn's studysets
first: Owner, Editor and Viewer, keys wrapped to members' key pairs,
invites by handle or email, links off at first, the material to publish
chosen item by item, and agents as accounts of their own whose changes
are suggestions a person accepts. Its open questions come before
building, first how rows move between spaces.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>