The root AGENTS.md holds what applies to every app and package (how work is done, Serverpod, checks with Melos and the tools, CI); Learn's own is apps/learn/AGENTS.md, Todo's a note that it is still the scaffold. The root README introduces the project, Learn's moves to its folder; LICENSE.md says which licence covers which directory, Todo's packages unlicensed until decided; one CONTRIBUTING.md. ADR 0015 records the decision; ADR 0014 and 0003 note what moved. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
95 lines
6.4 KiB
Markdown
95 lines
6.4 KiB
Markdown
# ADR 0014: What every Halkyon server has, as a Serverpod module of its own
|
|
|
|
- **Status:** accepted
|
|
- **Date:** 2026-10-19
|
|
- **Amended by:** [ADR 0015](0015-one-repository.md) (2026-10-19): halkyon_shared's packages
|
|
now live in this repository's `packages/`, which the apps take by path. Where this record
|
|
says the module, `halkyon_ui` and `halkyon_app` are pinned by git to signed commits of
|
|
halkyon_shared's main, read: taken by path, a change to the module and its hosts one
|
|
commit.
|
|
|
|
## 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 it serves (`app`, which its
|
|
instances name in their basics); 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 shares the app side, `halkyon_app` (19 Oct 2026: the instances, signing in, the
|
|
gates and the sync plumbing, with texts of their own), which sits on the module's client.
|
|
An instance's basics name the app it serves (`app`, set by the host's configuration:
|
|
Learn's is `learn`), and an app accepts only an instance of its own, so another Halkyon
|
|
app's address is refused before the app moves there.
|
|
- 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.
|