CriterionEval = Annotated[CriterionPass | CriterionFail, Discriminator('passed')]Per-criterion verdict.
Discriminated union on passed: pass-verdicts have no gap; fail-verdicts
require one. GraderResponse.model_validate enforces the shape at the
trust boundary so a grader cannot emit {passed: True, gap: ...} or
{passed: False} with no gap.