# CQA-3 — no failing check reads `captured_at`, so two whole skew-capture days taken after the close passed every gate — and the rule that would disqualify them has been awaiting your countersign since the day the field was born

status: open · raised panel run 25 (2026-09-17; frontier-WATCH catch, verified
independently from the committed store — a defect row, **not** a research or
capture proposal, so it does **NOT** discharge the scout's 8-week cadence
clock; same treatment as BTB-1) · class: moat continuity (G4) + registration
hygiene (G5) · effort ~0.25 pd · horizon: live daily, and it recurs on every
double-failure session · **PRECONDITION: not approvable for build until you
rule on the 2026-09-02 B4 append — see "The ruling this forces"**

---

**Plain-language summary for an owner reading one paragraph.** On 2026-09-14
and 2026-09-15 the option-skew capture — the crown-jewel forward stream — ran
**entirely after the market closed**: every one of the 889 and 888 rows was
taken at 18:24 ET and 18:03 ET respectively, when quotes are stale and wide.
Both days passed every quality gate with a perfect score, because **no failing
check anywhere reads the `captured_at` field**, and the row counts looked
normal. They are not recorded as displaced in `CAPTURES.md` either. A draft
rule that would disqualify exactly these days was written into the B4
activation pin on 2026-09-02 — the same day the `captured_at` field was
introduced — but it is marked *"binds only with the owner's countersign"* and
you have not ruled on it. So these two days are the first real data that force
that ruling, and we found out about them by hand, not from an instrument.

## Mechanism — verified at HEAD `dfde575`

**1. The two days are post-close, and only `captured_at` says so.** Read
directly from the committed `data/options_skew.jsonl` with stdlib json (the
package does not install in this container — see "What did not run"):

| date | rows | distinct `captured_at` | ET | `healed` |
|---|---|---|---|---|
| 2026-09-11 | 889 | 19:11Z + 21:38Z | in-session + top-up | unset |
| **2026-09-14** | **889** | **22:24:02Z only** | **18:24 ET — post-close** | unset |
| **2026-09-15** | **888** | **22:03:07Z only** | **18:03 ET — post-close** | unset |
| 2026-09-16 | 884 | 19:16:04Z + 22:01:07Z | in-session + post-close top-up | unset |

Nothing else distinguishes them. `healed` is unset on all four days, so the
mixed-provenance marker does not fire either.

**2. No failing check reads the field.** `grep -n "captured_at"
src/thales/data/captures.py` returns **zero hits** — the QA module never reads
it at all. The `options_skew` branch of `capture_qa`
(`src/thales/data/captures.py:712-790`) tests only per-date row counts against
a trailing median, plus unparseable lines. 889 and 888 against a trailing
median of ~889 sail through. Across the whole `src/` tree the field is read in
exactly two places — `data/options_skew.py` (which writes it) and
`data/resession.py` (the re-session tool, which reads it per row). So the
honest claim is narrow and is the one that matters: **the field is
machine-readable, and no check that can go red looks at it.**

**3. The manual register was not updated either.** `CAPTURES.md` records
displaced days for 08-06/07 and 08-27/28/29 only. **Nothing records 09-14 or
09-15.** That matters because the draft rule's own clause (b) makes
`CAPTURES.md` the register of record for days whose basis is in question.

**4. The writer already owns the instrument it does not use.**
`session_phase()` exists in `src/thales/utils/calendar.py`, and
`snapshot-shortability` already uses it to REFUSE a pre-open capture (STAMP-3,
built). `snapshot-skew` (`src/thales/cli.py:4656-4664`) calls
`market_session_date()` and `is_trading_day()` and **never checks the phase**.
That asymmetry between two captures in the same repo is the sharpest form of
this finding: one stream knows what time it is, the other does not.

**5. How the two days happened, for context only.** Both the launchd dispatch
agent (`ops/launchd/com.thales.dispatch-skew.plist:15` — *"Mon-Fri 12:00 LOCAL
= 15:00 ET — in-session, an hour before the close, the capture's intended
time"*) and the backup cron in `skew-snapshot.yml` (19:00Z = 15:00 ET) are
configured **in-session**. Landing post-close needs BOTH to fail: the agent
produced no run on 09-14 or 09-15 (single skew commit each day, 22:29Z and
22:10Z; the Actions record shows no `workflow_dispatch` for `skew-snapshot.yml`
on either date) **and** GitHub's cron then arrived ~3 h late. That conjunction
is supposed to be rare. It happened on **2 of the 9 days on which
`captured_at` exists at all** (the field landed 2026-09-02), consecutively — a
22% base rate over the only window in which the question is measurable. The
ops-side half of this was considered separately this run and DISMISSED
(`dismissed/dsp-1-dispatch-liveness-attested-on-send.md`): every session in
the window still traded in-session on the backup cron, so there is no trading
harm to claim. This row is about the data and the gate, nothing else.

## The ruling this forces — why this row is not approvable yet

`research/2026-08-01_b4_skew_activation_pin.md:235-258` is headed
**"2026-09-02 append — PROPOSED refinement (binds only with the owner's
countersign)"** and closes *"if the owner declines it, the field still exists
and (a)–(c) simply do not apply."* Its three clauses:

