# Row 300 build notes (Launchpad): R6-N1, an HTTP-level test of the user-session option swap (#1807) Brief: docs/plans/2026-10-23_r6-followup-rows.md ยง Brief: R6-N1. Reviewer Duckworth, gate owner Sage. Base c13c337e1 (refactor, after row 294 landed at d3059c2c3). Branch launchpad/row300. ## Change Tests only. One new file, packages/webui/tests/user-option-swap.test.mjs. No product change and no existing assertion or fixture touched. - **Gate stubbed open.** The file registers an in-thread load hook (`registerHooks`, as thread-claims.test and library-cli.test do through `--import`) keyed on the one URL `serve.mjs?gate=open`. On that URL alone it prepends `return true;` to `userRoute`'s body, then imports `startServer` from it. Every other import of serve.mjs, and every relative import inside the copy, is the real module. If the gate's signature line is not in serve.mjs exactly once, the hook throws. A renamed gate then fails loudly, instead of quietly testing the closed gate. The test also asserts that the copy's `userRoute` answers true for `POST /api/bus/message`. - **Setup.** This is granted-user.test.mjs's setup, cut to what the test needs: the fake OP, the in-process broker for `demo`, createSso with the access hook, and `userOptionsFor` over alice's transport. Every owner option is a stub that records its own name. The owner transport records `['transport', verb, args]`. One list, `owner`, holds both kinds of owner call. - **Positive control.** The owner signs in and posts `/api/bus/message`. Exactly `['option', 'send']` must be recorded, so the route reaches `send` and the count works. - **The check.** Alice signs in and walks G4's owner routes, copied, plus two chat reads. No answer may say `owner-only`, so the gate really is open. After each route, every owner record must be `['transport', 'plugins', {}]`, the read mos-1394 allowed. The message route must reach the user transport (`message.send`), and every transport opened is alice's. Why a new file: granted-user.test.mjs's setup imports startServer statically. Passing the gate-open copy into it would change that fixture, and a fixture change goes to Mos. The route list and setup are copied, with a comment naming their source. ## Mutant M12: serve.mjs:689 drops `send` from the user-session destructure, so a user session keeps the owner's `send`. Killed when "R6-N1" reports not ok. Expected failure: on `POST /api/bus/message`, the owner record holds `['option', 'send']`. run1/mutants.py applies it (anchor exactly once), runs the new file alone, restores with git checkout and checks the tree is clean. ## Gate Duckworth approves the manifest. On Sage's CLEAR: run1/gate.sh does npm ci (root and webui), the new file whole, the webui suite whole, then M12. Logs go to /mnt/storage/scratch/launchpad-r300/run1, outside the tree. Not run yet. MANIFEST.sha256 lists the one new path. ## Run 1 (sage-3408 CLEAR, 12:05Z) npm ci root and webui were green. The new file was red on its first run (mos-1272, placed by me): `TypeError: Cannot read properties of undefined (reading 'set-cookie')` in signIn. When I cut down granted-user.test.mjs's `call`, I dropped `headers: res.headers` from what it resolves, so the sign-in found no cookies. The fix puts `headers` back, the same as granted-user.test.mjs:40. No assertion changed. The new file then passed alone (1/0). The fix is a new commit, and the manifest is re-hashed. The gate resumes at new-file on the new head. Run 1 resumed (TAG -r2) on db5122920, manifest db8be52ce895603e687985acfae29e53e3e8782ae945c3cded6cca36141bfa3b: - new file whole: 1 pass, 0 fail. - webui suite whole: 375 pass, 0 fail, 0 skipped, 0 cancelled, 58.2 s (limit 600). - M12 killed. "R6-N1" reports not ok at :151: `POST /api/bus/message: ["option","send"]`, where `["transport","plugins",{}]` is expected. The restore left the tree clean. Duckworth N1 (duckworth-0263, non-blocking): the setup passes no `upstream`, so the swap's `upstream = null` has no recorder here. The test proves the swap for the options it stubs. It proves nothing about `upstream`.