A key proof was a signature by the account's signing key, which every
signed-in device keeps in its keyring: whoever could read one, with a
code from its authenticator, set a new password without the password or
the phrase (the second reading's H1). The phrase now proves itself with
an Ed25519 key derived from its bits alone (halkyon_keys'
recoveryProofKeysOf, KDF context hlkrprf1, a sibling of the recovery
key), whose public half every setup, new phrase and account reset sends
and the register keeps (account_password.recoveryProofKey); the device
derives the secret half only while the phrase is typed. An account
without one (none exist outside development) proves no phrase until it
sets a new one; decoys check against a decoy proof key alike.
Migration (module, Learn's and Todo's servers, Learn's device schema):
ALTER TABLE "account_password" ADD COLUMN "recoveryProofKey" bytea;
nullable, so rows made before keep working and no data is touched.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
ADR 0017, step 3. `password.beginChange` and `password.change` take the
old password's SRP proof and a TOTP or recovery code, and the same keys
wrapped under the new password; a session alone changes nothing, and an
account without a confirmed authenticator is told to set one up first.
`password.setRecoveryKeys` replaces the keys' wrapping under the
recovery phrase for a TOTP code, so a device holding the keys can make
a new phrase.
A change of password ends every other session of the account in every
service of the instance; resets of the password and of the account end
them all. The register ends its own and keeps a notice per member
(`sessions_ended_notice`, a migration that only creates it), told at
once and hourly until it confirms, as deletions are told: it names a
moment, so sessions made since stay, and the session that asked for a
change. A member answering one ends its sessions made before that
moment; a member signing someone in after a reset ends its own first
and makes the new session only once its clock is past the moment.
A reset not due says when it takes effect (`takesEffectAt` on
`PasswordRefusedException`), across members too.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
Their migrations add halkyon_base's five tables for passwords,
authenticators, recovery codes, resets and sign-ins begun (nothing
else), and their pruning forgets sign-ins never finished and
authenticators never confirmed, hourly, and every GitHub ID once
GitHub sign-in has ended, daily.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
halkyon_base runs as the instance's register or as one of its members.
The register keeps the ways in, handles and profiles; members ask it
over HTTP with the instance's shared secret (instanceSecret), and open
sessions of their own for its accounts, under the same account ID.
- AccountsMode from the environment: INSTANCE_MEMBERS on the register,
ACCOUNT_REGISTER_URL on a member. Learn always holds the register
(Explore joins the profiles); Todo is a member where it is set.
- AccountRegister: LocalRegister, RemoteRegister, an in-memory one in
tests. Calls between services on the web server, refused without the
secret (constant-time compare); a server needing it doesn't start
without it.
- Deleting an account deletes it everywhere: notices per member kept in
the deletion's transaction (account_deletion_notice), told at once and
hourly until confirmed; members prune daily whom the register no longer
knows, never while it knows none of them or can't be reached.
- Deploy: INSTANCE_SECRET and INSTANCE_MEMBERS in compose and .env.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
People's public profiles are the instance's register's (ADR 0016), so
the module now holds them: the public_profile table (unchanged, so
Learn's migration records versions only; devices get the table empty,
as they hold every module's tables), the profile and profiles
endpoints under `base`, the handle rules, the link checks and the page
at /@handle, whose foot names the app as the host's configuration says
(profilePageNote). Deleting a person deletes their profile in the
module's core.
Hosts read profiles through the module (profileOf, profilesOf,
personWithHandle, fixHandle; saveProfile for their tests), behind an
AccountRegister that is this server's own database for now. Learn's
feedback, discussions and publishing ask it; publishing fixes the
handle just before it writes the release, outside its transaction.
Explore still joins the table in its search, so Learn holds the
register wherever it runs. Learn's app calls the module's endpoints;
a profile not found is ProfileNotFoundException. Todo's database gets
the table too (it was never deployed).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
Its server, client, app and curator, and the tools that write its
assets (maps, Anki fixtures, icons), as they are; paths are fixed in
the commits that follow.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-22 18:11:02 +02:00
Renamed from halkyon_learn_server/migrations/migration_registry.txt (Previous history)