feat(catchup): durable segment-repair degradation lane for daily repair fairness
The daily segment_think repair pre-phase already bounds itself and emits a
machine-readable timeout in its phase.complete sidecar, but nothing downstream
reasoned about that degradation: a degraded/timed-out pre-phase let daily
synthesis advance daily.updated, so day_is_complete() returned True and every
caught-up/health surface reported the day as fully caught up — and a poison day
could consume catchup drain slots forever without backing off.
Add a durable, (day, raw-input-fingerprint)-keyed segment-repair lane to the
existing source-layer catchup-state model, reusing its backoff/threshold/
retention discipline:
- catchup_state.py: new KIND_SEGMENT_REPAIR lane with record_segment_repair_
attempt/outcome writers and a fail-closed read_segment_repair_summary reader;
success clears the lane, repeated degradation advances backoff -> quarantine
at STUCK_THRESHOLD, a fingerprint change reopens a fresh attempt. All catchup-
state read-modify-writes are now guarded by hold_lock for cross-process safety
(thinking subprocess + supervisor both write the file).
- thinking.py: records the lane at the daily pre-phase boundary (degraded on
failure carrying wall_clock_exceeded/timeout_seconds/bounded, cleared on
success) for both supervisor-spawned and manual runs.
- supervisor.py: run_catchup_drain skips active/backed-off/quarantined repair
days (unless the fingerprint changed) while later eligible days still fill
slots.
- pipeline_health.py / journal_stats.py: fold the lane into read_backlog_view
with distinct reason codes (segment_repair_degraded/stuck/unknown) mapped onto
PENDING/STUCK/UNKNOWN, fix the day_is_complete COMPLETE short-circuit to
consult repair state first, and set BacklogView.degraded so doctor caught-up,
stats totals, convey verdict, and the health UI all go honest. Unwritable/
unreadable/malformed state surfaces as UNKNOWN, never caught up. Additive
fields only — no stats SCHEMA_VERSION bump.
- check_journal_io_access.py: register catchup_state.py as the owner of its
ops/runtime state file (it now uses hold_lock).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>