docs(adr): one account across an instance's services (ADR 0016)

A register in one service's server, the others its members; one account ID
everywhere; each app signs in itself; handles and profiles in
halkyon_base; deleting the account deletes it everywhere. The way to
Todo as the agenda of the others (ADR 0018), after the password, TOTP
and keys (ADR 0017).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
Tizian 「ティツィアーン」 2026-10-19 21:56:56 +02:00
commit 2033e5606f
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64

View file

@ -0,0 +1,66 @@
# ADR 0016: One account across an instance's services
- **Status:** decided 19 Oct 2026, by the developer.
- **Builds on:** ADR 0010 (email sign-in), ADR 0011 (profile pages and the account domain),
ADR 0014 (halkyon_base), ADR 0015 (one repository).
- **Followed by:** ADR 0017 (password, TOTP and the keys), ADR 0018 (the agenda).
## Context
An operator may run several Halkyon services side by side (learn.example.com,
todo.example.com, later train. and meditate.). Each has its own server, database and
sessions, and until now its own accounts: the same address signed in to Learn and to
Todo reached two people, with two handles and two deletions.
The reason to join them is Todo as the agenda of all the others, both ways: Learn's cards
due today, Train's planned exercise and Meditate's session appear in Todo, and moving one
there moves it in the app it belongs to (ADR 0018). That needs one person behind them all.
Handles on the account domain (ADR 0011) need it too: two services handing out handles on
one domain would hand the same one to two people.
## Decision
1. **Within one operator's instance only.** Across operators would need an OpenID Connect
provider with web sign-in pages, browser sessions and consent; not now.
2. **A register in one service's server.** halkyon_base runs in one of two modes. The
service the operator names holds the register (on learn.halkyon.eu, Learn's server: it
holds the identities and handles already, so nobody's account moves); the others are its
members and ask it. With a single service nothing changes: it is its own register.
3. **One account ID everywhere.** A member's own person (its `AuthUser`) takes the
register's account ID, so a deletion, a handle or an agenda item names the same person
in every service.
4. **Each app signs in itself.** Apps on one device can't read each other's sessions, so
Todo signs in on its own (today with an email code or GitHub, after ADR 0017 with the
password and TOTP) and reaches the same account: the member's server runs the sign-in by
asking the register (it mails the codes and resolves addresses and GitHub IDs), then
opens a session of its own for that account. As Proton's apps do.
5. **Trust between servers:** a secret the instance's services share (`instanceSecret`;
`INSTANCE_SECRET` in `deploy/.env`), sent with every call a member makes; members reach
the register at an address the operator gives (its address on the instance's own network
when on one host). Nothing a person's device sends can call the register as a member.
6. **Anonymous accounts stay per service.** Becoming identified (an address or GitHub
added) makes the anonymous person an account of the register under the same ID, or
switches to the account the identity belongs to, as linking and switching do today.
7. **Handles and profiles move into halkyon_base**, served by the register (the tables
unchanged, as the identities moved in ADR 0014): one handle per account on the account
domain, shown alike by every service. What fixes a handle (Learn: the first publish) is
the host's, told to the module.
8. **Deleting the account deletes it everywhere.** From any app: a member passes it to the
register, which deletes the account and its ways in, then tells every member to delete
that person's data; a member it can't reach is told again until it answers, and each
member also deletes, as it prunes, people the register no longer knows.
9. **Released apps:** the profile endpoints move, so the change rides on build 21's raise of
`minimumAppBuild` if it is through its checks by 22 Oct, else on a later release with a
raise of its own.
## Consequences
- The register's server learns when someone signs in to which service of the instance (the
privacy page says so); the services already share one operator.
- An operator running one service configures nothing new. One running several names the
register, gives every service the shared secret, and the register its members.
- If the register's server is down, members can't sign anyone in or create handles;
sessions already open keep working, and deletions wait.
- Learn's sign-in, profile and deletion code paths go through the module's register mode on
learn.halkyon.eu; Todo's through its member mode.
- Passwords, TOTP and end-to-end keys (ADR 0017) and the agenda (ADR 0018) build on this.