# T4.4 — Retention ordering guard | Field | Value | |---|---| | Phase | P4 — Verification | | Size | S — under 1 day | | Status | Not started | | Flags | — | | Spec | inlined below | | Blocks | — | ## Goal Reduction must not outrun verification or grading. Eligibility is *grading has terminated*, not *grading succeeded*. ## Facts (inlined — no spec read needed) - Eligible states for reduction: **`Graded`, `Ungraded`, or `Archived`**. - **Never a step-level finish timestamp.** A step can finish, be reduced, and then run-level verification finds nothing left to check. - `Ungraded` belongs in that set, and the tighter-looking `Graded`-only rule is the one that gets written. A run that never gets a score — a novel `TaskId` with no reference, a tournament group that closed without it, a tenant over its grading ceiling — is **finished**. Gating on `Graded` alone leaves it irreducible forever. - The tenants that hit this are the low-volume ones and the cost-capped ones: the two populations least able to absorb unbounded storage, and the two least likely to have anyone watching for it. - `Graded`-only passes every other test in this phase and strands storage only in production. So the `Ungraded`-is-reducible case must be asserted directly. ## Steps 1. Write `fn is_reducible(run_state) -> bool` matching exactly `Graded | Ungraded | Archived`. Exhaustive match, no `_` arm. 2. Make reduction (T8.4) call it as a precondition and **reject** — a typed error naming the current state — rather than skipping silently. A silent skip is indistinguishable from success and leaves no trace to investigate. 3. Delete any path that reads a step-level finish time for retention decisions. 4. Wire the rejection into metrics so refusals are countable. 5. Test both halves: a run resting in `Grading` is rejected; a run terminal in `Ungraded` is reduced. ## Acceptance - A run still resting in `Grading` is **not reducible**, and attempting it is a **rejected operation** rather than a silent skip. - A run that reached terminal `Ungraded` **is** reducible — asserted directly. ## Verify **Harness:** runs parked in each lifecycle state, plus the reduction entry point from T8.4 (or a stub of it that calls the same guard). **Integration test** — `tests/it_retention_guard.rs`: 1. Table-driven over **every** run state. For each, call reduce and assert: - `Graded`, `Ungraded`, `Archived` → **permitted**; - every other state, including `Grading` and `Verifying` → **rejected with a typed error naming the current state**. 2. Assert the rejection is an `Err`, not `Ok(())`. A silent skip is indistinguishable from success and leaves nothing to investigate. 3. **Assert `Ungraded` is reducible directly**, with a run that reached `Ungraded { NoReference }`. This is the case a `Graded`-only implementation fails, and it is the only assertion that catches it. 4. Assert no code path reads a step-level finish timestamp for retention — grep, plus a test where a step finished long ago while the run is still `Verifying`: reduction must be refused. 5. Assert refusals increment a metric, so a stuck reducer is visible. **Command:** `cargo test -p retention guard` **False pass:** - Testing only the `Grading`-is-refused half. `if state == Graded` passes that, passes every other test in P4, and strands storage in production for exactly the low-volume and cost-capped tenants least able to absorb it. Step 3 is the whole point of this task's acceptance criterion. - A table covering only the states someone remembered. Enumerate the run-state enum exhaustively with no `_` arm so a new state forces a decision. ## Traps - `if state == Graded` — passes this phase, strands storage in production. - Returning `Ok(())` on ineligible runs "because there is nothing to do". Then a stuck reducer looks healthy. --- Background (not required to do this task): [rust-agentic-sys.md](../../../rust-agentic-sys.md) §5.2, §10.3 · [rust-agentic-task.md](../../../rust-agentic-task.md)