eduos-rotation-probe-teacherless-class-fixture-gap-teacher-leg-unprovable
Diagnosed and solved. The environment contains no application codebase — only /workspace/problem.json — so I reconstructed the probe's candidate-selection logic, reproduced the false-FAIL and the fix with a faithful SQLite model, and wrote the solution.
Deliverables (both in /workspace):
- solution.md — the self-contained writeup (title, root-cause analysis, exact fix, general pattern, verification).
- verify_fixture_gap.py — runnable reproduction; exits 0.
The probe needs both RBAC legs provable on the same fixture row:
- student leg: assignment not in enrollments → expect 404
- teacher leg: classes.teacher_id IS NOT NULL (teacher owns the class) → expect 200
The old candidate select only guaranteed "unenrolled":
SELECT a.id FROM assignments a
WHERE a.id NOT IN (SELECT assignment_id FROM enrollments WHERE student_id=:student_id)
LIMIT 1;
After the reseed, the only unenrolled assignments sat in teacherless classes (teacher_id IS NULL). The endpoint correctly 404s a non-owning teacher, so the 200 was unprovable by fixture, not broken by product — a false FAIL.
Join classes and require an owning teacher, and treat zero candidates as a named SKIP rather than a FAIL:
SELECT a.id
FROM assignments a
JOIN classes c ON c.id = a.class_id
WHERE a.id NOT IN (SELECT assignment_id FROM enrollments WHERE student_id = :student_id)
AND c.teacher_id IS NOT NULL
LIMIT 1;
if candidate is None:
return TestResult(status="SKIP",
reason="fixture-gap: no unenrolled assignment in a teacher-owned class "
"(classes.teacher_id IS NOT NULL); student=404/teacher=200 unprovable")
The script reproduces all four discriminating cases and passes:
[OK ] old select, teacherless-only fixture -> FAIL (assignment 100: got student 0/teacher 0)
[OK ] fixed select, teacherless-only fixture (the bug case) -> SKIP (fixture-gap: no unenrolled assignment in a teacher-owned class)
[OK ] fixed select, teacher-owned candidate available -> PASS (assignment 101: student 404 / teacher 200)
[OK ] old select, mixed fixture (picks teacherless first) -> FAIL (assignment 100: got student 0/teacher 0)
All fixture-gap semantics verified. exit=0
This matches the live result: 26 PASS, 0 FAIL, 1 SKIP, exit 0.
# Evidence - Problem class: eduos-rotation-probe-teacherless-class-fixture-gap-teacher-leg-unprovable - Model: openrouter/deepseek/deepseek-v4.1-flash - Solved: 2026-09-18T08:01:03.192Z - Verification: solution produced by pi in sandbox; see signatures.json
{"description": "Regression battery probe asserting detail-endpoint RBAC (student 404 / teacher 200) on an unenrolled assignment false-FAILed after demo-DB reseeds: the reseeded fixture batch concentrates unenrolled assignments in TEACHERLESS classes (classes.teacher_id IS NULL), so the teacher=200 leg is unprovable by fixture, not by product - the endpoint correctly 404s teachers on teacherless-class assignments. Fix: the candidate select JOINs classes and requires c.teacher_id IS NOT NULL; zero candidates emit a documented fixture-gap SKIP (with the gap named) instead of a false FAIL. General pattern: any probe that needs a second role to succeed on a fixture must select candidates whose fixture shape supports BOTH legs, and treat an empty candidate set as a documented SKIP, never a product failure. Validated live: battery 26 PASS 0 FAIL 1 SKIP exit 0.", "environment": "", "language": "", "model": "openrouter/deepseek/deepseek-v4.1-flash", "problem_class": "eduos-rotation-probe-teacherless-class-fixture-gap-teacher-leg-unprovable", "provider": "openrouter", "solved_at": "2026-09-18T08:01:03.192Z", "version": ""}