Skip to content

fix(repos): a non-finite gate metric no longer passes its gate - #313

Merged
xaleryb merged 4 commits into
intel:mainfrom
napetrov:fix/measurement-predicate
Oct 31, 2026
Merged

xaleryb merged 4 commits into
intel:mainfrom
napetrov:fix/measurement-predicate

Conversation

@napetrov

Copy link
Copy Markdown
Contributor

Part of #4.

Summary

A NaN or an infinite gate metric passed its gate, so the cell was a success. gate_ok tested _is_number (bool excluded, NaN and inf not), and NaN < min is false, so the minimum check never fired; inf cleared every minimum. Five other sites re-wrote the same test with different rules. They now share one predicate:

dokimi/measure.py
  is_number(v)       int or float, not bool
  is_finite(v)       is_number, and not NaN or inf (an int is finite whatever its size)
  as_float(v, bound) float when finite and |v| <= bound, else None
  SCORE_BOUND        1e150, the largest |score| repo report reads

gate_ok          is_finite(metric), and `not metric >= min`   (a NaN minimum fails)
score_value      as_float(metric, SCORE_BOUND)
report           _optional_number -> as_float(v, SCORE_BOUND)
process_counts   as_measure -> as_float(v, SCORE_BOUND)
manifest         gate.min: is_finite; score.max: as_float(v, SCORE_BOUND); limits: as_float
reference_checks weight, threshold: as_float(v)

Kept different, on purpose:

  • A gate metric that is a finite int past the float range compares exactly. The gate sums nothing, so SCORE_BOUND (a bound on report sums) does not apply to it.
  • report._fail_unmeasurable_score asks is_number, to tell a number that is unmeasurable from a value that is not a number at all.

Evidence

  • Before: gate_ok and outcome on a gate-only task with metrics.m = NaN:

    gate_ok(task, {"pass": True, "metrics": {"m": nan}})  -> True    (should be False)
    outcome(...)["success"]                               -> True    (should be False)
    

    tests/test_measurement_predicate.py::test_a_cell_whose_gate_metric_is_not_finite_is_not_a_success[nan] and [inf] fail on intel/main; so do test_a_gate_with_a_non_finite_minimum_fails_every_metric[nan], the manifest cases for a NaN or infinite gate minimum and an out-of-bound score.max, and the reference cases for 10**400 (an OverflowError before the fix).

  • After: the same tests pass; the rest of the file pins the unchanged finite behaviour (scores, process counts, 1e200 weight, exact gate comparison of a finite int).

  • Local: make ci-local CI_LOCAL_ARGS=--offline: ruff, mypy, bandit, the fragment check and the comment-block check pass; project-wide mypy is clean. Pytest, two seeds: 10966 passed, and one failure each time, tests/test_process.py::test_sighup_kills_the_live_groups_too, a 30 s wait on a child process under four parallel workers. It failed on both seeds of the full run and passes alone and with test_process.py run on its own. process.py is unchanged in this PR, so this is a timing failure under load on the shared box, not a regression from this change; it is still open for the maintainers to judge. deptry is not installed in the local venv, so it did not run here.

Merge Danger

Door: two-way. Reverting restores the NaN pass. No stored format or schema changes.

Blast Radius: gate

  • A cell whose gate metric is NaN or infinite becomes a failure (success: false) instead of a success. Cells already recorded keep their result; a re-run cell is graded again.
  • repo validate refuses a gate minimum that is NaN or infinite, and a score.max past 1e150. Before, the latter made repo report exit 1 on the whole batch.
  • An integer past the float range in timeout_s, budget_usd, max_turns or a reference-check weight or threshold is reported as a problem rather than raising OverflowError.
  • Finite values change nowhere else, apart from a score.max past 1e150, which is refused above.

