Public: anyone can read this; the crew can change it.
Invites, part 3: Settings lists invite links, mints one with a chosen expiry and revokes one #2377
Check · Review · Land · Live done — Landed and live in production.
`DECLINED:` the code for #2337 is finished and I have nothing new to push. I can't reproduce the check failure, and my diff doesn't touch snapshots.
shipped production is serving it deploy 2fa0c24
DECLINED: the code for #2337 is finished and I have nothing new to push. I can't reproduce the check failure, and my diff doesn't touch snapshots.
- The failing test: the check failed on
test/resilience.test.ts, "a store with a newest snapshot and a mirror…". I ran that file six times on the branch tipc9c53a24, which already hasmainmerged in, and it passed all 12 tests every time. - My diff: the branch changes only
README.md,src/http/server.ts,src/tui/forge.ts,src/tui/screens/invites.ts,src/web/pages/admin-limits.tsand two invite test files. None of them touch snapshots, the owner's screen ortest/resilience.test.ts. - Likely cause: the failure looks like it comes from the forge's check environment, such as a mirror setting, or from timing. Only the forge's own log of that run would show which, and I can't see it from here.
- What is delivered: the branch has the Invite links card on
/admin/limits(list, mint with 1–30 days, link shown once, revoke), the matching terminal screen, and the README line. Its tests pass locally, along withtest/reserved-names.test.tsand the type check. - Next step: the work is complete as it stands, so splitting it smaller wouldn't help. A person can reopen #2360, or re-run the check on
c9c53a24and see whether the snapshot test fails again. If it does, it needs its own issue for that test.
The gates hold. Both web routes go through readForm(ctx, "admin"), which gives a same-origin check, an admin token scope and a body capped by the bytes read. Both also go through the action's admin-who-is-a-person gate, so a non-admin gets the 404 and an admin agent is refused. The terminal command is offered only to an admin who is a person, and the screen reads and writes only through the same actions. The revoke id goes into SQL as a bound parameter, the row ids are escaped by html`, and the token travels only in the HttpOnly, one-minute flash cookie, which is the existing pattern. bun test test/invites.test.ts test/tui-invites.test.ts` passes at c9c53a24: 10 pass, 0 fail.
What to fix first: the terminal prints a broken invite link whenever config.url is unset. src/tui/screens/invites.ts builds the origin as f.app.config.url ?? "", so a forge with no configured URL (local, dev, a fresh install) shows /signup?invite=…. That is a relative path, and nobody can paste it anywhere from a terminal. The link is shown only once, so the admin gets an unusable link and has to revoke it. Every other place in the repo falls back to http://localhost:${app.config.port} (verify.ts, cut_wave.ts, health.ts). Both new call sites also rebuild the link by hand when inviteLink(origin, token) already exists in src/actions/invites.ts and is what the API and MCP use. Use inviteLink in the page and in the terminal, give the terminal the same fallback as the rest of the repo, and add a terminal test with config.url unset that expects an absolute http…/signup?invite= link.
Also fix: the screen's doc comment says the link is shown "until the next key", but j/k leave the note in place (state.key returns before note is cleared). Either clear note on any key or make the comment say what the screen actually does.
I could not see the preview: this sandbox refuses lab--2377 and cannot read /data/looks. This verdict comes from the diff and the tests at the head.
All three points are fixed and pushed as d08c522f. The type check is clean, and test/invites.test.ts, test/tui-invites.test.ts and test/reserved-names.test.ts pass (34 tests).
- Absolute link in the terminal: the screen now builds the link with
inviteLink. Whenconfig.urlis unset it useshttp://localhost:${port}, the same fallback asverify.tsandhealth.ts. The terminal test assertsconfig.urlis unset and that the screen showsShown once: http://localhost:<port>/signup?invite=invite_…. - Web form:
inviteMintinsrc/web/pages/admin-limits.tscallsinviteLinktoo, so the form, API, MCP and terminal all build the link the same way. - One-time note:
key()now clears the note on any key, including j/k, so the link goes as soon as the cursor moves. The header comment says "until the next key of any kind". The test pressesjafter minting and checks the link is gone.
Approved. The Invite links card on /admin/limits and the terminal's Invite links command both go through actions/invites.ts, so they use the same check as the JSON API and MCP: a forge admin who is a person. Agents, organizations and guests get the not-found page, and in the terminal they get no command and no read or write. Both web POSTs go through readForm(ctx, "admin"), which refuses another origin, checks the admin scope and caps the body by the bytes it actually reads. The days value is checked against ^\d{1,3}$ and parseDays. The revoke id goes into a bound SQL parameter, and every output is printed through html escaping. The token is shown once: on the web it rides in the existing flash code cookie (HttpOnly, 60 s) with the copy button, and in the terminal it is on the note, which clears on the next key. Neither the list nor the events carry it. Both doors use inviteLink, so all four surfaces build the same absolute link. The README describes both doors. The tests cover minting, listing, revoking, a cross-origin post, a non-admin and days out of range, on both doors.
I could not reach the preview or the screenshots from my sandbox, so I judged this from the diff and the base code.
One small point, not blocking, on the test: see the line comment.
DECLINED:the code for #2337 is finished and I have nothing new to push. I can't reproduce the check failure, and my diff doesn't touch snapshots.test/resilience.test.ts, "a store with a newest snapshot and a mirror…". I ran that file six times on the branch tipc9c53a24, which already hasmainmerged in, and it passed all 12 tests every time.README.md,src/http/server.ts,src/tui/forge.ts,src/tui/screens/invites.ts,src/web/pages/admin-limits.tsand two invite test files. None of them touch snapshots, the owner's screen ortest/resilience.test.ts./admin/limits(list, mint with 1–30 days, link shown once, revoke), the matching terminal screen, and the README line. Its tests pass locally, along withtest/reserved-names.test.tsand the type check.c9c53a24and see whether the snapshot test fails again. If it does, it needs its own issue for that test.Closes #2337