# Row 227, Case 2 C2-14: end-to-end acceptance on a scratch root (#1728) Owner Gizmoduck (sage-3581, 10-23), reviewer Dewey, who also runs the script. Brief: `docs/plans/2026-10-20_case2-rows.md`, section "C2-14 end-to-end acceptance on a scratch root", read at 45dca69f6. Branch `gizmoduck/row227` on 45dca69f6. ## Files - `scripts/test-case2.sh` (new). The entry point: syntax check, one driver run, one counted line per step, an order check on the step ids, and a check that the made-up token is in no driver output. - `scripts/case2/e2e.mjs` (new), its fixture. Every step runs in one node process, because her remote is row 199's fake Gitea over TLS with a scratch CA, and it lives only as long as the process that started it. `checkMapping` takes `https://` remotes only (`packages/library/src/mapping.mjs:46-48`), so a plain file remote isn't an option. The driver reuses the test fixtures already on refactor and adds none of its own: `packages/seat/tests/fake-gitea.mjs` (`scratchCa`, `fakeGitea`) and `packages/library/tests/helpers.mjs` (`BASE`, her layout's tree, `MARKER`, `git`, `mappingEntry`). ## Two stand-ins 1. **The human.** `human-cli.mjs` proves a human shell and refuses any caller with an agent among its ancestors (`packages/bus/src/human.mjs`; `packages/cli/tests/access.test.mjs:3-4,285-286` pins the refusal). Dewey and I both run under an agent, so a shell driver of human-cli would refuse every human verb. The driver binds the human in-process with `broker.bindHuman`, as the bus tests do (`packages/bus/tests/library-sync.test.mjs:27`). The bus checks after the bind are the real ones. 2. **The fake harness.** No launcher and no model. The driver binds the creative instance's run on her thread (`bindLaunch` with `thread`), claims it, receives her message and answers in reply to it. That covers the thread routing and the companion's messaging lists (`roles/companion.json`), and leaves the launcher out. If Dewey wants the launcher in the path, `fakeHarnesses` (`packages/cli/tests/helpers.mjs:133`) is the way in. That is a bigger fixture, and I'd rather take it with C2-06's own fake harness (brief C2-06) than build a second one here. ## Steps The ids are the driver's. "Waits on" names every row whose code the step needs and that isn't on refactor at 45dca69f6. A waiting step prints `skip waits on row : `, counted apart from pass and fail. When its rows land, the step gets written and its skip line goes. | Step | Brief text | State | | --- | --- | --- | | S1a | bare repo in her layout | runs: classify on the fixture mapping (canon, manuscript, playground, unmapped, second universe) | | S1b | with a supporting folder | waits on 283 (C2-01d, supporting folders and the editor field) | | S2a | it syncs | runs: `library.sync` over https from the fake Gitea, served is her head | | S2b | | runs: `library.file` reads canon and playground at served, refuses an unmapped note `project-read-path` | | S2c | | runs: her fast-forward push is served by the next sync | | S2d | | runs: the token is under no file of the data root, the mirror included | | S3a | asks a creative agent in its thread | runs: project, thread on instance `bean` (companion), `thread.send` | | S3b | through a fake harness | runs: the fake harness receives it and its reply is stored in her thread, `in_reply_to` her message | | S3c | stores a reply whose cites verify | waits on 216 (C2-05a lookups) and 217 (C2-06 guard and page store) | | S4 | refuses a playground cite with `"as": "canon"` | waits on 217 (`playground-cited-as-canon`) | | S5a | holds a force-push as `library-diverged` | runs: refused, served unchanged, `diverged.commit` is the forced tip, the refusal recorded | | S5b | | runs: `accept` of a stale commit refuses `library-accept-stale`, `accept` of the tip serves it | | S6 | fixture-grant write, one commit with the trailer, notice in her thread | waits on 283, 285, 286, 288 | | S7 | story-path write refuses `library-write-story-path`, ref unchanged | waits on 283, 288 | | S8 | her revert lands as one commit | waits on 288 | | S9 | her `library.file.save` lands as one commit, her `editor` as author | waits on 283, 288 | | S10 | the Console renders the reply and the notice | waits on 220, 225 | Row notes: - Sage's in-flight list was 216, 217, 220, 225, 226, 283, 285 and 286. Rows 284, 287, 288 and 289 also touch these steps, and the brief's "After" names the Q226 rows (told Sage in gizmoduck-0419). I mark a step with the rows its code needs, not every row that touches it: - 288 (C2-02) is the write verb, the notice, the revert and `library.file.save`, so S6-S9 all name it. - 287 (C2-21, the grant executor) isn't named on S6, because the brief asks for a fixture grant. - 284 (C2-05c) adds supporting docs to the lookups. S3c cites canon, so it waits on 216 and 217 only. - 289 (C2-11b) is the Console library page. S9 is the verb, which 288 owns. If Dewey reads S9 as the page, 289 joins its list. - 226 (C2-13, install script and precheck) gates no step. A scratch root isn't an install. - PRD 1.17 (row 308) made story files DOCX. The brief predates it and the fixture tree is Markdown. S1-S5 read that tree, which is what the landed reader takes. S6-S9 take the file shape 288 lands with. ## Choices - One driver process, not one per step: the fake Gitea's port and CA would change with each process, and the library entry's remote is fixed at bus start. - The bus timer stays on with a one-hour period, since `startBroker` needs `librarySync` to carry the CA's `command`. Its pass at start can hold the mirror, so a step's sync retries `library-sync-busy` for up to about 10 s. - The refusal check opens the bus store read-only (`node:sqlite`, `readOnly: true`). The runtime hands back no store, and no bus verb lists the events. - Git in the driver runs with `GIT_CONFIG_GLOBAL=/dev/null` and `GIT_CONFIG_NOSYSTEM=1`, so a caller's hooks, signing or credential helper can't reach it. `helpers.mjs` `git` already passes the identity and `core.hooksPath=/dev/null`. ## Carry for Dewey - The launcher in the path waits for C2-06's fake harness (row 217). Sage accepted both stand-ins, labelled, in sage-3608. When 217 lands, S3b moves to its fake harness behind the launcher. ## Not yet run Darkwing's re-pin window holds the slot (sage-3581), so as of this draft no node ran on it: no `node --check`, no driver run, no suite. `bash -n scripts/test-case2.sh` passes. The first run waits for Sage's CLEAR, and the runs go below. ## Runs