# RESEARCH_AUDIT_LOG.md — the pipeline's own measurement series

Append-only. One dated block per run of the `Thales research audit` routine
(`ops/RESEARCH_AUDIT.md`), carrying M1–M5 and the G1 residue count.

**The series is the point, not any single block.** Every measurement here is
about the machinery that produces beliefs, and drift is invisible from inside
a single run — a reviewer softening by 5% a month looks like a normal day
every day. So the run appends **even on a quiet month**: a gauge with gaps is
not a series, and a missing block is indistinguishable from a healthy one.

Recomputable by construction: every entry should name its source (a git
range, a queue diff, a registry count) so the next reader can check it rather
than trust it.

---

## Baseline note — 2026-08-11 (seeded, no run yet)

Seeded when the procedure was written; the first real block lands on the
routine's first run. For orientation, the state at seeding:

- pipeline age: panel live 2026-08-06, triage 2026-08-10, implementer never
  yet had an approved item to build;
- queue: 9 open, 0 approved, 0 built, 7 dismissed;
- trial registry: 412 rows (`results/research_registry.jsonl`);
- closed questions since the pipeline started: 0 via the pipeline — every
  closure so far predates it;
- known unmeasured: G1 residue (no prior fresh-context audit), G2
  time-to-loud, G4 capture-gap census.

The honest read at seeding: **the pipeline has produced findings but has not
yet closed a question**, and its most valuable output to date has been defects
in its own scaffolding (the beacon transport, the runner-loss window, the
capture-QA branch). That is a legitimate early phase, and M5 exists to notice
if it becomes a permanent one.

---

## 2026-09-03 — run 1 (window: 2026-08-11 seeding → 2026-09-03)

Run context, recorded first: the scheduled 2026-09-01 first run completed its
measurements and died on the shared Fable quota before writing anything
(NORTHSTAR §7b; healthchecks alarmed correctly). This re-run is therefore the
series' first real block; the window is everything since the 2026-08-11
seeding. Environment: routine container, no broker keys, no `data/raw`, no
owner Mac; `memory/` is absent from the checkout (relevant to M4 below).
Facts below come from the repo's git history and files unless a GitHub
source is named.

### M1 — proposal-survival drift · source: `git log` over `research/queue/` + the run commits' own diffs

Panel runs 1–17 (2026-08-06 → 2026-09-02). The frozen archive alone
under-reports — the run-6/7/8 narratives were deleted from it (incident
already recorded at `research/RESEARCH_QUEUE.md:2003`); counts were
recovered from each run commit's diff. Survival = items added to
`queue/open/` ÷ candidates considered, over runs whose denominator was
recorded at all:

| period | runs | considered | survived review | added to open | rate |
|---|---|---|---|---|---|
| 08-06..08-12 | 1–5 | 10 | 4 | 4 | 0.40 |
| 08-13..08-19 | 6–10 | 6 | 1 | 1 | 0.17 |
| 08-20..08-21 | 11–12 | 4 | 3 | 3 | 0.75 |
| 08-24..09-02 | 13–17 | 11 | 8 | 7 | 0.64–0.73 |

