Step 9c marker contract: reconstruct the files-run list from junit, never by re-running the selector
Highlight text on any card (body, plan, or an event) to anchor a comment, or leave a whole-task comment here. Mention @claude to summon a reply.
No comments yet.
kind: infra · surfaced from the #2270 Step 9c gate, 2026-08-21.
Goal
Make the Step 9c epm:test-verdict marker contract name a VALID reconstruction source for its
required "the files run" field, so an agent following the contract literally cannot record a
file list that differs from what the gate actually executed.
The gap
.claude/skills/issue/steps/13-step-9.md specifies that the epm:test-verdict v1 note records
"scope used (touched/full), the files run, the gate timeout bound used ..., pass/fail
counts, and ALL selector stderr diagnostics". It does not say WHERE the file list comes from at
verdict time, and the two obvious sources are not equivalent:
- Re-running
select_step9c_tests.py --files-onlyis INVALID. Its diff base is the FETCHEDorigin/main, which moves. On #2270 the gate launched against a 187-file selection and ran for 1h04m; a re-run of the selector ~10 minutes after the verdict returned 136 files. An agent that composes the marker by re-running the selector therefore records a list that is neither what ran nor a superset of it — a durable, confidently-wrong audit record, and precisely the class the marker exists to make auditable. - Valid sources, both already produced by the existing recipe and requiring no new
instrument: (a) the junit XML at
/tmp/step9c-junit-issue-<N>.xml— the record of what pytest actually collected (distincttestcase/@file, falling back to@classname); (b) the launch argv the step-1b identity-verify line already prints into the launcher's own output.
On #2270 the drift was caught only because the appended list came back 136 lines against a recorded 187 and the mismatch was visible. Nothing in the contract forces that comparison, so the same composition silently produces a wrong list whenever the two counts happen to be closer — or when no one thinks to check.
Proposed fix (single-file, prose-only)
In .claude/skills/issue/steps/13-step-9.md, in the paragraph beginning "The
epm:test-verdict v1 marker note records:", qualify the "the files run" item with one clause:
reconstruct the list from the junit XML (or the recorded launch argv), NEVER by re-running the
selector — the selector's diff base is the fetched origin/main and moves under a
multi-hour gate, so a re-run is not a reconstruction of what ran. Cite the #2270 measurement
(187 at launch vs 136 ~10 min after the verdict) so the reason is legible rather than
asserted.
Consider the same clause wherever else a post-hoc "what did the gate run" question is answered — the Step 10d gate blocks reference step 1b's recipe and inherit the same hazard.
Acceptance criteria
- The Step 9c marker-contract paragraph names junit / recorded-argv as the reconstruction source and explicitly rules out a selector re-run, with the #2270 counts cited.
- A prose-pin test asserts the clause is present (the existing
tests/test_issue_skill_*.pyfamily is the natural home), so the guidance cannot silently rot out. - No behavioral change to the selector, the gate recipe, or the compare — this is a contract-clarification round.
Provenance
Surfaced by the #2270 orchestrator while composing that task's epm:test-verdict note: the
187-file gate list was appended by re-running the selector, came back 136, and was rebuilt from
the junit XML before posting. question_relation: substantially-different from #2270's own Goal
(that task changes pod.py terminate selector semantics; this one changes a SKILL prose
contract), so it is filed rather than folded.