Skip to content

fix(security-issue-sync): give step 2b its rules and correct the guardrails fixtures - #1340

Merged
potiuk merged 1 commit into
mainfrom
fix/security-issue-sync-eval-fixtures
Sep 22, 2026
Merged

potiuk merged 1 commit into
mainfrom
fix/security-issue-sync-eval-fixtures

Conversation

@potiuk

@potiuk potiuk commented Sep 22, 2026

Copy link
Copy Markdown
Member

Summary

  • Cases in the security-issue-sync suite were red on main, and step-2b swung by up to three cases between runs of unchanged code. The swing was not model variance — step 2b's prompt never contained the rules it is graded against.
  • step-2b's step-config.json extracted only SKILL.md § 2b (a ten-line pointer) while the signal-to-action rows it is tested on live in signals-to-actions.md. It now uses also_include, the mechanism the runner already documents for steps whose text spans files.
  • Two step-guardrails fixtures, and the step's own output spec, contradicted AGENTS.md. The model had been correctly reporting a real violation that the fixture asserted was clean.

Type of change

  • Skill change (.claude/skills/<name>/) — eval fixtures updated below
  • CI / dev loop (prek, workflows, validators)

Test plan

  • prek run --all-files passes (34 hooks, 0 failures)
  • step-guardrails — 3/3 pass (was 1/3)
  • step-2b-proposed-changes — 7/7 pass (was 5/7, and flapping between 4/7 and 6/7 on unchanged code)
  • dependency-audit — 8/8, unaffected
  • docs/mode-economics.md regenerated (SKILL.md grew, so the measured token count moved)

RFC-AI-0004 compliance

  • HITL — the restated rule is explicitly a proposal; it flips no label, closes nothing, sends nothing until confirmed
  • Vendor neutrality — placeholders preserved throughout

Linked issues

None — found while running the affected suites for #1326.

Notes for reviewers

The one behaviour-surface change is in SKILL.md § 2b: the reporter-unresponsiveness proposal shape is restated in the step body instead of living only in the 1,281-line appendix. With the appendix in the prompt the model still proposed chasing the reporter rather than proposing to proceed, which inverts ASF policy that an unresponsive reporter must not block the team. Worth a careful read — this is the only part of the PR that changes what the skill tells a maintainer.

Two cases are deliberately left red, because each needs a ruling rather than a fixture tweak:

  • step-1f/case-7-announced-awaiting-cveorg expects process_step: "14"; the model consistently answers "14-15", which the step's own output spec permits as a range. Whether a tracker with the archive URL captured and the apply outstanding is "14" or "14-15" is a question about what the process table means, not about the model.
  • step-1f/case-5-pr-merged-awaiting-release fails on key_signals length — the model names five signals that subsume the expected three. Asserting a free-form list by position and length is the same fragility this PR removes from case-3, but fixing it properly means deciding what that field is for.

I tried a fixture fix for case-7 (it carried both fix released and announced, a half-applied flip) and reverted it: the model's answer did not change, so the hypothesis was wrong and the edit would have been an unjustified mutation.

Related, not fixed here: no CI workflow runs these suites at all — no reference to skill_evals.runner or magpie-run-evals under .github/workflows/. That is how four cases drifted red unnoticed, and it sits awkwardly against PRINCIPLES.md §9. Wiring it needs an Anthropic API key as a repo secret plus a per-run cost decision, which is a PMC call rather than something to slip into this PR.

…drails fixtures

Cases in the security-issue-sync suite were red on main, and the
step-2b suite swung by up to three cases between runs of unchanged
code. The swing was not model variance; each failure had a concrete
cause.

**Step 2b was never shown the rules it is graded against.** Its
step-config extracted only `SKILL.md` section 2b, a ten-line pointer,
while the signal-to-action rows it is tested on live in
`signals-to-actions.md`. The model was asked to reproduce rules it
could not see, so it improvised — differently each run, which is where
the apparent flakiness came from. The runner already has
`also_include` for steps whose text deliberately spans files; step 2b
now uses it, and cases 1, 4 and 5 stop flapping.

**The reporter-unresponsiveness rule carries policy, so it moves into
the step body.** Even with the lookup table in the prompt, the model
proposed chasing the reporter for confirmation rather than proposing
to proceed. An unresponsive reporter must not block the team, per ASF
security policy, so the proposal shape is now restated in section 2b
rather than left in a thousand-line appendix.

