ADR 0044's D49 (amended in building it): what reads and seals Learn's
content moves out of the Flutter packages, so Learn's agent, which runs
without Flutter, shares it rather than repeating the privacy rules.
packages/halkyon_content (new, pure Dart) takes from halkyon_app the
envelope and its cipher, SealedFile, UnlockedAccountKeys, shared
spaces' gateway with SharedKeys and KeyPins, FileCache and
FileServerGateway, with their fakes and tests; halkyon_app exports it,
so no app changes. Its rich_text.dart takes RichContent from
halkyon_editor, which exports it. newAccountKeys makes an agent's key
pair. halkyon_core takes DelayedDeletions and gains SyncRequests, what a
synced gateway asks of the sync, which SyncService implements.
apps/learn/halkyon_learn_core (new, pure Dart) takes Learn's privacy,
sync, payloads and the models, codecs, gateways and repositories of
studysets, cards, notes, tags and feedback, with what they reach of
files, learning paths, citations and study options; the app imports them
from there. A subject's icon, colour and name stay in the app
(SubjectLook); what only the app shows (progress, exam coverage, the
card browser, deletions' waits) stays too. Nothing changes for people
using the apps.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
What is written in a shared space after a member went takes a key they
never get (Halkyon's ADR 0044, D22). The instance marks the space as one
of its members goes, removed, leaving or deleted (`keyRollDue` on
`shared_space`, set in `removeMember`, through which each of them
passes); an invitation taken back never had a key and marks nothing, nor
does a new role. The Owner's or an Editor's device that next keeps its
keys up after a sync is told so (`sharedSpaceKeys.rollDue`, never true
for a Viewer, who seals no key) and makes a new key, sealed to those who
stay (D33). The first seal bringing a key ID the space never had clears
the mark in the same transaction (D34); members the rolling device
missed get it later as newcomers do. Earlier rows keep the key they had,
which whoever went had read already.
The migration, the same in the module and in each server including it
(Learn, Meditate, Todo, Train):
ALTER TABLE "shared_space"
ADD COLUMN "keyRollDue" boolean NOT NULL DEFAULT false;
It adds the column with false for every space there is, so no space is
due a roll from it; PostgreSQL adds a column with a constant default
without rewriting the table. The server before the switch reads and
writes the table naming its own columns, so it keeps working.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
Step 4 (e) of ADR 0044, its first part, for every app's screens.
- halkyon_app: `SharedSpaceGateway` on the module's `sharedSpace`
endpoint (the spaces, their members and roles, inviting, handing on,
leaving), refusals as `SpaceRefused`; `SharedKeys.trustIn` gives
each member's safety code and whether it was compared or their keys
changed, `markCompared`, and `trustNewKeys`, which pins the keys
whose code was compared and never those the instance hands next.
`FakeSharedSpaceServer` keeps the module's rules for tests, in step
with `FakeSharedKeysServer`, which now knows members' handles.
- Learn's sharing makes its space through it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
ADR 0044, D20: a row in a shared space counts as genuine when any
member this device pinned signed it, current or former, never by the
author column the row names.
- `ContentCipher.trustSigners`: content sealed with a key is checked
against the signing keys given for it, in place of the account's
alone; `FakeContentCipher` tells authors apart likewise.
- `KeyPins` keeps who it saw in each space (`sawIn`) and the signing
keys pinned for someone before a reset (`earlierSigning`), so what
they wrote stays readable; `signersIn` gives a space's signers.
`key_pins.json` holds both (`pins`, `spaces`); a file written by
step 3, on no released app, counts as none.
- `SharedKeys` pins the person's own keys too, records the members it
checks, and trusts the space's signers for each key it shares or
opens.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
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>