Thales
← research journal

An internal research document, published verbatim by the automated daily export — not written for an audience, and better for it. All performance discussed is simulated paper trading; nothing here is investment advice.

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:

periodrunsconsideredsurvived reviewadded to openrate
08-06..08-121–510440.40
08-13..08-196–106110.17
08-20..08-2111–124330.75
08-24..09-0213–1711870.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.


2026-10-01 — run 2 (window: 2026-09-03 → 2026-10-01)

Run context first. The routine fired on schedule and completed. Environment: routine container, no broker keys, no config/.env, no data/raw, no data/lab, no owner-Mac access; memory/ is absent for the second consecutive month (the state mem-1-memory-dir-missing.md documents, dismissed 09-14 as queue overflow). Facts below come from this repo's git history and files, or from the GitHub API where named.

Two measurement-integrity notes that the next reader needs before anything else, because they silently corrupt this block's method:

  1. The routine's checkout arrives SHALLOW (depth 50, oldest commit 2026-09-15). git log --since=2026-09-03 on it returns 50 commits and appears to show a twelve-day hole on main that does not exist. Every count below was recomputed after git fetch --unshallow (864 commits), with date filtering applied by hand and no --since. A committer-date histogram of origin/main shows commits on every weekday from 09-03 onward; the only real gap is 09-18 → 09-26, which is the billing outage below.
  2. main LAGGED the pipeline's real state for twelve days. GitHub refused every Actions job on this repo from the 09-19/20 weekend until 2026-10-01 01:18:22Z (GitHub Free, allowance spent, no payment method; root cause b10cbfe / #182, signature runner_id 0, steps: 0, 3–13 s). The auto-merge guard is itself an Actions workflow, so routine PRs stopped landing: 11 were open at the time of this run. The claude.ai routines (panel, triage, implementer, the audits) ran throughout and pushed their beacons normally — the outage cost merges, not runs. Any measurement taken from main alone in this window under-reports; each measurement below says where it looked.

