1. The prompt
My whole prompt, tidied up:
Add passkey login. Default to email or passkey; keep username and password, but discourage it.
No ticket, no design doc.
2. The agent drafts the spec
Before writing any code, the agent read the existing auth code. Rackd2 already had a magic-link flow, so email login could reuse it. Next it chose a library: SimpleWebAuthn instead of a full auth framework. Then it wrote the behaviour as Gherkin, in my words rather than in code.
It came back with 15 scenarios and two open questions:
- Should email login stay a link or become a 6-digit code?
- How long should "Not now" on the passkey offer be remembered? (proposed: 30 days)
3. I approve
My entire review:
yeah great do it
I accepted both defaults: the magic link stays, and "Not now" is remembered for 30 days. I reviewed the spec and nothing else. This is the approved file, verbatim from features/web/login.feature:
Feature: Logging in without a password
Email links and passkeys are the easy way in. A password still works, but it is tucked away.
Scenario: The login page leads with email and passkey
When I open the login page
Then I see a "Continue with email" button
And I see a "Sign in with a passkey" button
And I do not see a password field
And I see a small "Use a password instead" link
Scenario: Logging in with an email link
Given I have an account with email "lifter@example.com"
When I ask for a login link for "lifter@example.com"
Then I am told to check my inbox
And a login link is emailed to "lifter@example.com"
When I open the login link
Then I am logged in
Scenario: An unknown email gets the same answer, but no email
When I ask for a login link for "nobody@example.com"
Then I am told to check my inbox
And no email is sent
Scenario: A login link works only once
Given I have an account with email "lifter@example.com"
And I have used my login link
When I open the same login link again
Then I am not logged in
And I am told "This login link is invalid or has expired. Request a new one."
Scenario: An expired login link is refused
Given I have an account with email "lifter@example.com"
And my login link is 16 minutes old
When I open the login link
Then I am not logged in
And I am told "This login link is invalid or has expired. Request a new one."
Scenario: Too many login links in an hour
Given I have an account with email "lifter@example.com"
And I asked for 5 login links in the last hour
When I ask for a login link for "lifter@example.com"
Then I am told to check my inbox
And no email is sent
Scenario: I am offered a passkey after logging in by email
Given I have an account with no passkey
When I log in with an email link
Then I am offered "Add a passkey for faster login"
And I can choose "Not now"
Scenario: Adding a passkey
Given I am logged in
When I add a passkey named "iPhone"
Then my settings list a passkey named "iPhone"
And I am not offered a passkey again
Scenario: Saying "Not now" is remembered for a while
Given I have an account with no passkey
And I chose "Not now" yesterday
When I log in with an email link
Then I am not offered a passkey
Scenario: Logging in with a passkey
Given I have an account with a passkey
When I sign in with my passkey
Then I am logged in
Scenario: Cancelling the passkey prompt
When I start signing in with a passkey
And I cancel the passkey prompt
Then I am not logged in
And I am told "Passkey sign-in was cancelled. Try again or continue with email."
Scenario: A passkey this site does not know is refused
Given a passkey that belongs to no account
When I sign in with that passkey
Then I am not logged in
And I am told "We don't recognise this passkey. Continue with email instead."
Scenario: Removing a passkey
Given I am logged in with a passkey named "iPhone"
When I remove the passkey "iPhone"
Then my settings list no passkeys
And signing in with that passkey is refused
Scenario: Logging in with a password still works
Given I have an account with email "lifter@example.com" and password "squat-heavy-1"
When I open the login page
And I choose "Use a password instead"
And I log in with "lifter@example.com" and "squat-heavy-1"
Then I am logged in
Scenario: A wrong password is refused
Given I have an account with email "lifter@example.com" and password "squat-heavy-1"
When I log in with "lifter@example.com" and "wrong-password"
Then I am not logged in
And I am told "Wrong email or password."4. The agent implements
I didn't read any of this. Here's a summary for anyone who wants to know what's under the hood:
- SimpleWebAuthn v14 (
@simplewebauthn/server+@simplewebauthn/browser) handles both registration and authentication. - Discoverable credentials (resident keys): "Sign in with a passkey" works without typing an email first. The authenticator says who you are.
- Single-use challenges that expire after 5 minutes. A challenge is deleted as soon as it's used, so it can't be replayed.
- Migration 0009 runs in Rackd2's SQLite-backed Durable Object. It adds a
passkeystable, awebauthn_challengestable and apasskey_prompt_dismissed_atcolumn on users, which stores the "Not now" answer. - Login page UX. "Continue with email" and "Sign in with a passkey" are the two big buttons. No password field is shown. "Use a password instead" is a small link.
- Passkey offer after email login. "Add a passkey for faster login" with a "Not now" option, remembered for 30 days.
- Settings. A Passkeys list shows each passkey's name and when it was added and last used. "Add a passkey" asks you to name the new one, and each passkey has a Remove button. A removed passkey is refused at sign-in.
5. Verification: why I don't need to read the code
This is the part that closes the loop. Each check is something I can trust without opening a diff:
- The spec wasn't touched. The file in the repo was compared with the approved draft using
cmp, and they're byte-identical. The agent can't "fix" a failing test by quietly changing the story. - Everything is green. 489 BDD tests and 31 unit tests pass, and
tscis clean. - Real WebAuthn, not a stub. The steps use a software WebAuthn authenticator that creates real attestation and assertion responses. SimpleWebAuthn verifies them exactly as it would in production, so the crypto path is actually exercised.
- The tests can fail. As a mutation check, the agent broke the implementation on purpose twice, and both times the scenarios you'd expect (and only those) went red. A suite that can't fail proves nothing. This one fails where it should.
- Live smoke check. After deploying, the live login page showed the new layout and the email and passkey endpoints responded as expected.
- Shipped in a single commit: 154e66a.
The last manual check
The software authenticator proves the server logic and the protocol, not how each browser and OS behaves. So I did the one manual step: I logged in by email, accepted "Add a passkey", and signed in with my passkey on a real device. It just worked. That was the first time I touched the feature, and it was as a user, not as a reviewer. If another device ever reveals an edge case, it becomes a new scenario first.
Takeaway
The time I spent on this feature was one prompt, a read of 15 plain-English scenarios, a five-word approval, and one real passkey sign-in. The spec is the interface between me and the agent. The checks (an untouched spec, a green suite, real crypto and a mutation check) are what let me skip the code review.