{brand:short_name} is the one internal app that does, once, the jobs every {brand:company} product would otherwise rebuild. People never see its name, which is why its screens and e-mails say {brand:company}: they see a {brand:company} account, a text, a voucher, a tutel.ink link or a chat with Tutela.
The story so far is told below, a day at a time: the latest day is open, and every other opens with a click. The changelog (CHANGELOG.md) lists what changed; this page says why. TuteCore's own journey, up to the release Atrium started from, stays in TuteCore.
At a glance
| Chapter | What | Commits | Tests |
|---|---|---|---|
| 1 · 6 Nov | From a release, not a clone | 166bbec a3dc12e | 618 |
| 2 · 6 Nov | The checklist, read against the code | see the changelog | 618 |
| 3 · 6 Nov | What's already built, and the build order | see the changelog | 618 |
| 4 · 6 Nov | One account, organisations apart | see the changelog | 618 |
| 5 · 6–7 Nov | The foundation, built | 5c6bb8e to 137a685 | 767 |
| 6 · 7 Nov | Running, and where it goes next | see the changelog | 770 |
| 7 · 7 Nov | Nuntius’s send path | 0fe72e3 to 339a283 | 856 |
| 8 · 7 Nov | Clavis, decided | see the changelog | 856 |
| 9 · 7 Nov | Sign-in across domains, proved | a throwaway branch | 856 |
| 10 · 7 Nov | What people will see of Clavis | see the changelog | 856 |
| 11 · 7 Nov | An account that needs nothing else | see the changelog | 863 |
| 12 · 7 Nov | One door, whatever you sign in with | see the changelog | 888 |
| 13 · 7 Nov | Every product, one sign-in | see the changelog | 900 |
| 14 · 7 Nov | From “Start free” to a running product | see the changelog | 913 |
| 15 · 7 Nov | A school’s own address, on one list | see the changelog | 924 |
| 16 · 7 Nov | An account’s own place | see the changelog | 940 |
| 17 · 7 Nov | No code to wait for | see the changelog | 947 |
| 18 · 7 Nov | What products ask of accounts | see the changelog | 954 |
| 19 · 7 Nov | One door, looked at hard | see the changelog | 955 |
| 20 · 7 Nov | Billing, decided and drawn twice | see the changelog | 955 |
| 21 · 7 Nov | What the products sell, as data | 85ea5df | 968 |
| 22 · 8 Nov | Paid once, however the news arrives | df5f806 to 5345d30 | 981 |
| 23 · 8 Nov | The year as a ribbon, and nobody dropped while Atrium sleeps | a57c68b | 1,005 |
| 24 · 8 Nov | A receipt until the GRA says otherwise | 39144a6 | 1,017 |
| 25 · 8 Nov | A switch that won’t move until it’s lawful | 8daecbe | 1,025 |
| 26 · 8 Nov | Read by someone who didn’t write it | 0ea1bc0 | 1,033 |
| 27 · 8 Nov | Money that can only go back once | cf158d3 1a7697c | 1,047 |
| 28 · 8 Nov | A paper for the money that went back | faf856e | 1,053 |
| 29 · 8 Nov | Sixteen hours to answer the bank | 4cd7c09 | 1,072 |
| 30 · 8 Nov | The keys handed over, and a certificate read | c4316e1 | 1,079 |
| 31 · 8 Nov | Locks before the doors open | c0daab6 to 1befb32 | 1,113 |
| 32 · 8 Nov | Who pays for a text | 3878ab0 to 5e4376d | 1,133 |
| 33 · 9 Nov | Six hundred parents, never half | 40a971f | 1,145 |
| 34 · 9 Nov | The name on the phone | e1056dd | 1,154 |
| 35 · 9 Nov | Every product its own address | 8dba249 | 1,163 |
| 36 · 9 Nov | Never written to again | 07cc351 | 1,171 |
| 37 · 9 Nov | Two readers who hadn’t written it | c692e46 | 1,183 |
| 38 · 9 Nov | Seven characters a parent can trust | 7cc0679 | 1,192 |
| 39 · 9 Nov | One code, spent once | d754358 to 8e64ec9 | 1,214 |
| 40 · 9 Nov | The limit that waited for the queue | f5b3ffe | 1,227 |
| 41 · 9 Nov | The library, and the door that stalled | 229fa30 | 1,281 |
To follow along on this Mac
- Folder
/Users/reji/dev/Tutelora/tuteatrium, branchdevelop, pushed totutelora/atriumthrough thegithub-tualias. A push deploys nothing- Run it
- Always running: the launchd agents
com.tutelora.atrium-8100,-queue,-queue-bulk,-schedule. People athttp://id.localhost:8100, the console athttp://api.localhost:8100, short links athttp://ink.localhost:8100 - Mockups, docs
- The
mockupsentry on 8101, thedocsentry on 8102:http://127.0.0.1:8102/reference/atrium-system-map.html - Sign in
- The console's Service is
service@atrium.test, athttp://api.localhost:8100/console. The demo's people, athttp://id.localhost:8100:ama@northwind.test(Owner, of Northwind, which holds TuteLoop) andkofi@northwind.test(Admin in both). Since 8 November every local account, the Service’s included, usesDEMO_PASSWORDfrom.env, which git never sees. An owner sets up two-factor at the first sign-in; Ama’s is set up, so she is asked for an authenticator code. Local only: no other database has these. A plainlocalhost:8100orapi.localhost:8100/says Not Found by design, since only the three hosts answer, and only at their pages - Demo data
php artisan migrate:fresh --seed(local only), which prints the accounts to sign in with. It's TuteCore's demo: two workspaces and their people, nothing of Atrium's own- Tests
npm run buildfirst, thenphp artisan test --parallelon PostgreSQL (atrium_test; the app's database isatriumdb). Without the build, every page test fails with "Not a valid Inertia response"- Checks
php artisan brand:check·php artisan core:status·php artisan app:check- Docs
- The system map beside this page;
reji/docs/reference/*.md; plans inreji/plans, starting with the master plan; task lists and lessons inreji/tasks
6 November 2026From the release to a plan for discussion4 chapters
One account, organisations apart
The premise. The plans held an organisation's products inside one Accounts and organisations module, at accounts.tutelora.app. The Architect stopped the work to clarify: an architecture like Atlassian's, with a central account at id.tutelora.com, each product keeping its domain, organisations registered with no product information, and a dedicated subscription subsystem.
What was found. Atlassian's own pages bore that out, with detail: it runs organisations apart from id, signing up for an app creates an organisation too, and what's sold, what's running and who may use it are separate concerns. In Atrium almost nothing had to change, because tenant already had no product columns. The cost is in the products, above all Campus.
id.tutelora.com, with no admin host; api on tutelora.com too. Accounts can exist with no organisation. Organisations register alone or from a product. Parents and pupils hold the same account and reach id quietly, by invitation or through the school's web address. Admins add people at id or inside the product. Atrium owns an organisation's details. Self-service billing is in from the start. The new subsystems are named Album and Tabula.id to pay.The plan. reji/plans/2026-11-06-IA-identity-architecture.md. The spec is unedited, because its diagrams show the old design too; reji/docs/spec-amendments.md records what overrides it.
What's already built, and the build order
The premise. The spec sets an order for the modules and gives its reasons. Asked to justify that order, the honest test was whether its reasons hold against what the products have actually built, so the apps were read before a word of the justification was written.
What was found. Most of what the spec describes already runs in Campus: phone sign-in by SMS code, phone-only accounts, about 4,600 lines of SMS with wallets and sender IDs, a chain of mail providers, vouchers, three payment gateways. So most of Atrium is extraction from a live product, not new work.
routes/api.php, which Tutela needs. No pilot date is recorded anywhere, and Campus is already in production, so no Atrium module blocks a pilot.PROJECT.md.How it was checked. An agent surveyed the apps; the claims that decide the order were then read by hand in each app's code, with file and line in the plan. The system map's data was checked by script: every flow node and edge points at something real.
The plan. reji/plans/2026-11-06-M00-atrium-master-plan.md.
The checklist, read against the code
The premise. Step 0 of the spec's build plan evaluates TuteCore against what a shared platform needs. TuteCore was built for apps people use, and Atrium's main users are other apps.
What was found. Of the twelve items, none ships as it stands: seven are part-way and five missing. An API token must belong to a person and a membership, so a product's server can't hold one. Every route answers on every host, so tutel.ink would serve the login page. An account reaches a membership only while the two hold the same e-mail.
/v1 against /api/v1; a shared service against "no microservices". Each needs a ruling rather than a quiet choice, so they became decisions AF-1 to AF-8.The plan. reji/plans/2026-11-06-AF-atrium-foundation.md, drafted and not approved. Later that day the Architect set the rule that the TuteCore repository isn't changed, so everything here is built in Atrium.
From a release, not a clone
The premise. Atrium starts from TuteCore. The first step was a clone of tutelora/core, but TuteCore's own runbook says a new app starts from a tagged release: its history stays its own, and with the clone's origin still pointing at Core, a plain push from this folder would have gone into the starter pack.
v2.1.1, with no remote. The short name is Atrium, not Tuteatrium, because the spec says "shortname atrium": internal names drop the Tute- prefix, so a repository shows at a glance what is plumbing. The folder keeps its name. The port block is 8100–8109.npm run build had been skipped, so pages had no Vite manifest. The run also reported exit code 0, which was only tail's, so the figure in the output was what counted.core:status reads 945 files as released, 0 differ. brand:check passes. app:check fails only on what this Mac doesn't run: the scheduler, a file vault key and the malware scanner.7 November 2026The foundation built, running, and pushed; Nuntius’s send path built; Clavis decided, proved, drawn, built and assessed; Tabula decided, drawn twice, and its first slice built17 chapters
What the products sell, as data
The premise. Before anyone can pay, Atrium has to know what each product sells, for how much, and what each offering allows; and, as the Architect asked, nobody pays for a product until its billing is switched on, unless the company agrees otherwise with one organisation.
GET /v1/entitlement, hear entitlement.changed, and report their counts. The console has Billing.billing_state and the permissions’ group subscription. Prefixed public IDs need 30 characters, which the shared column helper doesn’t give. The products’ colours went into a migration first; the brand guard sent them to the brand layer, config/brand.php. The three ways an organisation can be billed are code, not Input Fields: each changes what Atrium does.The plan. TB2: paying through Paystack’s hosted page, with the billing role’s pages at id.
Billing, decided and drawn twice
The premise. With Clavis built, the next module was Tabula: how the company charges its own customers. Its discussion opened with what it is, what stays in the product, and how things carry on if Atrium is away.
The plan. Plan TB §6.1: six slices, TB1 to TB6.
One door, looked at hard
The premise. Plan IA asked for a security assessment before id is first deployed, because one breach of it would reach every product. With every slice of Clavis built, it was time.
id are met: a Content Security Policy, uploads served from another host, a fresh check before changing an organisation's addresses, products or people, and legal advice on children's data before pupils' accounts move.__Host- prefixed.The plan. reji/docs/SECURITY_ASSESSMENT_ID.md: its Priority 1 is the next work on id, before it's deployed.
What products ask of accounts
The premise. Products keep their own roles (CL-16), so they need to add people, know them, and know when they leave, without Atrium learning what each person does inside them.
The plan. reji/plans/2026-11-07-CL-clavis-accounts.md §6.1; next, CL9, the docs and the security assessment of id.
No code to wait for
The premise. The Architect chose Google, Microsoft and passkeys for the first version (CL-10). They had to fit the rules already set: one account per person, and nothing that makes an account by the back door.
The plan. reji/plans/2026-11-07-CL-clavis-accounts.md §6.1; next, CL8, the products' side of the API.
An account's own place
The premise. With sign-in, products and organisations in place, a person needs somewhere of their own at id: to get to their products, manage how they sign in, see where they're signed in, and make an account a school set up for them their own.
id's own menu; a launchpad home; how you sign in; devices and signing out everywhere; an organisation's products and adding one; claiming a school-made account, folded into the holder's own when their number already has one (CL-4).id's own design and the billing-only view remain: the starter pack's People page serves, and billing is plan TB's.The plan. reji/plans/2026-11-07-CL-clavis-accounts.md §6.1; next, CL7, Google, Microsoft and passkeys.
A school's own address, on one list
The premise. A school that uses portal.jaeda.edu.gh for Campus mustn't be able to point the same name at Loop, and an address nobody has proved must open nothing.
The plan. reji/plans/2026-11-07-CL-clavis-accounts.md §6.1; next, CL6, the account and organisation pages at id.
From "Start free" to a running product
The premise. Someone new arrives from a product's "Start free" with nothing: no account, no organisation. They should leave with both, and the product running, without being asked anything the product already tells us.
product_kind). Joining with a number that already has an account now signs in to that account, so one number never gets a second. The cost brake had waited for this slice, since joining is the first thing that texts a number nobody has proved. The organisation pages in id's own navigation are a reshaping of the starter pack's People and Settings, and they move to CL6 with the account pages, where that navigation is built.The plan. reji/plans/2026-11-07-CL-clavis-accounts.md §6.1; next, CL5, addresses.
Every product, one sign-in
The premise. The proof showed Passport with a thin layer of Atrium's own could sign one person into products on different domains. This chapter made it Atrium's, to the house rules.
id. Products hear by signed webhook when a session they hold ends, or a membership in their organisation does.The plan. reji/plans/2026-11-07-CL-clavis-accounts.md §6.1; next, CL4, joining and organisations at id.
One door, whatever you sign in with
The premise. With accounts free of e-mail, the door has to take whatever a person holds: a phone, an e-mail or a username, in one field, and never say whether an account is behind it.
The plan. reji/plans/2026-11-07-CL-clavis-accounts.md §6.1; next, CL3, Atrium as the products' OIDC provider.
An account that needs nothing else
The premise. Everything in Clavis stands on one change: an account that needs no e-mail, no password and no organisation. It changes the core model, so the house rule applied: every surface listed, the list agreed, then one pass.
user.email kept by the database as the primary verified e-mail so the sixty places that read it read on; a workspace lets an account in through any verified e-mail or phone it holds, or a membership the account claimed; the person's phone moves to the account; nothing mailed to an account without an e-mail; account IDs start acc_.The plan. reji/plans/2026-11-07-CL1-accounts-audit.md; next, CL2, signing in by phone, e-mail or username.
What people will see of Clavis
The premise. With the decisions made and sign-in proved, Clavis was drawn: every way in at id, a person's own account, joining, running an organisation, and what a product shows, including Atrium being unreachable.
id, so id never asks what kind of organisation they are; with no product chosen, it shows the products and lets them sell themselves. Addresses follow Campus's protocol: a short name from the initials, or matched to one the organisation already uses, and its own domain never assumed. The school's form copies Campus's, with region and district required. The app's word is "Verified".How it was checked. Every state was opened in light and dark and at phone width; none scrolls sideways, and no word a person reads says "Atrium".
The plan. reji/plans/2026-11-07-CL-clavis-accounts.md §5 and §6.1: nine slices, starting with accounts that need no organisation.
Sign-in across domains, proved
The premise. Clavis rests on one thing nobody had run on this stack: Atrium acting as an OpenID Connect provider for products on their own domains. Plan IA said to prove it before drawing anything, because a failure would change Clavis's shape.
What was weighed. Two ways, on one throwaway branch with its own database, judged by the same nine checks. A: Passport with jeremy379/laravel-openid-connect, about 1,360 lines. B: Passport with a thin layer of Atrium's own, about 220 lines on Passport's hooks.
User fails for a phone-only account, as CL-1 expected. Adding Passport also moved Laravel up a minor version, so that step gets its own commit and full suite.id. Option B passed all five tests and all nine checks; A failed two. Not proven: back-channel sign-out, Turnstile, Google, Microsoft, passkeys, real SMS, HTTPS.The plan. reji/plans/2026-11-07-CL-P-oidc-proof.md. Next: Clavis's mockups.
Clavis, decided
The premise. With the send path built, Clavis was next: one key for every product. Plan IA had settled its shape, so the discussion was about what IA left open.
id can't be reached.What was weighed. The Architect questioned three of the recommendations. On roles: Atrium should know nothing of a product's roles. That overturned two recommendations which had quietly needed it, how strong a sign-in must be and who may add people, and both now sit with the product. Atrium keeps only that someone is in an organisation, and three roles for running the organisation itself. On contact numbers: a person's own proven numbers are identity, while what a school types about someone is not, since a clerk's typo would otherwise attach a stranger's phone. On custom domains: one list in Atrium, with the product still serving the traffic, so an outage can't take a school's website down.
The plan. reji/plans/2026-11-07-CL-clavis-accounts.md. Next: a throwaway proof of OIDC on this stack, then the mockups.
Nuntius’s send path
The premise. Clavis can’t land without SMS, because Campus’s phone sign-in sends its codes that way. So the first module after the foundation is the smallest part of the company post office: one text, its receipt and its count.
What was weighed. A webhook settles a text once Atrium knows the provider’s ID for it, and a timed-out send has no ID. So unknown stayed, narrowed: a report that matches no ID is matched by number within an hour. It’s never retried and never called failed, since either could double-text or misreport. Campus’s own code refunds a timeout, which is the fault left out.
How it was proven. A test for each driver answer, each state, a retry counting once, a code in no log, another server hearing nothing, and an outside client never going out under the company’s name. Every page was looked at in light, dark and at phone width, and switching provider ran through the real password step. Not proven: no real provider has been called, and nothing calls Atrium yet.
Running, and where it goes next
The premise. With the foundation built, it needed a home: a repository, something keeping it up here, and a place to stage it.
What was weighed. cPanel has no Redis and no supervisor, and Campus already solved that: the scheduler starts a worker each minute. Reusing it took one setting. What staging can't show, separate workers and real load, waits for the VPS. The one thing that could stop it is PostgreSQL: Atrium needs 15 or later with pg_trgm, which has to be confirmed on the host.
How it was proven. The agents were bootstrapped, the server was killed and came back on its own, and real mail went through the queue worker.
The foundation, built
The premise. The Architect approved the mockups, which opened the gate. The plan's order held: the inherited foundation strengthened first (C1 to C4), then Atrium's own (A1 to A7).
What was built. Worker pools; idempotency keys reserved before a request runs; signed webhooks on the mail engine's catalogue; request IDs through logs, jobs and outbound calls. Then the name, three hosts, the client's record and what it holds, server keys and the first /v1 call, metering and server reports, and the console from the approved mockups, carrying their stylesheet across so the pages match.
server. The daily usage rollup waits until Nuntius needs it.How it was proven. 767 tests, 3 skipped, with one running from a product's signed call to the console's figures, and every page looked at in light, dark and at phone width. Not proven: nothing has run on a server, and no product calls Atrium yet.
8 November 2026The console’s sign-in mended and Billing checked in the app; Tabula built: paying, renewals and lapses, invoices, switching billing on, its money paths reviewed, money back, credit notes, chargebacks and withholding’s review; the build order handed over; and what must be true before the first deployment: id’s Priority 1, and Atrium’s own risks assessed and fixed; then Nuntius’ rest decided and drawn, and its money built: what a text costs, and buying credit11 chapters
Who pays for a text
The premise. Nuntius could send one text; the spec wants a post office: a school’s free texts, then credit it buys, a fee reminder to 600 parents paced to the networks, its own sender name, products’ e-mail from their own domains, and an address that bounces known to every product.
What was found. Campus, Desk and Loop were read first, and their faults checked in the code: in Campus a school could never buy credit (the top-up has no caller), a bulk send was one job sending one number at a time with no pacing, and its SES endpoint would take forged complaints from anyone’s topic; Desk records bounces but never stops a send while its pages say it does.
The plan. Bulk sends paced to the networks, sender names, products’ e-mail and the shared block list (NU3 to NU6).
Locks before the doors open
The premise. Nothing new for anyone to use: what must be true before Atrium is first deployed. id’s own assessment had said it wasn’t ready (no Content Security Policy, uploads served beside the session, no fresh proof before an organisation’s addresses, products or people change), and nobody had yet assessed Atrium as a platform.
The plan. The first deployment waits on the Architect’s cPanel account and DNS. Next in the build: Nuntius’ rest.
The keys handed over, and a certificate read
The premise. The Architect: “Take over go autonomous. decide the build order and only pause if you need some input from me.” The order and each module’s decisions became Claude’s, still written into the plans and drawn as mockups so they can be read afterwards; the pauses are kept for facts only the Architect holds and for anything outward or irreversible.
The plan. Next, what must be true before the first deployment: id’s Priority 1, and an assessment of Atrium’s own risks.
Sixteen hours to answer the bank
The premise. TB-22: a chargeback leaves the plan running until the bank decides; the Service defends or accepts; lost, the period ends, a credit note follows, the card is dropped and the organisation hears the outcome, never the dispute.
The plan. Money back is built. Before live money: Paystack’s dispute resolutions confirmed with test keys, and withholding’s review step (Priority 2). The next module waits for its discussion.
A paper for the money that went back
The premise. A refund that leaves its tax invoice standing tells the GRA the money is still with the company. TB-20 asks for a credit note once the money has gone, never before, so a refund that fails never leaves a paper to undo.
The plan. TB9: chargebacks, and the money paths reviewed again.
Money that can only go back once
The premise. The review’s first priority was the paths that take money back. The Architect chose to build them before the next module, and decided TB-17 to TB-22 as recommended: the Service alone gives money back, choosing each time what happens to the plan; the amount is suggested and only lowered; a credit note follows only once the money has gone; a wallet Paystack can’t reach is paid by hand and recorded; a chargeback leaves the plan running until the bank decides.
The plan. TB8, credit notes: their own count, issued once the money has gone, cleared through E-VAT. Then TB9, chargebacks, and the review again.
Read by someone who didn’t write it
The premise. Code that moves money should be read, before it moves any, by a reader with no stake in how it was built, and every claim that reader makes should be checked against the code before it’s believed.
The plan. Tabula is built. Next, the Architect’s: the facts only they hold (prices, the VAT registration, E-VAT access, live Paystack, the terms), and the priorities the review left.
A switch that won’t move until it’s lawful
The premise. Everything built so far waits behind one switch per product. Turning it on must be impossible until the law and the terms allow it, must give every organisation fair notice, must reward those who came first, and must charge nobody a day early.
The plan. TB6: the docs, and a security review of the webhook, the gateway and the invoices.
A receipt until the GRA says otherwise
The premise. Ghana’s law wants every tax invoice issued through the GRA’s own system, with its mark and QR on the paper, and lets a school pay net of what it withholds. A school must still get its paper the moment it pays, whether or not the GRA answers, and a correct deduction must never read as an underpayment.
The plan. TB5: switching billing on for a product, with its notice and founding price.
The year as a ribbon, and nobody dropped while Atrium sleeps
The premise. A paid plan has to renew without anyone remembering to: a card charged on the day, mobile money reminded in time, a slow payment given its grace, and only then Free, with nothing deleted. And none of it may count against a school while Atrium itself was down.
The plan. TB4: tax invoices and receipts, cleared through the GRA’s E-VAT.
Paid once, however the news arrives
The premise. An organisation’s owner, or the person who pays its bills, should be able to choose a plan and pay for it without Atrium ever touching a card, and a payment must count exactly once, though Paystack can tell Atrium three ways and none of them is certain to arrive.
The plan. TB3: renewals and lapses, with the organisation’s Billing page and the year as a ribbon.
9 November 2026The pilot declared free; Nuntius’ bulk sends built: a fee reminder to a school’s parents reserved whole, paced to the networks, resumed without double-sending, and stopped with the rest given back; then sender names, asked at id and registered with each provider; products’ e-mail, each from its own address along a chain of mailers; who isn’t written to, with sharp screenshots kept from now on; Nuntius reviewed by two fresh readers, two serious faults fixed; Tutelink, short links, decided, drawn and built; Tessera, vouchers: Campus’s read, decided, drawn, built, and reviewed by two fresh readers; and Legatus, the client library every product will install, built, proved live in a browser and reviewed, with every change the products need recorded for after deployment9 chapters
The library, and the door that stalled
The premise. Everything Atrium does is for products that can’t yet reach it: none signs anyone in through id, hears a webhook or calls /v1. The Architect asked for the client library, and set a rule for the session: Atrium only, and every change another product needs written down for after Atrium is deployed, one product at a time.
What was weighed. Its own repository or a folder in Atrium’s: a folder, proved where its other half lives, its own repository when the first product installs it. A framework package or a bare client: the products are all Laravel 13, so a package that wires itself saves each one the work. How a product ends a session Atrium ended, when Atrium never sees the person’s browser: a row per session, keyed by id’s sid, checked on every page. Whether the recheck fails open or closed: open, so Atrium being away signs nobody out; a definite answer still ends it.
atrium/legatus): sign-in with PKCE and an ID token checked against a cached key, the membership asked once; step-up and sign-out through id; sessions ended by Atrium’s signed webhooks and a 15-minute recheck; one client for every call, retrying only what can’t act twice; the school’s plan kept for when Atrium is away; the server’s report every five minutes; a fake for products’ own tests. The register of what each product must change, reji/tasks/other-apps.md, with Campus’s known faults moved into it.max_age for step-up, an as_of on the plan, and sign-out everywhere on a password reset.The plan. Atrium deployed (A8 waits on the Architect’s cPanel account and DNS); then each product joins through Legatus, from its own folder, by the register. Tutela, Aerarium and Lector wait for their triggers.
The limit that waited for the queue
The premise. A voucher is money in a parent’s hand, so before any school sells one, someone who didn’t write Tessera should try to break it: the plan’s last slice, as for Nuntius.
What was weighed. One review or two: two, in parallel, one of redeeming and one of batches with the console, each given the intent and the files but not the author’s view of them. Each finding was checked in the code before it was believed. And the hourly ceiling on misses, whether to stop everyone once it trips or only those who missed: only those who missed, so a guesser can’t shut out parents who type a right code.
vouchers:check, picking up a batch whose worker died.The plan. Tessera is built. What follows waits on triggers: the client library when a product connects its server, then Tutela, Aerarium and Lector, each when it’s needed. Waiting on the Architect: Campus’s payment fault, tutel.ink’s DNS, and the SES set-up.
One code, spent once
The premise. A school sells admission vouchers, online to a parent who has just paid and on cards a bank sells over the counter; one day GWCL may sell its own. The code is the money: whoever types it gets what it grants, once. Campus already had vouchers, and reading them first found what not to copy. A code was checked outside its transaction and marked used without a condition, so two people could spend one. Guessing was unlimited, behind a public prefix and answers that confirmed a code existed. Lower-case codes always failed. Codes sat in plain text in the table, the e-mailed link and the logs. In passing, a worse fault in Campus’s payments: anyone signed in can mark any pending payment paid.
What was weighed. How long a code: twelve characters from 31 with no look-alikes, about 59 bits, so the lock-out isn’t the only thing between a script and a parent’s money. What the check character should be: Luhn, at first. Whether issuing goes behind the shared idempotency store: no, because it keeps the whole answer, code and all, in plain text, so the same reference gives the same code back from an encrypted copy instead. How to print 50,000 codes: dompdf builds the whole document in memory, so the cards are a PDF written by hand, streamed a page at a time.
The plan. A fresh reader’s review of Tessera’s code paths (TE4). Campus’s payment fault is for the Architect.
Seven characters a parent can trust
The premise. A fee reminder’s link is longer than the text it rides in, and a 160-character text costs money for every part it runs to. The spec’s answer is tutel.ink: short links, trusted because they only ever lead to a school’s own pages. No app had built it: Campus, Desk and Loop all send full addresses.
What was weighed. Where a link may go: “any Tutelora domain” against the calling product’s own addresses and the school’s verified ones, the narrower, so one product can’t point a link at another’s pages. A 301 against a 302: the 302, so turning a link off works even for someone who tapped it before. Counting every fetch against counting taps: WhatsApp and iMessage fetch a link to draw its preview, and that isn’t a parent.
The plan. tutel.ink’s DNS waits on the Architect. Next in the build order: Tessera, vouchers.
Two readers who hadn’t written it
The premise. Nuntius now spends money, the organisations’ on texts and the company’s on codes, and sends in volume. Before anyone is charged, someone who didn’t write it should try to break it: the plan’s last slice.
What was weighed. One review or two: two, in parallel, one of the money and one of the sending, each given the plan’s intent and the files but not the author’s opinion of them, so neither read was anchored. Every finding was then checked against the code before it was believed.
The plan. Nuntius’ rest is built; WhatsApp and inbound replies wait for Desk. Next in the build order: Tutelink, the short links.
Never written to again
The premise. An address that bounces, a parent who marks a school’s e-mail as spam, or one who just wants the reminders to stop: each must be honoured by every product, or one product’s carelessness spoils every product’s sending. Campus took bounce reports from anyone’s SNS topic, so anyone could silence any address; Desk recorded bounces and sent anyway.
What was weighed. Keying the list to an account or to the address: the address, since most parents have none. Unsubscribing on the link’s first open, as the mockup drew it, or asking first: asking, because mail scanners open every link and would unsubscribe people who never chose to; a mail client’s one-click still acts at once. And whether an erasure takes the list with it: no, a suppression list is how someone is never written to again.
act, which turned out to be the console’s blinking status light, so the button would have blinked; found through the sharp screenshots, whose own tool was fixed to finish animations rather than cancel them.The plan. A fresh reader’s review of the money and send paths (NU7), then Tutelink.
Every product its own address
The premise. Products send e-mail of their own: fee reminders, receipts, reports. Sent each from its own servers, a bounce or a provider’s outage stops one product while another works; sent from one shared address, one product’s complaints harm them all. Campus had a chain of mailers with quotas; nothing else did.
What was weighed. Editing mailers and their keys on the console against keeping them in the environment, as every mailer’s credentials already are: the environment, with the chain and each mailer’s month shown on the console. SES through its SDK against its SMTP interface: SMTP, so nothing new is installed. Templates for products against their own words: their own words, as texts already move, until a product’s wording needs to live here.
no-reply@notify. its own domain, named for the school, under its own SES configuration set. Each recipient gets an e-mail of their own. Each e-mail goes along the chain, the first mailer with quota left that accepts it taking it; with every one spent it goes anyway. One row an e-mail with every recipient on it, never a row each. Counted, never charged.The plan. The shared block list: hard bounces and complaints from SES through SNS with its topic allow-listed, and unsubscribing with one click (NU6); then a fresh reader’s review (NU7).
The name on the phone
The premise. A parent trusts a text from “JAEDA SCH” more than one from a company they’ve never heard of. Each SMS provider registers a name by hand, takes days, and sometimes refuses; Campus recorded the names but no school could ask for one, and nobody could see where a request stood.
What was weighed. One request with one state hides that one provider registered the name and another refused it; a row per provider tells the truth. Switching to a new name at once would put texts under a name no provider has approved; keeping the old one until the new one is registered with that provider never leaves a gap.
The plan. Products’ e-mail through Nuntius with a provider chain (NU5), then the shared block list and unsubscribing (NU6), and a fresh reader’s review (NU7).
Six hundred parents, never half
The premise. A school sends a fee reminder to every parent at once. Campus did that as one job sending one number at a time, with no pacing, no progress and no stop, and a timeout half-way could lose the record of texts already sent. The Architect also settled the money for now: no prices yet, the pilot is free, everything in it complimentary.
What was weighed. Pacing by counting a second’s worth and then waiting let two half-seconds bunch together, and a slow answer was made up with a burst; spacing each text a fixed gap after the last never does either. A send in one job could die mid-way and resend; a cursor written before each text means a run that dies resumes after it, and the text in flight is marked unsure rather than sent twice.
fill() overrode Eloquent’s own and broke every save; a cancelled text is stored as failed, so the list drew a cancelled send all red; the dialog offered “Cancel” beside “Cancel them”; counts like 1224 weren’t grouped because the words’ library writes over :count; and “36 didn’t arrive” sat beside “none failed”, since an undelivered text keeps its charge.The plan. Sender names asked for at id (NU4), products’ e-mail through Nuntius (NU5), the shared block list and unsubscribing (NU6), then a fresh reader’s review (NU7).