# CL · Clavis, accounts (build plan step 2)

**Status:** CL-1 to CL-18 decided by the Architect, 7 November 2026: as recommended, except CL-6, CL-10, CL-11 and CL-13, which take the alternative. The throwaway OIDC proof ran the same day and says go, with Passport and a thin layer of Atrium's own (`2026-11-07-CL-P-oidc-proof.md`). The mockups (`reji/ui/html/cl/`) were approved by the Architect the same day, which opens the build (§6). What the apps do was read from their code, and nothing was run. Nobody has looked at production data.

---

## 0 · What Clavis is, what it covers, and if Atrium fails

### What it is, in simple terms

Clavis is **one key for every Tutelora product**. A person has one account, for life, and signs in once at `id.tutelora.com`. Each product then trusts Atrium's answer to one question: *who is this?*

Clavis answers only that. It doesn't say what they may do. A teacher's key opens Campus's door, and Campus decides that she teaches JHS 2. It's like signing in with a Google account: Google proves who you are, and the app decides the rest. Album, built with it, keeps the list of who belongs to which organisation.

A concrete case: Kofi teaches at two schools and pays his dues in Loop. Today that's three logins. With Clavis it's one account, two memberships and one more product, and if a school removes him he loses that school and keeps his account.

### What Atrium handles, and what stays in the product

| | Atrium | The product |
|---|---|---|
| **Who you are** | The account: its permanent ID, name, and the person's own proven phones and e-mails. They change them once, and each product hears (CL-17) | A local `user` row keyed to the account's ID, with a copy of those details, so all its own records keep working |
| **Proving it** | Sign-in and sign-up pages, codes, passwords, authenticator, recovery, bot and cost limits, new-device notices. It proves as strongly as a product asks | How strong is enough, from its own roles (CL-5). A product's page never takes a credential: it sends people to `id` and receives them back |
| **Roles and permissions** | None for any product, and it never knows a product's roles. Only who may run the organisation itself at `id`: owner, admin, billing (CL-16) | **All of them**: class teacher of JHS 2, bursar, parent of Ama, and who may add people (CL-9) |
| **Belonging** | Only that a person is in an organisation, yes or no, which is what lets a school remove someone once. It grants nothing in any product | Its own screen for adding someone, which calls Atrium |
| **What the school knows** | Nothing | Pupil records, guardian links, and numbers nobody has proven, such as an emergency contact (CL-17) |
| **Sessions** | The session at `id`, and "sign out everywhere" | Its own session after sign-in, its own API tokens, its idle timeout |
| **Which school** | Doesn't decide. The token names no school | Picks the school from its address, as today, and shows its own switcher |
| **Web addresses** | The one list: which organisation owns each custom domain, which product it points to, and proving it. One address, one product (CL-18) | Serving it: the certificate and the traffic, from its own copy of the list |
| **Duplicates** | Merges and splits accounts, and tells products | Moves its own records when told |
| **Staff** | The console and its Service | Its own Service sign-in (CL-14) |

Campus keeps its own sign-in until Clavis has proved itself, one school at a time (P4).

### If Atrium fails for a short period

**The aim: people already working don't notice.** A product asks Atrium nothing on an ordinary page. It holds its own session, its own `user` row, its own roles and its own copy of the school's name, logo and domains, so Atrium going down doesn't touch those.

| What happens during the outage | What the person sees | How |
|---|---|---|
| Someone is signed in and working | Nothing | The product's own session carries on. The 15-minute recheck (CL-8) keeps the session if it can't reach `id` |
| Their session ended, but the browser is remembered | Nothing, or a brief pause | The product's own "remember me" lets them back in while `id` can't be reached (CL-15) |
| A staff member signs in on a new device | The product's page: "Sign-in is briefly unavailable. Please try again in a few minutes" | No fallback. A second way in during an outage would be a second lock to attack |
| A parent asks for a code | The same page | Codes don't queue: they expire in 10 minutes (NS-8) |
| Someone new signs up | The same page | Sign-up isn't urgent, and nothing is lost |
| An admin adds a teacher in the product | "Atrium can't be reached. Try again shortly" | A retry can't add them twice (IA §7) |
| A school removes someone at `id` | Can't be done until `id` is back | Nothing to catch up on |
| Someone was removed just before the outage | They may keep working until it's back | **The accepted cost.** The webhook retries, and the recheck catches it on return |
| The product's own staff must get in | The product's Service sign-in works | It stays local (CL-14) |
| Someone opens a school's own domain | Nothing | The product serves it from its own copy of the list (CL-18). Traffic never passes through Atrium |
| A school adds a new custom domain | "Try again shortly" | Only adding and proving waits |

