Repository navigation
feat(auth): setup links tie the agent's fetch, login and key to the person's Final step panel - #917
Conversation
|
Review (GPT 6.1 Sol, xhigh): six fixed in 23c0af4, one dismissed
Checks: typecheck, lint and 94 affected test files (1,121 tests) pass locally. |
|
Follow-up from #918's security review: a deadlock this PR introduced, fixed in 4a03a91. The expiry fix above made Red then green:
|
|
Follow-up from #918's second security round: one more lock-order inversion, fixed in 2455397.
Red then green:
|
5756507 to
72daafa
Compare
…erson's Final step panel The panel could only notice the end of each step: any new computer of the person's, then a patch of theirs. Its setup line now carries a one-time setup link (llms.txt?s=...). The agent's fetch marks it picked up, llms.txt prints patchy login --setup, the first login it starts binds to it and a second contests it, and the mint records which key it made and who confirmed it. The panel follows those facts, with Get a new line when a setup can't finish. Events carry a separate setup id, so a person's setup reads as one funnel in PostHog. Co-Authored-By: Claude Opus 5.5 <[email protected]>
…imit pickups A setup line's login that runs out unanswered now ends expired, written before the login is deleted and shown on the panel from the login's deadline even when nothing polls. A mint after the line ran out ends expired instead of connected, and a contested line's bound mint still records its key and setup id. /llms.txt pickups spend a per-address minute's budget before reaching the database. The CLI says when a new setup line abandons a pending login, and links are 26 base32 characters. Co-Authored-By: Claude Opus 5.5 <[email protected]>
…tup lines start's cleanup wrote expired outcomes to setup lines before deleting their logins, the reverse of the poll and every answer, which lock the login first. Cleanup racing a poll of the same expired login could deadlock. It now locks the expiring logins first, in one order. Co-Authored-By: Claude Opus 5.5 <[email protected]>
Deleting a day-old line that an abandoned login still named made the foreign key's SET NULL lock the login after the line, the reverse of every login path, so pruning could deadlock the login's cleanup or poll. Such a line now waits until the login's own cleanup has run. Co-Authored-By: Claude Opus 5.5 <[email protected]>
2455397 to
d915399
Compare
## What changed Once the person's agent starts a login with the setup line, the Final step panel shows that login's confirmation right there, so the person never opens a second tab. The link the agent prints keeps working exactly as before. Closes #911. Part of #906. Stacked on #917. ## Look at - **The form is the device page's own.** `confirmForm` in `packages/auth/src/DevicePages.ts` is now shared by the device page and the panel. In the panel it reads "Name this computer", prefilled from the login's hint, with **Confirm** and **Not me**, and carries the setup link as a hidden field. - **The server decides, not the page.** A form with `setup` posts to the existing `POST /login/device`, which sends it to `DeviceLogins.confirmInline` / `denyInline`. - Confirm rechecks, under the login's lock then the user lock then the line's row lock, that the line is the signed-in person's, live, not retired, still `pending` (so not contested) and bound to this code. Anything else confirms nothing. - Every outcome goes back Home, where the panel shows where things stand. A refreshed sign-in replays nothing, and a form rendered for another account answers nothing. - It keeps the device route's same-origin check, account binding, rate limit and replacement warning. - Confirm locks the line before checking it against a fresh clock, so a wait for the lock can't outlast the line. - The panel's fragment can't be framed (`frame-ancestors 'none'`) and posts only to Patchy. It names the account it was rendered for, and the script reloads the page rather than swap in another account's form after an account switch. - **Not me settles the login by where it actually is**, in one transaction: - **Pending:** denies it. - **Confirmed, key not collected:** deletes it, so the terminal's next poll finds nothing and no key is made. - **Key already made:** says "That computer is already connected" and points to Your machines. It never says nothing was connected. The line ends `denied`, and the panel says "Nothing was connected. If you didn't just paste the line, you're fine: no computer can publish as you." with **Get a new line**. - **Polling never eats a half-typed name.** The panel's script leaves the form alone while it has focus or an edited name. | In the panel | After Confirm, key not yet collected | Connected | Not me | | --- | --- | --- | --- | |  |  |  |  | Where this differs from #911's wording: - Refusals (a stale account, an expired code, an emptied name) go back Home instead of rendering inside the panel. The panel then shows the current state, which is usually the same form or the fallback. The name field is `required`, so an empty name rarely reaches the server. - Not me ends the line `denied` instead of retiring it. The effect is the same: the line can't bind another login, and Get a new line starts over. - The "already connected" answer is a small page with a link to Your machines, not panel copy, because the panel is gone once a key exists. ## How it was verified - `pnpm exec vitest run` across `packages/auth`, `packages/portal`, `packages/sdk`, `packages/limits`, `packages/core`, the CLI login and the server's setup-pickup and migration tests: 491 tests pass. New ones cover: - confirming from the panel, and both entry points answering the same login; - a stale panel form once its line is contested, or for another account; - Not me for pending, confirmed-but-uncollected (the CLI's poll then gets nothing, so no key is made) and already-minted logins; - the panel rendering the form only for a live, uncontested, bound line. - `pnpm typecheck` and `pnpm lint` pass. - Screenshots above are from a dev instance (`pnpm dev up team`): an agent's login started with the line, answered in the panel. - GPT 6.1 Sol security review at xhigh, three rounds, **signed off** on a758add. It found clickjacking, an account-switch swap, two lock-order deadlocks (fixed in #917), an expiry-during-lock-wait approval, and two ways to block Not me. All are fixed with tests, red then green where it's a race; see the review comments. - The account-switch reload and Not me with an emptied name were checked in Chromium on a dev instance. ## Release impact - [ ] **Breaking** — requires action from anyone already running this - [x] **Not breaking** — no action required No schema change: an optional `setup` field on the device form, and new queries over #917's tables. - **What does a revert leave behind?** Nothing. A panel form open at the time posts a `setup` field the old route ignores, so it acts as an ordinary device confirmation. - **Does the release before it still work with that?** Yes. Made by Claude Opus 5.5 in Claude Code, running in T3 Code. --------- Co-authored-by: Claude Opus 5.5 <[email protected]>
## What changed This PR adds release 18's What's new entry, written from `b23e6ad`. It covers everything merged since release 17's `through` (`5dd94f3`), and it sets the `patchy` release to `0.18.0` in `packages/patchy/package.json` and `CURRENT_RELEASE`. With this release, onboarding ends in a Final step on Home that follows the person's agent through setup and lets them confirm it right there (#906). | Change | PRs | | --- | --- | | New: A Final step to connect your AI agent | #916, #917 | | New: Confirm your computer without leaving Home | #918 | | Improved: Setting up ends with your first page | #914 | | Improved: The logo takes you Home | #913 | | Behind the scenes: the first time someone opens a colleague's tool is counted, for activation | #915 | One merged PR gets no line: #905 is release 17's own entry. ## Look at - **The entry**, at the top of `releases` in `packages/core/src/whatsNew.ts`. Its labels are the pages' own: Final step, Later, Not me, Home, Patches. - **Rollback safety, from the live commit `a049a3c` to `b23e6ad`.** - No PR in the range is labelled `one-way`, and every PR answers both Release impact questions with "Yes". - Migrations 0026 and 0027 are additive: the `setup_prompt_sign_ins` and `setup_links` tables, and a nullable `device_logins.setup_link_id` with `ON DELETE SET NULL`. Release 17 never reads them. - `rollback-rehearsal` exercised both migrations on the landed heads: - #916: `Exercised rollback paths passed: a049a3c → 6037d86 → a049a3c → 6037d86, in 234s.` - #917: `Exercised rollback paths passed: a049a3c → d03a5bf → a049a3c → d03a5bf, in 217s.` - #918 and #915 change no migration or handler descriptor, so they had nothing to rehearse. - A rollback takes the Final step panel and setup links away. Home goes back to the old guide, and a CLI at `0.18.0` stays usable against release 17: `--setup` is only sent when `/llms.txt` prints it, and release 17's `/llms.txt` doesn't. ## How it was verified - `pnpm typecheck` and `pnpm lint` pass, and so do `packages/core`, `packages/api`, `packages/portal` and the skill lock test (245 tests), including `whatsNew.test.ts`, `schemas.test.ts` and `WhatsNewPages.test.ts`. - The live commit was pinned from `/healthz/deep`: `a049a3c`, release 17. The Deploy run updated last succeeded, and none is unfinished. - Each PR in the range was read, including its Release impact answers. All six landed with the reviewed trees: each squash left `main`'s tree identical to its PR's reviewed head. ## Release impact - [ ] **Breaking** — requires action from anyone already running this - [x] **Not breaking** — no action required This PR changes only the changelog text and the `patchy` version. As with every release, a CLI installed before this deploy is told to upgrade. - **What does a revert leave behind?** Nothing. People whose seen marker reached 18 would just see no new release. - **Does the release before it still work with that?** Yes. If this release misbehaves, roll back to `a049a3cf544ae89691eec547dea46773febfdf05` (release 17). Made by Claude Opus 5.5 in Claude Code, running in T3 Code. Co-authored-by: Claude Opus 5.5 <[email protected]>
Stacked on #916 (then #914, #913). Merge those first; the onboarding stack ships together.
What changed
The Final step panel could only notice the end of each step: any new computer of the person's, then a patch of theirs. Nothing moved for about a minute after the paste, and any old laptop counted.
The panel's line now carries a one-time setup link:
Set up Patchy using <instance>/llms.txt?s=<link>./llms.txtprintspatchy login --setup <link>.The panel follows those facts. When a setup can't finish, it says why and offers Get a new line. Events carry a separate setup id, so one person's setup reads as a single funnel in PostHog.
Closes #910. Part of #906.
Look at
packages/auth/src/SetupLinks.tsand migration 0027.device_logins.setup_link_idis nullable andON DELETE SET NULL.DeviceLogins.startbinds,denymarks denied, a login that runs out marks expired before it is deleted, and the mint records the key and the confirming user in its own transaction.token.mintedgainssetupId.SetupPickupport, whichapps/server/src/SetupPickup.tsprovides. The SDK gains no Auth dependency. Pickups spend a per-address minute's budget (rate.setupPickup.perMinute) before reaching the database. An unknown, expired, over-budget or failing link serves the plain/llms.txt, so setup never breaks.patchy login --setup <link>keeps the link in its pending login.--setupwith--completeis refused.How it was verified
pnpm exec vitest runacrosspackages/portal,packages/auth, the front door, the CLI's login,packages/api,packages/limits,packages/core, Patches and the server's migration record: 1,121 tests pass. New ones cover:token.minted'ssetupId;pnpm typecheckandpnpm lintpass.docs/API.mdanddocs/limits.mdare regenerated, and the skill's lock hash is refreshed.pnpm verifyand the rollback rehearsal. CI runs both.Release impact
The API field and the CLI flag are optional and additive. The migration adds a table and a nullable column.
setup_linkstable anddevice_logins.setup_link_id. Old code never reads them.--setupcommand would need the matching CLI, but/llms.txtinstalls the instance's own release first.Made by Claude Opus 5.5 in Claude Code, running in T3 Code.