**Verdict: RISING at roughly constant volume — the softening signature this
measurement exists for.** Two recorded mitigations before reading it as
reviewer rot: the reviewer is still visibly cutting (run 15 killed 2 of 4,
`528793d`; run 14's row survived only "with three required edits",
`56825fb`; run 16's rows were "adversarially narrowed", `86152c2`), and the
high-survival fortnight coincides with a genuinely defect-rich delta (queue
migration `47e8e73`, the displaced-cron regime, the phantom equity mark
`29c6f23`) — run 13 pre-empts the charge in its own text ("volume is
delta-justified, not quota-shaped"). One month cannot separate softening
from a rich delta. **Next month's block recomputes this table; a second
rising month without a matching delta is the finding.**

Metric-integrity finding: the "considered" denominator is NOT-RECORDED in 5
of 17 runs (1, 4, 6, 10, 17) and got worse post-migration — run 17's count
exists nowhere in git (PR #131 names only the surviving row). The gauge is
degrading at its source. → feedback item 2 below.

Cadence: 17 runs over 20 weekdays; the run numbering is contiguous, so
**2026-08-25/26/27 are genuinely missed runs, not quiet days** (quiet runs
6/7/9/10 all committed narrative anyway). The window matches the
displaced-cron regime (`research/2026-08-31_displaced_cron_regime.md`);
the panel runs on claude.ai infra, so the cause is not recoverable from git.

### M2 — registration before computation (G5) · source: commit dates, hashes below

Population since seeding: 5 guard builds (`queue/built/`), 1 recorded
measurement (RWG-1 leg 2), 1 evaluator pinning (VRP forward-gate), and 0
`thales evaluate` runs (trial registry untouched since 2026-08-01,
`4286d89`).

- RWG-1 leg 2: registered `8359538` (2026-08-20, panel run 11) + rider
  `a3dfe8c` (08-21 06:47) → measured `9b2e268` (08-21 15:20). Order correct.
- 5 built items: proposal commits 2026-08-10..08-28 (`9fc741e`, `a3dfe8c`,
  `5972694`, `56825fb`) all precede build commits 2026-08-26..09-02
  (`2072f4b`, `8c0f731`, `6321abb`, `100cf61`). Order correct.
- VRP forward-gate evaluator (`fc389d7`, #136): pinned 2026-09-02, ~4 months
  before its ~Jan-2027 clock matures — the exam pinned before any student
  can see it, as §6 demands.

**Inversions: 0.**

### M3 — kill-criterion honesty · source: `git show` of proposal vs built text, all 5 `queue/built/` items

5/5 IDENTICAL wording from proposal to verdict — byte-for-byte through the
08-24 queue migration — and every criterion was actually evaluated, with
negative controls run and revert-reds shown (proposal/built hashes as in M2;
the one pre-build edit, STAMP-1 at `a1b0bb6`, is a dated insertions-only
triage annotation, +44 lines, criterion untouched).

Honesty-positive disclosures: AMG-4's built note says plainly its
pre-registered end-to-end probe PR was NOT run ("recorded as not done, not
as done"); all five record the route around `queue/approved/`.

One minor unflagged under-execution: STAMP-1's pre-registered negative
control says "replay the `captured_at` timestamps of the last 20 committed
capture days"; the shipped test replays 5 representative instants
(`tests/test_utils/test_market_session_date.py`) and the built note does not
flag the reduction. Not a criterion change — the 20-trading-day forward
shadow is separately pinned — but a control's sample size is part of the
registration, and shrinking it deserves a sentence.

**Silent divergence: 0. Unflagged control under-execution: 1 (minor).**

### M4 — reject-ledger integrity · source: `queue/open/` (12) vs `queue/dismissed/` (10) + `web/public/data/killlist.json` (13) + RESEARCH.md kill headers

**9 NONE / 3 SUPERFICIAL / 0 RESTATEMENT.** The three superficials share
territory but not the killed question — CNT-1 ~ f2-reopen (both "digest
count disagrees with truth", but F2 killed a queue-count regex parser and
CNT-1 is a clean-clock counting convention split); PIT-1 ~ b4-notch (both
"row-count floor", but PIT-1 is a writer-side persistence guard on the pit
stream, touching neither the skew stream nor the affirmed ≥400 pin);
SKW-2 ~ b4-notch (B4-adjacent, but proposes no floor amendment and itself
flags the mid-accrual sensitivity check). Owner can disagree; the full
quotes are in this run's transcript. No dismissed idea has been re-proposed
— notably, `cron-delay-capture` predicted itself "the single most likely
re-proposal next time the scheduler misbehaves"; the scheduler then
misbehaved for a week and the panel routed the pain into writer/QA hardening
instead. The tripwire was tested by events and held.

Caveat on source coverage: `memory/strategic-posture.md` is absent from the
routine checkout — exactly the state open item MEM-1 documents — so the
public kill list + in-repo kill records served as the standing-kills source.

### M5 — closure rate and cost (G3) · source: `results/research_registry.jsonl`, `results/diagnostics/oos_verdict_history.jsonl`, triage commits

- Trial-registry rows added since seeding: **0** (413 rows, last touched
  2026-08-01 `4286d89`). Deflated-Sharpe hurdle movement: **none**.
  Correction to the seed block: it recorded 412 rows at seeding; a recount
  says the file already held 413 (both registry commits are 08-01, the file
  untouched since). The seed miscounted by one.
- Questions closed since seeding: **5** — the built detectors, each with a
  reported criterion verdict (M3). Triage dismissals (4 on 08-24 `f51c479`,
  3 at the 12-cap on 08-31 `a1b0bb6`) are grooming, counted separately, not
  closures. The oos-monitor's 09-01 append (`oos_verdict_history.jsonl` row
  3, INSUFFICIENT_DATA) is a ledger row, not a closure.
- **Cost per closed question: 0 registry rows / 5 closures.** All five are
  instrument/operational closures; zero strategy questions closed — the
  charter's intended shape (apparatus over weather), and free. M5's warning
  direction (rows spent without closures) is not present; the inverse is.
- **Route counter — keep this in the series: builds via `queue/approved/`
  0 of 5.** All five built items were built on owner-interactive
  instruction, each citing "the EXQ-1 precedent", honestly recorded and
  human-merged — no rails breach. But `queue/approved/` has never held an
  item (its only history is the gate's own creation, `47e8e73`), and the
  Wednesday implementer has never had work. The designed
  panel→triage→approve→implement path has never once run end-to-end, and
  precedent is accreting around the unexercised gate. Recorded as a
  counter, not a proposal: if this reads 0/N again next month while the
  interactive route keeps building queue items, the pipeline's approval
  gate is a drawing, not a door, and that becomes the finding.

### G1 — auditor's residue · target: PR #132 / commit `100cf61` (STAMP-1+STAMP-2, market-session stamping)

Method: a fresh-context adversarial audit of the change as if no routine had
reviewed it, then compared against everything reported at the time (panel
run 14, the two built records, the 09-02 self-audit, the PR). The change's
core claim VERIFIES — the four incident instants and the pinned edge probes
replay correctly, the idempotency gate now keys on the session, and the
30-run negative control is genuinely in the test file.

**Residue: 7 = 1 MAJOR (3) + 4 MINOR (4). None of the five items appear in
any record made at the time.**

- **MAJOR — the pre-open→previous-day rule inverts for current-state
  captures.** `market_session_date` maps any pre-09:30-ET instant to the
  previous trading day, on the rationale "the only data that exists then is
  that day's close" — true for chains, false for `snapshot-shortability`,
  whose content is a current-state-only snapshot (the call site's own
  comment: "a threaded historical date would FABRICATE state",
  `cli.py:2153-2157`). A skew-snapshot run delayed into the 04:00–13:30Z
  window (measured dispatch delays reach 9 h; 9 h past 19:00Z lands exactly
  at midnight ET) writes morning-refreshed ETB/HTB flags permanently to
  yesterday's parquet, and the TIME-GATED flip panel dates an overnight
  flip to the wrong session. The pre-fix UTC stamp was content-correct for
  this one stream; #132 made it wrong. No test covers a pre-open instant
  reaching this writer. → filed as queue item STAMP-3 (feedback item 1).
- MINOR — the "a holiday stays the holiday" promise holds only after 09:30
  ET; a pre-open holiday instant jumps two calendar days back
  (`2026-09-07T12:00Z` → `2026-09-04`), contradicting the docstring; the
  pinned holiday test probes only 14:35Z.
- MINOR — the per-sleeve digest's no-run canary still judges the UTC date
  (`digest.py:49` via `cli.py:4378`, `today=None` → `date.today()`): the
  backwards fire #132 fixed in the fleet path survives in the manual
  single-sleeve path, and the retained clock-hermeticity entry sanctions it.
- MINOR — failure-alert email subjects stamp `date.today()` (UTC)
  (`cli.py:3628`, `:4392`): a post-midnight displaced failure emails
  tomorrow's date in the one channel a human reads to pick which session to
  investigate.
- MINOR (hygiene) — duplicate in-function import of the helper
  (`cli.py:4713`, `:4760`); the commit message's "capture-qa" rewiring
  claim is loose (`capture_qa` takes no date); `export-public`'s
  `date.today()` feeds a dead parameter.

Series note: this is G1 datum #1 (residue 7 on one change). The 09-02
self-audit's residue 23 was whole-system scope — not comparable; the
comparable series starts here, same method, one change per month.

### Feedback filed this run (each with its motivating measurement)

1. **`research/queue/open/stamp-3-preopen-current-state-inversion.md`** —
   the G1 MAJOR plus the three same-class UTC residuals as riders, full
   contract with a pre-registered, negative-controlled kill criterion
   (motivated by G1).
2. **PR editing `ops/RESEARCH_PANEL.md`** (will not auto-merge,
   deliberately): every panel run record must carry the triple
   `considered / survived-review / opened`, zero days included (motivated
   by M1's denominator being unrecorded in 5 of 17 runs and worsening).
3. Deliberately NOT proposed: any reaction to the rising M1 survival rate
   (one month cannot separate softening from a rich delta — the recompute
   next month decides), and any machinery for the 0/5 approved-route
   counter (the series carries it; machinery would be a sixth opinion).

Post-measurement note, same day: while this block was being written, CNT-1
moved open→built on main (`b50ae9b`, #141) — route again owner-interactive
("recorded honestly" in the file), so the M5 route counter closes the
window at **0 of 6** and M4's open-set is now 11. The closure count for
this window stays 5 (CNT-1's verdict belongs to next month's window).

Kill-criterion status of this routine (procedure §4): run 1 — adopted-change
counter starts now; the 09-01 quota death counts against the beacon record,
not this criterion.