Checklist

  • Tests added/updated; targeted files pass; full pytest -n 4 has one failure, test_sighup_kills_the_live_groups_too, a timing test (see Evidence)
  • ruff, mypy, and schema checks pass
  • Docs updated (or N/A) — see the docs-drift checklist in CONTRIBUTING.md (N/A: no user docs describe the gate's handling of NaN)
  • Self-reviewed per "Before opening a PR" in AGENTS.md: two fresh-context passes (diff and description only) over 40d325dc and afeaa1e1; no blocking finding; the nits were folded into afeaa1e1
  • No ad-hoc run artifacts committed
  • New files carry the SPDX license header (or are exempt in .github/.licenserc.yaml)

🤖 Generated with Claude Code

…st and checks

A NaN or infinite gate metric passes its gate, so such a cell is a success.
A NaN gate minimum is accepted, and an integer past the float range crashes
the manifest check and the reference-check validation. These tests fail on
the current code and go through the public callers: gate_ok, the cell's
outcome, the manifest check and validate_grading.

Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
The gate passed a NaN metric (no comparison holds, so the minimum check
never fired) and an infinite one (it clears any minimum), so the cell was
a success. Gate, score, report, manifest, process counts and reference
checks now read a number through one predicate in dokimi/measure.py:

- gate: a metric must be finite; a NaN minimum fails every metric
- score and process counts: finite and within SCORE_BOUND, as before
- report: the same bound as before
- manifest: gate minimum finite; score.max within SCORE_BOUND (report
  refuses a cell past it); positive limits are a float, so a huge int is
  a problem and not a crash
- reference checks: a weight or threshold a float cannot hold is a
  problem and not a crash

Finite values keep their numbers. A gate metric that is a finite int past
the float range still compares exactly, since the gate sums nothing.

Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
…ches the overflow

Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
@napetrov
napetrov requested a review from a team as a code owner October 31, 2026 01:24
@exai-bot

exai-bot commented Oct 31, 2026 •

Copy link
Copy Markdown

✅ OpenCodeReview: Review complete: 0 finding(s) across 10 selected item(s).

Review details
  • Commit reviewed: 41875ef against merge base 63c9cdb
  • Coverage: 10 files in 1 groups
  • Engine: OpenCodeReview v1.13.3, us.openai.gpt-6.1-sol
  • Usage: 94k tokens (44k from cache), 17s
  • Tool calls: 10 (9 file_read, 1 code_search)
  • Shared measurement predicate and integration across evaluation and repository workflows: 10 files

@codecov-commenter

codecov-commenter commented Oct 31, 2026 •

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 94.04%. Comparing base (63c9cdb) to head (41875ef).
❗ Your organization needs to install the Codecov GitHub app to enable full functionality.

Additional details and impacted files
@@           Coverage Diff           @@
##             main     #313   +/-   ##
=======================================
  Coverage   94.04%   94.04%           
=======================================
  Files         294      295    +1     
  Lines       38011    38002    -9     
  Branches     6432     6429    -3     
=======================================
- Hits        35747    35740    -7     
+ Misses       1454     1453    -1     
+ Partials      810      809    -1     
Files with missing lines Coverage Δ
dokimi/dokimi/eval/reference_checks.py 90.54% <100.00%> (-0.04%) ⬇️
dokimi/dokimi/measure.py 100.00% <100.00%> (ø)
dokimi/dokimi/repos/grading.py 91.05% <100.00%> (-0.41%) ⬇️
dokimi/dokimi/repos/manifest.py 94.34% <100.00%> (-0.02%) ⬇️
dokimi/dokimi/repos/process_counts.py 100.00% <100.00%> (ø)
dokimi/dokimi/repos/report.py 97.76% <100.00%> (-0.07%) ⬇️

... and 1 file with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@xaleryb

xaleryb commented Oct 31, 2026

Copy link
Copy Markdown
Contributor
  • Ran: the full suite, ruff, mypy and bandit on the head merged into current main (conflict resolved by keeping both sides). Also ran the new tests against the pre-fix base, a base-vs-head differential probe of gate, manifest and grading inputs (NaN, inf, -inf, 10**400, bool, str), and a mutation pass over the new guards.
  • No must-fix or mid findings. Every Before claim in the description reproduces on the base, and every one is fixed on the head. All mutants of the new guards are killed.
  • Left for the author: the branch conflicts with main in dokimi/tests/test_import_architecture.py, on the foundation layer list. Main added dokimi.llm_cli and dokimi.llm_json, and this PR adds dokimi.measure. Keep all three. With that resolution the suite passes.

report.py imports both dokimi.measure (this PR) and dokimi.utils.same_model (intel#273).
The foundation layer lists llm_cli, llm_json and process_io from main and measure from this PR.

Signed-off-by: Alexander Rybkin <alexander.rybkin@intel.com>
@xaleryb

xaleryb commented Oct 31, 2026

Copy link
Copy Markdown
Contributor

Merged current main into this branch (41875ef).

Suite, ruff, mypy, bandit and the strict benchmark run all pass on Linux.

@xaleryb xaleryb left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving 41875ef: CI green after main was merged in, and the automated review of this head raised nothing. No open findings from our review.

@xaleryb
xaleryb merged commit 8a29632 into intel:main Oct 31, 2026
26 of 31 checks passed
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.

4 participants