docs(adr): ADR 0021, how its first step is built (D31–D34)

A waiting account gets no session but a pass, the operator role is a
scope made first by SQL, the matching code is four BIP-39 words the
operator reads, and the operator's card is in Settings.

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-25 14:28:47 +01:00
commit 90ba4f0b94
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64

View file

@ -4,7 +4,8 @@
developer: D1–D6 on 23 Oct; on 25 Oct what an assistant may see and do (D7–D12), what it
keeps and does, where it lives, the proposal's points 4–8 and 10, how often it warns and
when it is built (D13–D18), who signs up and who operates (D19–D22), waiting accounts
(D23–D26), and what people are told and what never goes (D27–D30).
(D23–D26), what people are told and what never goes (D27–D30), and how the first step
is built (D31–D34).
- **Builds on:** ADR 0016 (the register), ADR 0019 (private studysets), ADR 0020
(SimpleLogin), ADR 0022 (the hand-off and the connector gone).
- **Changes:** the rule of 10 Oct 2026 that Halkyon calls no language model with an API
@ -175,6 +176,50 @@ including/excluding private studysets?)"):
the provider can act against one person's abuse without blocking the operator's key,
and can't tell who it is.
On 25 Oct, how the first step is built (sign-ups and operators):
- **D31 A waiting account has no session** (recommended, over a session with a `waiting`
scope that every endpoint, the sync and the uploads would have to refuse): every
session on every service is made in one place (`issueSession`), which refuses while
the account waits and hands the device a waiting pass instead (a random secret, kept as
its hash) with the matching code. The pass opens only the wait screen's calls: whether
the account still waits (and, once approved, its session, taking the pass), deleting
the account, and forgetting the pass. A member asks the register before it makes a
session, as it asks everything else about accounts (ADR 0016).
- **D32 The operator role is a scope**, `operator`, on the account and its sessions, as
`identified` is (recommended, over a table of operators): every operator's call requires
it (`requiredScopes`). The first operator, once signed up, is made by two statements on
the register's database, run as `deploy` in the deploy's folder: the scope onto the
account, and onto its sessions, as scopes are copied onto them (handle in lower case):
```sql
BEGIN;
UPDATE "serverpod_auth_core_user"
SET "scopeNames" = ("scopeNames"::jsonb || '["operator"]')::json
WHERE "id" = (SELECT "authUserId" FROM "public_profile" WHERE "handleKey" = 'their_handle')
AND NOT ("scopeNames"::jsonb ? 'operator');
UPDATE "serverpod_auth_core_session"
SET "scopeNames" = ("scopeNames"::jsonb || '["operator"]')::json
WHERE "authUserId" = (SELECT "authUserId" FROM "public_profile" WHERE "handleKey" = 'their_handle')
AND NOT ("scopeNames"::jsonb ? 'operator');
COMMIT;
```
and an operator never waits: the wait screen's next look approves them and gives them
their session.
- **D33 The matching code is four words of BIP-39's English list, read by the operator**
(the developer's "Four words" over six digits, which were recommended; then the
operator reading and BIP-39's list, both recommended, over the person reading theirs
out and over EFF's short list): the operator reads the words beside the handle and asks
whether the person sees them, who only says yes or no. The wait screen says that
Halkyon never asks anyone to read words out, as a recovery phrase is words too, of the
same list (already in the repository under the MIT License, unique by their first four
letters; four words hold 44 bits).
- **D34 The operator's section is a card in Settings**, "This instance", below the
account's (recommended, over the Profile tab): halkyon_app's, shown to operators alone,
opening the waiting accounts, the accounts and the operators; a badge on the Settings
cog while someone waits, as the sync trouble dot is.
## Proposal
1. **Who signs up, per instance:** `SIGN_UP`: `open` (as today), `approval`, or