Skip to content

Two fail-open paths in the grading pipeline: enclave no-regression guard and bidir label selection #674

Description

@atoz96

Filing as an issue rather than PRs: eval/ is maintainer-only per sensitive-paths-guard.yml, so #672 and #673 are blocked by design. Both findings are reproducible in pure Python with no GPU — happy for a maintainer to take them, or to close the PRs if the issue is the more useful artifact.

Both are the same class: a guard defeated by incomplete evidence rather than by wrong evidence. In each case the surrounding code fails closed on missing data and on bad data, but a partial record slips between them and reads as a pass.


1. eval/polaris/scoring.py — the no-regression guard passes when no context was measured

present = [k for k in GUARD_CTX_KEYS if k in guard]
speed_ok = all(guard.get(k, True) for k in present)   # all([]) is True

Running score() over the gate matrix:

input verdict
empty primary REJECT — fail-closed
empty guard REJECT — fail-closed
guard_4k_pass: false REJECT — names the context
{"top1": 0.99, "kl": 0.01} — accuracy ran, speed did not label M, pass: true, speed_ok: true

The last shape is what a partial infra failure produces: the accuracy pass completed, the speed pass did not, so the guard_*_pass keys are simply absent. The comment above the block says "Every measured context must hold >= tolerance vs main branch baseline" — with nothing measured, "every" is satisfied vacuously.

This is the script the enclave runs, so per EVAL-TRUST.md ("The DCAP quote proves this exact script, with these exact inputs, produced this exact output") a hardware-signed receipt would attest "no regression" backed by zero speed evidence.

Suggested fix: speed_ok = bool(present) and all(guard[k] for k in present), plus a distinct reason for "measured no decode context". One measured context stays sufficient. (The get(k, True) default is already unreachable — present only holds keys that are in guard.)

Also worth noting: scoring.py is the only module under eval/polaris/ without a test file, and it is the one that decides rewards.


2. eval/pr_eval_bot.py_public_eval_label can headline the passing side

The docstring states the invariant: "a passing Qwen3.6 XL cannot paper over a Qwen3.5 regression (PR #555)". Both selection loops require the failing side to carry a label, so a side with pass: False and no label falls past them to return res.get("label") — on a bidir run, the passing headline:

score_qwen35 score_qwen36 headline
S / pass XL / pass XL
REJECT / fail XL / pass REJECT
none / fail XL / pass none
None / fail XL / pass XL

Reward-visible rather than cosmetic: in the SN74 registry eval:XL is ×4.0 and eval:REJECT is ×0.0, so a bidir run whose failing side lost its label pays four times instead of nothing.

Suggested fix: after both loops, return "REJECT" instead of falling through — a failing side with no label of its own should fail closed. The existing preference order is unaffected: a side carrying REJECT still wins, and a side carrying a real none still headlines as none.


Verification

Both were confirmed by running the current code directly, and each has a regression test that fails on main and passes with the fix (in #672 / #673 if useful):

# scoring.py
AssertionError: True is not false        # speed_ok on zero measured contexts

# pr_eval_bot.py
AssertionError: 'XL' != 'REJECT'

python3 -m pytest eval/ -q is green with both fixes (100 → 111 and 100 → 103 with the added tests). No new dependencies; scoring.py's stdlib-only constraint is preserved.

Note on the two PRs

They were first opened with the template's proof-of-speedup section left with an unticked box and auto-closed by rtx5090-required. I did not tick the box — these are harness fixes with no speedup claim, and the attestation would have been false. I reopened them per the bot's own guidance (remove the section), but reopening a closed PR 422s here, hence #672/#673 as fresh PRs. Both then hit the sensitive-paths guard, which is working as intended.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions