227 lines
11 KiB
Markdown
227 lines
11 KiB
Markdown
# Row 77 (P19 #225): the Console runs a pinned copy, candidate packet, round 2
|
|
|
|
Author: Rocko. Reviewers: Darkwing (comment 27801) and Filbert (comment
|
|
27818), both "changes" on round 1 candidate `4927de66`. Gate: Sage.
|
|
Base: `04db738a` (`base.txt`), current `refactor`, which includes row 61
|
|
(`6c42344d`) and row 81 (`c9a5650e`). Row 81 touched
|
|
`packages/webui/README.md`; the round 2 change applied to it cleanly with
|
|
a 3-way apply. At freeze `refactor` is at `a3f6e9e6`: no commit after
|
|
the base touches this row's paths, `packages/tasks` or `packages/bus`,
|
|
and `build.patch` passes `git apply --check` on the checkout. The packet
|
|
is uncommitted. I made no commits, pushes or
|
|
Gitea calls, read no token, and started, stopped or installed no unit.
|
|
Tests use `--dir`, a stand-in `systemctl` and fixture copies only.
|
|
|
|
The round 1 packet is unchanged one level up (`../`). This directory
|
|
holds round 2 only; `build.patch` is the whole candidate against the base,
|
|
not a delta on round 1.
|
|
|
|
## Files (`files.txt`, `candidate-manifest.sha256`)
|
|
|
|
Round 1's eight files, with these round 2 changes:
|
|
|
|
| File | Round 2 change |
|
|
|---|---|
|
|
| `scripts/webui-service.sh` | `bus_on_copy`, called by `install`; `packages/tasks/src` and `packages/tasks/fields.json` in `COPY_PATHS`; directories read-only; the temp trap restores write before removing |
|
|
| `packages/webui/tests/service.test.mjs` | the stand-in answers the bus `list-units` and `show` calls; the bus-on-copy test |
|
|
| `packages/webui/tests/pinned-copy.test.mjs` | directory modes, `sed -i` and a new file refused, the rename-race cleanup test, `GET /api/task-fields` from the copy, a `.refactor` refusal, the README in the copy, `gc.auto 0` in the fixture repository |
|
|
| `docs/TOOLS.md`, `packages/webui/README.md` | the bus wording, the install refusal, read-only directories and removal, the rollback floor, the unpinned queue view, the update step not yet usable |
|
|
| `agents/rocko/work/queue-77/r2/mutants.py` | 34 mutants (round 1's 18 plus 16) |
|
|
| `agents/rocko/work/queue-77/byte-check.sh` | unchanged from round 1 |
|
|
|
|
Round 1's `agents/rocko/work/queue-77/mutants.py` is superseded by
|
|
`r2/mutants.py` and isn't in this candidate.
|
|
|
|
## Findings
|
|
|
|
### Darkwing F1, Filbert F1: a Console on a copy fails every human proof
|
|
|
|
The bus accepts a human proof only when `argv[1]` is its own
|
|
`human-cli.mjs` (`human.mjs:70`, `runtime.mjs:68`). A Console on a copy
|
|
spawns the copy's `human-cli.mjs`, so with the bus on the checkout every
|
|
bus call fails `human-required`.
|
|
|
|
Fix: `install --copy ID` refuses unless the bus unit runs from the same
|
|
copy (`bus_on_copy`, Sage approved the design). It reads the effective
|
|
`ExecStart` with `systemctl --user show UNIT -p LoadState -p ExecStart`,
|
|
because the live bus has a `pm.conf` drop-in and the unit file alone
|
|
isn't the effective value.
|
|
|
|
- With `--business ID` the unit is `[email protected]`. Without it,
|
|
every loaded `mosaic-bus@*.service` from `list-units --all` is checked.
|
|
- Every `path=` in `ExecStart` must lie under `<copy>/`. A path that only
|
|
shares the copy's prefix (`<copy>x/...`) refuses.
|
|
- It refuses when the unit isn't loaded (`not-found`), when no bus unit
|
|
is loaded, when `ExecStart` has no path, and when `list-units` or `show`
|
|
fails. Each refusal is one line and changes nothing.
|
|
- The check runs after `render` and before the running-state read, so
|
|
nothing is written on a refusal.
|
|
|
|
The bus runs from the checkout today, and `copy` doesn't include
|
|
`scripts/mosaic` (the bus entry point). So `install --copy` refuses on
|
|
the live system until a later row puts the bus on the copy. TOOLS.md and
|
|
the README say the update step can't be used yet and why. I didn't add
|
|
the bus paths to the copy: moving the bus is the later row (Sage: "Don't
|
|
move the bus yourself"), and row 85 (#1579, one-copy stack) is that
|
|
row.
|
|
|
|
TOOLS.md's "the bus child come from the copy" now says the bus child is
|
|
the human transport and that the bus checks its path.
|
|
|
|
Tests (`service.test.mjs`, "install refuses unless the bus unit runs from
|
|
the same copy"):
|
|
|
|
- refused: the bus on the checkout, with and without `--business`; the
|
|
prefix-only path; two units, one off the copy; `not-found`; no units
|
|
loaded; no `ExecStart` path; `list-units` failing; `show` failing
|
|
- accepted: the bus on the same copy, with and without `--business`
|
|
|
|
The exact-calls assertion in the existing install test now lists the bus
|
|
calls too.
|
|
|
|
### Darkwing F2, Filbert F2: row 61's task field table
|
|
|
|
`reads.mjs` imports `packages/tasks/src/fields.mjs`, which reads
|
|
`packages/tasks/fields.json` at request time. Both are in `COPY_PATHS`.
|
|
The comment says why and says the test serves from a copy: a missing
|
|
import fails it at load, and a missing file read at request time
|
|
(`fields.json`, `RELEASE`, `roles/`) fails the route that reads it, which
|
|
the test also GETs (Filbert's wording point).
|
|
|
|
The serve test GETs `/api/task-fields` from the copy and expects 200 with
|
|
a body equal to `packages/tasks/fields.json`. Without that GET, the
|
|
"copy misses fields table" mutant survived; with it, it dies.
|
|
|
|
### Darkwing 3, Filbert N1: read-only directories
|
|
|
|
`copy` now makes every directory 0555 (the top one included) as well as
|
|
every file `a-w`, so a writer that replaces by rename (`sed -i`, git,
|
|
editors) fails too. Because a read-only temp directory can't be removed,
|
|
the trap runs `chmod -R u+w` before `rm -rf`. TOOLS.md and the README give
|
|
the removal command: `chmod -R u+w DIR && rm -rf DIR`.
|
|
|
|
Tests (`pinned-copy.test.mjs`): every directory is 0555; `sed -i` on a
|
|
copied file fails and leaves it unchanged; writing a new file throws
|
|
`EACCES`. A new test makes `mv` lose the rename race (a stand-in `mv` that
|
|
creates the target and fails): `copy` reports `exists:`, and the copies
|
|
directory holds only that one entry, so no read-only temp directory
|
|
is left behind.
|
|
|
|
### Filbert N2: rollback floor (docs)
|
|
|
|
A copy from before row 77 holds a `cli.mjs` that refuses `--checkout`
|
|
("unknown option"), so a unit pointing at it exits 2 at start. I took the
|
|
docs-only option Filbert offered: TOOLS.md and the README say rollback
|
|
reaches only ids at or after row 77's landing. I didn't add a refusal to
|
|
`install`: a grep of the copy's `cli.mjs` would be a second source of
|
|
truth for the flag set. Before the next row lands, the update step can't
|
|
be used anyway (F1).
|
|
|
|
### Filbert N3: the queue view isn't pinned (docs)
|
|
|
|
By design (the brief), the queue read runs from the checkout. The README's
|
|
update-step paragraph and TOOLS.md say so: a landing on the queue read
|
|
shows at once.
|
|
|
|
### Filbert's three surviving mutants
|
|
|
|
| Survivor | Now |
|
|
|---|---|
|
|
| the commit's leading-character check deleted | killed: `.refactor` refusal case, expects exit 1 and "commit must be a commit id or branch name: .refactor" (mutant "commit may start with a dot") |
|
|
| `chmod 0755 "$COPY_TMP"` deleted, top directory 0700 | that line is gone; the directory-mode check covers the top directory (mutants "copy dirs stay writable", "copy subdirs stay writable") |
|
|
| `packages/webui/README.md` dropped from `COPY_PATHS` | killed: the copy test checks the README exists and is read-only (mutant "copy misses README") |
|
|
|
|
## Test flakes seen while building, and one fix
|
|
|
|
- Round 2's first full run after the mutants had one failure whose name I
|
|
didn't capture. To find it I ran the suite five more times with TAP
|
|
output. That turned up three failures:
|
|
- `bus-browser.test.mjs` "S5 pages in a browser": a 27 s condition
|
|
timeout.
|
|
- `bus.test.mjs` "humanCall kills a transport that runs past its
|
|
timeout": the pid file was missing.
|
|
|
|
Both are timing failures in files this row doesn't change, seen under a
|
|
load average of 20-30.
|
|
- The third was this row's own: the cleanup of the new pinned-copy
|
|
`tmp` helper (`chmod -R u+w`) failed with ENOENT under the fixture
|
|
repository's `.git/objects`. The fixture commits thousands of loose
|
|
objects, which starts a detached `git gc --auto`, and gc deletes
|
|
objects while chmod walks. I set `gc.auto 0` in the fixture
|
|
repository rather than making the cleanup ignore errors.
|
|
|
|
After that, six consecutive full runs at the earlier base `08941d8b`
|
|
were 97 pass, 0 fail (row 81 adds two tests at `04db738a`).
|
|
|
|
## Mutants (`mutants.py`, `out/mutants.txt`)
|
|
|
|
`python3 mutants.py <tree>` applies each mutant alone to the candidate
|
|
tree and runs `service.test.mjs` and `pinned-copy.test.mjs`. 34 mutants,
|
|
all killed. The 16 new ones:
|
|
|
|
- install skips the bus check
|
|
- bus path prefix only
|
|
- `not-found` accepted
|
|
- `show` failure ignored
|
|
- `list-units` failure ignored
|
|
- no loaded bus accepted
|
|
- no `ExecStart` path accepted
|
|
- only the first bus unit checked
|
|
- `--business` ignored for the bus
|
|
- copy misses tasks src
|
|
- copy misses the fields table
|
|
- copy misses README
|
|
- commit may start with a dot
|
|
- copy dirs stay writable, copy subdirs stay writable (`-maxdepth 0`), and
|
|
the trap leaves a read-only temp directory
|
|
|
|
The run in `out/mutants.txt` is on the candidate at `04db738a`.
|
|
|
|
## Byte check (`out/byte-check.txt`, `../byte-check.sh`)
|
|
|
|
Round 1's script, base `04db738a` against the candidate: `render`
|
|
without `--copy` gives the same unit and exit code for all 8 flag sets,
|
|
and the template body is unchanged.
|
|
|
|
## Gate (`out/gate/`, `out/gate.sh`, `out/after-gate.sh`)
|
|
|
|
Sequential, in the scratch worktree at `04db738a` with the candidate and
|
|
`node_modules` linked, full output kept per suite, load average 17-27:
|
|
|
|
| Suite | Result |
|
|
|---|---|
|
|
| webui node (`packages/webui/tests/*.test.mjs`) | 98 pass, 1 fail (below); rerun 99 pass, 0 fail |
|
|
| test-auth | 15 passed, 0 failed |
|
|
| test-conductor | 17 passed, 0 failed |
|
|
| test-config | 24 passed, 0 failed |
|
|
| test-discord | 66 passed, 0 failed |
|
|
| test-extension-package | 18 passed, 0 failed |
|
|
| test-foundation | 44 passed, 0 failed |
|
|
| test-queue | 27 passed, 0 failed (the canonical-root checks skip in a worktree, as always) |
|
|
| test-release | 14 passed, 0 failed |
|
|
| test-task | 98 passed, 0 failed |
|
|
|
|
The gate's one webui failure: `bus.test.mjs` "humanCall speaks the human
|
|
transport protocol without blocking the server" got `outcome-unknown`
|
|
where it expects `invalid-response`. That call has a 1500 ms timeout
|
|
around spawning a node child, and the load average was 27. Neither
|
|
`bus.test.mjs` nor `packages/webui/src/bus.mjs` is in this row's diff.
|
|
After the gate, `out/after-gate.sh` ran the whole webui suite again (99
|
|
pass, 0 fail) and `bus.test.mjs` alone five times (all pass). The only
|
|
failure marker in `out/gate/` is that one, in `node-webui.txt`.
|
|
`rocko-r77_default` was removed after the Docker suites.
|
|
|
|
The TOOLS.md update-step paragraph was reflowed during the gate
|
|
(whitespace only, prose); no suite reads `docs/TOOLS.md`. The mutants and
|
|
the rerun ran after the reflow.
|
|
|
|
## For the reviewers
|
|
|
|
- The live unit and the live bus are untouched. The live Console can't
|
|
take this change through `install --copy` until a later row moves the
|
|
bus; until then it stays on its current unit.
|
|
- The design decision with the most weight: an install refusal rather
|
|
than a Console that spawns the checkout's `human-cli.mjs`. The latter
|
|
would keep proofs working but put an unpinned file in the pinned
|
|
Console's trust path.
|