**When it's back:** webhooks that waited are delivered, and rechecks resume, so anyone removed is put out within 15 minutes.

**Not covered, and worth saying:** this is for a short outage, minutes to hours. New sign-ins stop for its whole length, and a long one needs Atrium's own recovery plan, which is backups and a restore (`reji/docs/BACKUP.md`). If Atrium is up and the SMS provider is the one that's down, staff switch to the other by hand (NS-3), and e-mail and passwords still work.

---

## 1 · Settled already, so not reopened

| Decision | Where |
|---|---|
| Everything a person does is at `id`; products keep their own domains; staff stay at `api` | IA-1, IA-2 |
| An account needs no organisation; both ways in, alone at `id` and from a product's "Start free" | IA-3, IA-4 |
| One account type. Parents and pupils arrive by invitation or the organisation's web address, and reach `id` quietly | IA-5 |
| A school's page on `id` wears its name and logo, and a product's page never takes a credential | IA §7 |
| A school-made account reaches only that school until its holder proves their phone or e-mail | IA §7 |
| Phone sign-in lives in Atrium only; products reach it through the client library | IA-7 |
| Merge and split from the first day; adding people has two doors, one action | IA §6, §7 |
| No third-party scripts on `id`, uploads served elsewhere, a host-only `__Host-` cookie, step-up for what matters | IA §5, §6 |
| The company pays for sign-in codes; codes skip client limits; the burst brake stays in Clavis | NS-2, NS-7 |
| Campus's codes move last, with Clavis, school by school, reversibly | NS-9, IA §9 |

---

## 2 · What the apps already do

