# Row 90 (#1584): The approved requirement ids as data, round 2 Owner: Dewey. Reviewer: Filbert, then Sage's gate. Round 1 approved by Filbert (comment 28021, rev 826) on candidate `26d1fbdf…`, base `23338c7b`. Round 2 is the rebase that lead decision 125 asks for: row 63 landed first, as `b8539480` (rev 838), and row 90 now sits on it. No behaviour of row 90 changed from round 1. Base `b8539480`. `build.patch` is `git diff --cached --binary b8539480 --` the 21 paths in `candidate-manifest.sha256`: round 1's twenty plus `packages/seat/src/sandbox.mjs`. Apply it with `git apply --index` to a detached worktree of `b8539480`; the manifest then checks 21/21 OK. No commit from `b8539480` to `refactor` HEAD touches `packages/`, `scripts/`, `roles/` or `contracts/`. ## What changed from round 1 Three files. The other 18 have the same hunks as round 1 (compared per file with hunk line numbers ignored), `verbs.mjs`, `tools.mjs` and `tools.test.mjs` included: row 63 touched them too, but in other places. | File | Round 2 change | Why | |---|---|---| | `packages/tasks/README.md` | the `task.create` paragraph merges both sides (below); the other hunks are round 1's | row 63 changed the same paragraph | | `packages/tasks/tests/verbs.test.mjs` | the import line, a context line in round 1, now reads `import { world, sleep, SCOPES } from './world.mjs';` as row 63 left it; row 90's added lines are unchanged | row 63 changed the same line | | `packages/seat/src/sandbox.mjs` | `RUNNER_FILES` gains `"packages/tasks/src/requirements.mjs"`, one line | row 92 landed between the bases (below) | **The README merge.** At round 1's base the paragraph had one sentence on `requirement`. Row 63 added three sentences after it on `project`. Round 1 replaced the `requirement` sentence with the `requirements.json` text and added two sentences: `unknown-requirement`, and that `request` is the `request` field of a received message. Round 2 keeps both: round 1's `requirement` and `request` sentences first, then row 63's `project` sentences word for word, then the unchanged `task.created` sentence. The paragraph is rewrapped to the file's width. No word of either side is dropped or changed. **The sandbox line.** Row 92 (`57948a81`, #1586) landed after round 1's base. A role session's runner now runs in a bwrap sandbox that binds only `RUNNER_FILES`, the files on the runner's import walk, and `packages/seat/tests/sandbox.test.mjs` fails unless the list equals that walk exactly. The runner's `bus/src/client.mjs` imports `BusError` from `broker.mjs`, and row 90's `broker.mjs` imports `../../tasks/src/requirements.mjs`, so on `b8539480` with only the twenty paths a sandboxed runner can't load the module and dies at start. The rebased gate's first run showed it: seat 4 and cli 8 failing (the `RUNNER_FILES` walk test, and the launcher's PM, coder, kill and host-restart tests, whose runners ended `failed`). No line of either side conflicted, and a path drift check on the twenty paths was clean; only the gate found it. The sandbox line is the semantic fix (decision 127). `requirements.json` stays out of the sandbox: the runner never constructs a `Broker`, and only the `Broker` constructor reads the file. Row 91 (Darkwing, #1585, in build) doesn't touch `sandbox.mjs` or `RUNNER_FILES` and adds no file to the runner's import walk (Darkwing's reply to me, about 23:29Z). It does change `prompt()` in `packages/harness/src/bundle.mjs`, which row 90 changes too, on other lines; whichever row lands second reruns the rebase check. The rebase checks: row 63's six paths at `b8539480` are byte-identical to the scratch stack I rebased on (row 63 round 2's candidate on `cca5f23e`), and `git diff 23338c7b b8539480` on the twenty paths is exactly row 63's own diff on them. ## Gate (my run on the round 2 candidate) Two runs, sequential, in a detached worktree of `b8539480` with `node_modules` symlinked. `gate.sh` and `gate-summary.txt` (run 1) and `gate-rerun.txt` (run 2) are in the packet; the full logs are not. 1. The twenty paths, 23:15:51-23:20:59Z, manifest 20/20. Green: tasks 89, bus 111, harness 66, webui 101, business 60, control-board 124, and every `scripts/test-*.sh`: auth 15, conductor 17, config 24, discord 66, extension-package 18, foundation 44, queue 27, release 14, task 26 (`DOCKER_HOST` at a dead socket). Red: cli 112/120 (8 failing) and seat 29/33 (4 failing), the sandbox failures above. 2. With the sandbox line, the 21-path candidate, 23:26:31-23:26:50Z: seat 33/33 and cli 120/120, rc=0. Not rerun on the 21-path candidate: the other suites. The sandbox line changes only `RUNNER_FILES`, which `seat` and the cli launcher load; `harness` names `sandbox.mjs` in a comment only. Per decision 127, Filbert's review and Sage's landing gate run every suite on the 21-path candidate, `harness` included. Counts against round 1's gate (base `23338c7b`): tasks 79 to 89 (row 63's tests), webui 86 to 101, cli 115 to 120 and seat 27 to 33 (rows 77 and 92 landed between the bases). ## Mutants Round 1's 44 (`mutants.py`, the same swaps, run in a scratch copy of the twenty-path candidate against the `bus`, `harness` and `tasks` suites, each run in its own process group with a 300 s limit), 23:20:59-23:25:39Z: **44 of 44 killed**, none hung (`mutants.txt`). Afterwards the copy matches the manifest's twenty paths again. The sandbox line has no mutant of its own: the seat walk test fails when it is missing, as the first gate run showed. ## Round 1 follow-ups Filbert's three non-blocking test notes from round 1 stay follow-ups: the title check accepts `\r`; `in` for `Object.hasOwn` survives; and an allowlist holding only the first and last ids plus `REQ-TASK-1` survives. None is in round 2, which changes only what the rebase forced. Round 1's `build.md` (`agents/dewey/work/queue-90/build.md`) still holds the design, the decisions and the limits. Round 2 changes none of them.