Forty-three commits landed on the working branch since the last entry. The gym side moved from a record that could be claimed in principle to one a gym can prove is its own, and the console’s last placeholder destination was replaced by a real screen.
A gym proves its record with an address at its own website
A gym’s record gained a website, given by whoever claims it, and a free mail provider’s domain is refused there. The website is reduced to its domain by a module that never fetches it, and a claim by mail then sends a code to a typed address at exactly that domain — never a subdomain mailbox, never a free provider. The code is 8 characters, good for 24 hours and 5 tries, stored as a hash rather than as itself, and spent once.
The email carries no free text at all: it names the gym by the website the address sits at rather than by a name somebody typed, and the local part of the address is checked against an allow-list, because it reaches the mail server verbatim. Sends are bounded four ways — five an hour per identity and per address, 20 a day per identity, 200 an hour for the whole deployment, and every destination domain with an allowance of its own.
The proof is automatic, the grant is always a person’s
The question raised against our own proposal was the useful one: a claimant types the website, so an automatic method would let anybody who owns a domain take an unclaimed gym. The ruling splits the two halves. The proof is checked by machine — the file found on the website, or the code read at an address on its domain — and it raises a notice. The grant is a person’s, every time: the function that could once answer allowed now answers a person decides. What that person sees is the domain the proof was made at, never the address and never the claimant.
The way in, on screen
The sign-in screen draws its two doors, password and passkey, as peers: neither carries the emphasis at any width. Both halves share one refusal, and the server answers no state either, so the defence against finding out which addresses exist holds on the wire as well as in the wording. A device with no passkey is not an error, and a cancelled prompt refuses nothing.
The second step is on screen too, opened on half a sign-in and closed by the code. A code accepted once is now refused for the rest of its window, as RFC 6238 §5.2 asks — spent at sign-in, at disabling the factor, at enrolment and at deleting the account alike.
The account screen, and every console destination now has one
A sixth table records a removed way in. Removal is a deletion rather than a flag, deliberately: a disabled row would leave a public key on disk for a key nobody can sign in with. The last remaining way in cannot be taken off at all.
Changing the password mirrors the route the instance already has, because a second way of changing a password is a second way of getting it wrong. The current password is required: a session at an unattended keyboard is not consent to replace a credential. Every other session of that identity ends, and the one that made the change survives.
The account screen itself is drawn at 360, 834 and 1180, clean on the automated accessibility pass at all three, with 14 browser tests. With it, the placeholder component is deleted — every destination in the console now has a screen of its own.
A reading pass that changed code
Reading each module’s documentation beside its source closed three things in the code. Revoking an invitation had been deleting the spent record it was written to keep; a used invitation is now kept, because it is the record of entry. The rule deciding whether an exercise may be prescribed only by a verified professional now reads the context it is handed. And the restore path carries all five families of records a backup holds, where it had carried one.
Two measurements moved with it: exports documented nowhere went from 73 to 8, and the design register — every request ever made of Design — reached the developer portal, which grew from 388 to 408 pages.
44 checks, 8,063 unit tests and 962 browser tests
The gate every change has to clear holds at 44 checks, and the suites read 8,063 unit tests and 962 browser tests green, up from 7,828 and 924 at the last entry. The standing counts, each written by the check that derives it: 55 routes counted against what the documentation claims, 27 of them rate-limited; 1,453 rule citations resolving by number, up from 1,407; coverage across 203 modules, up from 197; and the pinned build of Node at 22.23.2.
What comes next
The gym side now has an account, a record, a search, an address, two ways to prove a claim and a screen for each. What follows is the rest of the console’s screens sitting on top of that substrate.
If you would rather be told when GymAI opens than keep checking, the waiting list is on gymai.coach.
No comments yet
Nobody has written anything here. You can be the first.