| App | What's there | Lift | Leave |
|---|---|---|---|
| **Campus** (live) | Global `users`, phone-only accounts, one number one account, `identity` (an ID with no personal data, grants no access), per-school `people`, roles per school with no membership table, verified custom domains, one-use hand-off between hosts | `SignInCodes` (six digits, HMAC, 10 minutes, five tries, limits per number, address and range; an unknown number gets a row and no text, so asking reveals nothing), `SignInWatch`, `SignInFactors`, `ReleaseNumber`, `JoinRules`, `MergeAccounts`' ledger and undo, Turnstile with proof-of-work behind it (CL-6), trusted browsers, the Google and Microsoft rule (matches a verified e-mail and never creates) | Fake `@learner.invalid` addresses for pupils, the faults in §3, SIM-swap checks with no provider behind them |
| **Atrium** | `person`, `user`, `membership` (Loop's model, handbook 2.3); two-factor, lock, step-up, idle timeout; `PersonMatcher`, merge and split; a typed `public_id`; the `id` host; Nuntius's send | All of it | `user.email` and `password` being required, `Membership::lets()` matching on e-mail, invitations and sign-up by e-mail only, sign-up requiring a workspace, and two-factor asking memberships, so an account with none can't be required to have it |
| **Loop** | Person, Member, User; phone and password; one identifier field that picks the column by `@`; an OTP only at onboarding; members with no login | The one identifier field | Its removal, which only archives the member and revokes nothing that was found |
| **Desk** | One `users` table with customers as a role; a one-time e-mail link for customers | The e-mail link as a sign-in way | · |

**The handbook doesn't cover this.** It assumes each app is one monolith with its own database (0.1), so sign-in across apps is a recorded exception in `PROJECT.md`, with the handbook left as it is (AF-1). So are 2.3 when Campus moves, and 6.4 (CL-8).

---

## 3 · Faults found in Campus's live code

Campus's to fix, in Campus. Neither of the first two is lifted. Both were read in the code, and neither was run.

- **The pause after 10 wrong codes probably never starts on the database cache.** `Cache::increment` returns false for a missing key on that store, so `SignInCodes::countFailure` never creates the counter (`SignInCodes.php:253`, `DatabaseStore.php:285`). The tests use the array store, so they pass. `database` is the default, and which store production uses wasn't checked. The five tries per code and the per-number limits still hold.
- **A code sign-in skips the authenticator app.** `PhoneSignInController.php:342` leaves out the two-factor step when the way in was a phone, though someone who enrolled in an authenticator expects it. Google and Microsoft sign-in do ask.
- **Less certain, reported by a reader and not checked:** an account made before its number is verified can be left with no number; a merge into a phone-only account can silence its mail; a person who only uses codes may not be able to reach step-up pages, which ask for a password.

---

## 4 · Decisions

**Decided by the Architect, 7 November 2026.** The recommendation stands except where a row says the alternative was chosen. CL-14 and CL-15 came out of working through the failsafe in §0. CL-16 to CL-18, and the changes to CL-5, CL-7 and CL-9, came from the Architect's questions on 7 November 2026 about roles, contact numbers and domains.

| # | Decision | Recommended | Alternative |
|---|---|---|---|
| **CL-1** | Whose model | Atrium's `person`, `user`, `membership`. The `user` is the account, with an `acc_…` ID that every product stores and that never changes. Sign-in identifiers become child rows, and `lets()` goes. Campus's `identity` maps one to one when it moves, and its per-school `people` stay | Lift Campus's `identity` into Atrium: a second idea of a person beside `person` |
| **CL-2** | A family on one phone | Keep one number, one account. Someone with no phone of their own signs in with a username (CL-3), and a parent sees their children through the product. A shared number stays a contact number on the school's record | One number holds several accounts, with a "who's signing in?" after the code. Easier for families, but anyone holding the phone becomes anyone on it, teenagers' records included |
| **CL-3** | Usernames | Optional. Issued by a school for someone with no phone or e-mail, or chosen by the holder. Unique across Atrium, any case, never reused. Password only, no code. One sign-in field that tells a phone, an e-mail and a username apart by their shape | None, and pupils keep fake e-mail addresses. The spec asks for usernames |
| **CL-4** | Claiming a school-made account | The school may reset it until it's claimed. The holder claims it by proving a phone or e-mail with a code. If that number already has an account, the membership moves there and the school-made one retires, through merge's ledger. After a claim the school has no hold over it | No school-made accounts, only invitations, which fails pupils with no phone |
| **CL-5** | How strong a sign-in must be | **The product decides, from its own roles, and Atrium never learns why.** A code alone gets someone to `id`. If Campus sees a staff member arrive on a code, it sends them back asking for a password (OIDC's `acr_values`), `id` asks for that alone, and the token says what was proved. So a stolen SIM opens a parent's view and not a bursar's. A product can ask again at a sensitive moment. `id` keeps two rules of its own: an enrolled authenticator is always asked, and a number quiet for months needs a password. Its own pages for owners, admins and billing also ask for more | The strictest role across memberships, which was my first recommendation. It needs Atrium to know which product roles are strong, which is what CL-16 rules out |
| **CL-6** | Stopping bots | ~~Proof-of-work only, plus Campus's limits and brake. Turnstile is a third-party script, which IA's rule forbids on `id`~~ | **Chosen by the Architect.** Allow Turnstile as a named exception. Stronger against bots, but it runs someone else's code beside every session Recorded as a named exception to IA §6's rule: Turnstile's script on the sign-in and sign-up pages only, with proof-of-work kept for when it can't load. Its keys are the Architect's to provide |
| **CL-7** | OIDC | Authorization code with PKCE, one client per `server`. Return addresses come from Atrium's own list (CL-18). Tokens carry the account's ID, its name and what was proved, with no organisation or role, so a product still decides which school and what they may do. **Proved 7 November 2026: go, with Passport and a thin layer of our own** (about 220 lines), not `jeremy379/laravel-openid-connect`, which has no `acr`, `amr` or `auth_time` and puts the row number in its access token (plan CL-P). `bambamboole/laravel-oidc` is too young to hold every product's sign-in | Use `league/oauth2-server` directly: more of it to own |
| **CL-8** | Leaving and signing out | Signed webhooks (`membership.ended`, `account.signed_out`, `account.merged`) end a product's local sessions at once, and the client library rechecks with `id` every 15 minutes in case one is missed. Signing out of a product also signs out of `id`, because school computers are shared. "Sign out everywhere" is at `id` | Check with `id` on every request, as handbook 6.4 implies: every page of every product then depends on Atrium being up |
| **CL-9** | Who may add people | **The product decides, from its own roles.** Its key adds a person to an organisation holding that product, naming the admin who acted, for the log. Atrium doesn't judge the admin. That's safe only because of CL-16: a membership grants nothing, so a compromised product could at worst add a name to a school it already serves. It couldn't open another product or make anyone an owner. At `id`, owners and admins add people | The admin holds an Atrium role, which was my first recommendation. Every head teacher would then need a second role in a second place, for no gain once a membership grants nothing |
| **CL-10** | Ways in, first version | ~~Phone with a code, e-mail with a password or a code, username with a password, authenticator. Google, Microsoft and passkeys follow as a later slice, before Campus moves. Passkeys are bound to a domain, so people re-enrol at `id`~~ | **Chosen by the Architect.** All at once, which makes the first slice larger with no one yet waiting for them Google and Microsoft keep Campus's rule: they match a proven e-mail and never create an account. Their sign-in keys are the Architect's to provide. Passkeys at `id` need re-enrolling, since Campus's are bound to Campus's domain |
| **CL-11** | How Campus's accounts move | ~~Proven phones and e-mails come across as proven, with `identity` mapped to the account. Password hashes and authenticator secrets don't: each person proves themselves with a code at first sign-in, and staff set a new password. The cost is small (6 active, 201 inactive on 5 November). `instance` keeps Campus's UUID as its own reference, and Album never adopts it~~ | **Chosen by the Architect.** Copy the hashes, so nobody notices. The most trusted store then takes on secrets of accounts nobody uses Password hashes and authenticator secrets come across, encrypted in transit and checked to verify with Atrium's hasher before any school moves; passkeys can't. CL-5's quiet-number rule still asks for a password on a long-unused account |
| **CL-12** | Outside clients | No Clavis for now: they have a key and their own login. Revisit with their commercial arrangement, which is still open | Offer it now, which brings their people into Atrium's identity with no contract behind it |
| **CL-13** | Pupils, and the law | **Revised by the Architect, 7 November 2026, after the build: Clavis doesn't hold children's accounts.** Pupils' accounts stay in the products, where children's data is cleared. Atrium's organisation-made accounts (CL-4) are for adults with no phone or e-mail of their own. The text below is what was decided first. ~~Adults first. Pupils' accounts stay in Campus until the Architect has legal advice, which doesn't change IA-5, only its order. When they come, Atrium holds the least it can: a name, a username, their own phone if any, and no date of birth or class~~ | **Chosen by the Architect.** Move pupils with everyone else, before the advice The open questions in §4.2 still stand: the advice is wanted as soon as it can be had, and the Bill could still affect where children's data may be held |
| **CL-14** | A product's Service sign-in | Stays local to each product, with its own identity and its own lock. It's how staff reach a product when Atrium is down, and it answers IA §9's open question about the Service under delegation. It holds no one else's rights | Move it to Clavis: one login for staff, but nobody can reach a product during an outage, which is when they most need to |
| **CL-15** | Coming back during an outage | The product's own "remember me" cookie lets a person back in only while `id` can't be reached, for up to 24 hours by default, as a setting. Otherwise it goes through `id`, which applies any removal | None: anyone whose session ended stays out until `id` returns, which on a phone with short sessions may be most people |
| **CL-16** | What Atrium keeps of roles | **Nothing about any product.** A membership says only that this person is in this organisation. Three roles run the organisation itself at `id`: owner, admin, billing. They grant nothing in a product, and `product_access` is dropped, since a product says who may use it | **Identity alone, with no membership:** removal becomes once per product, so someone can be left behind in Books after leaving Campus. There's also no list of a person's organisations, no one answerable for the organisation and its bill, and nothing to limit a school-made account to its school |
| **CL-17** | Are contact numbers identity? | **A person's own proven phones and e-mails are identity, and only those.** They sit on the account, each on one account, and products take them from Atrium rather than keeping copies that drift, so someone changes their number once. Joining an organisation shares the sign-in phone or e-mail, and they're told so; a second one stays private unless the holder chooses. **What a school types about someone stays in the product until it's proven:** a guardian link points at the guardian's account, and an emergency contact stays a number on a record | **Every contact number on the account.** A clerk's typo would then attach a stranger's phone to someone's identity, or block the real owner from signing in, and schools would see numbers other schools wrote down |
| **CL-18** | Custom domains | **Atrium keeps the one list, and proves ownership; the product serves the traffic.** A domain belongs to the organisation, points to one instance, and is proved by Campus's CNAME check, lifted once. A second product asking for the same address is refused with a clear reason, and moving it is a deliberate step. Products keep a copy and resolve hosts from it, so an outage touches nothing already live. Certificates and retired-host redirects stay in the product | **Atrium routes the traffic too,** which would allow one address for two products, but puts every school's site behind Atrium, against the failsafe. **Or each product verifies and tells Atrium,** which was my first recommendation: two sources of truth, and a check repeated in every product |

### 4.1 · What these change in plan IA (applied 7 November 2026)

- `product_access` (IA §5) is dropped; it was never built. In scenario 3, "she picks who gets access" becomes a hand-over to the product.
- "Atrium checks the person, not just the product" and "Which role may add?" (IA §7) are answered by CL-9.
- "Each school's address must be registered... the product has to tell Atrium" (IA §7) is replaced by CL-18.
- Not settled here: when a product says someone has no role left, whether their membership ends by itself. They may still be in another product, or be a parent at the same school. For the slices.

**What was read for these.** Campus works out who's staff from its own role rows (`PhoneSignInController::isStaff`), so it can decide CL-5 itself. Its CNAME points a school's host at its platform subdomain, as both routing and proof (`HostResolver`), so DNS already stops one name pointing at two products: today's real risks are a baffling failed check and stale claims. Loop and Desk have no custom domains, so only Campus's move touches this. When Campus texts someone, it prefers the school's record and falls back to the proven phone (`User::routeNotificationForSms`), so CL-17 changes that order when it moves.

### 4.2 · What's known about the law

Not legal advice, and this needs a lawyer before pupils move.

- **Act 843 (2012) is in force.** Secondary sources say section 37 treats a child under parental control as special personal data, needing consent or necessity. They don't fully agree on the route, and the Act's own text couldn't be read: GhaLII refused the request and Judy Legal is behind a sign-in.
- **A controller must register with the Data Protection Commission,** renewed every two years. Whether Tutelora has wasn't checked.
- **The Data Protection Bill, 2025 would replace it.** It was still pending in the latest sources found (May 2026), and no enactment was confirmed. One law firm reads it as requiring children's data to stay in Ghana, **which would bear on where Atrium is hosted.**

### 4.3 · Facts only the Architect has

1. Who gives the legal advice, and whether Tutelora is registered with the Commission.
2. Today's account counts. `tutecampus/reji/docs/reference/identity-duplicates-report.sql` would give them if run on production. The 6 and 201 are from 5 November.
3. Any pilot or launch date that falls inside this build, for Campus or Desk (master plan §4).

---

## 5 · Mockups

Drawn and approved 7 November 2026, in `reji/ui/html/cl/` (generator `build-mockups.py`). Every state is in one file per concept, in light and dark and in phone frames. People read Tutelora, never Atrium.

| File | What it's for | States |
|---|---|---|
| `signin.html` | The one door into every product, at `id` | Phone first, the code, a wrong code, paused, an unknown number, e-mail, username, a password asked for by the product (CL-5), a school's own address (IA-5), back to the product, phone |
| `account.html` | A person's own account, whoever their organisations are | Home with Kofi's organisations, no organisation yet, how you sign in (CL-17), a number taken (CL-2), devices and signing out everywhere (CL-8), claiming (CL-4), already having an account, phone |
| `join.html` | How someone new arrives | Start free from a product (IA-3), the school's record with its kind assumed from Campus, registering at `id` where the kind is asked, starting the product with its address, setting up, Products (what Jaeda runs and what fits beside it), adding TuteBooks with its address, Jaeda's own address and its plan on one page (CL-18), phone |
| `organisation.html` | Running an organisation at `id` | People with Atrium's three roles (CL-16), adding a person, the invitation, details, web addresses (CL-18), an address taken, billing only (IA-11), phone |
| `tutelogue.html` | The Tutelogue, tutelora.com's product catalogue, where people pick a product before `id` | Everything, for schools, a product page (TuteCampus), phone |
| `product.html` | What Clavis looks like inside a product | Adding a teacher in TuteCampus (CL-9), invited, Tutelora unreachable, sign-in unavailable (the failsafe, §0), a parent's first visit on her phone (IA-5), phone |

Paying and invoices are only sketched under Billing: they're plan TB's.

**Changed at the Architect's review, 7 November 2026:** the standard word is "Verified", not "Proven". Someone arriving from a product isn't asked what kind of organisation it is: the product names the kinds it serves, as data, and the full list from Input Fields appears only when registering at `id`. The account's home was redrawn as a launchpad: where he left off, what needs him, and his organisations with their products as tiles. Products was redrawn from what an owner is deciding, with each product described and its features listed from its own sources. Addresses follow Campus's protocol: a short name gives `tutecampus.app/jis` at once. It defaults to the name's initials, unless the organisation already uses a short name on another product, which a new product then matches. The organisation's own domain is never assumed, added then or later, and inert until its CNAME is verified. The school's form copies Campus's, with region and district required. People pick a product in the Tutelogue, tutelora.com's catalogue, before reaching `id`; registering at `id` with no product asks which product to start with, never the organisation's kind.

## 6 · Order

Nothing starts before the decisions.

1. ☑ The throwaway OIDC proof (CL-7), 7 November 2026: go, with option B (`2026-11-07-CL-P-oidc-proof.md`).
2. ☑ Mockups drawn, revised at review and approved, 7 November 2026.
3. ☑ The slices below, in order, each with its tests, docs and a journey chapter: CL1 to CL9 built 7 November 2026. The security assessment of `id` (`reji/docs/SECURITY_ASSESSMENT_ID.md`) says it isn't ready to deploy until its Priority 1 is done: a Content Security Policy, uploads off `id`, a fresh check for organisation changes, and legal advice on children's data.

### 6.1 · Slices

Built in Atrium. The Tutelogue is tutelora.com's own page and isn't built here; Atrium only takes the product it hands over. Campus's move is Campus's own plan (IA §9, step 8), and the client library is IA's step 7.

| Slice | What | Done when |
|---|---|---|
| CL1 ☑ | **Accounts without organisations.** The `user` is the account, with an `acc_…` ID. Its sign-in identifiers (phone, e-mail, username) become rows of their own, each verified one on one account (CL-1, CL-2, CL-3, CL-17). `user.email` and `password` are no longer required, `Membership::lets()` gives way to the person, and an account can stand with no organisation (IA-4) | A phone-only account, an e-mail one and a username one each exist with no organisation; no verified identifier is on two accounts; the existing sign-in tests still pass |
| CL2 ☑ | **Sign-in at `id`.** One field that tells a phone, an e-mail and a username apart. Codes by text, lifted from Campus with its fault fixed, through Nuntius; e-mail with a password or a code; a username with a password. The pause, the same answer for an unknown number, Turnstile with proof-of-work behind it (CL-6), an enrolled authenticator always asked, and a password asked of a long-quiet number (CL-5) | Every state in `signin.html`, by test, and each limit and pause |
| CL3 ☑ | **Atrium as the OIDC provider**, from the proof's option B: one client per server, return addresses from the instances and their verified domains, PKCE required, a product's `acr_values` asking for the password alone, `acr` and `amr` in the token, sign-out at `id`. Signed webhooks tell products of a membership ended and a session signed out (CL-8); signing out everywhere and merging accounts send theirs with CL6, where they're built | The proof's nine checks pass in Atrium's own suite |
| CL4 ☑ | **Joining and organisations at `id`.** The cost brake, moved from CL2: it holds codes to numbers that don't sign in yet, and joining is the first thing to text one (sign-in never does). Start free with a product named, the school's form copied from Campus (region and district required), registering at `id` by picking a product, the kind following from the product. Album's pages: details, people with owner, admin and billing (CL-16), adding and removing once, invitations from the organisation, never a person | `join.html` and `organisation.html`, by test, and removal reaching every product |
| CL5 ☑ | **Addresses** (CL-18): short names from the initials or matched to the organisation's other products, `{product}/{short}` and `{short}.{product}` at once, the organisation's own subdomain verified by its CNAME, one address to one product, and the list sent to products | An address taken by another product is refused; an unverified one routes nothing |
| CL6 ☑ | **The account at `id`**, and the organisation's pages in `id`'s own navigation as `organisation.html` draws them (People with its filters and role cards, Details, Billing only), moved from CL4, which built joining, invitations by text, the billing role and the record's region and district on the starter pack's pages: the launchpad home, how you sign in, devices and signing out everywhere (with its webhook, moved from CL3), claiming a school-made account and folding it into an existing one (CL-4), telling products of the merge by webhook (moved from CL3), Products and adding one with its short name, own address and plan | `account.html`, by test, and a claim that merges keeping everything |
| CL7 ☑ | **Google, Microsoft and passkeys** (CL-10): Google and Microsoft match a verified e-mail and never create an account; passkeys at `id` | Each links, signs in, and refuses an unverified match |
| CL8 ☑ | **The products' side of the API**: adding a person through a product's key, naming the admin who acted (CL-9); the account's own verified details sent to products (CL-17); the 15-minute recheck (CL-8); a product's Service sign-in left local (CL-14) | A product adds, hears of a change, and rechecks, by test |
| CL9 ☑ | **Docs and the security assessment of `id`**, which IA §11 requires before its first deployment. Pupils move with everyone else (CL-13), and the legal advice is still wanted | The manual, reference, map and journey are updated, and the assessment is written |

