# RESEARCH_QUEUE.md — the standing research queue

The daily research panel's memory and output (`ops/RESEARCH_PANEL.md`).
Append-only, two sections, one file — deliberately not a dated report per day.

**Open** = proposed, survived adversarial review, awaiting the owner.
**Approved** = the owner moved it here. This section is the ONLY thing the
implementer routine will build — moving an item is the human gate, and it is
deliberately one line of effort for the owner and a hard precondition for the
machine.
**Dismissed** = considered and rejected, one line each. That section exists so
tomorrow's panel does not re-deliberate today's rejects; re-litigating a
dismissal without new evidence is the same C0 breach as re-opening a kill.

Nothing here has been actioned. Items become real work only when the owner
picks them up; each carries a pre-registered kill criterion so it can die
cheaply.

Seeded 2026-08-05, empty, before the panel's first run (2026-08-06 13:00Z).

---

## Open

### 2026-08-06 · Panel run 1

Seven items, ranked. Three need owner **judgement** (a policy call only you can
make); four are defects with an obvious fix and need only authorisation. The
`judgement` field is there so triage costs seconds.

Composition note, recorded deliberately: **none of these is a strategy idea.**
Zero add a row to `results/research_registry.jsonl`, zero add DSR debt, zero
relitigate the kill list, zero propose changing a frozen pin. Every one is a
P0-loudness or P2-instrument defect, which the charter ranks above research.
Seven on day one reflects a cold start — the deep panel predates the 08-03/08-05
ops commits, PR #88 had never been reviewed, and the queue-and-clocks lens had
never run at all. If a later run produces seven again, suspect the reviewer.

---

**1. MEM-1 — the `memory/` directory does not exist in the repo, and the C0
kill list is one of the files that is missing.** · judgement: **YES** ·
effort 0.25 pd · horizon: immediate

*Mechanism.* `git log --all --diff-filter=A -- 'memory/*'` is empty: `memory/`
was never committed, and it is not in `.gitignore` either — it lives only on
the owner's Mac. Meanwhile 22+ references to 11 distinct `memory/*.md` files
appear across committed docs. Two of them are load-bearing for the autonomous
loop: `ops/RESEARCH_PANEL.md` §2 instructs this panel to check
`memory/strategic-posture.md` (the kill list enforcing the C0
anti-relitigation rule), and `ops/WEEKLY_AUDIT.md` check #8 instructs comparing
"the memory index" against RESEARCH.md/CLAUDE.md — a check that has been
reporting green while structurally unable to read one of its three inputs.
`tests/test_docs_consistency.py` verifies that `ops/*.md` referenced files
exist; nothing checks `memory/*.md`, so the dangling references are invisible
to the suite whose stated purpose (line 5) is that "an agent acts on what is
written".

*Not fatal today, and say so:* `ops/RESEARCH_PANEL.md` §2 enumerates the kill
list inline and `web/public/data/killlist.json` carries 13 entries, so cloud
routines are degraded, not blind. This panel used both.

*Test design / kill criterion.* Extend `tests/test_docs_consistency.py` to
assert every referenced `memory/*.md` path resolves. **It reds today.** The
owner must then either commit `memory/` or strip the references; doing nothing
does not satisfy it, and "it works on my machine" is unavailable because CI's
checkout is the one that matters. **If the owner's intent is that `memory/`
stay private and local, the correct resolution is to strip the references and
inline what the routines need — not to weaken the test.**

*Owner judgement required:* commit vs de-reference is a policy call.

---

**2. CQA-1 — `capture_qa` cannot fail on a calendar-explained skew day, so a
truncated capture file reports OK.** · judgement: no · effort 0.25 pd ·
horizon: immediate

*Mechanism.* `src/thales/data/captures.py`, `options_skew` branch (~500–560).
`probs` accumulates `"{bad_lines} unparseable line(s)"` *before* the coverage
check. When `_skew_window_gap` returns an explanation the code appends
`{"ok": True, …}` and sets `return_early = True`, so the `if not return_early:`
block never runs and **`probs` is discarded entirely**. The parallel
`options_chains` branch handles this correctly via `_only_explained`
(`captures.py:438`) — one of two parallel edits, not a design choice.

