T13 Per-Gate `last_failed_at` Index — distinguishing "stopped running" from "running + catching" (v7.10)
F17 materialized "when did this gate last run?" per gate in O(1). But "ran" and "caught a violation" are different signals, and F17 only had the first — a gate could fire every cycle and never catch anything (healthy, or toothless) and F17 couldn't tell. T13 extends the index to schema v2 with a per-gate last_failed_at, sourced from the integrity-snapshot history (coverage rows carry no pass/fail signal). 6 cycle-time gates populated from 14 snapshots. The interaction with v7.10's GATE_COVERAGE_ZERO mis-wire detector became the improvement: a 0-candidate gate WITH failure history is demonstrably running, so requiring last_failed_at is None before flagging cleared 4 false advisories.
What do T1 / T2 / T3 mean?▾
T13 Per-Gate last_failed_at Index
Status: shipped 2026-06-10. Extends F17 (the
last_fired_atmaterialized index) with failure history, completing the meta-layer's ability to reason about each gate's health.
The gap F17 left
F17 (v7.9.1) materialized last_fired_at per gate from the append-only gate-coverage.jsonl stream — answering "when did this gate last run?" in O(1). The v7.10 GATE_COVERAGE_ZERO meta-check uses it to flag gates that went silent.
But "ran" and "caught something" are different signals, and F17 only had the first. A gate could be firing every cycle and never catching a violation — healthy, or possibly toothless — and F17 couldn't tell.
The scoping surprise: coverage rows have no failure signal
The obvious move — derive last_failed_at from gate-coverage.jsonl — doesn't work. Coverage rows track {candidates, checked, skipped} (did the gate run), not pass/fail. A gate "failing" means it emitted a finding, and findings aren't in the coverage stream.
The clean source turned out to be the integrity-snapshot history (.claude/integrity/snapshots/<ts>.json): each snapshot carries a timestamp + a findings[] array of {code, severity}. Scanning them gives, per gate code, the most recent snapshot where it appeared.
What shipped
refresh-gate-last-fired.py (schema v1 → v2) now overlays, per gate:
last_failed_at— most recent snapshot timestamp where the gate code appeared infindings[]total_failure_snapshots— count of distinct snapshots it appeared in (deduped within a snapshot)last_failure_severity— the severity in the most-recent failing snapshot
Gates that appear only in failure history (a cycle-time code with no coverage row) get a minimal index entry, so the index stays complete. The snapshots dir is derived relative to the ledger path so a tmp-dir ledger in tests resolves to an empty dir (hermetic) while production resolves the real one.
Live: 6 cycle-time codes populated from 14 snapshots [T1] (BROKEN_PR_CITATION last failed 2026-06-04, TIER_TAG_LIKELY_INCORRECT 2026-06-07 across 7 snapshots, etc.). Refresh stays well under the <1s budget.
The interaction that became a refinement
Adding failure-only codes to the index made the v7.10 GATE_COVERAGE_ZERO mis-wire detector false-fire on 4 cycle-time codes (BRANCH_ISOLATION_HISTORICAL, CACHE_HITS_AUTO_INSTRUMENTATION_INACTIVE, PHASE_LIE, TIER_TAG_LIKELY_INCORRECT): they have candidates==checked==skipped==0 (no Mechanism A coverage) but DO appear in snapshots. That's not a mis-wire — they're cycle-time advisory codes that legitimately don't emit coverage.
Documented limitation
Write-time gates block commits rather than logging, so their blocks never reach a snapshot — last_failed_at is meaningful for cycle-time + advisory codes only. Recorded in the index's failure_history_note; a write-time block ledger is a T13.1 follow-up.
Verification (2026-06-10)
test_refresh_gate_last_fired.py+test_gate_coverage_zero.py: 27 pass [T1] (incl. 5 new merge-failure-history tests + 1 mis-wire-vs-failure-history test)- Full coverage/integrity suite: 22 pass [T1]
make integrity-check→ 0 findings [T1] (the 4 falseGATE_COVERAGE_ZEROadvisories cleared)
Cross-references
- Source case study (FT2):
docs/case-studies/t13-last-failed-at-index-case-study.md - Spec:
docs/master-plan/test-coverage-master-plan-2026-05-13.md§4 T13 - Predecessor:
f17-last-fired-at-index(thelast_fired_atindex this extends)
- •
last_failed_atis meaningful for cycle-time + advisory codes only — write-time gates block commits rather than logging findings, so their blocks never reach an integrity snapshot and never populate failure history (recorded as the index'sfailure_history_note; a write-time block ledger is a deferred T13.1 follow-up). - •Failure history is derived from just 14 integrity snapshots populating 6 cycle-time gates — a thin, single-environment corpus, so "last failed" reflects the local snapshot history rather than a comprehensive cross-environment record.
- last_failed_at conflated with last_fired_at (a gate that ran but never failed shows a stale last_failed) — would mislead the meta-check.
- Snapshot scan slows the index refresh beyond the <1s budget.