# The gap alarm ended by timeout, not by resolution

**2026-10-19 · daily audit · observability (P0 loudness)**

## What was observed

`thales captures` carries an interior-gap detector (built 2026-08-07) that
answers the question staleness cannot: *did we skip a trading day and carry
on?* An interior hole is invisible to every other check — the newest file is
present and correctly sized, row counts are normal, so the stream reads green
while a day is gone for good. For a time-gated capture that day is
unrecoverable: what the system saw on a date cannot be re-fetched afterward.

The detector looks back **20 trading days**. That bound is deliberate and
documented: "old known gaps are history — they belong in CAPTURES.md, not in a
daily alarm."

The bound has a consequence nobody wrote down. A hole that is *never*
recorded does not resolve — it **expires**. After 20 sessions it leaves the
window, stops being reported, and the stream reads clean again. The alarm ends
by timeout.

That happened today, in the open. The eight sessions lost to the
2026-09-21→09-30 Actions-billing outage were never entered in
`captures.ACKNOWLEDGED_GAPS` (zero entries for any of them) and never given a
per-stream notch in `CAPTURES.md`. At 19:08Z the day's own skew capture
advanced the window by one session, and `2026-09-21` dropped out of
`options_skew`, `options_chains`, `options_iv_term` and `shortability`. No
layer anywhere said so, because by construction none could: the only reader of
a gap is the detector whose window it just left.

It had happened once before and was caught only by hand. `CAPTURES.md`'s
`pit_snapshots` section records that `2026-06-16` "predates the interior-gap
detector and **had been recorded nowhere** until this sweep" (2026-08-17). The
same outcome, reached the other way — and a manual sweep is not a detector.

As of today the remaining exposure is 35 stream-days across seven streams, all
within five sessions of the same fate: `2026-09-22` goes after the next
capture, and on the current cadence the last of them (`2026-09-30`) is
invisible by **2026-10-28**.

## What it turned out to be

A detector defect, not a system failure — the shop's most common finding
class, and the third time a silence has been traced to an instrument's own
bounds rather than to the thing it watches.

The architecture was already right and is worth restating because it is easy
to invert: **`CAPTURES.md` is the ledger; `ACKNOWLEDGED_GAPS` is only a
recent-window alarm suppressor.** Acknowledging without recording is a
silenced alarm. What was missing is the converse — nothing made the recording
*deadline* visible, so a hole could pass out of reach while every layer stayed
green and truthful.

## What was done

`captures.expiring_gaps()` counts every unacknowledged in-window gap down to
the session it becomes invisible, and `thales captures` prints one line per
stream: how many days, the soonest, and how many further captures remain. The
deadline now announces itself.

Two properties are load-bearing:

- **Forward-only.** It counts down gaps still inside the window and never
  resurrects ones already outside it. Full history would bury the live signal:
  momentum's `order_log` alone carries 39 interior holes from its
  monthly-rebalance era, every one of them legitimate. A detector that fires
  on ordinary cadence is worse than no detector.
- **Same window as `capture_gaps`.** The countdown and the alarm cannot
  disagree about which days are in scope.

Five tests, three of them negative controls at exactly the boundary that
matters (deep-inside gap not counted down; already-expired gap not
resurrected; acknowledged gap not counted down while its unexplained
neighbour still is). Full suite 1799 passed / 1 skipped.

`CAPTURES.md` now states the deadline in its header, where the contract lives.

## What remains

The countdown prevents the next loss; it does not recover `2026-09-21`, and it
deliberately will not resurrect it. Writing the September outage's per-stream
notches into `CAPTURES.md` — and the matching `ACKNOWLEDGED_GAPS` citations —
is queue row `GAP-1`, and its framing needs one correction: the record is
**writable forever**; what expires is the detector's visibility, which this
change makes announce itself. The urgency is real but the deadline was never
on the record, only on the alarm.
