refactor(auth)!: sign-in and the account come from halkyon_base
Sign-in (anonymous, GitHub, email codes), an account's ways in and its deletion, the pruning and the instance's basics move into the halkyon_base module (ADR 0014), which every Halkyon server includes. Learn configures it in server.dart and deletes its own data with a person through the module's hook, in the order it did. The identity tables move unchanged: the migration records versions only. The app calls the module's endpoints; instance.info() stays as it was. BREAKING CHANGE: the sign-in endpoints moved into the module, so apps up to build 20 can no longer sign in or start; minimumAppBuild is 21. Deploy this only together with the release of build 21. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
parent
f43b92e4db
commit
a463f24444
1 changed files with 28050 additions and 6268 deletions
86
docs/adr/0014-halkyon-base-module.md
Normal file
86
docs/adr/0014-halkyon-base-module.md
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
# ADR 0014: What every Halkyon server has, as a Serverpod module of its own
|
||||
|
||||
- **Status:** accepted
|
||||
- **Date:** 2026-10-19
|
||||
|
||||
## Context
|
||||
|
||||
Halkyon is to have other services beside Learn (a to-do app, a training app), each a
|
||||
Flutter app with a Serverpod server of its own, run by whoever runs an instance. Their
|
||||
look already comes from `halkyon_ui` in halkyon_shared. What each of their servers needs
|
||||
besides its own features is what Learn's has built since September: signing in
|
||||
anonymously, with GitHub and with a code by mail (ADR 0010), the account's ways in and its
|
||||
deletion in ADR 0001 rule 6's order, the pruning of what nobody can return to, and what an
|
||||
instance tells its apps before anyone signs in (its sign-in ways, its source, the oldest
|
||||
app build it works with, where it keeps people's data).
|
||||
|
||||
The app's side of signing in (its gateway, sessions, the start screen) can only be shared
|
||||
once these endpoints are shared: an app's generated client calls its own server's
|
||||
endpoints, under their names. The developer chose to share the server side first (19 Oct),
|
||||
rather than letting each app's server copy Learn's.
|
||||
|
||||
Serverpod's way of sharing server code with endpoints, models and tables is a module: a
|
||||
server package and a generated client package, which a host project names in its
|
||||
`generator.yaml` and reaches as `client.modules.<nickname>`. Learn already runs
|
||||
Serverpod's own modules (`serverpod_auth_idp`, `serverpod_auth_core`) and the sync
|
||||
engine's.
|
||||
|
||||
## Decision
|
||||
|
||||
1. **`halkyon_base`, a Serverpod module in halkyon_shared.** `halkyon_base_server` and
|
||||
`halkyon_base_client`, nickname `base`, pinned by git to a signed commit of
|
||||
halkyon_shared's main as Learn pins the sync fork. It holds the anonymous, GitHub and
|
||||
email sign-ins with their endpoints, models and tables (`email_identity`,
|
||||
`email_code_request`), the scopes and `grantIdentified`, the code mails, the account's
|
||||
endpoint (`delete`, `signInWays`), `deletePerson`, the pruning functions, and the
|
||||
instance's basics (`instance.basics()`, `InstanceBasics`).
|
||||
2. **A host configures it in one place, before its pod starts.**
|
||||
`HalkyonBase.configure(pod, HalkyonBase(...))`: the app's name, which sign-in mails
|
||||
name; where the project publishes its source, which the instance names unless its
|
||||
operator sets `SOURCE_URL`; the oldest app build the server works with; and what the
|
||||
host deletes of a person who goes. Learn's is `learnBase()`
|
||||
(`lib/src/auth/learn_base.dart`), set up in `server.dart`.
|
||||
3. **A person's own data is the host's, deleted through a hook.** `deletePerson` ends the
|
||||
person's sessions first, then runs the host's deletions inside its transaction, then
|
||||
deletes their identities and the person; what must wait until the deletion holds
|
||||
(Learn's release repositories) runs after the commit. Learn's hook,
|
||||
`deleteLearnData`, deletes their space and its files, profile, discussions, published
|
||||
studysets, blocks, what they read, assistant connections and file allowance, as
|
||||
`deletePerson` did before. The pruning goes through the same deletion. Each host
|
||||
schedules the pruning in its own future call.
|
||||
4. **The tables move as they are.** Serverpod (checked with v4.0.4, in a throwaway
|
||||
project and module, then on Learn) matches tables by name alone when it compares a
|
||||
migration's definitions, and ignores which module owns them. So the moved models keep
|
||||
their class, table, column and index names, and Learn's migration
|
||||
(`20261019121406578-halkyon-base`, made with `create-migration --empty`) holds no SQL
|
||||
but the rows recording each module's version; its definition gives the two tables to
|
||||
`halkyon_base`. The rows stay where they are, and the password keys keep their names
|
||||
(`emailSignInPepper` among them), so every address's hash stays valid. The device's
|
||||
local migration creates the two tables, empty, as it holds every module's tables
|
||||
(Serverpod's own auth tables among them); nothing on a device reads them.
|
||||
5. **Released apps are told to update, rather than served by wrappers.** A module's
|
||||
endpoints are called under its nickname, and its models go over the wire under its
|
||||
name (`halkyon_base.EmailSignInRefusedException`), so Learn's released apps (up to
|
||||
v0.17.0, build 20) call endpoints that are gone and can't read the refusals. Learn
|
||||
keeps no wrappers: `minimumAppBuild` is 21, the first build on the module, and
|
||||
Learn's own `instance.info()` stays with every field it had, as released apps read
|
||||
`minimumAppBuild` there. Its other Learn fields (the assistant connector, Jellyfin)
|
||||
stay Learn's; the fields it shares with `InstanceBasics` are filled from the module.
|
||||
6. **Licences as Learn's.** The server package is under the SSPL, as Learn's server; the
|
||||
client package under the AGPL, as Learn's client and app; each combines with its
|
||||
counterpart in every app as Learn's own packages do.
|
||||
|
||||
## Consequences
|
||||
|
||||
- **The server with the module deploys only together with the release of build 21.**
|
||||
Until people update, released apps can't sign in, switch accounts or start afresh, and
|
||||
say the server needs a newer app. Not before 22 Oct 2026 either: the hackathon's judges
|
||||
use v0.17.0 until then.
|
||||
- Another Halkyon server gets all of this by naming the module and configuring it, and its
|
||||
app can share the app side of signing in (`halkyon_app`, next) on the module's client.
|
||||
- A change to sign-in, deletion or the instance's basics is made in halkyon_shared and
|
||||
reaches Learn by moving the pin; Learn's tests of its own deletion and its
|
||||
`instance.info()` set the module up as the server does (`useLearnBase`).
|
||||
- Serverpod's version moves in the module too, together with every other package.
|
||||
- A table holding a person's data in a host gets a line in that host's deletion hook;
|
||||
the module can't know it.
|
||||
Loading…
Reference in a new issue