Counts the ways a retain pipeline silently degrades, so an eval can refuse to score a run that was not actually measuring what it claims.
Both degradations are deliberate resilience behaviours: extraction falls back to storing raw content when the LLM fails, and consolidation skips a scope after exhausting its retries. Retain still succeeds in both cases, which is right for production and wrong for a benchmark — the arm quietly gets worse while everything reports healthy.
This is not hypothetical. On 2026-08-14 a head-to-head ran with 10 extraction fallbacks and 5 skipped merges: the Foresight arm was storing raw blobs instead of extracted facts, which is precisely the deficit worth -0.267 answer accuracy (prior calibration, Cluster A). The harness would have printed a confident scoreboard had it not independently run out of wall-clock, and that number would have entered the record.
Log lines already existed for both. A log line is not a guard, because nothing can assert on it.
Summary
Types
@type t() :: %{ extraction_fallbacks: non_neg_integer(), merges_skipped: non_neg_integer() }
Functions
True when nothing degraded — the only state in which a run may be scored.
@spec events() :: [[atom()]]
Telemetry events this watches. Public so a test can assert the emitters use them.
@spec start() :: term()
Begin counting. Returns a handle for stop/1.
Counters are :atomics-backed because the eval fans instances out across tasks, so
increments arrive from many processes at once.
Stop counting and return the totals.