*Verified, twice.* Identical two unparseable lines → `ok=False` when the
coverage drop is unexplained, `ok=True` when it is explained, with the
corruption absent from the detail string. Merged to main 2026-08-03 (f1e3061);
`thales capture-qa` runs daily in `skew-snapshot.yml` under `if: always()`, and
the heartbeat beacon fires on `success()` — so a corrupted capture day yields a
green job **and** a healthy beacon. Half-written files are the stated reason
this QA exists (module docstring: "staleness alone can't see a half-written or
degenerate file"), and calendar-explained days recur on a ~monthly cadence.

*Currently latent:* the live `data/options_skew.jsonl` has **0** unparseable
lines. This is a hole in the net, not a fish already through it.

*Kill criterion.* This is a defect, not a hypothesis, so what it needs is an
acceptance test plus the charter's mandatory negative control: plant ≥1
unparseable line on a calendar-explained day, assert `ok is False` **and** that
the detail carries the unparseable count; then revert the fix and assert the
test reds. Add the symmetric test on `options_chains` so the branches cannot
drift apart again. No test currently covers `bad_lines`/`_only_explained` on
either branch.

---

**3. PC-1 — PR #88's "explained" escape has no ceiling, and it fires by
construction for meanrev.** · judgement: no · effort 0.25 pd · horizon:
**before #88 merges — free now, expensive after**

*Mechanism.* `_check_position_count` in `execution/health.py` on
`origin/audit/position-count-detector`: `if cap_bound: return (True, …)` with
`excess` unbounded. A book at 5× its targeted name count passes identically to
one 3 names over. The PR's own docstring states meanrev's turnover cap "binds
by construction" and has fired the identical warning daily since 2026-07-20 —
so `cap_bound` is true daily for that sleeve and the detector becomes
structurally silent there. That is the same failure the PR exists to fix
("an alarm that cannot be wrong is an alarm nobody reads"), sign-flipped into
an alarm that can never fire. Its 8 new tests cover the unexplained path; none
asserts an upper bound on the explained one.

*Kill criterion — pin the ceiling BEFORE measuring.* "Add an upper bound"
invites picking the bound after looking at meanrev's current excess, which fits
the ceiling to the behaviour it polices. Pre-registered here instead:
**`excess > expected` (book ≥2× the last selection's target) warns regardless
of `cap_bound`**, with a test at 3× asserting `ok is False`. Rationale: a
turnover cap can defer exits; it cannot manufacture a book twice its target.
Register in the PR description before touching the code.

---

**4. GAP-1 — six permanent capture-days are already lost, and charter gauge G4
claims they are machine-checked when no instrument can see them.** ·
judgement: **YES** · effort 0.75 pd · horizon: detector kill resolves in 20 td
(~2026-09-04)

*Mechanism.* Verified interior holes inside the pinned clean-clock window:

| stream | class | missing trading days |
|---|---|---|
| `data/processed/portfolio_snapshots.jsonl` | TIME-GATED | 2026-06-17, 07-16, 07-17 |
| `data/processed/order_log.jsonl` | TIME-GATED | 2026-06-17, 07-16, 07-17 |
| `data/pit_snapshots/` | TIME-GATED | 2026-06-16, 07-15, 07-16 (T-1 lag) |
| `data/equity_history.jsonl` | BACKFILLABLE | **0** — healed by `backfill-equity` |

Causes: the 2026-06-17 Tiingo 429 and the 07-16/17 clock-rotted
`test_fleet_gather_and_renders` fail-close. Both incidents are marked **CLOSED**
in `AUDIT.md` — but both closures were written against the one *backfillable*
stream ("gaps self-heal on today's backfill-equity", "fully green"). The three
irreplaceable streams were never mentioned. `data/options_skew.jsonl` has all
four dates, so these were ordinary trading days, not calendar effects.

*Why nothing caught it.* `thales captures` reports only `last` +
`staleness_td` of the newest file — I ran it: every stream reads **OK**,
including all three carrying holes. `capture_qa()` iterates over files that
exist, so an interior hole is invisible by construction. Meanwhile
`ops/DAILY_AUDIT.md` step 7 instructs "No gap days", and charter G4 asserts
capture-day gaps are "measured by `thales captures` / capture-qa,
machine-checked". **That claim is false today** — a pinned gauge reading pass
while blind, on the asset the charter calls irreplaceable (§1 asset #2: "a
missed day is a permanent notch").

*Precondition, before any code:* enumerate every existing interior hole across
all registered streams' full history and record them in `CAPTURES.md` as
permanent notches. G4's metric is "capture-day gaps (target zero)" and the
current count is literally unknown.

*Kill criterion.* Negative control first: delete one interior day from a temp
copy of each time-gated stream and assert the checker reds **naming the date**,
and greens on the untouched copy. The scan must cover full history, not a
trailing window, or it re-blinds by drift. Then: **if the census surfaces zero
interior gaps beyond the six above, AND a 20-trading-day live shadow run raises
zero true alerts while raising ≥1 lag/calendar false positive, abandon the
standing detector** — record the six notches and stop. Do not tune the detector
to survive.

*Owner judgement required:* G4's wording is in the hash-frozen §1–§5 span. The
honest fix may be to make the gauge true (build the check) rather than amend
the text — but if amendment is wanted, that is a countersigned append, not a
panel action.

---

**5. B4-NOTCH — the skew stream's calendar notch has already destroyed 1 of 9
accrued weeks of a hash-frozen test, and the option to say so expires.** ·
judgement: **YES** · effort 0.25 pd (memo only) · horizon: **pin a date now**

*Measured from owned committed data* (`data/options_skew.jsonl`, 42 dates):

```
ISOwk 27 (06-29…07-03):  283 283 283 283 283   ← all five below the 400 floor
2026-07-06:              283                    ← six consecutive days
2026-08-03:              281                    ← one-day notch
all other days:          888–897
```

B4's activation pin (`research/2026-08-01_b4_skew_activation_pin.md:71`)
requires ≥400 valid rows for a signal date, so **ISO week 27 is skipped
entirely by the pinned primary statistic** — ~11% of accrued weeks gone from an
already screen-grade test whose first read is 2027-06. The pin also drops an
observation when the gap between signal dates exceeds 10 trading days, and the
week-26 observation chained 06-26 → 07-10 = exactly 10 td, landing on the
boundary; a 7-day notch would cost the adjacent observation too.

*Checked and cleared:* the 283-name set is **byte-identical across all six
days** and a strict subset of the full 890-name set — exactly what "only names
with weeklies qualify" predicts. It is a genuine calendar notch, **not** a
silent cap, and therefore not a sibling of CQA-1.

*Kill criterion — a forcing function, because report-only means this resurfaces
in 2027-06 when amending is no longer possible without a C0 breach.* The
≥400-row floor must be **affirmed or amended, countersigned, by a date pinned
now, and in any case before the first skew-vs-return computation. Silence
resolves to AFFIRM** — pin stands, notch accepted, the ~11% loss recorded as
known-at-registration. Default-to-affirm is what stops the option being
exercised after someone has peeked at a return.

*Owner judgement required, and it is the only item here that expires.*

---

**6. CLK-1 (downgraded) — two dated criteria mature in ~10 weeks with no
evaluator in the repo, and neither is on the §6 calendar.** · judgement: no ·
effort 0.5 pd · horizon: clocks bind 2026-10-20 / 2026-10-28

Not a standalone item — this is the **precondition to charter §7 rot-item 5's
already-planned cycle 3** (wire the §6 deadline ledger into AUDIT.md/the
digest). Recorded so cycle 3 starts from a known list.

- `options_iv_term` **vacuity kill** — CAPTURES.md pins "at 60 trading days
  (~late 2026-10), cross-name median < 5 expiries/name → VACUOUS, stream
  RETIRED". Stream `Since:` 2026-08-03 → matures ~2026-10-28. No evaluator
  exists: `capture_qa` checks a *daily* median against a breakage bar of 2
  (`captures.py:481-488`), explicitly commented as **not** the pinned criterion.
- `shortability` **minimum accrual** — CAPTURES.md pins 60 td plus the
  hypothesis "excluding HTB names from the momentum top-50 changes selection
  ≥1 name/month", measured BEFORE any return is looked at. Since 2026-07-26 →
  matures ~2026-10-20. Zero consumers of the borrow flags exist outside the
  capture/QA path.

Adding *rows* to §6 changes no pinned criterion — §6 is a declared living
section, so this is not a C0 boundary.

*Kill criterion — bidirectional, or it is vacuous.* The cycle-3 ledger check
must red if any §6 row lacks a named evaluator **or** if any dated criterion in
`CAPTURES.md` / a hash-frozen memo is absent from §6. Only the second direction
catches missing rows; a one-way check would have passed today. **If the check
cannot be written without hand-maintaining a second list (i.e. it only restates
§6), abandon the tooling** and keep §6 as a doc with a weekly-audit review step.

---

**7. SV-1 (downgraded) — the state schema version is un-bumpable on the two
rewrite-in-place files.** · judgement: no · effort 0.1 pd · horizon: n/a

`execution/state.py:26–29`: `_stamp` honours a pre-set `"v"`. The two
rewrite-in-place readers return the parsed dict *including* `"v"`, `daily.py`
mutates it and writes it back, so the version freezes at whatever was first
written. Verified: with `SCHEMA_V` monkeypatched to 2, a read-modify-write of
`kelly_ledger.json` still emits `"v": 1`; JSONL appends correctly emit 2. The
code comment says to bump it "only with a dated note and a dispatching reader"
— a dispatching reader would then see v=1 on a v=2 document and mis-parse.
Harmless today (no v=2 exists); it defeats the mechanism precisely when first
used. **Not worth its own work item — fold into the next touch of
`state.py`** (stamp unconditionally at the two rewrite sites, or strip `"v"` in
the readers), with the monkeypatch round-trip as the test.

---

*Panel gap, recorded honestly:* the change-reviewer lens did not review
`origin/audit/weekly-2026-08-03` (PR #87), the second open audit branch in the
delta window. Flagged as a gap, not a clearance — it carries into panel run 2.


### PC-1 — pin a ceiling on the "turnover cap" escape (owner judgement)

**Raised** panel 2026-08-06 · **measured** 2026-08-07 while amending PR #88.

`health._check_position_count` excuses any book-size excess when the last
selection's turnover hit its cap. That escape currently has **no ceiling**, so
a permanently-throttled sleeve stays "explained" at any size.

**Why it is not a code decision.** The bound was pinned at 2x target FIRST, as
the panel required, then measured: **meanrev holds 129 against a 52-name
target (2.5x)** — its cap binds every day, so exits defer indefinitely. So:

- **2x** reds the negative control every day → alert fatigue, the class this
  shop keeps paying for;
- **3x** clears 2.5x — but choosing it *because* it clears the observed value
  is the bound being fitted to the behaviour it polices, which is the exact
  failure PC-1 was raised to prevent;
- **unbounded** (today) leaves the hole the panel found.

There is no defensible way for an agent to break that tie: every option is
either an alarm you will learn to ignore or a number fitted to data. It needs
a policy call — *is a 2.5x book acceptable for a never-promotable control?*

**Options:** (a) pin a ceiling on principle and accept meanrev reds until its
book converges; (b) exempt the control sleeve explicitly and record why;
(c) treat meanrev's book size as a known-degraded state and pin the ceiling
for weights sleeves that are NOT the control.

**Kill criterion:** if after pinning, the ceiling neither fires in 6 months
nor changes any decision, it is decoration — remove it and record that the
unbounded escape was acceptable all along.

**Effort** 0.25 pd once the policy is chosen. **Horizon** no expiry, but it
sits in live alerting code, so the longer it stays undocumented-by-default the
more it reads as considered.

## Approved

_(none yet — move an item here to authorise implementation)_

## Built

_(none yet — the implementer moves items here with date, PR and outcome)_

## Dismissed

- 2026-08-06 · `TECH_DEBT.md:88` stale P1 (survfree CPCV "not survivorship-free") · evidence is real — golden store reached 74% delisted coverage, `RESEARCH.md:59` pins survfree-GOLDEN@504 as THE baseline, and the accurate residual already sits lower in the same file as a P2 — but NORTHSTAR §7 rot-item 3 already tracks this triage pass. Already tracked ≠ new finding.
- 2026-08-06 · `execution/daily.py` stale module header (documents "Tuesdays only", hysteresis `exit_band`, "25% weekly") + stale comments at :1403/:1437 · P5 doc rot, bottom of the priority order, and `TECH_DEBT.md:718` already binds daily.py on touch. Costs a queue row to say a docstring is stale.
- 2026-08-06 · C6 decimal-money prerequisite as a §6 clock row · already carried by `research/2026-08-01_c6_governance_DRAFT.md:242`; would duplicate the owner's own draft.
- 2026-08-06 · `thales placebo-ensemble` prints "over the same window" while its null runs on 33 panel returns vs 37 equity days · rider split off GAP-1 rather than bundled (bundling two findings under one row is how padding enters). Align the windows or restate the string before it goes decision-grade ~2026-09-08. No queue row.
- 2026-08-06 · weekly-option availability per name, newly derivable from the B1 expiry ladder · triple-killed: B1's consumer class is pinned against stock selection, it is a near-degenerate size/liquidity proxy on Russell-1000 (the same degeneracy that killed the binary borrow flag), and it sits inside the thrice-ratified exhausted large-cap characteristic space.
- 2026-08-06 · re-proposing panel items A5 (TRACE bond-tape probe) and B3 (SEC fails-to-deliver acquire-and-freeze) · never built and still outstanding, but already delivered to the owner in the versioned 2026-08-01 report. Re-proposing would duplicate, not add. Noted only so the empty queue is not read as "nothing outstanding".