- **(a)** a signal date's rows count as valid only if `captured_at` falls
  inside the NYSE session (09:30–16:00 ET);
- **(b)** rows without the field are assumed in-session unless `CAPTURES.md`
  records the day as displaced;
- **(c)** a date whose only capture is post-session does not qualify as a
  signal date.

**No countersign exists** — not in the memo, not in `CAPTURES.md`, nowhere in
`research/`. The append is 15 days old and has been pending for the entire
lifetime of the field it governs. `CAPTURES.md`'s own B4 amendment summary
(`:136-160`) does not mention (a)–(c) either, so a reader consulting the
ledger rather than the memo would not know the question is open.

Note also that the neighbouring stream has already ACCEPTED this condition:
`CAPTURES.md:281-287`, inside `### options_iv_term`, carries a standing
*"Post-close quote caveat"* telling consumers to treat a day's row as an
approximate EOD mark. So the two streams, produced by the same workflow run,
currently sit under **different and unreconciled** basis standards. That is
the registration-hygiene half of this row.

**So the decision tree is:**

- **You decline (a)–(c)** → there is no rule to enforce; the row collapses to
  a single `CAPTURES.md` line recording 09-14/09-15 as post-close, plus (if
  you want it) a reconciliation of the skew stream's basis standard with
  `options_iv_term`'s accepted caveat. A record, not a build. **Triage must
  not approve the build ahead of this ruling.**
- **You countersign (a)–(c)** → the two days become non-qualifying signal
  dates and the gate below is what makes the rule real instead of aspirational.

## What to build, if the rule is countersigned

A single failing condition in `capture_qa`: **a capture date whose rows ALL
carry a `captured_at` outside 09:30–16:00 ET fails, naming the stream and the
date**, and such days get written to `CAPTURES.md`'s displaced-day register
per clause (b). ~0.25 pd. The cheaper half — recording 09-14/09-15 in
`CAPTURES.md` — is worth doing under either branch of the ruling, because the
raw `captured_at` values are the only record today and clause (b) points a
future reader at a register that does not mention them.

## Test design and the REQUIRED negative control

The control is free and comes from real data, and it is what keeps the gate
from being over-broad:

- **must RED on 2026-09-14** (every row post-close), and
- **must stay GREEN on 2026-09-16**, which carries an in-session `19:16:04Z`
  capture *and* a post-close `22:01:07Z` top-up of 5 names.

A gate that reds 09-16 is wrong and must not ship: mixed provenance is the
normal, healthy refire pattern that `captured_at`/`healed` exist to express,
and SKW-3 (built) already fixed the real hazard in that shape. This row is the
next axis of **SKW-4 leg 1 (built)**, which filtered rows stamped on a
non-session *date*; this one is about rows captured outside the session
*clock*. Cite both so triage does not re-derive them.

## Kill criterion — pre-registered

- **Dies mechanically at pickup** if `grep -rn "captured_at"
  src/thales/data/captures.py` is non-empty **and** a fixture date whose every
  row carries a post-16:00-ET `captured_at` returns `ok: False` from
  `capture_qa`. One command, one run.
- **Dies on the ruling** if the owner declines the B4 append — see above.
- **Premise falsifiers, both already checked and negative:** `captured_at` on
  09-14/09-15 falls inside the session (it is 18:24:02 and 18:03:07 ET); or
  `CAPTURES.md` or an existing memo already records those days as displaced
  (neither does).

## What is NOT claimed

- **No statistical damage has occurred.** B4's primary is a **weekly**
  cross-sectional rank-IC with a ≥400-valid-row floor. 2026-09-16 is
  in-session and well over the floor, so ISO week 38 keeps a qualifying signal
  date under clause (c). The defect is that the condition was invisible and
  found by hand, not that a number is wrong.
- **No trading harm.** Every session in the window traded in-session on the
  backup cron.
- **This is not CQA-2.** CQA-2 is `files[-1]`-only reads (a missing today-file
  failing nothing) plus alert-subject attribution; its missing-today check
  would have PASSED on both days, since the files exist with normal row
  counts. Same module, orthogonal defects — fold partners for triage, neither
  subsuming the other.

## Effort · horizon

~0.25 pd for the gate plus its two-sided control; the `CAPTURES.md` record is
minutes. Live daily. **`research/queue/open/` is at its 12-item cap, so this
evicts an incumbent** — the ranking argument, no more: a quarter of a
person-day, damage that is permanent per occurrence, a 2-in-3-session
observed rate, and a pending owner ruling that this data forces either way.

## What did not run

`pip install . -c requirements-ci.lock --no-build-isolation` FAILS in the
routine container (`ModuleNotFoundError: No module named 'hatchling'`), so
`thales`, `polars` and `pytest` are unavailable. Every number above was read
from the committed store with stdlib `json`, or from `git log`, or from the
GitHub Actions API via the session's GitHub tools. Nothing was run against a
broker, and no capture, config or trading path was touched.
