feat(evals): compute cross-model fixture saturation from committed results - #1465
Conversation
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars. ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #1465 +/- ##
========================================
Coverage 99.57% 99.57%
========================================
Files 127 128 +1
Lines 22539 22693 +154
Branches 2766 2788 +22
========================================
+ Hits 22443 22597 +154
Misses 96 96 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
…sults Refs #1461 This repository measures whether an eval corpus is broad (gitapex_check_dimension_coverage.py) and whether its assertions are well-formed (gitapex_lint_fixture_assertions.py). Nothing measured whether the corpus is hard, so battle-testing-a-skill's dimension 14 ("growth history (case count over time)") could be satisfied by a corpus every model already solves. gitapex_compute_corpus_saturation.py computes that from committed JSON alone -- no model invocation, no credential -- by counting, per fixture, how many distinct model_id results scored exactly 1.0. Report-only: exit 0 for any well-formed run directory regardless of the figures; exit 2 only for input it cannot parse. No threshold, no gating. Measured on the only committed multi-model run (results/2026-07-28-issue-500-phase1, 23 fixtures x 3 tiers): 5 of 23 saturated (21.7 percent), 18 of 23 discriminating (78.3 percent), and 2 uniformly hard -- scoring-axis-uncontrolled-speed-claim and tool-capability-verification-selection, both scored identically by all three tiers, flagged as assertion-defect candidates rather than as difficulty. Recorded in evals/evaluating-skill-quality/eval-status.md. Three responses per fixture is far below the ~90 per item Vania et al. use to fit an item-response model, so the report says plainly that this is a count over the tiers a run happened to include, never a difficulty or discrimination parameter. The Hypothesis property layer found a real defect while being written: a result file with an empty scores[] vanished from the model list, so a fixture only one of two models scored was reported as saturated across "every model". Fixed by registering a model before reading its entries; pinned by two example tests. Verified: 37 tests pass; 100 percent line and branch coverage on the new module; mypy strict clean; ruff clean; the two committed run directories reproduce the figures this issue's ACM cites as its proof method.
… reason Refs #1461 Three findings from this pull request's own independent review round, all confirmed against the code before fixing: 1. FixtureVerdict.spread was production surface no production code ever called -- only its own test did. Removed, per CLAUDE.md section 4: nothing beyond what the issue asked for. 2. That property's test was named test_spread_is_zero_when_every_model_ agreed while asserting 0.4 on two models that disagreed. Removed with the property it covered. 3. The NOT COMPUTABLE line stated "a cross-model rate needs at least 2 models" unconditionally, contradicting the counts printed beside it whenever two models were present but shared no fixture. The reason clause now names whichever condition actually failed, pinned by two tests -- one per branch. Verified: 37 tests pass; 100 percent line and branch coverage on the module (154 statements, 44 branches, 0 missed); mypy strict clean; ruff clean; both committed run directories still reproduce the figures the issue's ACM cites as its proof method.
970546f to
22b23bc
Compare
Standing down on
|
Summary
Adds
evals/scripts/gitapex_compute_corpus_saturation.py, a report-only tool that computes what share of a committed eval run's fixtures every model already solves, and records the resulting figure for the one multi-model run this repository has. This repository measured corpus breadth and assertion well-formedness but never corpus difficulty, sobattle-testing-a-skill's dimension 14 ("growth history (case count over time)") could be satisfied by a corpus that carries no information.Facts
All figures below come from committed JSON only -- no model was invoked, no credential is required.
Against the only committed run carrying more than one
model_id(evals/evaluating-skill-quality/results/2026-07-28-issue-500-phase1/, 23 fixtures xclaude-haiku-4-5-20251001/claude-opus-5/claude-sonnet-5):The two uniformly-hard fixtures are
evaluating-skill-quality-scoring-axis-uncontrolled-speed-claim(0.833333 on all three tiers) andevaluating-skill-quality-tool-capability-verification-selection(0.750000 on all three). Reported separately from the saturated list, as assertion-defect candidates rather than as difficulty.Against a single-model run (
results/2026-08-26-issue-1347-structural-identifier-portability/), the tool reportsNOT COMPUTABLE, names the 1 model found, and exits 0 rather than emitting a zero rate.New module at the current head: 100 percent line and branch coverage (154 statements, 44 branches, 0 missed), 37 tests.
The full
6761 passed in 101.69ssuite run was taken at the first head (970546f9). After the independent-review fixes below, the targeted 37 tests plus all 41 wired local gates were re-run and pass; the full suite has not been re-run locally since, and CI re-runs it on this head.uv run --frozen mypyclean on all three new files;ruff checkandruff format --checkclean..github/scripts/gitapex_gate_local_preflight.py:local preflight: all 41 wired gate(s) passed.A defect the property layer found while being written, disclosed rather than quietly fixed: a result file with an empty
scores[]array vanished from the model list entirely, because the model was registered lazily on its first entry. A fixture only one of two models had scored was then reported as saturated across "every model" -- unmeasured read as measured. Fixed by registering a model before reading its entries; pinned bytest_a_model_that_scored_nothing_still_counts_as_a_modelandtest_a_model_contributing_only_before_arms_still_counts_as_a_model. No example test in the first draft generated that shape.Assumptions
results/*/directory, not assumed.manifest.jsoncarries neithermodel_idnorscores;dispatch-trace-check.jsoncarries amodel_idbut noscores; the before/after gate records' overlappingafterentries agree exactly with their sibling-after.jsonaggregate (checked on the 2026-08-15 issue-1124 run: all five overlaps agree).Risk / blast radius
Very low. The module is new, imported by nothing, wired into no workflow, and registered in no
.gitapex/ssot.jsongate cluster -- it runs only when a human invokes it. It writes nothing and reads only committed JSON. The one existing file touched isevals/evaluating-skill-quality/eval-status.md, narrative bookkeeping that gates on nothing. No existing behavior changes.Rollback
Revert the merge commit (
git revert -m 1against it). Nothing depends on the new module, so the revert is self-contained; theeval-status.mdsection reverts with it.Verification
Issue #1461's Acceptance Criteria Map, restated in full with a Result column added.
evals/scripts/gitapex_compute_corpus_saturation.py, reading oneresults/RUN/directory and grouping per-fixture scores bymodel_idresults/2026-07-28-issue-500-phase1/; reproduces 5 of 23 saturated and 18 of 23 discriminatingtest_phase1_reproduces_the_saturation_figures_the_issue_cites. The risk is addressed in text: the module docstring and the printed report both refuse the IRT vocabulary explicitlymodel_idhas no cross-model comparison to make; a silent 0 would read as "no saturation"model_idcount; below 2, report not-computable with the run path and the count foundresults/2026-08-26-issue-1347-structural-identifier-portability/; reports not-computable, names 1 model, emits no ratetest_single_model_committed_run_is_reported_as_not_computable; a property test also asserts no percentage is ever printed in the not-computable branch. The independent review found the stated reason could contradict the printed counts and it now names whichever condition actually failed, pinned by one test per branchscoring-axis-uncontrolled-speed-claim(0.833333 on all 3 models) appears in that section and not in the saturated listtest_phase1_flags_uniformly_hard_fixtures_outside_the_saturated_list, which also asserts the set is a subset of the discriminating set. A second such fixture (tool-capability-verification-selection, 0.750000 on all 3) was found and is reported too. The report labels both as candidates, never as verdictsevals/evaluating-skill-quality/eval-status.mdas a summary plus a pointerevals/scripts/*.pymodule here carries pytest coveragetests/test_gitapex_compute_corpus_saturation.pycovering the multi-model, single-model, and uniformly-hard branchesgitapex_gate_evals_scripts_coverage.pyenforces for this directorytest_main_prints_the_report_and_exits_zeroandtest_main_exits_zero_on_a_fully_discriminating_run; a property test asserts exit 0 for every generated well-formed table. Exit 2 is reserved for unparseable inputChecklist
skills/*/SKILL.md, adocs/superpowers/specs/*.mddesign doc, a security-relevant skill, or a deterministic checker script (skills/*/scripts/*.py,evals/scripts/*.py,.github/scripts/*.py), a## Skill audit evidencesection discloses the required verdicts/waivers (see.github/scripts/gitapex_gate_skill_audit_disclosure.py)evals/*/split.md, that entry discloses a Transfer check line (see.github/scripts/gitapex_gate_transfer_check_disclosure.py) -- not applicable, nosplit.mdentry addedskills/*/SKILL.md's Stop-boundary bullets or named dispatch branches,evals/SKILL_NAME/tasks/*.yamlgained at least as many new fixtures (see.github/scripts/gitapex_gate_skill_branch_fixture_coverage.py) -- not applicable, noSKILL.mdtouchedSkill audit evidence
checker-script-adversarial-reviewis RAN. The round is thereviewing-an-artifactinvocation recorded under Independent review verdict below: five adversarial personas (correctness, blast-radius, reuse-and-simplification, convention, security) against this checker script's own diff, each candidate then put through the FABRICATED pre-check, an independent verification against actual behavior, and a counterfactual check. It produced three confirmed findings, all fixed in this PR before this line changed from NOT-RUN to RAN.defeat-test-disclosureis RAN, with the artifact to show for it.tests/test_gitapex_compute_corpus_saturation_properties.pyis a Hypothesis layer built specifically to defeat the classification logic rather than exercise its happy path: two of its properties are model-based (they recompute the intended partition from the generated table, so the module's own output cannot define correctness), and it found the empty-scores[]model-dropping defect described under Facts. The example layer adds nine loud-failure cases -- score conflict, boolean-as-score, string-as-score, missing and blankfixture_id, non-object entry, unparseable JSON, non-directory path, and blankmodel_id.No
skills/*/SKILL.mdis touched by this PR, sobattle-testing-a-skillandevaluating-skill-qualityverdicts are not applicable here.deterministic-gate-qualityis likewise not applicable: this module is registered in no.gitapex/ssot.jsongate cluster, wired into no workflow, and always exits 0 on well-formed input -- it is a report tool in the same class asgitapex_check_dimension_coverage.py, not a gate.Independent review verdict
Outer layer (GitHub-native reviewer): did not produce a review; disclosed, not equated with a pass. Anthropic's "Claude Code Review" GitHub App is not confirmed installed on this repository, so
request_copilot_reviewwas called forcopilot-pull-request-reviewer[bot]instead. It returned no output and, at the time of this write,pull_request_readreports zero reviews and zero review threads on this PR. Copilot's review is in any case Comment-only, with no pass/fail signal of its own -- materially weaker than the App's severity summary, not equivalent to it. The two third-party statuses that did report are not this layer and are recorded only for completeness:Devin Reviewposted "Full review skipped: trial expired and no credits remaining";CodeRabbitposted "Review skipped: manual review required for this OSS repository". Neither reviewed the diff.Inner layer (
reviewing-an-artifact,loweffort): ran to completion; 3 confirmed findings, all fixed; 0 remaining. Step 0 found no specialist deferral (this module is report-only and registered in no gate cluster, soevaluating-deterministic-gate-qualitydoes not own it; no workflow file, noSKILL.md, no instruction channel, no secrets-scan request), no causal-diagnosis redirect, and the target in scope. Step 1 classified the target dangerous (a new module carrying real parsing and classification logic), so the Step 2 fan-out ran rather than the safe-path skip.Isolation disclosure, per that skill's own Stop boundary: the five persona passes did NOT run in fresh, isolated dispatches. This session authored the target, so prompt-level adversarial framing is not equivalent to context isolation. An isolation mechanism does exist in this harness; it was not used here because this session operates under an instruction not to dispatch subagents unless explicitly asked. The verdict below should be read with that weaker guarantee in mind.
Confirmed findings, all three fixed in commit
e39fa8d1(rebased into22b23bcb):FixtureVerdict.spread(reuse-and-simplification, root-cause) -- production surface no production code called; verified by grep across the tree, whose only two hits were the definition and its own test. Removed, per CLAUDE.md section 4.test_spread_is_zero_when_every_model_agreedasserted0.4on two models that disagreed, so the agreement case it named had no coverage at all. Removed with the property it covered.NOT COMPUTABLEreason clause could contradict the counts printed beside it (correctness, root-cause) -- with two models present but no shared fixture, it printed2 model(s) ... a cross-model rate needs at least 2 models, naming a condition that was in fact satisfied. Reproduced live before fixing. The clause now names whichever condition actually failed, pinned by one test per branch.Audit trail -- candidates raised and rejected, with reasons:
is_uniformly_hard/is_saturated(rejected at the counterfactual check): exact equality is the honest reading of "every model scored the same value", and a tolerance would make the classification less faithful, not more. Not a defect; a documented property of the definition.str()around an already-validatedmodel_id(rejected below the confidence bar): no behavior difference, no failure scenario.Zero
unconfirmed-concernfindings: no security-tier signal is present anywhere in the target (no credential handling, no injection sink, no auth path, no network, no subprocess -- the module reads local JSON and prints a report), andloweffort produces no speculative class otherwise.One CI check is red for a pre-existing reason this PR did not cause, disclosed rather than presented as green.
eval-gatefails at its "Run touched skills' evals suites" step in 2 seconds. The same step fails identically, in the same 2 seconds, on unrelated PR #1417 (run 33273426408, job 99155895803), whose diff shares nothing with this one. This PR touchesevals/evaluating-skill-quality/eval-status.md, which is what makes that gate selectevaluating-skill-qualityand attempt a live suite run at all; that recording is required by the issue's own fourth acceptance criterion. Nothing in this diff participates in that step's execution path. This is an owner-side escalation, not an agent-fixable defect.Related Issue
Closes #1461