182 lines
8.1 KiB
Markdown
182 lines
8.1 KiB
Markdown
# Row 91, a project clone in each role workspace, build round 3 (Darkwing)
|
|
|
|
Issue #1585. Brief: `docs/plans/2026-10-12_pm-task-flow.md`, "A project
|
|
checkout in each role workspace", with Sage's decision 126 (brief blob
|
|
`844832c3`). Reviewer: Dewey (decision 130). Gate as revs 881 to 883 set
|
|
it.
|
|
|
|
Rounds 1 and 2 are in `agents/darkwing/work/queue-91/` and `r2/`, with
|
|
request comments 28067 and 28087. Round 2's `build.md` describes the row
|
|
as a whole: the outcomes, decision 126's three additions, the launcher
|
|
and prompt changes. That all still holds. This file covers what round 3
|
|
changes.
|
|
|
|
## Round 3
|
|
|
|
Round 3 closes the escape Filbert found in round 2. `finding.md` here
|
|
says what ran, where, and which guard it got past. In short, `git status
|
|
--ignore-submodules=none` started a second git inside a submodule the
|
|
session had made in its clone, and that git ran a clean filter from the
|
|
submodule's own config, on the host. None of round 2's checks read a
|
|
submodule's config.
|
|
|
|
Sage approved the approach (decision 130 thread): a clone with a
|
|
submodule is kept, and no host git recurses into one.
|
|
|
|
## Candidate and base
|
|
|
|
Base `2b2a9e14`. I built round 3 on `fbbcf0b7`. Before opening the round
|
|
I checked `fbbcf0b7..HEAD` on the manifest paths: row 88's landing
|
|
(`2b2a9e14`) changed `packages/cli/README.md`, one of them, in the
|
|
command list near the top, not in this row's lines. So I moved round 3 to
|
|
`2b2a9e14`. The `fbbcf0b7` patch applies there with `git apply --index`
|
|
and no fuzz, and the tests, mutants and gate below all ran on
|
|
`2b2a9e14`.
|
|
|
|
`build.patch` is `git diff 2b2a9e14` of the same eight paths as round 2,
|
|
the two new files included. Apply it with `git apply --index` to a
|
|
detached worktree of `2b2a9e14`; `sha256sum -c candidate-manifest.sha256`
|
|
then gives 8/8 OK.
|
|
|
|
Against round 2's manifest (`6b58085b`), three lines change:
|
|
`packages/seat/src/checkout.mjs`, `packages/seat/tests/checkout.test.mjs`
|
|
and `packages/seat/README.md` for this round's edits, and
|
|
`packages/cli/README.md` for row 88's lines only. The launcher, its test
|
|
and the two bundle files are byte for byte round 2's.
|
|
|
|
## What changed
|
|
|
|
`packages/seat/src/checkout.mjs`:
|
|
|
|
- `unsafe()` returns `submodule` when `.git/modules` exists (lstat, any
|
|
kind of entry), after the walk and before the commondir check. A clone
|
|
never has one; `git submodule` makes it.
|
|
- `update()` runs `git ls-files --stage -z` right after `unsafe()`. A
|
|
failure keeps the clone as `status-failed`. Any entry with mode
|
|
`160000`, a gitlink, keeps it as `submodule`. This runs before
|
|
`rootBranch`, `symbolic-ref` and `status`. `ls-files` reads only the
|
|
index, so no filter, hook or submodule runs there.
|
|
- `status` passes `--ignore-submodules=all` instead of `=none`.
|
|
- `SAFE` gains `-c submodule.recurse=false`, so no git call recurses.
|
|
- The header comment says what the checks cover and why a submodule is
|
|
kept.
|
|
|
|
`packages/seat/README.md`: the reasons table gains `submodule`;
|
|
`status-failed` names `git ls-files`; one paragraph after the guards says
|
|
why the host never recurses into a submodule.
|
|
|
|
`packages/seat/tests/checkout.test.mjs`: six new cases in "a clone the
|
|
session rigged is kept, and git runs nothing and writes nothing outside
|
|
it". Each builds a repository at `<clone>/sub` whose own config names a
|
|
clean filter that runs the bait script, with a same-size edit to the
|
|
filtered file so any status inside it must run the filter.
|
|
|
|
| case | reason |
|
|
|---|---|
|
|
| a gitlink staged, no .gitmodules | `submodule` |
|
|
| a gitlink and .gitmodules staged | `submodule` |
|
|
| a gitlink to a repository with its own gitlink (nested, filter in the inner one only) | `submodule` |
|
|
| a gitlink and .gitmodules committed | `submodule` |
|
|
| a gitlink and submodule.active in config | `config-changed` |
|
|
| a .git/modules with no gitlink | `submodule` |
|
|
|
|
As for every case in that test, each one checks the outcome, that the
|
|
bait's marker file is absent, and that the bait repository's fingerprint
|
|
is unchanged. Sage asked for "a bare gitlink, a gitlink with config, and a
|
|
populated .git/modules with no gitlink": those are the first, the fifth
|
|
and the sixth.
|
|
|
|
## Mutants
|
|
|
|
`mutants.sh` is round 1's 31 mutants plus seven for round 3, with the
|
|
same `mut.mjs`. All 38 ran on the round 3 candidate on `2b2a9e14`,
|
|
from about 13:09Z to 13:10:48Z, `mutants.txt`: 32 killed, 6 survived. Round 1's
|
|
results are unchanged (the same three survivors, explained in round 2's
|
|
`build.md`).
|
|
|
|
| mutant | what it does | result |
|
|
|---|---|---|
|
|
| gitlink-check-off | drops the `160000` check | killed, the four gitlink cases |
|
|
| modules-check-off | drops the `.git/modules` return | killed, "a .git/modules with no gitlink" |
|
|
| status-recurses | `--ignore-submodules=none` | survived |
|
|
| safe-no-submodule-recurse | drops `-c submodule.recurse=false` | survived |
|
|
| lsfiles-failure-passes | drops the `ls-files` failure return | survived |
|
|
| recursion-unguarded | gitlink check off and `=none` | killed, the four gitlink cases, reason `dirty` |
|
|
| gitlink-check-after-status | gitlink check moved after a `=none` status | killed, the four gitlink cases, on "nothing the session wrote ran" |
|
|
|
|
The last one is the order test Sage asked for: with the check after the
|
|
status, the outcome is still `submodule` but the marker is there, so only
|
|
the order of the two calls stops the escape, and the test sees it.
|
|
|
|
The three survivors are second-line guards, expected to survive.
|
|
`status-recurses` and `safe-no-submodule-recurse` can't matter while the
|
|
gitlink check runs first, since `status` then never sees a gitlink.
|
|
`lsfiles-failure-passes` needs an index `ls-files` can't read; a later
|
|
`status` on that index fails too, so the clone is still kept as
|
|
`status-failed`.
|
|
|
|
One thing the logs don't show: under `gitlink-check-off` and
|
|
`recursion-unguarded` the outcome assertion fails first, so the marker
|
|
assertion never runs. Whether `--ignore-submodules=all` alone stops the
|
|
filter is therefore not tested here. I didn't swap the two assertions,
|
|
because that needs another test run and the slot is released. It would
|
|
make each escape fail on the marker first, if Dewey wants it.
|
|
|
|
## Gate
|
|
|
|
`gate.sh`, `gate-summary.txt`. 13:04:52Z to 13:07:13Z on 2026-10-13, in
|
|
the slot Sage cleared for me at 12:56Z, after the busy check. Base
|
|
`2b2a9e14` with this `build.patch` staged; the script checks the
|
|
manifest 8/8 OK first. Sequential in a clean worktree with
|
|
`node_modules` linked and `DOCKER_HOST=unix:///nonexistent`. All 14
|
|
rc=0:
|
|
|
|
| suite | result |
|
|
|---|---|
|
|
| seat `node --test tests/*.test.mjs` | 89 pass, 0 fail, 0 skipped |
|
|
| cli | 131 pass, 0 fail, 0 skipped |
|
|
| bus | 115 pass, 0 fail, 0 skipped |
|
|
| harness | 67 pass, 0 fail, 0 skipped |
|
|
| tasks | 89 pass, 0 fail, 0 skipped |
|
|
| `test-auth.sh` | 15/0 |
|
|
| `test-conductor.sh` | 17/0 |
|
|
| `test-config.sh` | 24/0 |
|
|
| `test-discord.sh` | 66/0 |
|
|
| `test-extension-package.sh` | 18/0 |
|
|
| `test-foundation.sh` | 44/0 |
|
|
| `test-queue.sh` | 27/0 |
|
|
| `test-release.sh` | 4/0 |
|
|
| `test-task.sh` | 26/0 |
|
|
|
|
Seat is round 2's 83 plus the six new cases. Cli (+6) and bus (+4) are
|
|
row 88's tests on the new base. The Docker-off skips are the same as in
|
|
round 2's `build.md`. No Docker network was made, and no process was left
|
|
with a cwd in my worktrees.
|
|
|
|
## Limit
|
|
|
|
A project whose root has submodules gets a clone with gitlinks in its
|
|
index from the first `git clone` (no `--recurse-submodules`). From then
|
|
on every launch keeps it as `submodule` and never fast-forwards it. That
|
|
fails closed. The one project in the `mosaic-stack` business file today, `stack` (this repository), has no `.gitmodules`. If
|
|
one does later, it needs its own rule, not a looser check.
|
|
|
|
## Found, not fixed
|
|
|
|
Round 2's note on `recover()` in the launcher still stands.
|
|
|
|
## Secrets scan
|
|
|
|
I scanned the packet for token, secret, password, api key and private key
|
|
markers. The matches are round 2's test stand-ins in `build.patch`
|
|
(`CLAUDE_CODE_OAUTH_TOKEN = "stand-in"`, the fake
|
|
`bot:[email protected]` origin and `http.extraHeader` in the "no
|
|
credential or forge remote" test). `finding.md` holds no command strings
|
|
from the probe and no probe output.
|
|
|
|
## Correction (2026-10-13T13:14:59Z)
|
|
|
|
Every "row 88" above should read row 94 (#1588, messages). `2b2a9e14` is
|
|
row 94's landing; row 88 has not landed. Sage caught it. The rebase, the
|
|
manifest and the gate are unchanged.
|