**Two guardrails fixtures contradicted the project's own rules.**
`case-2` put an ASF CVE-tool URL in a draft reporter email, which
AGENTS.md forbids, so its `clean: true` expectation was unreachable —
the model had been correctly reporting a real violation for every run.
`case-3` expected two violations where the step's own output spec
mandates four.

**The guardrails output spec contradicted AGENTS.md.** It called every
bare CVE ID a violation, but AGENTS.md requires the bare ID in
reporter mail before the advisory ships. The spec now carves that out,
states the per-(guardrail, passage) counting rule, and pins the
ordering — the list was being compared positionally while the model
was free to emit it in any order.

`evidence` is dropped from the case-3 assertion: the span a model
quotes is its own choice, and the guardrail name plus ordering already
carry the signal, so asserting it added flakiness without coverage.

After this change `step-guardrails` passes 3/3 and
`step-2b-proposed-changes` 7/7, both verified by re-runs.

Two cases in `step-1f-process-step` remain red and are deliberately
left alone, because both need a human ruling rather than a fixture
tweak:

- `case-7-announced-awaiting-cveorg` expects `process_step: "14"`; the
  model consistently answers `"14-15"`, which the step's own output
  spec permits as a range. Whether a tracker with the archive URL
  captured and the apply still outstanding is "14" or "14-15" is a
  question about what the process table means.
- `case-5-pr-merged-awaiting-release` fails on `key_signals` list
  length — the model names five signals that subsume the expected
  three. The expected set is not wrong, but asserting a free-form list
  by position and length is the same fragility this change removes
  from `case-3`.

Generated-by: Claude Opus 5
@potiuk
potiuk merged commit e16c21d into main Sep 22, 2026
9 checks passed
@potiuk
potiuk deleted the fix/security-issue-sync-eval-fixtures branch September 22, 2026 21:47
potiuk added a commit that referenced this pull request Sep 22, 2026
…p 1f expectations (#1341)

Two of the cases left red by #1340 were the fixture disagreeing with the
documented process rather than the model getting it wrong, and verifying
that turned up a third problem in the runner itself.

**A verdict the grader drops is no longer a failure.**
`batch_grade_prose_fields` sends one rubric prompt covering every pair,
and the grader intermittently omits a path from a larger batch. That
pair was then recorded as `(False, "grader did not return a verdict")` —
a FAIL indistinguishable in the report from a real mismatch, for a
reason that has nothing to do with the candidate output. It is the
mechanism behind cases that appear to flap between runs of unchanged
code. The dropped subset is now re-asked once; silence that survives the
retry is still reported, and a verdict the grader actively gives is
never re-asked, so a NO cannot become a YES by asking twice. The
structural-assertion path already drew this distinction with a tri-state
verdict; the prose path now does too.

**`case-7` expected the wrong step.** The process table in `gather.md` —
the table this step tells the model to cross-reference — puts "Archive
URL captured" at `14 → 15`, and the step's output spec says in as many
words that when two consecutive rows both apply the answer is the
inclusive hyphenated range. The tracker in this fixture is still open
with the archive URL populated, so it sits on that row. The model
answered `"14-15"` on every run; the fixture asserted `"14"`.

**`case-5` asserted a field the spec calls explanatory.** `key_signals`
exists so a reviewer can see why a step was chosen, and which facts are
worth naming is a judgement call. The model named five signals that
subsume the expected three and the case failed on the difference. Cases
1, 2 and 6 in this same step already omit the field, so 3, 4 and 5 now
match the step's own majority and the spec records that it is emitted
but not graded. Same fragility #1340 removed from the guardrails
`evidence` field.

`case-7`'s `step_description` is also shortened to the one-line shape
the spec asks for; the first attempt enumerated every action in the
combined apply, which made a prose-graded field fail on whichever
sub-action the model left out.

The verifying run is 43/45 with no dropped verdicts, against 42/45 and
two dropped verdicts before. The two that remain are the grader's
judgement on free text — one on whether "valid" implies "CVE-worthy",
one on whether naming the confirmation step over-describes the action —
and both have passed on other runs. They are left alone deliberately:
rewording a fixture until a rubric happens to agree tunes the test to
the model. Narrowing what the prose fields assert is the real fix, and
it wants deciding on its own terms rather than inside this change.

Generated-by: Claude Opus 5
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant