Public: anyone can read this; the crew can change it.
The shelf and search ask who may enter every private app in one read: main green on its three N+1 guards #868
Check · Review · Land · Live done — Landed and live in production.
Main is red on three N+1 tests in test/surfaces-adversarial.test.ts. Work in progress; a full description follows.
shipped production is serving it deploy e71d959
Main (ad0830b) was red on three N+1 guards in test/surfaces-adversarial.test.ts. I recorded every SQL statement of the small and the large run and compared them by shape.
1. /apps (25 → 74 queries). The shelf asked canEnterApp once per app row (#782's sealed row). On an org-owned private app, each call reads the org owner (SELECT * FROM user) and the viewer's team permission. That is two queries per row. The fix:
enterableApps(src/core/repos.ts) iscanEnterAppfor a whole page in a fixed number of reads, clause for clause. A public app is open. A private one answers to whoever may write the repository or is on its crew, usingwritableRepoIds, the existing SQL twin ofcanWritewith the crew in it. Whennamed, it also answers to an active person the app names, using oneapp_memberread (appsNaming, new insrc/core/app_members.ts).- The shelf (
src/web/studio/shelf.ts) calls it once per render. Search'sappState(src/web/pages/search.ts) had the same per-row pattern (a repository read pluscanEnterAppper hit); it now reads one precomputed set built with the page'sApps.
2 and 3. The dashboard cards and needsYouCard (large ran one query fewer than small). Not a regression in the code under test. Since 3f66212 ("Sign-ins"), sessionUser stamps last_seen_at once per interval, so a session's first request writes one row its second does not, and the small run was always the first. Each of the three tests now spends that first request before counting, with a comment saying why. The access code is untouched by this part.
Access does not move. canEnterApp is unchanged and still answers every single-app surface. The batch must agree with it, and test/enterable-apps.test.ts (new, in GATE) proves it. It builds personal and org repositories, public and private, with public and private apps, with and without the crew, some naming people. It checks the batch against the per-row rule for every viewer: anonymous, site admin, owner, org writer, org reader, a named person, a suspended named person, a stranger and a crew agent, with named true and false. It also checks the matrix is not trivial (a stranger is refused every private app; a named person gets more with named).
Signatures: no existing function's signature changed. Two functions are new (enterableApps, appsNaming). The private appState in search takes the page's Apps.
Verified: one bun test run under the gate lock, 361 pass: surfaces-adversarial, private-app, app-members, fork-pull-approval, asks, asks-expiry, web-apps, search, enterable-apps, studio, reserved-names, shards, check-list and hollow. Typecheck is clean, with current main merged in.
🤖 Generated with Claude Code
Main is red on three N+1 tests in test/surfaces-adversarial.test.ts. Work in progress; a full description follows.
🤖 Generated with Claude Code