M1 — proposal-survival drift · source: origin/beacon/panel markers (all 14 runs) + panel PR bodies (#149, #162, #165, #170, #172, #173, #175, #179, #183, #188, #192, #194, #195); the two carriers agree in all 13 PR-carrying runs

Panel runs 18–32. Window totals: considered N=113 · survived-review M=34 · opened K=8.

periodrunsconsideredsurvived reviewadded to openK/NM/N
09-03..09-0418–1922110.050.05
09-11218220.250.25
09-14..09-1822–2640830.080.20
09-21..09-2527–28201600.000.80
09-28..10-0129–3223720.090.30
window14 runs1133480.070.30

Verdict: the rising series recorded last month is BROKEN on the opened rate — and that rate has stopped measuring what M1 was built to measure. K/N fell 0.64–0.73 → 0.07 window-wide and never exceeded 0.25 in any sub-period. But the survived-review rate did not fall: M/N is 0.30 window-wide with a 0.80 spike in runs 27–28 (6 of 8, then 10 of 12). The two series decoupled, and the cause is mechanical and traceable, not inferred: from run 23 onward every beacon marker records queue/open/ at or over its pre-registered 12-item cap (ops/QUEUE_TRIAGE.md:86-91; 12 at runs 23–25, 13 at runs 26–31), and survivors were routed to pre-merge PR comments, dismissal annotations or the run note instead of rows — run 27 "ZERO rows opened… no incumbent displaced", run 28 "ZERO rows opened… stays at 13", run 31 "NOTHING evicted".

So: K/N now measures the cap, not reviewer permissiveness. M/N is the gauge that still measures the reviewer, and on M/N the verdict is NO CLEAR DRIFT at high variance (0.05 → 0.25 → 0.20 → 0.80 → 0.30), n far too small to separate from the delta. Last month's RISING watch is neither confirmed nor cleared; it is now being read on the wrong instrument. → feedback item 3.

Outlier recorded: run 19's N=21 is 3.4× the window median N=7.5.

Triple-recording compliance: 14 of 15 run numbers = 0.93, against last month's 12 of 17 = 0.71. Of runs that produced any artifact, 14/14 = 1.00. Both carriers are in use exactly as ops/RESEARCH_PANEL.md §4 now requires, and every one of the 13 PR bodies contains the literal triple. Last month's feedback item 2 (PR #145, owner-merged 2026-09-03T03:49:17Z) worked, and is measurably the reason this month's M1 could be diagnosed at all. The single gap is run 20, which produced no artifact of any kind.

Cadence: 21 weekdays, 14 runs recorded, 15 run numbers consumed (18–32, contiguous). Seven weekdays with no run, in two blocks:

  • 09-07..09-10 (4 days) — the Fable 7-day model quota went into overage on 09-07 and every Fable routine died on turn 1 (ops/ROUTINES.md:52-57, owner decision 2026-09-12 "Opus everywhere", PR #164). This is where run 20 went: a run that consumed a number and wrote nothing. 1 lost record.
  • 09-22..09-24 (3 days) — panel genuinely dark, beacon/panel did not move. Cause NOT-ESTABLISHABLE from git (the scheduler control plane exposes only the most recent run). Recorded as dark, not diagnosed.

M2 — registration before computation (G5) · source: commit order on the full log, plus results/lab/ledger.jsonl

Inversions: 8 — the first non-zero M2 in this series. All eight are in the hypothesis lab; the queue and the forward clocks are clean.

Clean populations, for the record:

  • 8 queue builds landed on main, 0 inversions. SKW-1 (528793d 08-31 → 6ea3160 09-05) · KSP-1, IGD-1 (6ea3160 09-05 → 47ea024 09-06) · SKW-3 (8a656f5 09-04 → 877dce6 09-06) · SKW-4 (6ea3160 → 877dce6) · FOS-1 (f09b15e 09-02 → ee68ebe 09-06) · CNT-1 (ec8e80e 08-29 → b50ae9b 09-02) · STAMP-3 (f5abbbe 09-02 19:39:45 → 99656aa 19:58:53 — 19 min 08 s, the tightest order-correct gap in the series). Post-build edits to built files are append-only; criteria untouched (M3's domain).
  • Both forward clocks that matured 2026-09-09, 0 inversions. Placebo evaluator built c016632 2026-07-18, band pinned in-module, numbers first committed 82e802e 09-09 — 39 days. oos-monitor pinned fab0945/7a709eb 2026-06-11, verdict row 09-09. Recorded without calling it an inversion: the oos-monitor's maturity counter changed unit inside its own clock window (b50ae9b, CNT-1, 7 days before firing).

The lab's eight. Only results/lab/ledger.jsonl is committed (.gitignore:104-105); reports stay local. For every study, the spec YAML and its own result row entered git in the SAME commit, and the run clock pre-dates that commit. Lab run clocks and lab commit timestamps are both the owner-Mac clock (−0700), so they are directly comparable.

study (verdict)run clockspec+result commitgap
cross_index_v1 run A (NULL/NULL)09-27 22:39:33547950a 22:55:24−15m51s
comprehensive_v1 run A (NULL/NULL)09-27 23:12:51864da4e 23:21:00−8m09s
cross_index_v1 run B09-27 23:15:08spec 547950a 22:55+19m44s — CORRECT
comprehensive_v1 run B09-27 23:15:11864da4e 23:21:00−5m49s
the declared error budgetledger registered_at 09-28 21:05:07e53fa72 22:30:04−1h24m57s
trend_us_1926 (INCONCLUSIVE, slot 1/5)09-28 21:05:16e53fa72 22:30:04−1h24m48s
trend_parity_1962 (INCONCLUSIVE, 2/5)22:38:11a9cffcd 23:53:46−1h15m35s
vol_manage_1936 (KILL, 3/5)22:38:21a9cffcd 23:53:46−1h15m25s
treasury_carry_1962 (KILL, 4/5)22:38:22a9cffcd 23:53:46−1h15m24s
turn_of_month_1927 (KILL, 5/5)23:56:51ada5bff 23:59:38−2m47s

State the finding precisely, because the loose version would be an accusation and would be wrong. Nothing here shows a spec written after its result. What it shows is that for 8 of 9 lab runs the pre-registration is ASSERTABLE but not VERIFIABLE — the only evidence that the spec pre-dated the run is the ledger row that also carries the verdict, written by the same process in the same second. That is the exact property G5 exists to buy ("git makes it verifiable rather than assertable", NORTHSTAR §3). Sharpest instance: config/lab.yaml's error_budget block did not exist in git at all before e53fa72 — the commit that also carries confirmatory result #1 — so the declared p≤0.005 one-shot budget has no git existence preceding the first verdict charged against it. Run B of cross_index_v1 is the one order-correct case and shows the mechanism works when the spec is committed first.

Counter-evidence, measured and weighed: no spec was edited after its run. Each of the 8 YAMLs has exactly one commit, and I independently recomputed all 9 of 9 run rows' spec_sha256 against sha256(file)[:16] at HEAD — 9/9 match, and spec_text is byte-identical to the file. Practical exposure today is nil: every verdict is NULL, KILL or INCONCLUSIVE, nothing is promoted, and the reserve is 5 of 5 spent.

Partially-overlapping record made at the time, credited so this is not claimed as wholly novel: panel run 29's note (PR #188 §4) records that both committed exploratory runs are stamped code_version +lab-dirty — i.e. the code was uncommitted at run time. Nobody recorded the spec and budget commit order, nor extended it to the five confirmatory studies. → feedback item 2.

M3 — kill-criterion honesty · source: git show of proposal vs built text for all 8 new-in-window queue/built/ items; all 11 open PRs checked for stranded records (none touch queue/built/)

New-in-window set = 8 (cnt-1, stamp-3, skw-1, ksp-1, igd-1, skw-3, skw-4, fos-1). Last month's block measured 5 items, not 8 — igd-1/ksp-1/skw-1 reached built/ on 09-05/06, and cnt-1/stamp-3 merged after the run-1 commit window opened. The populations are disjoint; the series is continuous.

Criterion wording IDENTICAL 8 of 8. Every built file is a pure append — diff proposal→HEAD is one hunk each, the pre-registered body byte-identical. Silent divergence: 0 (third consecutive month).

But the control-execution reading got worse, 1 → 3, and that is this section's finding:

  • CRITERION NOT EVALUATED: 1 — SKW-1. Its built footer carries no verdict at all. Criterion as registered: "A test asserting every Alpaca SDK client construction site in src/ applies the deadline wrap (negative control: remove the wrap at the skew site → the test reds)" plus a "Falsifier: if the wrapped client breaks the skew capture within 5 shadow trading days … revert." Shipped: four per-site tests, no enumeration of src/ sites — a fifth construction site would not red the suite. The 5-shadow-day falsifier is reported nowhere in built/, RESEARCH.md, #125 or #150.
  • UNFLAGGED CONTROL UNDER-EXECUTION: 2 new. SKW-1 as above (census scope shipped as per-site tests, not flagged). CNT-1 leg 1 — criterion: "assert the digest's live_oos_days equals load_live_returns(...).size"; shipped (test_digest.py:214-225) asserts the literal == 1 with a comment, the loader comparison existing only on the public_export surface. The footer describes the edit, not the narrowing. (CNT-1 leg 3, the decisive control, is at full scope.)
  • CARRIED, UNCORRECTED, STILL UNDISCLOSED: STAMP-1. Last month flagged it; nothing changed. Criterion: "replay the captured_at timestamps of the last 20 committed capture days"; shipped _NORMAL_CAPTURES = 5 instants, unchanged at HEAD. Correcting last month's own phrasing: the test holds 35 parametrized cases, but 30 are run instants — the capture-day population the criterion names is 5 of 20. Its second leg ("then shadow 20 trading days") is also unreported.
  • Honesty-positive, recorded at full value: three disclosed leg shortfalls (ksp-1's test 3 "Not built", skw-4's leg 3 re-routed, igd-1's legs 3/5 flagged). Verdict reported in the PR body: 3 YES, 4 PARTIAL, 1 NO.

And one finding about this routine itself, which belongs here rather than in a footnote. STAMP-3's proposal (PR #144) was authored by run 1 of this very audit, and its negative control reads "replay the committed capture band (20:14–20:43Z normal days, the 09-01 post-close instant, and the pinned incident instants)" — a verbatim description of the already-shipped 5-instant set that the same run had just flagged as a reduction of STAMP-1's "last 20 committed capture days." The STAMP-3 criterion is satisfied as written and the relation is stated in neither #144 nor the STAMP-3 footer. The measurement is that a shrinkage flagged by this auditor was then normalized by this auditor into a new pre-registration within twenty minutes. No remedy is proposed; it is recorded so the series carries it and so next month can check whether it recurs.

M4 — reject-ledger integrity · source: queue/open/ (13 on main, +LAB-1 pending in #188) vs queue/dismissed/ (32) + web/public/data/killlist.json (13) + CLAUDE.md "Key Decisions"/"Research stance" + RESEARCH.md kill headers

8 NONE / 5 SUPERFICIAL / 0 RESTATEMENT over the 13 live open items (last month: 9 / 3 / 0 over 12). 11 of the 13 are new this window. No open slug exists in dismissed/; no dismissed idea was re-proposed verbatim or near- verbatim. The five SUPERFICIALs each carry their distinction on the record — BTB-1 and CQA-3 touch other clauses of the B4 pin than b4-notch resolved; DGX-1 absorbed fos-2 as a declared fold; PIT-1 is unchanged from last month and re-verified; FRZ-1 is the sharpest resemblance of the window (sibling-killed lab-3 the same panel run) and its distinction is itself a measurement — lab-3 died because the auto-merge guard denies writes to the files at risk, where FRZ-1 measures ALLOW for its own two files. Last month's three SUPERFICIALs all stand in hindsight (CNT-1 built, SKW-2 dismissed un-built, PIT-1 open).

The section's real finding is not a restatement; it is that the tripwires themselves have no reader. Five dismissed items' reopen conditions have fired or had their date pass, and not one was evaluated by any routine: LCK-1 (fired ~09-11, survived triage 09-14 and panel runs 21–23, caught by hand on 09-16) · BCN-1 (fired 09-21, annotated "the dismissal has lapsed by its own terms", not reopened) · RDR-1 (its own leg (b) fired; pre-registered action "reopen immediately" declined on the record) · GAP-1 (reopen "immediately if a seventh permanent capture-day is found" — massively exceeded by the 8-session outage; escalated, not reopened, now with a dated expiry of 2026-10-19) · CQA-2 (named by panel run 29 as the fourth fired-unevaluated instance). grep -i reopen over ops/QUEUE_TRIAGE.md, ops/RESEARCH_PANEL.md, ops/prompts/triage.md and ops/prompts/panel.md returns only write-side instructions. → feedback item 1.

Two further records-integrity facts, both recomputable:

  • A dismissal rests on a premise since measured false. b4-notch's dismissal asserts "The pin is hash-frozen"; FRZ-1 measures that no hash was ever cut for either the B4 or the B5 pin.
  • CLK-1's reopen date (2026-10-06) has not passed, but both its legs are already true at HEAD. NORTHSTAR.md §6 carries no row for the shortability clock or for options_iv_term — the only two forward clocks maturing before the December gate (slipped by the outage to ~2026-10-29 and ~2026-11-05 per panel run 29 §7) — against §6's own header rule that "every entry must have a pinned evaluator before maturity." Four independent records now name this gap (CLK-1's dismissal 08-31, triage 09-14, LCK-1's annotation 09-16, RDR-1) and it has never been a row. Owner-only: adding a §6 row needs an evaluator this routine may not invent, and §6 is a living section so the edit crosses no C0 boundary. Escalated, not acted on.

M5 — closure rate and cost (G3) · source: results/research_registry.jsonl, results/lab/ledger.jsonl, origin/beacon/implementer, build commits

  • Trial registry: 413 → 413, delta 0. Untouched since 4286d89 2026-08-01. Deflated-Sharpe hurdle movement: none. No hurdle recomputed at N=413 exists in any repo artifact (last recorded figure 3.32 at ~250 trials, RESEARCH.md:2318) — NOT-RECORDED, and worth noticing: the registry's purpose is to move that hurdle, and nothing has read it in two months.
  • Lab ledger: 0 → 10 rows (1 budget + 9 runs), 1,370 trials = 1,344 exploratory + 26 confirmatory; 1,370 distinct rule ids. Runs 3–4 are same-settings re-runs charging n_new_trials: 0, exactly as CLAUDE.md claims. Segregation HOLDS: zero lab-shaped rows in research_registry.jsonl and no commit to it after 08-01.
  • Closures: 17. 8 built queue items with a reported criterion verdict (M3's set; SKW-1 counted as a build, not a closure, would make it 7 — kept at 8 to match M3's population, with the caveat visible) + 7 lab study verdicts (2 NULL, 2 INCONCLUSIVE, 3 KILL — null results at full value per NORTHSTAR G3) + 2 fired forward clocks (placebo: live = 2nd percentile of 200 random 50-name nulls, null median +0.912; oos-monitor first non-INSUFFICIENT: OK, n_live 61). Excluded and counted separately: 22 queue dismissals (grooming), the oos_verdict_history.jsonl append (the clock's own carrier, counted once), and all 11 stranded PRs.
  • Cost per closed question: 0 registry rows / 17 closures. M5's warning direction — registry rows spent without closures — is again absent; the inverse holds for a second month. Lab: 1,370 trials / 7 closures = 196 trials per closure (672 per exploratory closure, 5.2 per confirmatory). The confirmatory reserve is 5 of 5 spent, 0 passes, and a sixth study is refused in code.
  • ROUTE COUNTER — the pre-registered trigger from run 1 is now MET. Last month recorded 0 of 6 and pre-registered: "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." It reads 0 of 13 cumulative. research/queue/approved/ has never held an item: its entire history is 47e8e73 2026-08-21 adding README.md, and nothing else, ever. All 6 new builds record the route honestly ("built on the owner's direct instruction … NOT via queue/approved/") — no rails breach. The Wednesday implementer has 5 run records in its existence (origin/beacon/implementer: 08-12, 08-19, 09-02, 09-16, 09-30), every one "nothing approved, no PR opened"; 2 of this window's 4 Wednesdays carry a record at all. Independently corroborated by triage's own counter firing 8 of 8 weeks unused on 2026-09-28 (PR #190) and by panel run 29's "eight weeks, thirteen proposals, zero approvals". So the finding, stated plainly: the designed panel→triage→approve→implement path has never once run end-to-end in two months, and the pipeline's actual throughput is panel-finds → triage-dismisses-to-fit-the-cap → owner-interactive hotfix. No machinery is proposed. Two routines already carry this counter; a third instrument would be a sixth opinion, and the only remedy — use the gate, move it, or retire the implementer — is the owner's alone.

G1 — auditor's residue · target: the hypothesis lab's research-integrity machinery (547950a, 864da4e, e53fa72, a9cffcd, ada5bff, 2026-09-27/28; src/thales/lab/, ~15 families, 1,370 trials)

Method: a fresh-context adversarial audit, given the lab's seven published integrity claims and told to falsify each, with no access to what anyone reported at the time; then compared by this session against the full record — the five commit messages (which self-report a pre-run adversarial review that found and fixed three MAJOR honesty holes), panel run 29's note (PR #188 §4), panel run 30's lab-3/LAB-4 dismissal, and the weekly audit of 09-28.

Residue: 6 = 1 MAJOR (3) + 3 MINOR (3). Last month: 7 = 1 MAJOR + 4 MINOR on a single-commit target. Two data points do not make a trend, and the targets are not comparable in size — this one is a whole new subsystem. Recorded flat, not improving.

The most important G1 number this month is not the residue. Of the three MAJORs the fresh audit surfaced, two were already on the record before it looked: lab confirm printing the vault verdict before charging the ledger (panel run 29's fold instruction, #188 §4, same file:line) and the vault's α ladder being drawn from the study spec, unbounded, and outside the declared budget (panel run 30's lab-3/LAB-4, reproduced twice at the time). A fourth candidate — the ledger-integrity tripwire being weekday-cron only — was considered and struck at review by run 29, with its reasoning recorded. So the pipeline's own reviewers independently found two thirds of the MAJOR class on this target, ahead of a fresh adversarial pass with a falsification brief. That is the most encouraging measurement in this block, and it is comparable next month.

MAJOR (residue) — every lab-wide guard becomes silently vacuous under a non-editable install, and the warning that exists cannot fire. src/thales/lab/data.py:61-67: repo_root() is Path(__file__).parents[3] and _resolve makes every lab path relative to it. Verified in this container, which installs exactly as the routine prompts prescribe (pip install . -c requirements-ci.lock, non-editable):

repo_root()                -> /home/user/thales/.venv/lib/python3.11
results_dir(real cfg)      -> .../lib/python3.11/results/lab   (absent)
prices_dir / data_dir      -> .../lib/python3.11/data/lab      (absent)
ledger.load(results_dir)   -> 0 rows
ledger.budget(rows)        -> None
ledger.known_trials(rows)  -> set()
ledger.vault_dates(rows)   -> set()
ledger.confirmations(rows)  -> {}

Consequences, simultaneously and with no error: check_vault (study.py:103-115) short-circuits on if seen and … and passes vacuously — the vault date is unpinned; check_budget returns None, so exploratory_alpha (study.py:95-100) falls back to defaults.fwer_alpha = 0.05, double the declared 0.025 pool; known_trials is empty so the lab-wide ratio collapses to 1.0 and the lab-wide correction is switched off; confirmations is empty so all five confirmatory slots read re-spendable and confirmatory.py's len(done) >= slots refusal cannot fire. The --config note at cli.py:54-62 is built from the same broken repo_root(), so canonical equals the mis-resolved directory and the warning is unreachable. Default config loading does fail loudly first (FileNotFoundError on .venv/lib/python3.11/config/lab.yaml), so reaching this state takes an absolute --config and a fresh lab fetch — stated plainly so the reachability is not overclaimed. The owner-Mac install mode is NOT-RUN (no access).

Two things make this more than a packaging nit:

  1. The house already learned this exact lesson one module over and wrote the guard. src/thales/backtest/cpcv.py:486-497 uses the identical parents[3] idiom, wraps it in if candidate.exists(): … else "results", and documents why in its own docstring: "A cwd-relative default silently flipped the DSR trials count to N=1 when run from outside the repo … collapsing the multiple-testing hurdle." The lab reproduces the same defect class, with the same consequence, unguarded. These are the only two parents[ sites in src/.
  2. It falsifies the premise on which a related finding was struck. Run 29 struck the vault/floor-tripwire finding partly because "the runtime backstop is not vacuous in the real repo, since results/lab/ledger.jsonl holds four records all sealed under 2023-01-01 for check_vault to refuse against." Under a mis-rooted install it holds zero records and check_vault is precisely vacuous. It also bears on lab-3's "exposure is nil today" reasoning, two legs of which ("the reserve is exhausted, 5 of 5"; "vault openings to date: 0") are ledger reads that return unspent/zero in this state. Recorded as new grounds the owner may weigh, not as a fired reopen condition — and dismissed → open is a sanctioned move for no routine anyway, which is feedback item 1's whole point.

MINOR (residue) ×3.

  • The never-shrinks test floors run rows, exploratory trials and confirmatory runs, but nothing floors confirm rows. tests/test_lab/test_repo_state.py:33-38; summary()'s n_confirmations is computed and never asserted. Deleting one confirm line resets k = len(done)+1 (cli.py:~584) and re-opens the vault at the lowest bar with the full suite green. There is no hash chain and no per-row digest, so an edit to any row is equally undetectable while the floors hold.
  • CSCV PBO is computed on raw excess-of-cash Sharpe, not the benchmark edge. study.py:~266 passes ex = R - cash. DSR and the luck bar were explicitly fixed to the Q1 differential in 864da4e for exactly this reason; PBO was not, so in an equity-heavy grid the in-sample winner is selected partly on equity beta. Reporting only — no headline verdict depends on PBO.
  • A study whose every rule errors is not charged. _eval_chunk (study.py:160-167) correctly swallows and still charges per-rule failures, but study.py:~434's if not ok: raise RuntimeError(...) aborts before _record, so N attempted rules go uncounted.

Claims the fresh audit tried to falsify and could NOT — recorded at full value, because verification is the other half of this gauge. The vault seal is physical at the panel layer: truncation happens at load for both panels before any rule exists (data.py:571-573, :647-649; Panel.truncate on close/open/signals/cash), sealed=True is the default with no study-settable knob, and load_panel(sealed=False) has exactly one call site repo-wide — lab confirm. lab expand, lab show and resolve_window all load sealed; lab report only prints a saved file. The one-shot is keyed on hypothesis_id, checked before any work, with no --force, ids derived from canonical family+params so a rename cannot mint a second id, a fail-closed holdout-overlap default of "2999-01-01", and k = len(done)+1 with Σ α/2^k = α and no off-by-one. Charge-before-write holds in both run paths (_record appends the ledger row as its first statement). The declared budget is enforced in code, not merely reported — a sixth confirmatory study raises BudgetError("the confirmatory reserve is exhausted (5 of 5)"), the budget must pre-date any confirmatory result, and a study may not choose its own bar. The lab-wide correction is the reported verdict. All 15 registered families are truncation-tested with a seeded-peeking negative control, and the per-run spot-check aborts before any charge. Lab/momentum segregation holds by construction. Named gaps inside those verifications: the raw files under data/lab are untruncated, so a new code path reading prices_dir directly would bypass the seal and no test pins sealed=False to one call site; and the run-time causality spot-check covers only the first hypothesis per family and 3 cut dates, so a lookahead on a particular parameter branch would pass at run time (the static test's per-family cases cover this; the run-time one does not).

Feedback filed this run (each with its motivating measurement)

  1. PR editing ops/QUEUE_TRIAGE.md §1 and ops/prompts/triage.md (will not auto-merge, deliberately) — the two-part ask panel run 29 §3 drafted and could not execute: sweep queue/dismissed/ for reopen conditions dated within 14 days or already triggered and report each one, plus a clause naming who may act on a fired one, because dismissed → open is currently a sanctioned transition for no routine. Motivated by M4's five fired-and-unevaluated conditions. Filed as a procedure edit rather than a queue row for the reason RDR-1 itself gives: the fix is prose in two mirrored files that must move in one sitting, which the implementer's rails forbid — so a PR from this routine is the only instrument that reaches it. This is not a relitigation of RDR-1; satisfying it retires RDR-1 permanently by its own "dies permanently" clause, and it lands before the terminal clause expires at Monday's triage. Clause (b) is drafted with the conservative option (reopening is the owner's move alone, granting no routine new power); the owner may substitute the other.
  2. PR editing ops/RESEARCH_IMPLEMENTER.md (same PR, will not auto-merge) — two clauses on the build standard: (i) a pre-registration must be committed in its own commit, before the computation it governs runs; a spec and its result in one commit is not a pre-registration (motivated by M2's 8 inversions); (ii) a built record must quote the registered negative control verbatim alongside what was executed, and label any reduction in scope or sample size (motivated by M3's 1 criterion-not-evaluated and 3 unflagged control under-executions, up from 1 last month). Falsifiable: next month's M2 counts inversions, target 0; next month's M3 counts unflagged under-executions, target 0.
  3. PR editing ops/RESEARCH_AUDIT.md §1 M1 (same PR, will not auto-merge) — make survived-review ÷ considered the primary survival gauge, with opened ÷ considered reported but explicitly marked cap-limited whenever queue/open/ stands at or over the pre-registered 12-item cap. Motivated by M1's measured decoupling (K/N 0.64–0.73 → 0.07 while M/N held at 0.30 with a 0.80 spike, the queue at or over cap in every run from 23 on). Without this, next month's M1 reads a reviewer collapse that did not happen. Falsifiable: if the cap binds again the two rates diverge again; if the cap is lifted they should re-converge.
  4. Deliberately NOT proposed, with reasons, so the restraint is auditable: no machinery for the route counter (two routines already carry it and the remedy is the owner's); no queue row for the G1 MAJOR (queue/open/ is at 13 against a 12 cap and #188's LAB-1 already spends the next eviction — the fix is a guarded repo_root(), three lines, and this block is its record, the same posture run 29 took with its fold instruction); no NORTHSTAR §6 rows for the two October clocks (the row requires a pinned evaluator this routine may not invent — escalated to the owner instead); no reaction to M4's five SUPERFICIALs (none is a restatement and the distinctions are on the record).

Kill-criterion status of this routine (procedure §4): run 2 — adopted-change counter 1 of 1 runs. Run 1's two items were both adopted: PR #144 (STAMP-3 queue row) auto-merged 2026-09-03T02:39:46Z and was built within 20 minutes as the #147 hotfix; PR #145 (the panel triple) was owner-merged 2026-09-03T03:49:17Z. §4's inverse check applied honestly rather than flattered: neither was cosmetic — #144's MAJOR was a live capture-stamping defect fixed the same evening, and #145 lifted triple compliance 0.71 → 0.93 and is the sole reason M1's cap-decoupling could be diagnosed this month instead of being read as reviewer rot. Three consecutive runs with no adopted change would drop this routine to quarterly; the counter stands at 0.

Post-measurement note, same day: while this block was being written, PR #188 (panel run 29) merged to main — the first routine PR to land after the auto-merge guard came back at 01:18Z. So LAB-1 is now live and M4's open set reads 14 against the 12-item cap rather than 13, and the eviction decision falls to Monday's triage. The measurements above are left as taken; M4's classification of LAB-1 (NOT a restatement of lab-3 — gate isolation versus registration hygiene) stands unchanged by the merge. Nine of the eleven PRs stranded by the outage were still open at the close of this run.