# Claim register

Every number that appears in the README, in the video, on a chart, or in the
submission text maps to a row here, and every row points at a generated artifact.

A number with no row is a defect. `tests/test_claim_drift.py` enforces this, and as of
D3 it also compares values, not just names: every number quoted in a results row must
appear in the artifact that row cites, at the precision it is quoted to. The exact check
sits beside it, because a large receipt holds enough numbers that "appears somewhere"
can be satisfied by coincidence: `scripts/sync_readme_results.py --check` regenerates
the generated table from the receipts and compares the rendered rows, and both the
standing gate and CI run it. Updating the README by hand without regenerating the
receipt now fails in three places.

## Rules

1. **Numbers are generated, never typed.** If it was typed, it is wrong until proven.
2. Every row cites an artifact path and the commit that produced it.
3. A claim whose artifact changed but whose text did not is a **drift failure**, not a rounding difference.
4. Video numbers are registered too. The video is public and unversioned; drift there is not recoverable after submission.
5. Claims about what the system *cannot* do belong here as well, and are checked the same way.
6. `FOR_JUDGES.md` is submission text, so its numbers are covered too. It is generated
   from the receipts by `scripts/sync_for_judges.py`, and `--check` fails if the committed
   page differs from what the receipts produce by one character, so a row here would be a
   second copy of the same number rather than a check on it. The generator is the row.
   `tests/test_for_judges.py` recomputes the gate tally by a second route, because
   `--check` compares the page against the generator and cannot catch the generator.

## Register

| Claim | Value | Where it appears | Artifact | Commit | Verified |
|---|---|---|---|---|---|
| The reviewing bottleneck this project exists for | 426 of 600 consecutive observations sampled from the live API carry `waterfall_status: unknown`, so 29.0% have a decisive human verdict and the rest have none. Measured over 24 cursor pages ending 2026-07-15, with 0.4 s between requests and no throttling observed | README opening paragraph, `FOR_JUDGES.md` challenge fit | `docs/SATNOGS_API_RECON.md` section 5 | D15i | 2026-08-20 |
| Corrected and uncorrected captures both occur | 4 corrected, 3 uncorrected of 24 | A3 finding, README, video | `artifacts/a3_overlays/summary.json` | `c7ca696` | 2026-08-16 |
| Metadata cannot reveal correction status | `doppler-correction-per-sec` null and `rigctl-port` 4532 on 24/24 | A3 finding | `artifacts/a3_overlays/summary.json` | `c7ca696` | 2026-08-16 |
| Strongest uncorrected match | 25.1 sigma curved against 2.8 sigma vertical, obs 14740031 | A3 finding | `artifacts/a3_overlays/summary.json` | `c7ca696` | 2026-08-16 |
| Strongest corrected match | 54.2 sigma vertical against 7.3 sigma curved, obs 14746118 | A3 finding | `artifacts/a3_overlays/summary.json` | `c7ca696` | 2026-08-16 |
| Observations with no measurable narrowband trace | 17 of 24 vetted with-signal | A3 finding | `artifacts/a3_overlays/summary.json` | `c7ca696` | 2026-08-16 |
| Pass geometry against reported max_altitude | median 0.22 deg, p99 0.53 deg, 99.5% within 1 deg, 199 of 200 | A4 finding, README, video | `artifacts/PHYSICS_VALIDATION.json` | `7fbb980` | 2026-08-19 |
| Reference quantisation on max_altitude | integer-valued on 200 of 200 records, so the comparison bounds the error near half a degree and cannot resolve anything finer | README, D5 build log | `artifacts/PHYSICS_VALIDATION.json` distribution.reference_quantisation | D5 | 2026-08-19 |
| Pass azimuth against reported rise and set azimuth | median absolute 0.268 deg at rise and 0.265 deg at set, max 1.962 deg, 100% within 3 deg, n=200 | README, D5 build log | `artifacts/PHYSICS_VALIDATION.json` azimuth_agreement | D5 | 2026-08-19 |
| Azimuth convention counterfactuals | swapped atan2 arguments 93.9 deg median absolute, mirrored azimuth 27.0 deg, against 0.268 for the shipped convention | README, D5 build log | `artifacts/PHYSICS_VALIDATION.json` azimuth_agreement.counterfactuals | D5 | 2026-08-19 |
| Gate 6 point lift (chronological, shipped capped queue) | 1.582x over random at budget 50 | KILL_GATE.md, gate 6 section | `artifacts/QUEUE_RECEIPT.json` | C2 | 2026-08-18 |
| Gate 6 governing 95% CI (chronological) | [1.353, 1.740], union of episode and station, contains 1.5. The upper bound read 1.755 until D15, when the bootstrap stopped rounding each draw's budget and started taking its ceiling, which put it exactly on the 87 over 50 cap | KILL_GATE.md | `artifacts/QUEUE_RECEIPT.json` | C2 | 2026-08-18 |
| Gate 6 bootstrap median (chronological) | 1.589 over 4000 of 4000 surviving resamples | KILL_GATE.md | `artifacts/QUEUE_RECEIPT.json` | C2 | 2026-08-18 |
| Gate 6 cold_station point lift | 2.253x, union [1.920, 3.859] (PASSED), 217 decisive. The 3.896 this row carried until D15g appears nowhere in the receipt | KILL_GATE.md | `artifacts/QUEUE_RECEIPT.json` | C2 | 2026-08-18 |
| n_queue_conflicts at budget 50 (chronological) | 20 of 50, against 12.644 expected by random | KILL_GATE.md | `artifacts/QUEUE_RECEIPT.json` | C2 | 2026-08-18 |
| Every ordering's lift over random (chronological) | queue 1.582, image uncertainty 1.186, FIFO 1.107, physics-only 1.028 | KILL_GATE.md | `artifacts/QUEUE_RECEIPT.json` | C2 | 2026-08-18 |
| Pass episodes hold 1.004 observations | 8 of 2716 episodes hold more than one; 87 episodes over 87 decisive chronological test rows, mean size 1.000 | KILL_GATE.md, C2_PREREGISTRATION.md | `artifacts/QUEUE_RECEIPT.json` episode_clustering | C2 | 2026-08-18 |
| Conflicts cluster by ground station | ICC 0.0887 / 0.0784 / 0.1347 / 0.0909, design effects 1.132 to 1.552 | KILL_GATE.md, C2_PREREGISTRATION.md | `artifacts/QUEUE_RECEIPT.json` station_clustering | C2 | 2026-08-18 |
| Cost of entity-concentration control | cold_station: 40 displaced, 36 conflicts to 27, lift 3.005 to 2.253, still PASSED | KILL_GATE.md | `artifacts/QUEUE_RECEIPT.json` uncapped_reference | C2 | 2026-08-18 |
| Transmitter cap is inert on this corpus | 0 displaced on all four splits, under non-exclusive attribution | KILL_GATE.md | `artifacts/QUEUE_RECEIPT.json` concentration | C2 | 2026-08-18 |
| Baselines beaten under both groupings | chronological: physics-only. cold_station: FIFO. cold_transmitter: FIFO and physics-only. cold_combined: physics-only | KILL_GATE.md C4 section | `artifacts/QUEUE_RECEIPT.json` replay_conclusion | C4 | 2026-08-18 |
| Queue never beats image-uncertainty ordering under the both-groupings standard | leads by +5 to +11 conflicts on every split, not established on any | KILL_GATE.md C4 section | `artifacts/QUEUE_RECEIPT.json` replay_conclusion | C4 | 2026-08-18 |
| Queue never loses to any baseline | 0 of 12 comparisons reach baseline_better under either grouping | KILL_GATE.md C4 section | `artifacts/QUEUE_RECEIPT.json` replay_conclusion | C4 | 2026-08-18 |
| Conflicts found at budget 50 (chronological) | queue 20, image uncertainty 15, FIFO 14, physics-only 13 | KILL_GATE.md C4 section | `artifacts/QUEUE_RECEIPT.json` replay_episode | C4 | 2026-08-18 |
| Superseded: gate 6 CI published in C1 | [1.00, 1.20] was a resample artefact, not a measurement | KILL_GATE.md failure log | reproduced at [1.0000, 1.2200] from the old loop | C2 | 2026-08-18 |
| Fitted corridor offset, obs 14740031 | 113.0 px, 13,985 Hz, 32 ppm, read off the rendered overlay at 1:1 | console observation page | `apps/web/public/data/cards.json` corridor | C5 | 2026-08-18 |
| SatNOGS produces 6,380 observations with a waterfall per day | measured over 2,500 stored waterfalls spanning 9.40 hours, from the capture time each station wrote into its own object key. Extrapolated from a sub-day span, so it is one observation of the rate rather than a long-run average | D17 build log entry, README | `artifacts/THROUGHPUT_RECEIPT.json`, `scripts/measure_throughput.py` | D17 | 2026-08-20 |
| One core keeps up with the whole network 10.77 times over | 1.2576 s per observation single-threaded at the dominant measured stage, 743 observations in 934.4 s, giving 68,702 a day against 6,380 | D17 build log entry, README | `artifacts/THROUGHPUT_RECEIPT.json` | D17 | 2026-08-20 |
| Ingestion costs more per observation than inference does | 1.8197 s wall clock against 1.2576 s of compute, a ratio of 1.447. The ingestion figure is bound by a 0.4 s courtesy interval and a 1.7 MB image download, neither of which is a property of this pipeline | D17 build log entry, README | `artifacts/THROUGHPUT_RECEIPT.json`, `artifacts/DATASET_MANIFEST.json` | D17 | 2026-08-20 |
| Console contrast, seven page types | 2,235 text nodes measured, 0 below requirement, 0 with an unresolvable background. The node count moves with every paragraph added to the console and is recorded as the population the zero was measured over; the zero is the claim | D16 build log entry | `apps/web/audit/a11y-probe.js` output | D16 | 2026-08-20 |
| The earlier contrast run was invalidated by a stylesheet change | the C6 run measured 1,475 nodes with 0 failures; the page ground later became a gradient set through the `background` shorthand, which resets background-color to transparent, so the probe's walk found no opaque colour and fell back to inventing white. Re-run on the same probe it reported 662 of 706 nodes on the landing page below their floor against a page that rendered correctly | D16 build log entry | `apps/web/audit/a11y-probe.js`, `apps/web/app/globals.css` | D16 | 2026-08-20 |
| Console keyboard reachability | 193 focusable elements over seven pages, 0 unreachable, 0 without a focus ring | D16 build log entry | `apps/web/audit/a11y-probe.js` output | D16 | 2026-08-20 |
| Every page has one h1, no heading level is skipped, no media element is unlabelled | 7 of 7 pages; the landing page's explainer video was the one unlabelled element and now carries an aria-label | D16 build log entry | `apps/web/audit/a11y-probe.js` output | D16 | 2026-08-20 |
| Carbon text-03 fails contrast as a text colour | 3.60:1 on the page background, 3.01:1 on a tile, against a 4.5:1 requirement | C5 build log entry, globals.css | computed from the Gray 100 palette | C5 | 2026-08-18 |
| Console client bundle, queue route | 306 kB to 7.5 kB after the data layer stopped crossing the client boundary | C5 build log entry | `next build` route table | C5 | 2026-08-18 |
| Console page weight over the wire | 8 to 18 kB brotli per page, 3.9 kB CSS for the whole site | C5 build log entry | measured against the deployed site | C5 | 2026-08-18 |
| Degraded states exercised by shipped cards | 0 of 25, stated on the page rather than implied | console provenance page | `apps/web/public/data/cards.json` | C6 | 2026-08-18 |
| Superseded: console reported four splits with no partition counts | `{}` on all four, and two null arm sections, from `.get()` against wrong key names | C5 build log entry | fixed by `_require` in `scripts/build_console_data.py` | C5 | 2026-08-18 |
| Superseded: queue receipt named an hour bucket as its episode key | `start[:13]` in the prose while the code grouped by orbital revolution | C5 build log entry | key now pinned by `const` in `contracts/queue_receipt.schema.json` | C5 | 2026-08-18 |
| Superseded: console claimed no network request after load | the router prefetches same-origin RSC payloads on link visibility | console provenance page | measured request list on the built site | C6 | 2026-08-18 |
| The pool's frequency axes were read by the one reader that can be wrong, and 43 of them are | every number in `artifacts/GATE3_POOL.json` came through easyocr, which is `parse_waterfall`'s default; the deployed endpoint passes `"auto"` and prefers the model-free template matcher. Re-reading all 2,424 pool axes with the matcher: 2,381 agree inside 1 percent, 43 do not, 0 could not be read. 42 of the 43 differ by more than 2x, so they are a different reading and not a rounding. Worked example, observation 14745990: the pool holds 1,157.02 Hz/px and the matcher reads 123.76, and the tick labels run -30 to +30 kHz across 823 px, so 60 kHz spans 485 px and the committed axis is wrong by 9.35x. The live endpoint returns UNCORRECTED at 9.40 sigma on it where the pool holds UNRESOLVED at 2.64 | `docs/CLAIM_REGISTER.md`, `mobile/README.md` | `artifacts/AXIS_READER_AUDIT.json`, `scripts/audit_pool_axes.py --check`, `tests/test_axis_reader_audit.py` | E20 | 2026-08-24 |
| Gate 3's rate survives the axis disagreement, and the pool was not re-derived | 8 of the 289 scored observations carry a disputed axis and 1 of those does discriminate. As published 224/289, rate 0.7751, 95 percent lower bound 0.7309. Dropping the disputed rows: 223/281, bound 0.7499. Counting every one as a miss: 223/289, bound 0.7273. All three clear the pre-registered 0.7, and the disagreement was costing the gate rather than inflating it. Re-deriving the pool with the other reader would move gate 3's rate through 14 published surfaces including the home page plate, to change a number that already clears its threshold under the worst reading of the disagreement, so it was audited and disclosed instead | `docs/CLAIM_REGISTER.md` | `artifacts/AXIS_READER_AUDIT.json` gate3_effect, recomputed against `GATE3_RECEIPT.json`'s own rows by `tests/test_axis_reader_audit.py` | E20 | 2026-08-24 |

## Pre-registered limits

These are stated before results exist, so they cannot be quietly relaxed once
numbers arrive:

- No generalisation claim survives a failed cold-entity result.
- Queue lift is only claimed when the grouped 95% interval sits above random.
- "Target-consistent trace" is never restated as confirmed identity, decoded
  telemetry, mission success, or a corrected community label.
- Physics is claimed to help only if it lowers Brier score against a **calibrated**
  image-only baseline, not an uncalibrated one.
- Any metric measured on a single window is reported as such, never as a
  population constant.
| Elevation was measured from the geocentric vertical, not the geodetic normal | up to 0.1924 degrees of systematic error on every elevation the pipeline ever computed | C7 build log entry, `pipeline/tracetriage/physics.py` | `geodetic_normal`, plus the before/after comparison against SatNOGS `max_altitude` | C7 | 2026-08-18 |
| The A4 elevation validation could not have detected that error | median absolute difference against SatNOGS `max_altitude` 0.2082 before, 0.2249 after; the error is smaller than the reference's own scatter | C7 build log entry | `artifacts/PHYSICS_VALIDATION.json`, re-run both ways | C7 | 2026-08-18 |
| The geodetic fix moved no gate verdict | gate 5 margin +0.02080 to +0.02079, gate 6 lift 1.582 to 1.582, both intervals unchanged to three decimals | C7 build log entry, `docs/KILL_GATE.md` | both receipts re-run at seed 42 and the published bootstrap count | C7 | 2026-08-18 |
| The fix moved only the arms that consume elevation | four arms changed in the fifth decimal, six arms bit-identical including every corridor arm | C7 build log entry | `artifacts/FUSION_RECEIPT.json`, before and after | C7 | 2026-08-18 |
| A physics-only ranking equals the full queue on the cold-station split once the horizon is correct | 19 conflicts at budget before, 27 after, against the queue's unchanged 27 | C7 build log entry | `artifacts/QUEUE_RECEIPT.json` gate6.per_split.cold_station | C7 | 2026-08-18 |
| Superseded: KILL_GATE.md published two different 95% intervals for gate 6 | summary said [1.00, 1.20] and 3.00x, the table and the receipt said [1.353, 1.755] and 2.253x | C7 build log entry | now generated by `scripts/sync_kill_gate.py` | C7 | 2026-08-18 |
| Superseded: gate 6's sample size was published as 88 observations | it is 87; gate 5's 88 had been copied across, and the queue deduplicates 410 rows to 407 | C7 build log entry | `n_test_decisive` and `n_groups` in the queue receipt | C7 | 2026-08-18 |
| Superseded: the pre-registration stated 88 observations in 87 episodes at mean size 1.000 | arithmetically impossible; the receipt says 87 in 87 at 1.000 | C7 build log entry | `gate6.per_split.chronological.episode_clustering` | C7 | 2026-08-18 |
| Exported pass geometry agrees with the scored propagation exactly | elevation and derived Doppler are asserted equal, not close, to `propagate_pass` output | `tests/test_physics.py::TestPassGeometry` | two equality tests over the full sample series | C7 | 2026-08-18 |
| The four instruments share one clock and the physics is consistent across them | at closest approach: 62.40 deg elevation, 758 km range, -197 Hz; +9,693 Hz at 2,054 km rising, -9,682 Hz at 2,032 km setting | console observation page | measured from the built site through the rendered DOM | C7 | 2026-08-18 |
| The replay costs no dropped frames | 662 consecutive frames, median interval 6.1 ms, maximum 6.3 ms, none over 32 ms, in an uncapped headless run | C7 build log entry | frame-interval probe on the built site | C7 | 2026-08-18 |
| The replay and the fourth instrument cost 1.73 kB of client JavaScript | observation route 5.22 kB to 6.95 kB, because the plots stayed server-rendered | C7 build log entry | `next build` route table | C7 | 2026-08-18 |
| The horizon circle is contained by its own plot frame on all four sides | asserted from `getBBox` on the rendered SVG, on the 62 degree and the 88 degree pass | C7 build log entry | browser bounding-box probe | C7 | 2026-08-18 |
| Amber was the wrong colour for an inconclusive verdict | Carbon assigns grey to unknown states; NASA Appendix F reserves yellow for caution | C7 build log entry, `apps/web/app/globals.css` | Carbon data-visualisation colour guidance and the Appendix F display standard | C7 | 2026-08-18 |
| NOT_ESTABLISHED matches SatNOGS' own review vocabulary | SatNOGS manual vetting has three states and one is Unknown; its automated rating adds a separate four-state axis | C7 build log entry | SatNOGS wiki, vetting documentation | C7 | 2026-08-18 |
| Retracted: the gate tally on every page is computed from the receipts | it was 3 of 6 met with gates 5 and 6 read from their receipts and gate 3 typed as a literal PASSED, so the tally was 2 generated and 1 typed | console side rail, `provenance.json` | superseded by the two rows below | C7 | 2026-08-18 |
| Every colour in the console is a token | 11 inlined hex values removed, 0 remaining in `app/` and `components/` | C7 build log entry | repository grep for hex literals in TSX | C7 | 2026-08-18 |
| The corridor explainer uses only values from the card it names | 61 px, 5,648 Hz, 13.0 ppm at 92.6 Hz per pixel, observation 14745984 | `media/explainer` video, `scripts/explainer_corridor.py` | the exported card for that observation | C7 | 2026-08-18 |
| Brier score, chronological holdout | 0.1292 shipped arm, 0.1495 image-only, 0.2085 prior-only floor | README, evaluation page | `artifacts/FUSION_RECEIPT.json` splits.chronological.arms | C7 | 2026-08-18 |
| AUC, chronological holdout | 0.875 shipped arm, 0.842 image-only | README, evaluation page | `artifacts/FUSION_RECEIPT.json` splits.chronological.arms | C7 | 2026-08-18 |
| Calibration slope and intercept | slope 1.483, intercept -0.246, ECE 0.0713 | README, evaluation page | `artifacts/FUSION_RECEIPT.json` splits.chronological.arms.image_corridor | C7 | 2026-08-18 |
| Selective risk near 80% coverage | risk 0.0857 at 79.5% coverage | README, evaluation page | `artifacts/FUSION_RECEIPT.json` splits.chronological.selective.curve | C7 | 2026-08-18 |
| Queue lift over random, chronological | 1.582x, 95% CI [1.353, 1.740], NOT_ESTABLISHED | README, queue page, KILL_GATE.md | `artifacts/QUEUE_RECEIPT.json` gate6.per_split.chronological | C7 | 2026-08-18 |
| Blinded human decidability rate | PASSED. 60 of 60 first-occurrence observations decidable, rate 1.0000, exact one-sided 95% [0.9513, 1.0000] against a 0.80 threshold. Intra-rater agreement 8 of 12 repeated plates, which is the weaker number and carries no claim. The reviewer is the author, so this is blinded by a pre-committed salted sha256 per item and is not independent, and the receipt says so | README, evaluation page, KILL_GATE.md, FOR_JUDGES.md | `artifacts/GATE4_RECEIPT.json` | E15 | 2026-08-22 |
| Queue lift over image-only uncertainty | 1.582x against 1.186x at the same budget | README, replay page | `artifacts/QUEUE_RECEIPT.json` gate6.per_split.chronological | C7 | 2026-08-18 |
| Queue lift over first-in-first-out | 1.582x against 1.107x | README, replay page | `artifacts/QUEUE_RECEIPT.json` gate6.per_split.chronological | C7 | 2026-08-18 |
| Cold-station holdout | PASSED, 2.253x, 95% CI [1.920, 3.859] | README, queue page | `artifacts/QUEUE_RECEIPT.json` gate6.per_split.cold_station | C7 | 2026-08-18 |
| Cold-transmitter holdout | 1.656x, 95% CI [1.336, 1.894], NOT_ESTABLISHED | README, queue page | `artifacts/QUEUE_RECEIPT.json` gate6.per_split.cold_transmitter | C7 | 2026-08-18 |
| Cold station and transmitter together | 1.292x, 95% CI [1.073, 1.520], NOT_ESTABLISHED | README, queue page | `artifacts/QUEUE_RECEIPT.json` gate6.per_split.cold_combined | C7 | 2026-08-18 |
| Physics beats image-only on Brier | NOT ESTABLISHED. Margin +0.02079, 95% CI [-0.01301, 0.05036], spans zero | README, evaluation page, KILL_GATE.md | `artifacts/FUSION_RECEIPT.json` gate5 | C7 | 2026-08-18 |
| Console page weight over the wire, C7 | 8.4 kB replay to 26.9 kB observation, compressed, measured on the deployed site | C7 build log entry | curl with Accept-Encoding against https://tracetriage.vercel.app | C7 | 2026-08-18 |
| Superseded: console page weight 8 to 18 kB | the observation page grew to 26.9 kB when the pass geometry was exported to it | C7 build log entry | superseded by the C7 measurement above | C7 | 2026-08-18 |
| Offline test suite size and result | 745 collected, 744 passed, 1 declared expected failure | C7 build log entry | `pytest -q --tb=no -p no:warnings` progress census, 744 `.` and 1 `x` | C7 | 2026-08-18 |
| Retracted: "745 offline tests pass" | the passing count is 744; the `xfail` in tests/test_claim_drift.py has been collected since the scaffold commit, so 745, 732 and 721 were all collected counts | earlier build log entries | superseded by the row above | C7 | 2026-08-18 |
| Superseded: explainer video at 1,646,670 B | that was the render whose scene hardcoded the old palette; it is no longer the committed file | earlier build log entry | superseded by the row below | C7 | 2026-08-18 |
| Explainer video and poster, as committed | 1920x1080, 24.000 s, 1440 frames, 1,613,559 B; poster 960x540, 31,237 B, cut at t=18.55 s | home page | `ffprobe` and `stat` on `apps/web/public/media/`, and the poster frame located by matching the previous poster against every frame of the previous render so the thumbnail shows the same moment | D18 | 2026-08-20 |
| Both files serve from the deployed origin, at the bytes in the tree | HTTP 200 and 1,635,786 B for the clip, HTTP 200 and 31,382 B for the poster, matching `stat` on the committed files. The clip is the D20 re-render, not the D18 one: the palette changed again between them, and this row is the second render | home page | `curl -o /dev/null -w '%{http_code} %{size_download}'` against https://tracetriage.vercel.app after the D21 deploy | D21 | 2026-08-20 |
| The deployed console serves the plum palette, not the warm one | `--ui-background:#150e1d` in the stylesheet the deployed index links, fetched from the origin rather than read from the build | home page, provenance page | `curl https://tracetriage.vercel.app/_next/static/css/072dd5873af0172f.css` | D21 | 2026-08-20 |
| The licensed typefaces hold the first paint for about 800 ms | 956 ms median to first contentful paint as served, 152 ms with the font host blocked, 944 ms with the self-hosted faces blocked instead. Five interleaved rounds per variant, fresh browser each, built export over a local static server, so it is a cold first visit and not a returning one | provenance page, layout comment | `apps/web/audit/paint-probe.js` and the interleaved A/B it came from | D19 | 2026-08-20 |
| The cause is `font-display: auto` on the kit, not the connection | all 90 faces the kit declares were `auto` when this was measured on 2026-08-20, and that is an Adobe account setting rather than anything in this repository, so it can change with no commit here; the two self-hosted faces are `swap`. A second preconnect changed nothing because one was already there, and no query parameter on the kit URL overrides the descriptor | provenance page, layout comment | `curl https://use.typekit.net/iie4ixd.css` with three query variants, and `document.fonts` read per family | D19 | 2026-08-20 |
| The console's lint script had never run on any commit | `npm run lint` and both eslint devDependencies were in `package.json` from the first commit of the console and no eslint config file existed, so ESLint 9 exited before reading any source. Its first successful run reported 2 errors and 5 warnings, one of them a `no-img-element` directive that suppressed nothing because it sat on the second line of its own comment. Lint is now a CI step | E2 build log entry | `npm run lint` in `apps/web`, before and after `apps/web/eslint.config.mjs` existed | E2 | 2026-08-21 |
| Superseded: the licensed typefaces hold the first paint for about 800 ms | true of every build up to 1f630a0 and false after it. The four rows below replace it, and the 956 ms figure is not comparable to the 596 ms in them: that harness started a fresh browser process per round and this one opens a fresh context | provenance page, layout comment | superseded by the rows below | E2 | 2026-08-21 |
| The font host no longer holds the first paint | 596 ms median before, 236 ms after, against a floor of 200 ms with the kit pointed at a closed port. The after case sits on the floor rather than near it: its fastest round is 192 ms and the floor's slowest is 232 ms. Five interleaved rounds per condition, one build patched three ways, fresh browser context each, uncompressed loopback server | provenance page, layout comment | `artifacts/FONT_PAINT_RECEIPT.json`, from `scripts/build_font_ab.py` and `apps/web/audit/font-paint-ab.mjs` | E2 | 2026-08-21 |
| The Adobe `font-display` setting was applied and reached neither face this console uses | the kit serves 18 faces at `swap` and 72 at `auto`; the 18 are `acumin-pro`, and `neue-haas-grotesk-display` and `din-2014-narrow` are both still `auto`. So the fix is in the loading order rather than in the descriptor, and it works whatever the kit is set to | provenance page, layout comment | `curl https://use.typekit.net/iie4ixd.css`, every `@font-face` grouped by family | E2 | 2026-08-21 |
| The fix costs 0.0115 of layout shift and 15,543 bytes on five of eight pages | CLS 0.0115 after against 0 before, identical in all five rounds, from the one reflow when the licensed face replaces Plex. Bytes: 44,536 before against 60,079 after on `/evaluation/`, because the head script waits for all three faces on every page, and 60,080 against 60,082 on the three pages that render all three. Both costs land after the first paint and cache for a year | provenance page | same receipt (`measurement.summary.*.cls`, `third_party_bytes`) | E2 | 2026-08-21 |
| Three licensed faces are rendered anywhere in this console, and they are the three the head script waits for | `neue-haas-grotesk-display 500`, `din-2014-narrow 600`, `din-2014-narrow 400`, over eight pages at 1440 and 420 px wide, `rendered_unwaited` empty on every one. The first list had the display face at 400, which no page renders, and omitted the label face at 400, which three pages do, and every page still reported clean, because by the time a probe can run anything rendered has finished loading. The script now publishes what it waited for in `data-fonts` and the probe compares against that | layout comment, provenance page | `apps/web/audit/font-swap-probe.js` over the built export | E2 | 2026-08-21 |
| Corrected: third-party bytes for the licensed typefaces, cold | 60,082 bytes on the landing page, not the 43,598 published on 2026-08-18: a 4,482 byte stylesheet, a 172 byte licence counter and three faces at 23,455, 16,430 and 15,543. The old figure was curl against two of the three faces rather than a page load, and this page rendered three faces when it was written, so it was low by a face from the day it appeared | provenance page, colophon | `artifacts/FONT_PAINT_RECEIPT.json` (`third_party_bytes`), from `request.sizes()` on a cold context | E2 | 2026-08-21 |
| Corrected: "a blocked font host costs the lettering and not the reading" | true of a host that is blocked, which times out and paints in Plex, and false of one that is merely slow, which holds the text unpainted. The provenance page now says both | provenance page | the same A/B | D19 | 2026-08-20 |
| Third-party bytes for the licensed typefaces, cold | 43,598 B: 4,166 stylesheet + 23,224 display face + 16,208 label face | provenance page, colophon, C7d build log entry | curl against use.typekit.net, each URL fetched cold outside the browser | C7d | 2026-08-18 |
| Licensed font files cache for one year | Cache-Control public, max-age=31536000 on both woff2 faces; the kit stylesheet is private, max-age=600 | C7d build log entry, provenance page | response headers from use.typekit.net | C7d | 2026-08-18 |
| The licence counter sets no cookie and returns five bytes | HTTP 200, Content-Length 5, no Set-Cookie header | provenance page | response headers from p.typekit.net/p.css | C7d | 2026-08-18 |
| Retracted: the console requests nothing from another origin | false from C7d. Two licensed faces and one licence counter are requested; the narrowed claim is that no DATA is requested from another origin | provenance page, colophon, video caption | superseded by the two rows above | C7d | 2026-08-18 |
| Instrument label sizes now render as one set | 11.3, 11.7 and 12.9 px against 14 px body prose, from viewBox scales of 1.03, 1.30 and 1.18 | C7d build log entry | getBoundingClientRect against viewBox.baseVal on the built page at 1440px | C7d | 2026-08-18 |
| The wide instrument was rendering its labels at 24.7 px | 420 user units displayed at 1151 px, a scale of 2.74 applied to a 9 px declared size | C7d build log entry | same measurement, before the rescale | C7d | 2026-08-18 |
| Hero figure treatments compared before choosing | ink widths 259, 235, 222 and 173 px at 112 px for Plex Mono -0.022em, Plex Mono -0.075em, Plex Sans 600, Neue Haas Display 500 | C7d build log entry, globals.css comment | Range.getBoundingClientRect on four rendered variants | C7d | 2026-08-18 |
| The first third-party byte count was contaminated | 138,112 B across 6 files, every entry transferSize 0 and deliveryType cache, warmed by this session's own canvas probes | C7d build log entry | performance.getEntriesByType('resource') deliveryType field | C7d | 2026-08-18 |
| Hero top padding above the fold at 375px | 88 px before the change, 64 px after, headline still fully above the fold | C7d build log entry | getBoundingClientRect at a 360 px client width | C7d | 2026-08-18 |
| Display face family name | neue-haas-grotesk-display, not "Neue Haas Grotesk Display Pro", which is the desktop retail name and never resolved | globals.css comment, C7d build log entry | Adobe Fonts kit iie4ixd font-family declarations | C7d | 2026-08-18 |
| Elapsed-overlay cost per frame | 0.009 ms for both writes including a forced style flush | C7e build log entry, PassReplay.tsx comment | 400-iteration timing loop in the browser on the built page | C7e | 2026-08-18 |
| Sub-pixel guard drops three quarters of the rasters | 180 of 721 frames write over a 12 s pass at 60 fps, 75 per cent skipped | C7e build log entry, PassReplay.tsx comment | enumeration of the guard condition over the frame sequence | C7e | 2026-08-18 |
| Adding the overlays did not change the bundle | route JavaScript 7.12 kB and shared 102 kB, both unchanged | C7e build log entry | next build route table, before and after | C7e | 2026-08-18 |
| Lottie rejected, with the cost that decided it | about 60 to 70 kB gzipped runtime against a 1.73 kB clock; rejected on provenance, not weight | C7e build log entry, globals.css comment | published lottie-web bundle size against the measured client bundle | C7e | 2026-08-18 |
| The duplicate path costs 100 bytes, not 6 kB | 34,138 B against 34,038 B, both gzip; the 6 kB figure compared gzip against brotli | C7e build log entry, SkyPlot.tsx comment | gzip -c on the built observation page for both variants | C7e | 2026-08-18 |
| Retracted: removing the duplicate path saves about 6 kB | it saves 100 bytes; the original figure compared two different compressors | SkyPlot.tsx comment, C7e build log entry | superseded by the row above | C7e | 2026-08-18 |
| The overlay paints, proven by hit test | at 60 per cent progress a point 10 per cent along hits sky-trail and a point 92 per cent along hits path.plot-track | C7e build log entry | document.elementFromPoint on both probe points, screen coordinates from getScreenCTM | C7e | 2026-08-18 |
| The overlay reveals from the rise, not the set | at value 0 the cursor is at translate(169.40 44.46) and the path start is (169.4, 44.5) | C7e build log entry | getPointAtLength(0) against the cursor transform at value 0 | C7e | 2026-08-18 |
| Playback frame intervals during the overlay animation | median 6.1 ms, max 6.5 ms, 0 of 660 frames over 20 ms, on an unthrottled headless browser at about 164 Hz | C7e build log entry | requestAnimationFrame interval sampling over a 4 s playback window | C7e | 2026-08-18 |
| Every unmet gate has a named binding constraint and a closure condition | 3 unmet, 3 with a constraint, and the writer refuses to emit the receipt if that is ever untrue. Two closures are exact arithmetic and one is labelled `extrapolated` with its assumptions stated. Gate 3's binding constraint is `independent_episodes`: 9 episodes all discriminating is the smallest exact bound that clears 0.70, because 0.05 ** (1/9) = 0.7169, and it has 68. So its shortfall is 0 and what it needs is not more observations but observations that are not the same receiver on the same night. The closure says that in those words. It read "9 testable observations against 3" while the pool was A3's | README `Where the gates landed, and why`, `/evaluation#why`, `FOR_JUDGES.md` | `artifacts/GATE_POWER_RECEIPT.json`, `tests/test_gate_power.py` (28 tests, the bound recomputed against `scipy.stats.beta.ppf`) | E15 | 2026-08-22 |
| Gate 6's verdict is predicted by the room each split gave the measurement | On all 4 measurable splits, whether the 95% interval fits inside `ceiling - threshold` predicts the verdict with no exceptions: cold_station has 2.673 of room against a 1.939-wide interval and passed at 2.253, and the other three do not and did not. On chronological and cold_combined the interval's upper bound **is** the arithmetic ceiling, 1.740 and 1.520, so resampling cannot return a higher number however good the ranking is | README `Where the gates landed, and why`, `/evaluation#why`, `FOR_JUDGES.md` | `artifacts/GATE_POWER_RECEIPT.json` (`gates[].the_room_rule`), rooms recomputed from `artifacts/CIRCULARITY_RECEIPT.json` in `tests/test_gate_power.py` | E15 | 2026-08-22 |
| The gate tally on every page is computed from the receipts | 3 of 6 met; gates 3, 4, 5 and 6 read from their receipts, an unknown verdict raises. It read 2 of 6 until a person answered gate 4 and the tally moved without a page being edited, which is the property this row is about | console side rail, `provenance.json` | `build_gate_summary` in `scripts/build_console_data.py` | E15 | 2026-08-22 |
| Retracted: gate 3 PASSED | NOT_ESTABLISHED. 3 of 3 testable observations discriminate, and the exact one-sided 95% lower bound on that rate is 0.3684 against a 0.70 bar; 9 of 9 would be needed at a perfect rate. This was measured on A3's three-observation pool and stood until E16 replaced that pool with 303 observations selected without reference to a corridor | README, KILL_GATE.md, console side rail, provenance page | superseded by the E16 row below; the receipt these numbers came from is `artifacts/GATE3_RECEIPT.json` at commit `c7ca696` | C7f | 2026-08-18 |
| Gate 3's per-observation evidence is unchanged by the retraction | each of the 3 beats 200 time-permuted corridors with 0 reaching it, p = 0.005, and beats all 4 scaled-swing controls; every sigma reproduced to six decimal places after the change. Measured on A3's three; the same three still reproduce, and E16's pool is 300 more | KILL_GATE.md gate 3 section | superseded by the E16 rows below; `artifacts/GATE3_RECEIPT.json` at commit `c7ca696` | C7f | 2026-08-18 |
| Retracted: the KILL_GATE.md summary and failure log are generated so a re-run cannot leave them behind | the generator was a one-shot text fixup whose second run raised `AssertionError: gate 5 summary row not found` and which appended its own correction paragraph unconditionally | KILL_GATE.md correction note | superseded by the row below | C7 | 2026-08-18 |
| The KILL_GATE.md summary and failure log are generated and idempotent | rendering twice writes identical bytes; `--check` reports drift without writing; the first regenerated table was byte-identical to the hand-written one | KILL_GATE.md, `scripts/sync_kill_gate.py` | `tests/test_kill_gate_sync.py`, 7 tests | C7f | 2026-08-18 |
| Superseded: offline test suite 745 collected, 744 passed | 760 collected, 759 passed, 1 declared expected failure, after the 15 tests added in C7f | superseded by the row below | superseded | C7 | 2026-08-18 |
| Offline test suite size and result | 760 collected, 759 passed, 1 declared expected failure, 0 failures, 0 errors | C7f build log entry | `pytest -q --tb=short -p no:warnings` progress census | C7f | 2026-08-18 |
| The opening frame draws the null corridors that were scored | 7 statistics reproduce GATE3_RECEIPT.json to 1e-9: n_nulls 200, true_sigma 2.024118, null_median 0.545208, null_p95 0.557253, null_max 0.571026, n_at_least 0, p 0.004975124 | home page plate, C7g build log entry | `scripts/export_hero_nulls.py`, which writes nothing on a mismatch; `tests/test_hero_nulls.py` | C7g | 2026-08-18 |
| The plate's scorer-to-card coordinate transform is a translation by EDGE_MARGIN_PX | residual 0.176 px worst case; the exporter refuses to write above 0.5 px | C7g build log entry | `artifacts/HERO_NULLS.json` transform_residual_px | C7g | 2026-08-18 |
| Using the source PNG height instead of the crop displaced the drawn corridor by 235.7 px | 29 kHz at 123.76 Hz/px, against a 17.3 kHz Doppler swing | C7g build log entry | measured before the transform check was added | C7g | 2026-08-18 |
| The opening frame costs no client JavaScript | route JavaScript 3.03 kB and shared 102 kB, both unchanged; the reveal is CSS stroke-dashoffset on pathLength=1 | C7g build log entry | `next build` route table before and after | C7g | 2026-08-18 |
| The opening frame costs 20.1 kB of document | home page 20.1 kB to 40.2 kB gzipped for 7 measured polylines of 257 points; 16 nulls measured 63.3 kB | C7g build log entry | gzip -c on the built out/index.html | C7g | 2026-08-18 |
| Superseded: the neutral palette carries a warm graphite cast | measured and true on the day; the cast is now hue 305 rather than hue 70 and the row below is the current measurement | D16 build log entry | superseded by the row below | D16 | 2026-08-20 |
| The installed package could not measure anything, and every published example depended on it | `tracetriage triage 14740031` from any directory other than the repository root died with `No module named 'pipeline'`: six imports inside the shipped package used the checkout spelling, which resolves only when the repository root is the working directory. Fixed to relative imports and verified by installing the wheel into a fresh virtualenv with base dependencies only and measuring observation 14740031 from outside the repository: 13,985 Hz, 32.05 ppm, p = 0.0050 over 200 nulls | `docs/USE_WITH_YOUR_AGENT.md`, `.mcp.json`, E1 build log entry | `tests/test_live.py`'s ast walk over every shipped module | E1 | 2026-08-20 |
| A first-party import failure was reported as a missing dependency | the handler told the reader that `No module named 'pipeline'` meant a package was missing and to install the project, which could not fix it. A packaging fault and a missing dependency are both ImportError and need opposite advice, so the reason is now classified before it is printed, and the live MCP server raises `BUILD_BROKEN` rather than `DEPENDENCY_MISSING` | `pipeline/tracetriage/cli.py`, `pipeline/tracetriage/mcp_live.py`, E1 build log entry | the wheel install with no dependencies | E1 | 2026-08-20 |
| The axis block said OCR had read an axis no OCR engine touched | a base install has no easyocr in it and still reported `derivation: axis_ticks_ocr`, contradicting the claim above it that the axis needs no neural model. `derivation` keeps its value because frozen comparisons read it; a sibling `axis.reader` names the reader that ran, and `hz_per_px` is unchanged to the last digit (123.76237623762377), so this moved a label and not a measurement | `docs/USE_WITH_YOUR_AGENT.md`, E1 build log entry | the wheel install with no easyocr present | E1 | 2026-08-20 |
| `pip install tracetriage` was the first line a judge would run and it 404s | the project is not on PyPI, and that command was quoted in 11 places including the one-minute version at the top of the agent guide, where every MCP registration below it depends on the console script existing. Replaced with a clone and `pip install -e .`, which is what works, and the guide says plainly that it is not on PyPI | `docs/USE_WITH_YOUR_AGENT.md`, README, E1 build log entry | `https://pypi.org/pypi/tracetriage/json` returns 404 | E1 | 2026-08-20 |
| An absent p-value says which of five reasons it is absent for | `nulls.n == 0` has five causes: a flat corrected corridor, a pass swinging under the 3 kHz floor, no offset fit, no null scored, and an unresolved mode. Three are refusals and two are measurement failures, and the output used to be identical for all five. Each branch now names itself and a test asserts the code's reason set equals both prose tables' key sets | `pipeline/tracetriage/live.py`, `pipeline/tracetriage/cli.py`, E1 build log entry | `tests/test_live.py` | E1 | 2026-08-20 |
| The live path reproduces gate 3's receipt through a different entry point | `live.measure` calls `parse_waterfall`, `fit_corridor` and `calibrate_against_nulls` in the order `run_gate3.py` calls them; replayed on the receipt's own observations it gives 13,985.148 Hz and -7,148.936 Hz twice, sigma 2.024 / 1.539 / 1.652, p = 0.004975 over 200 nulls, compared at a relative tolerance of 1e-9. Requires `label_reader="ocr"`, because the receipt's axis came from easyocr | README, `docs/USE_WITH_YOUR_AGENT.md`, E1 build log entry | `tests/test_live.py` | E1 | 2026-08-20 |
| The corrected/uncorrected mode can be measured instead of annotated | `doppler_mode.verdict_from_scores`, moved out of the A3 script, reproduces all 24 of A3's human-checked verdicts from the image alone, and `sigma_curved` matches the committed value to the decimal on all 7 decisive rows. Not a tautology: A3 scored through a MAD floor of 1e-6 and this scores through one grey level | README, E1 build log entry | `tests/test_live.py`, the 24-observation replay | E1 | 2026-08-20 |
| Scoring the wrong corridor fails silently, and did | the first draft of `live.py` scored `phys.corrected` unconditionally and returned -13,981 Hz, -32.11 ppm, sigma 0.35 and `n_nulls: 0` on an observation whose best path is 2.5 sigma against an 8 sigma floor. Nothing raised. SatNOGS's own `waterfall_status` for it reads with-signal | E1 build log entry, `pipeline/tracetriage/live.py` | the first live run, then the measured mode | E1 | 2026-08-20 |
| The frequency axis can be read with no neural model on 99.2 percent of the corpus | over 500 waterfalls drawn at random from the stage-1 snapshot the template matcher in `glyph_axis.py` derives an axis on 496, and 0 produce a label set that is not an arithmetic progression over the tick positions, so the failure mode is a missing label and never a wrong one | README, `docs/USE_WITH_YOUR_AGENT.md`, `pipeline/tracetriage/glyph_axis.py` | the 500-image sweep, `tests/test_glyph_axis.py` | E1 | 2026-08-20 |
| The digit 3 is not one connected component at this glyph size | its middle stroke meets the bowls only diagonally, so 4-connectivity splits it into a 4-row and a 6-row piece, both fail the digit-height filter, and the label 30 reads as 0: a wrong value on a tick rather than a missing one. Cost 30 kHz on the last tick of 14740031 and moved that image's axis by 0.25 percent | E1 build log entry, `pipeline/tracetriage/glyph_axis.py` | `tests/test_glyph_axis.py`'s progression check | E1 | 2026-08-20 |
| The committed axis for observation 14736773 was derived through a misread label | easyocr reads its centre tick as `562`, so `artifacts/a3_overlays/summary.json` holds an axis fitted through a label of 562 kHz where the value is 0. Found by comparing the two readers; the template matcher reads it as 0 | E1 build log entry, `tests/test_glyph_axis.py`, `tests/test_live.py` | the two-reader comparison | E1 | 2026-08-20 |
| The base install is 166 MB and can still answer in Hz | measured by summing each distribution's installed files in this project's virtualenv: 166 MB base against 4,643 MB with the `full` extra, of which torch alone is 4,171 MB. The axis reader is what makes the light install useful, since easyocr declares torch, torchvision, opencv and scikit-image as its own dependencies | README, `docs/USE_WITH_YOUR_AGENT.md`, `pyproject.toml` | the per-distribution size measurement, and a wheel installed with --no-deps into a fresh virtualenv | E1 | 2026-08-20 |
| A station's receiver frequency error is separable from orbit error across satellites | measured live on station 1696 on 2026-08-09: -28.26 ppm median over 2 distinct satellites (38756, 64534), spread -28.43 to -28.10 ppm. A receiver's error is common to everything it hears and an orbit's is not, so agreement across distinct satellites is the part that points at the receiver. Not a calibration on one satellite, and the command says so. **Separability is shown possible here, not general:** the same command on 2026-08-20 measured 3 satellites at -34.48 to +5.37 ppm, and both runs are printed in the guide. A live queue moves, so this claim is about one station on one day | README, `docs/USE_WITH_YOUR_AGENT.md` | `tracetriage station 1696 --budget 5` against the live API | E1 | 2026-08-20 |
| Two font families hold the first paint on every console page, not one | `din-2014-narrow` and `neue-haas-grotesk-display`, both `font-display: auto`, measured on `/`, `/evaluation/` and `/observation/14740031/`. The probe's earlier tag-list selector reported only one of them on two of those three pages, because it omitted the `dt` that sets the label face | D23 build log entry, provenance page | `apps/web/audit/paint-probe.js` over three pages | D23 | 2026-08-20 |
| The tightest non-exempt contrast pair is 4.59:1 | `--text-03` on `--ui-01`, a margin of 0.09 over the 4.5 floor, smaller than either declared exemption's distance from its own | `scripts/check_contrast.py` docstring, D23 build log entry | `scripts/check_contrast.py -v` | D23 | 2026-08-20 |
| The review page produces a file `score_gate4.py` accepts | driven through Playwright: three items answered including `no`/`unsure`/`na`, header `item,artifact_usable,visible_signal,target_consistent,seconds,notes`, every axis column filled, untouched rows fully blank and therefore skipped by the reader. Two reviewers proposed relaxing the completion check; the relaxed form was tested and the scorer refuses it | D22 build log entry | the Playwright drive and `scripts/score_gate4.py` against a synthetic 72-row bundle | D22 | 2026-08-20 |
| Gate 4's published status is read from its receipt, not typed | three files carried the literal `OPEN`; all three now read `artifacts/GATE4_RECEIPT.json`, with `NOT_RUN` mapped to `OPEN` in one declared place per file and an unrecognised verdict stopping the sync. Regenerating everything changed exactly one byte-level field, gate 4's `decided_in` | D22 build log entry | `scripts/sync_kill_gate.py --check`, `scripts/check_artifact_freshness.py`, the provenance diff | D22 | 2026-08-20 |
| Every published waterfall's largest channel spread is 1 of 255 | measured over all 50 committed images; the test that guards the claim asserted a bound of 2 and now asserts 1 | `apps/web/app/globals.css`, README, D22 build log entry | `tests/test_hero_window.py` | D22 | 2026-08-20 |
| The architecture diagram fits its boxes in the fonts a reader actually has | 45 text runs measured with `getComputedTextLength` against the box each sits in, over three fallback stacks with IBM Plex substituted away: 0 overflow on all three, and 0 runs past the canvas edge. Before the fix, DejaVu and Verdana each overflowed 4 runs by up to 47px | README, D21 build log entry | the three-stack overflow harness and `scripts/build_architecture_diagram.py --check` in the gate | D21 | 2026-08-20 |
| The neutral palette carries a deep plum cast at no contrast cost | every neutral re-expressed in OKLCH at Carbon's own lightness, chroma 0.030 in the darkest steps falling to 0.004 in the lightest, hue 305. The hue rotation moves the largest ratio on the ramp by 0.026, which is rounding. One lightness did change, the page ground from Carbon's L 0.200 to L 0.182, which raises ratios rather than lowering them: text-01 on the ground is 17.11 against Carbon's 16.45, text-03 on a rule 3.50 against 3.49, ui-04 as a component boundary 3.73 against 3.60. All 26 pairs meet their floor | `apps/web/app/globals.css`, D20 build log entry | `scripts/check_contrast.py`, `tests/test_contrast.py`, `apps/web/audit/a11y-probe.js` over seven page types | D20 | 2026-08-20 |
| Every rendered colour pair meets its WCAG floor | 26 of 26; the two below 4.5 are declared with reasons, text-03 on ui-02 at 3.49 and ui-04 on the ground at 3.60, both non-text | D16 build log entry | `scripts/check_contrast.py` | D16 | 2026-08-20 |
| The accent ramp is a colourmap sampled at named stops, not a chosen hue | link and interactive-01 are inferno at 0.80 (#fca50a) measuring 9.09:1 on the ground and 7.62:1 on a tile; interactive-04 is 0.85, caution is 0.70. Inferno's crimson at 0.55 measures 3.22:1 on a tile and cannot carry a word, so verdict-failed is that stop with its OKLCH lightness raised by 0.086, hue and chroma held, the smallest lift clearing 4.5:1 on both surfaces | `apps/web/app/globals.css`, D16 build log entry | `scripts/check_contrast.py` | D16 | 2026-08-20 |
| Every published waterfall is achromatic, which is what the palette rule rests on | largest per-pixel channel spread 1 of 255 across 25 committed images, mean 0.01; grey is measured and colour is computed | `apps/web/app/globals.css`, D16 build log entry | `tests/test_hero_window.py::test_every_published_waterfall_is_achromatic` | D16 | 2026-08-20 |
| An earlier palette rationale rested on a false premise about the data | globals.css stated that verdict-passed was chosen to sit in the same family as "the viridis ramp the waterfalls are rendered in"; the waterfalls are greyscale and the ramp was being applied by the console rather than found in the data | `apps/web/app/globals.css` | `tests/test_hero_window.py` | D16 | 2026-08-20 |
| The plate applies a colour map and a display window, both linear and both monotonic | inferno as a 17-stop SVG feComponentTransfer after a luminance matrix, preceded by one linear window shared by all three channels, so the ordering of measured intensities is preserved and no pixel changes rank against another | home page plate, `components/CorridorHero.tsx` | the filter definition in the shipped markup, `tests/test_hero_window.py` | D16 | 2026-08-20 |
| The plate's display window starts at the measured noise floor, not at a percentile | the modal level is 51 of 255 (0.2000) and holds 23.3% of the frame; windowed from there to the 99.5th percentile at 0.4078, giving slope 4.8113 and intercept -0.9623, with 30.7% of the frame rendering black and 0.47% white | home page plate, `components/CorridorHero.tsx` | `tests/test_hero_window.py` re-derives both constants from the committed image | D16 | 2026-08-20 |
| The page gradient can only darken | its lightest stop is --ui-background, so every checked ratio is a lower bound on what a reader gets | `apps/web/app/globals.css` | the gradient stops in the built stylesheet | C7g | 2026-08-18 |
| Retracted: the plate's limitation sentence was typed, not generated | it asserted 3 of 3 discriminating at a bound of 0.368 with nothing reading either number; a proposed change to the discriminates criterion would have made it false with the suite green | home page plate | superseded by the row below | C7g | 2026-08-18 |
| The plate's limitation sentence is read from the gate receipt | verdict, threshold, observations_scored, observations_discriminating and rate_lower_bound_95 all come from GATE3_RECEIPT.json through HERO_NULLS.json | home page plate | `tests/test_hero_nulls.py::test_the_caption_numbers_come_from_the_gate_receipt` | C7h | 2026-08-18 |
| Frequency axis direction, re-measured per observation | measured at D6 on A3's pool, and superseded by the E16 row below, which remeasures it on 289. As written then: the shipped convention wins on all 3 observations where it is measurable; the other 4 are corrected passes whose flat corridor cannot orient an axis at all. It was measured on 2 of the 20 client families in the dataset. The constant applies where a waterfall was rendered, which is 2,500 of the 2,727 stored observations: 1,004 of those come from a measured family and 1,496 inherit it. Over all 2,727 rows the figures are 1,023 and 1,704, and both pairs are published because the second counts 227 observations with no image | README results, `pipeline/tracetriage/physics.py` | `artifacts/GATE3_RECEIPT.json` (`axis_sign_scope` and `observations[].axis_sign`) | D6 | 2026-08-19 |
| The axis sign is not measurable on a corrected pass | the corrected corridor is identically 0 Hz across the pass, so it mirrors onto itself; the two orientations tie within 1.18x on all 4 corrected observations while the 3 uncorrected ones separate by 11.3x or more. A3 published an argmax sign for the corrected passes anyway and it came out +1 on two of them | `pipeline/tracetriage/physics.py`, gate 3 receipt | `tests/test_physics.py::TestAxisSignEvidence` | D6 | 2026-08-19 |
| Rows below the station's horizon are masked and the count is published | 26 of the 150 records the console builds from (17.3 percent) contain a below-horizon sample and the worst window spends 16.60 percent of its rows there. All 7 gate-3 decisive observations carry 0 such rows, so every published statistic is unchanged by the mask | `pipeline/tracetriage/physics.py`, gate 3 receipt | `artifacts/GATE3_RECEIPT.json` (`rows_masked_below_horizon`) | D6 | 2026-08-19 |
| The TLE staleness bound is derived, not chosen | 10 days, from a 500 Hz frequency budget through a 119.4 Hz/s peak Doppler slope to 31 km of along-track error. Inert on both corpora: the 38 gate-3 and A3 records span 4 days and the 200 validation records 8 | `pipeline/tracetriage/physics.py` | `tests/test_physics.py::TestConstants::test_stale_tle_threshold_is_the_bound_its_comment_derives` | D6 | 2026-08-19 |
| The gate-5 interval is resampled over stations as well as observations | the episode grouping is inert on this corpus (88 groups over 88 observations, so the intraclass correlation cannot be estimated at all), while the same paired differences give a station ICC of 0.2471 and a design effect of 1.3741. Both intervals are measured and the published bound is their union, so the verdict cannot rest on whichever grouping happened to be narrower | `pipeline/tracetriage/fusion.py`, D7 build log entry | `artifacts/FUSION_RECEIPT.json` (`clustering`, `ci95_episode`, `ci95_station`, `governing_interval`), `tests/test_fusion.py::TestClusteredBootstrap` | D7 | 2026-08-19 |
| Every corrected interval carries the resolution of the endpoint it is read at | a Bonferroni correction over 21 comparisons reads the 0.119th percentile, which 4,000 draws place at the fifth-smallest resample. 16,800 draws are the minimum that puts 20 draws in the tail, and the shipped run uses 50,000, which puts 59.5 there | `pipeline/tracetriage/fusion.py`, D7 build log entry | `artifacts/FUSION_RECEIPT.json` (`percentile_resolution`), `tests/test_fusion.py::TestPercentileResolution` | D7 | 2026-08-19 |
| The ablation correction runs over the 21 comparisons its rule reads | the rule retains a block if an arm containing it wins on any split above the 300-row training floor, and 3 splits clear that floor with 7 comparisons each. Correcting over one split's 7 was a correction over a family narrower than the search | `scripts/run_fusion.py`, D7 build log entry | `artifacts/FUSION_RECEIPT.json` (`multiplicity_family_size`, `n_comparisons`), `tests/test_fusion.py::TestCrossSplitFamily` | D7 | 2026-08-19 |
| One shipped ground track was drawn inside a 360-degree frame | observation 14744250 sees its satellite 62.28 degrees north at 1518 km, so the horizon circle encloses the pole and spans every longitude; framing to it left the 15.5-degree track occupying 4.1 percent of the plot width. Pole enclosure is the trigger and it fires on 1 of 25 cards. A footprint-to-track ratio cannot substitute: that ratio has a median of 5.2 and a maximum of 17.2 over the same cards, so a cap of 3 would clip 23 of them | `apps/web/lib/projection.ts`, the ground-track panel caption | `apps/web/tests/footprint-frames.test.ts`, `apps/web/tests/projection.test.ts` | D7 | 2026-08-19 |
| The corridor fit's sigma is not on the same scale as the A3 sigma | the ratio between them runs from 0.869 to 12.401 across the 7 gate-3 observations, so no constant converts one into the other and the two numbers cannot be compared or averaged. Both are published per observation with the ratio beside them | `docs/KILL_GATE.md`, `pipeline/tracetriage/corridor_fit.py` | `artifacts/GATE3_RECEIPT.json` (`a3_reference.sigma_scale_ratio_to_fit`, `sigma_comparability`) | D7 | 2026-08-19 |
| Retracted: the corridor block's Brier gain survives multiplicity correction | it survived over 7 comparisons on one split, on an interval that resampled episodes of mean size 1.0. Over the 21 comparisons the ablation rule reads and on the union of the episode and station bootstraps at 50,000 draws, the margin is unchanged at +0.02026 and the corrected interval is -0.00050 to +0.04874, which does not clear zero. The corrected rule therefore retains no block beyond image | `docs/KILL_GATE.md` gate 5 and its failure log, D7 build log entry | `artifacts/FUSION_RECEIPT.json` (`splits[chronological].multiplicity_adjusted.image_corridor_vs_image_only`), `tests/test_fusion.py::test_the_shipped_receipt_corrects_over_the_cross_split_family` | D7 | 2026-08-19 |
| The corridor block's risk-coverage gain does survive the same correction | margin +0.05736, 95% interval +0.02605 to +0.09312, corrected interval +0.01192 to +0.11887 over the same 21 comparisons on the same union of groupings. So the two metrics disagree about the block, and the one that survives is the one selective review is measured on | `docs/KILL_GATE.md` gate 5, D7 build log entry | `artifacts/FUSION_RECEIPT.json` (`splits[chronological].selective.aurc_shipped_vs_image_only`), `tests/test_fusion.py::TestClusteredStatisticDifference` | D7 | 2026-08-19 |
| What the product ships and what the ablation recommends are separate fields | the queue is ranked by `image_corridor` (`SHIPPED_ARM`, which `run_queue.py` fits and `run_fusion.py` measures), while the corrected rule recommends `image_only`. The receipt publishes both and states the disagreement with the two measured reasons the ranker was not rebuilt | console evaluation page, `docs/KILL_GATE.md`, D7 build log entry | `artifacts/FUSION_RECEIPT.json` (`ablation_conclusion.shipped_arm_vs_recommendation`), `tests/test_fusion.py::TestAblationRule` | D7 | 2026-08-19 |
| The ground track's axes are scaled independently, and each card says by how much | the vertical stretch runs 0.5046 to 1.6342 across the 25 shipped cards, and 14733024 sits at 1.0081, so the caption reports the ratio per card rather than asserting a distortion. The footprint is a spherical locus drawn into a frame that does not preserve it | the ground-track panel's accessible label | `apps/web/tests/footprint-frames.test.ts` | D7 | 2026-08-19 |
| Gate 1's 400 cursor pages take about 3.0 hours, not 15 minutes | measured over the 110 pages of the stage-1 snapshot: median 27.1 s between finished pages (mean 45.5, p10 23.7, p90 33.1), 82.6 minutes in total, 22.7 waterfalls per page at a median 0.98 s each. The 0.4 s API spacing accounts for 2.7 minutes of the 400-page estimate and the images account for the rest | `docs/KILL_GATE.md` gate 1 | file modification times under `D:/tracetriage_data/snap-stage1`, quoted in the D7 build log entry | D7 | 2026-08-19 |
| Most of the model's own reviewer notes are refused | 11 of 25 drafts accepted and 14 refused, a 0.56 refusal rate, and every refusal was an ungrounded number (14 UNGROUNDED_NUMBER, 1 OVERCLAIM) | README AI approach section, E1 build log entry | `artifacts/EXPLAIN_RECEIPT.json` (`counts`) | E1 | 2026-08-19 |
| The model invents a plausible downlink frequency | 9 of 25 observations, errors 10 kHz to 1215 kHz, each written in megahertz within five percent of that observation's own receiver frequency; 0 in-range values were written in another unit | README AI approach section, E1 build log entry | `artifacts/EXPLAIN_RECEIPT.json` (`hallucinated_downlink_frequency`) | E1 | 2026-08-19 |
| The checker is measured in both directions | 525 of 525 adversarial checks refused for the reason they were built to trip, and 0 of 175 clean checks refused: 21 adversarial and 7 clean checks against each of 25 observations' packets, which is 28 drafts per observation rather than 700. Detection requires the expected violation code, so a checker that refused everything for one reason would show as a gap between detection_rate and refusal_rate_over_adversarial | README AI approach section and the README `What it produced` block, E1 build log entry | `artifacts/EXPLAIN_RECEIPT.json` (`checker_sensitivity.adversarial_checks`, `adversarial_drafts_per_observation`), `tests/test_explain_receipt.py::test_the_checker_sensitivity_is_reproducible` | E1 | 2026-08-19 |
| Generation is not reproducible at temperature zero with a fixed seed | 18 of 50 drafts differed on a repeat inside one process (0.36) and 42 of 75 across a process boundary after a model unload (0.56), with verdict flip rates of 0.10 and 0.12. One freeze produced 0 differences over 75 repeats, so the instability is itself variable | README AI approach section, E1 build log entry | `artifacts/EXPLAIN_RECEIPT.json` (`run_to_run_stability`), `tests/fixtures/granite_notes.json` (`stability`) | E1 | 2026-08-19 |
| The note is written from a closed packet, and the model cannot be reached from the annotation store | 26 printed fields per observation, one HTTP write verb in the repository at one asserted call site, and a destination proved loopback before the URL is built | README AI approach section step 9, E1 build log entry | `tests/test_annotate.py::test_the_annotation_store_cannot_reach_the_model_runtime`, `pipeline/tracetriage/granite.py` | E1 | 2026-08-19 |
| The evidence server is read-only, offline and dependency-free | 5 tools, 31 tests; no network or filesystem write survives an AST walk over 14 write names and open's mode argument, with one stream receiver exempt at one asserted call site; and the handshake answers under an interpreter started with -S -E, which drops site-packages | README AI approach section step 10, E3 and E4 build log entries | `tests/test_mcp_server.py` | E3 | 2026-08-19 |
| Most of the queue's top rows carry no evidence packet | 24 of the top 50, because the queue ranks all 407 test-split observations and only the 25 the console ships imagery for have a packet. Every row states it | E4 build log entry | `tests/test_mcp_server.py::test_the_queue_says_which_rows_the_other_tools_can_answer_about` | E4 | 2026-08-19 |
| A receipt stamped with HEAD's commit date cannot be reproduced from a clone | 1047 passed and 1 failed in the first clean clone taken after the publish; the receipt now carries the sha256 of the frozen drafts instead and every timestamp in it has to appear in the committed fixture | E4 build log entry | `tests/test_explain_receipt.py::test_the_receipt_carries_no_value_derived_from_the_current_commit` | E4 | 2026-08-19 |
| Gate 4 has an instrument now, and it has not been run | the worksheet builder produces a balanced 72-item bundle over 60 observations with 12 repeated, 20 per label class, and the scorer publishes `verdict: NOT_RUN` with no rate in it, because no response has been recorded. NOT_RUN is a third outcome beside PASSED, FAILED and NOT_ESTABLISHED rather than a synonym for any of them | `docs/KILL_GATE.md` gate 4, E6 build log entry | `artifacts/GATE4_RECEIPT.json` (`verdict`, `why`), `tests/test_gate4.py::test_an_unfilled_worksheet_scores_as_not_run_and_publishes_no_rate` | E6 | 2026-08-19 |
| The sample was fixed before the review rather than after it | what the repository commits is one sha256 per item, taken over the salt, the item id, the observation id and the image digest, and nothing else; the salt and the mapping are written outside the checkout, and the scorer re-hashes every image from disk, recomputes every commitment and refuses to score if one fails. The receipt then publishes the salt, so any reader can recompute all 72 | `docs/KILL_GATE.md` gate 4, E6 build log entry | `artifacts/GATE4_WORKSHEET.json` (`commitments`), `tests/test_gate4.py::test_every_commitment_verifies_and_a_changed_key_does_not` | E6 | 2026-08-19 |
| The committed manifest cannot be inverted against this repository | it carries two fields per item and names no observation id, no label, no model probability and no image digest, over a scan of all 25 shipped observation ids and five field names. It does state, in prose, which four things it withholds | E6 build log entry | `tests/test_gate4.py::test_the_committed_manifest_leaks_nothing_the_reviewer_must_not_see` | E6 | 2026-08-19 |
| A pass was arithmetically unreachable at the first sample size | at 8 unique observations the exact lower bound for 8 of 8 is 0.688, which is below the 0.80 threshold, so PASSED could not be produced by any set of answers | E6 build log entry | `tests/test_gate4.py::test_all_three_measured_verdicts_are_reachable` | E6 | 2026-08-19 |
| The blinded bundle is never committed | the images, the key and the filled form live outside the checkout by default, and a test walks the tracked file list to assert none of them is in it. Committing the key would end the blinding for every later reader | E6 build log entry | `tests/test_gate4.py::test_the_bundle_is_never_committed` | E6 | 2026-08-19 |
| Byte-identical repeats gave the pairs away to `sha256sum` | the two copies of a repeated observation were written with `shutil.copyfile`, so 45 files had 36 distinct digests in 9 groups of two, and those groups were the 9 repeats: recoverable with no salt, no key and no repository. Each item is re-encoded with its own id in a PNG text chunk now, giving 72 distinct file digests over 60 distinct pixel digests, and the test asserts pixel identity rather than the byte identity that leaked | E6 build log entry | `tests/test_gate4.py::test_a_repeat_is_the_same_image_and_never_lands_beside_its_twin` | E6 | 2026-08-19 |
| The commitment binds the images and not only the mapping | the scorer re-hashes all 72 files from the bundle before it scores a response, and refuses if one differs or if the bundle is gone. Before this it hashed the digest the key carried, so every image could have been replaced and all 45 commitments would still have verified, while the error message blamed exactly that case | `docs/KILL_GATE.md` gate 4, E6 build log entry | `artifacts/GATE4_RECEIPT.json` (`stimulus.images_rehashed_from_disk`), `tests/test_gate4.py::test_the_stimulus_is_rehashed_from_disk` | E6 | 2026-08-19 |
| The sample is sized so a plausible corpus can clear the threshold | at 36 observations the exact lower bound reaches 0.80 only at 34 of 36, a rate of 0.944, so a corpus whose true decisive rate is 0.90 would return NOT_ESTABLISHED however the review went. At 60 it reaches 0.80 at 54, and a true rate of 0.90 gives 0.8121. PASSED needs 54 or more decisive, FAILED needs 42 or fewer | `docs/KILL_GATE.md` gate 4, E6 build log entry | `artifacts/GATE4_WORKSHEET.json` (`what_this_sample_size_can_establish`), `tests/test_gate4.py::test_the_sample_size_can_establish_the_threshold_it_publishes` | E6 | 2026-08-19 |
| Tools change what the agent gets right, and the study is paired | 22 of 24 correct with the seven MCP tools against 2 of 24 without them, over the same 24 questions put to the same model at temperature zero. Of the 20 discordant questions the tool arm won 20, an exact one-sided p of 1e-06 | README pipeline step 11 and the README `What it produced` block, `FOR_JUDGES.md`, E7 build log entry | `artifacts/AGENT_RECEIPT.json` (`arms`, `paired`), `tests/test_agent.py::test_the_paired_counts_partition_the_tasks` | E7 | 2026-08-19 |
| The control declines rather than knows, and its guesses are marked | 18 of 24 questions answered unknown with no tools, 3 answers carrying a number that appeared nowhere it had read, and one of its two correct answers is a choice between two allowed words | E7 build log entry | `artifacts/AGENT_RECEIPT.json` (`arms.control`), `tests/test_agent.py::test_a_control_answer_with_an_invented_number_is_not_grounded` | E7 | 2026-08-19 |
| Every question was proved answerable before a model was graded on it | 24 of 24 questions have a named single tool call whose result contains the expected answer as a value, checked by walking the payload's leaves rather than by matching substrings. One question that failed this was replaced: the receipt tool serves top-level scalars and collection sizes, so a count nested inside an object was unanswerable | E7 build log entry | `tests/test_agent.py::test_a_question_the_tools_cannot_answer_is_refused` | E7 | 2026-08-19 |
| The agent's two wrong answers are two different defects | one question had the value in what it read and was answered from a neighbouring field, and one was never fetched at all. 23 of 24 questions ended with the answer somewhere in what the policy read | E7 build log entry | `artifacts/AGENT_RECEIPT.json` (`arms.tools.wrong_with_the_answer_in_front_of_it`), `tests/test_agent.py::test_the_wrong_answers_are_split_by_whether_it_ever_fetched_the_value` | E7 | 2026-08-19 |
| Similarity carries the outcome when the station is allowed, and stops carrying it when it is not | warm agreement at 5 is 0.6181 for the Granite embedding against a random arm at 0.5302, a margin of 0.0880 with a Bonferroni-adjusted 95% interval of [0.0338, 0.1558] over 8 comparisons; the same arm cold, which forbids the query's own station, its own physical site and its own satellite, is 0.5543 against 0.5283, a margin of 0.0260 whose adjusted interval [-0.0353, 0.0998] spans zero. The drop between the two conditions is a comparison in its own right and is measured as one, paired per query: 0.0639, adjusted interval [0.0160, 0.1182], which excludes zero. This sentence was asserted from the two intervals above until D15, which is not a test of the difference between them | `/precedent` console page, README pipeline step 12, E8 build log entry | `artifacts/PRECEDENT_RECEIPT.json` (`conditions.warm.comparisons.granite_text_vs_random`, `conditions.cold.comparisons.granite_text_vs_random`) | E8 | 2026-08-19 |
| Text embedding and seven standardised numbers are indistinguishable at this sample size | warm margin 0.0260 with an adjusted interval of [-0.0169, 0.0660] and cold margin 0.0168 with [-0.0406, 0.0783], both spanning zero over 739 queries resampled by 116 ground stations. The embedding is not established as better than the numbers it was compared against | `/precedent` console page, the README `What it produced` block, E8 build log entry | `artifacts/PRECEDENT_RECEIPT.json` (`conditions.*.comparisons.granite_text_vs_numeric_knn`) | E8 | 2026-08-19 |
| The station's own recent passes are a real arm and they have no cold definition | warm agreement 0.6052 over the 682 queries whose station had five other passes, with 57 undefined; cold scores 0 queries and publishes `agreement_at_k: null` with a named reason rather than a zero, because excluding the query's own station removes the arm's entire candidate set | `/precedent` console page, E8 build log entry | `artifacts/PRECEDENT_RECEIPT.json` (`conditions.cold.arms.same_station.not_applicable`), `tests/test_precedent.py` | E8 | 2026-08-19 |
| The approximate index is not what decides any of this | recall at 5 against exact cosine search is 0.9978 warm and 0.9997 cold over all 739 queries, and the cold condition is answered by a metadata filter inside the index rather than by discarding neighbours afterwards, so the filter and the exclusion rule are checked against each other | `/precedent` console page, E8 build log entry | `artifacts/PRECEDENT_RECEIPT.json` (`vector_index`) | E8 | 2026-08-19 |
| No arm in the precedent study sees an image | all four rank on what is knowable before the waterfall is opened: the seven numeric features are frequency, maximum elevation, pass length, station latitude and longitude and the local hour as a sine and cosine pair, and the embedded card carries none of the label fields. A test walks the rendered card for every excluded field | `/precedent` console page, E8 build log entry | `artifacts/PRECEDENT_RECEIPT.json` (`feature_names`), `tests/test_precedent.py` | E8 | 2026-08-19 |
| Shipped ranker against what the ablation recommends | the queue ranks with `image_corridor` and the corrected ablation rule recommends `image_only`, so they disagree. The block kept without corrected support is `corridor`, retained because the same arm's risk-coverage margin against the reference arm is +0.05736 with a corrected interval of +0.01192 to +0.11887 over 21 comparisons, which does clear zero while the Brier margin does not. This row is the README's results-table entry for the disagreement; the row above records the decision itself | README results table, console evaluation page, `docs/KILL_GATE.md` | `artifacts/FUSION_RECEIPT.json` (`ablation_conclusion.shipped_arm_vs_recommendation`), `tests/test_fusion.py::TestAblationRule` | D13 | 2026-08-20 |
| Granite text embedding against seven standardised numbers | indistinguishable in both conditions: warm margin +0.0260 with an adjusted interval of [-0.0169, +0.0660] and cold margin +0.0168 with [-0.0406, +0.0783], both spanning zero over 739 queries resampled by 116 ground stations. This row is the README's results-table entry for the head-to-head; the row above states the same comparison in prose | README results table, `/precedent` console page | `artifacts/PRECEDENT_RECEIPT.json` (`conditions.*.comparisons.granite_text_vs_numeric_knn`), `tests/test_precedent.py` | D13 | 2026-08-20 |
| The ceiling on gate 6's lift at its own budget | 1.740x. A budget of 50 over a population of 87 holding 22 conflicts caps any ordering at 22/12.6437, so the whole distance between the 1.5x threshold and a perfect oracle is 0.240. The queue found 20 of the 22, which is 91% of the ceiling | README circularity subsection | `artifacts/CIRCULARITY_RECEIPT.json` (`ceiling`), `tests/test_circularity.py::test_the_ceiling_is_the_population_over_the_budget` | D14 | 2026-08-20 |
| Ninety percent of the ranking score's weight sits on quantities the conflict definition reads | the score is 0.40 disagreement + 0.35 safe offset + 0.15 flat-row fraction + 0.10 ensemble uncertainty, and the three criteria threshold the model probability, the fitted offset and the flat-row fraction. Only the 0.10 on ensemble uncertainty is independent of the target. A second figure is published beside it: 0.75 is the weight on quantities a conflict in this corpus is actually defined from, because DEAD_CAPTURE carries 0.15 and fires on nothing here. Both are stated, because a reader given only the 0.90 is told the loop is worse than it is and a reader given only the 0.75 is told the definition is tighter than it is | README circularity subsection | `artifacts/CIRCULARITY_RECEIPT.json` (`shared_signals`), `tests/test_circularity.py::test_every_conflict_criterion_is_mapped_to_a_score_weight` | D14 | 2026-08-20 |
| Lift with the model taken out of the conflict definition | 1.557x, 95% CI [1.264, 1.740] on the union of the episode-grouped and station-clustered bootstraps, counting only the 19 conflicts flagged by STALE_CATALOGUE_FREQ. The restriction names STALE_CATALOGUE_FREQ and DEAD_CAPTURE, and DEAD_CAPTURE fires on nothing in this corpus: the highest flat_row_frac in the whole queue is 0.1371 against its own 0.15 threshold, so the `or` does no work and the receipt publishes the count that says so. NOT_ESTABLISHED, on the same ordering and the same population as the gate | README circularity subsection | `artifacts/CIRCULARITY_RECEIPT.json` (`targets.model_independent_only`), `tests/test_circularity.py` | D14 | 2026-08-20 |
| A saturated lift is reported as NOT_INFORMATIVE, not as a pass | restricting the target to MODEL_LABEL_DISAGREE leaves 3 conflicts and the queue finds all 3 inside the budget, so the lift is population/budget = 1.740x whatever the count had been and its narrow interval is an artefact of a constant. The verdict machinery would have printed PASSED | README circularity subsection | `artifacts/CIRCULARITY_RECEIPT.json` (`targets.model_dependent_only.saturated`), `tests/test_circularity.py::test_a_saturated_target_is_not_reported_as_a_pass` | D14 | 2026-08-20 |
| A random ordering scores 1.0 on the same statistic | mean lift 0.999235 over 2,000 seeded permutations of the same population against the same target, 5th to 95th percentile 0.712 to 1.265. Each permutation is scored by `compute_lift` rather than by an inline ratio: the first version computed found over its own expectation, which is 1.0 by identity, and returned 1.0 with the queue reversed, with every conflict flag inverted, and with `compute_lift` replaced by a function that raises. The floor check the whole comparison rests on | README circularity subsection | `artifacts/CIRCULARITY_RECEIPT.json` (`random_ordering_control`), `tests/test_circularity.py::test_a_random_ordering_scores_one` | D14 | 2026-08-20 |
| No random ordering of the same population matched the queue | 0 of 2,000 seeded shuffles found as many conflicts inside the budget as the shipped queue did, a permutation p-value of 0.0005, which is the smallest the test can report at 2,000 permutations. Every shuffle is scored by `compute_lift`, the same function gate 6 is measured with, so a defect in it moves this number. This is a different question from the bootstrap interval: the interval says how far the lift moves when the population is resampled, and this says whether an ordering that knows nothing could have produced it | README circularity subsection, `/` lede, `/evaluation#circularity` | `artifacts/CIRCULARITY_RECEIPT.json` (`random_ordering_control.p_value_permutation`), `tests/test_circularity.py::test_the_permutation_test_answers_the_question_the_bootstrap_does_not` | D15 | 2026-08-20 |
| One split's verdict could not have been informative at its own budget | `cold_combined` holds 20 conflicts in 76 observations at a budget of 50, so a perfect oracle scores 76/50 = 1.520x against a 1.500x threshold. Its published NOT_ESTABLISHED is a fact about the budget rather than about the ordering, and the receipt marks it `informative: false`. The other three splits have 0.24, 0.40 and 2.67 of room. The bound used to be computed for the chronological split alone, which is the one that needed it least | README circularity subsection, `/evaluation#circularity` | `artifacts/CIRCULARITY_RECEIPT.json` (`ceilings_by_split`), `tests/test_circularity.py::test_a_split_whose_oracle_barely_clears_the_bar_is_marked_not_informative` | D15 | 2026-08-20 |
| Sorting on the offset alone reproduces the queue's lift exactly | a one-line descending sort on abs(fitted_offset_ppm), at-bound rows zeroed, finds the same 20 conflicts at the same budget and scores the same 1.5818x. The paired comparison is indistinguishable under both groupings, episode [-4, +4] and station [-3, +3] conflicts. STALE_CATALOGUE_FREQ accounts for 19 of the 22 conflicts on this split, so the other three terms of the composite score are not established as buying anything here. Declared as a fifth baseline, which widens the Bonferroni correction on every other ordering comparison including the ones the queue wins | `/replay` console page, README circularity subsection | `artifacts/QUEUE_RECEIPT.json` (`gate6.per_split.chronological.replay_episode.comparisons.offset_magnitude`), `pipeline/tracetriage/queue.py::baseline_offset_magnitude` | D15 | 2026-08-20 |
| One of the three conflict criteria fires on nothing | DEAD_CAPTURE thresholds flat_row_frac at 0.15 and the highest value in the whole 407-row queue is 0.1371, so it flags 0 rows. MODEL_LABEL_DISAGREE flags 3 and STALE_CATALOGUE_FREQ flags 19. The score weight on quantities the definition names is 0.90 and the weight on quantities a realised conflict is actually defined from is 0.75, and both are published because they answer different questions | README circularity subsection, `/evaluation#circularity`, `/` conflict-definition table | `artifacts/QUEUE_RECEIPT.json` (`conflict_definition.criteria_fired`), `tests/test_circularity.py::test_an_inert_criterion_is_named_with_the_value_that_makes_it_inert` | D15 | 2026-08-20 |
| Every gate 6 interval lies at or below its own ceiling | the bootstrap holds each draw at the real measurement's selectivity, so no resample can be more selective than the thing it stands in for. The chronological 95% upper bound is 1.7400, exactly the 87/50 ceiling. It was 1.7547 = 93/53, above the ceiling, because the draw's budget was rounded rather than taken as a ceiling: 7.92% of draws exceeded 87/50. The point estimates are unchanged and every verdict is unchanged | README results table, `/` and `/evaluation` interval bars | `artifacts/QUEUE_RECEIPT.json` (`gate6.per_split.*.lift_ci95`), `tests/test_queue_lift_bootstrap.py::test_no_resample_beats_the_ceiling_of_the_real_population` | D15 | 2026-08-20 |
| The cold retrieval condition excludes the physical site, not only the station id | nine sites in the 739-observation pool host between two and four distinct station ids at identical coordinates, covering 22 ids and 210 observations, and the exclusion compared integers. Adding the site to the rule removes 4,238 query-candidate pairs the id rule left in. Granite's cold agreement moves from 0.5608 to 0.5543 and its cold margin over random from 0.0398 to 0.0260, so the leak was inflating the cold result rather than depressing it | `/precedent` console page, E8 build log entry | `artifacts/PRECEDENT_RECEIPT.json` (`cold_condition`), `pipeline/tracetriage/precedent.py::is_candidate`, `tests/test_precedent.py::test_the_frozen_cold_retrievals_hold_the_site_rule` | D15 | 2026-08-20 |
| The axis-sign constant reaches 1,496 observations, not 1,704 | AXIS_SIGN_CONVENTION applies where a waterfall was rendered. The dataset stores 2,727 observations and 2,500 of them have an image, so the census over all stored rows counted 227 observations the constant is never applied to and overstated its reach by 208. Both counts are published. 1,004 of the 2,500 come from a measured client family and 1,496 inherit the constant. The census now reads `artifacts/DATASET_MANIFEST.json`, so it regenerates without the 4 GB snapshot, and cross-checks the manifest against the API pages when they are present | README scope note, `/provenance` console page | `artifacts/GATE3_RECEIPT.json` (`axis_sign_scope`), `scripts/run_gate3.py::_axis_sign_scope` | D15 | 2026-08-20 |
| Gate 4's model arm decisive rate | 57 of 60 first-occurrence observations decidable, a rate of 0.9500, exact one-sided 95% [0.8758, 0.9862] against the 0.80 threshold. PASSED **as an arm**. The gate's own verdict is `NOT_RUN` and does not move, because gate 4 is titled blinded human decidability and this reviewer is not a person. The scorer will not write a rate at all without a `REVIEWER.json` declaring who produced it | `docs/KILL_GATE.md` gate 4 arm block, `FOR_JUDGES.md`, README results row, `/evaluation` gate 4 section | `artifacts/GATE4_RECEIPT.json` (`arm`), `tests/test_gate4.py::test_a_model_review_keeps_the_gate_open_and_publishes_its_numbers_separately` | E3 | 2026-08-21 |
| The same plate answered twice, by two different readers | 11 of 12 repeated pairs identical on all three axes. The 72 items were reviewed in twelve blocks of six and the repeat pairs are at least six items apart, so no block could see both halves of a pair: every pair is a comparison of two independent reads rather than a memory test. A ceiling on the decisive rate beside it | `docs/KILL_GATE.md` gate 4 arm block, `/evaluation` gate 4 section | `artifacts/GATE4_RECEIPT.json` (`arm.intra_rater`), `tests/test_gate4.py::test_a_repeat_is_the_same_image_and_never_lands_beside_its_twin` | E3 | 2026-08-21 |
| Neither review axis asks the network's own question | agreement with `waterfall_status` is 25 of 38 on `visible_signal` and 13 of 33 on `target_consistent`, and they miss in opposite directions. `visible_signal` counts a fixed local carrier as a signal and the label does not, so of 18 plates labelled without-signal the reviewer saw a trace in 13. `target_consistent` wants a curve drifting across frequency, so of 18 labelled with-signal the reviewer called 12 not pass-shaped, which is what a packet burst near zero offset looks like under that wording. Both rates and both confusion matrices are published and neither is named the right one. This is a finding about the rubric's wording, not about the labels | `docs/KILL_GATE.md` gate 4 arm block, `/evaluation` gate 4 section | `artifacts/GATE4_RECEIPT.json` (`arm.network_label_agreement.by_axis`), `tests/test_gate4.py::test_the_label_agreement_reports_both_axes_and_claims_neither_is_the_question` | E3 | 2026-08-21 |
| The reviewer bundle's digest is reproducible and its form is blank | 113,238,991 bytes, sha256 `c426e1d978b66cf62a8d024a2305c30ee374d3711eef91912a4bf181f4535643`, identical across two consecutive packs. Every zip entry carries a fixed 1980-01-01 timestamp, because `ZipFile.write` records each file's mtime and the digest published beside the archive was therefore never something a reader could reproduce. The response form is generated empty from the committed item list rather than copied out of the bundle, so an answered worksheet can never ship to the next reviewer | `/evaluation` gate 4 handoff table, `FOR_JUDGES.md` | `artifacts/GATE4_BUNDLE.json` (`archive`), `tests/test_gate4_bundle.py::test_the_archive_digest_is_reproducible_from_the_contents_alone`, `tests/test_gate4_bundle.py::test_the_shipped_form_is_empty_and_lists_the_committed_items` | E3 | 2026-08-21 |
| The motion harness measures against its own noise, not against a memory | an A/A run with both origins serving byte-identical builds gives paired first-paint deltas up to 0.17 ms on the landing page and largest-paint deltas spanning -5 to +612 ms on /evaluation. So sign consistency is not evidence at sub-millisecond magnitudes, and largest contentful paint on this harness cannot support a claim at all. Every later run reads those ranges as its floor and reports a delta inside the floor as inside it | E4 build log entry | `artifacts/MOTION_AA_CONTROL.json` (`paired_deltas`), `scripts/measure_motion_perf.py::_floor` | E4 | 2026-08-21 |
| The motion layer costs no layout shift and no blocking time | cumulative layout shift and total blocking time are unchanged on /, /evaluation/ and /replay/: exactly zero paired difference in both, on three rounds interleaved before and after, served with gzip, at lighthouse's desktop throttling. First paint on the landing page moved -2.1 ms with all three pairs the same sign, which is outside the A/A floor and is two milliseconds | E4 build log entry | `artifacts/MOTION_RECEIPT.json` (`paired_deltas`), `scripts/measure_motion_perf.py` | E4 | 2026-08-21 |
| The deep field recedes on scroll, and that is measured rather than asserted | six samples at each of two scroll positions. Before: 187 to 207 lit pixels at the top against 178 to 194 scrolled, ranges overlapping, so no recession is established and the probe reports none. After: 144 to 160 against 70 to 97, no overlap, with the scroll channel the render loop writes to the canvas moving 0.00 to 0.80. The first version of this check compared one sample either side on a build with no recession in it and saw the count fall 16%, because the field animates on its own | E4 build log entry | `apps/web/audit/deep-field-probe.js`, `apps/web/components/DeepField.tsx` | E4 | 2026-08-21 |
| The gate 4 clip shows a real commitment and ends on the verdict and who produced it | 37 seconds, self-hosted, `preload="none"`. Every number in it is read from `artifacts/GATE4_RECEIPT.json`, `artifacts/GATE4_WORKSHEET.json` or `artifacts/GATE4_BUNDLE.json` by a test rather than trusted. The digest shown on screen is the published commitment for item G4-001, not a plausible string of hex. The closing frame reads Gate 4: PASSED with the reviewer named as a person and as the author rather than an independent one, and both the verdict word and the reviewer kind are asserted against the receipt. It read Gate 4: OPEN over a passed gate for one run, because the three checks on it looked for an `arm` the human answer does not produce and all three skipped | `/evaluation` gate 4 section | `scripts/explainer_gate4.py`, `tests/test_explainer_gate4_values.py` | E15 | 2026-08-22 |
| Gate 3 on the pre-registered pool | PASSED_UNGROUPED_ONLY. 303 observations selected by a rule that never looks at a corridor, 0 of them unpreparable, 289 scored, 224 discriminate at 77.5%, and the exact one-sided 95% lower bound on that rate is 0.7309, which clears the 0.70 bar. Over the 68 independent (ground station, UTC date) episodes those 289 span, the grouped bound is 0.3662 and does not clear it. The plan groups before it decides, so the verdict carries both halves and the gate is not counted as met | README, KILL_GATE.md gate 3 row and section, console side rail, provenance page, `FOR_JUDGES.md` | `artifacts/GATE3_RECEIPT.json` | E16 | 2026-08-23 |
| Selecting the pool on the outcome would have passed the gate | the same scorer, the same nulls, the same grouping rule, run on both pools. Pool A is selected the way A3 was, on `sigma_curved - sigma_vertical >= 3.0`, which is a corridor result: 308 selected, 307 scored, 95.1% discriminating, bound 0.9258, and over 74 episodes a grouped bound of 0.7505, so it clears the bar both ways and reads PASSED. Pool B is selected without a corridor and reads 0.7751 and 0.3662. The 17.6-point gap between the two rates is the size of the selection effect on this corpus, measured rather than argued, and the pre-registration named pool B as the one that decides before either was scored | KILL_GATE.md gate 3 section, `docs/E16_PREREGISTRATION.md` | `artifacts/GATE3_RECEIPT.json` and `artifacts/GATE3_POOL_A_RECEIPT.json` | E16 | 2026-08-23 |
| The gate 3 pool is chosen without ever fitting a corridor | 2,727 observations examined, the whole stored dataset, 303 selected on predicted swing >= 3,000 Hz, `trace_q75` >= 3.5 and `sigma_vertical` < 8.0. `trace_q75` is the 75th percentile of the per-row maximum z-score, so it asks whether anything stands out of the noise anywhere in the image and never asks whether it lies on a predicted curve. The swing is a property of the ephemeris rather than of the image. Neither can be influenced by whether a corridor matches, which is the circularity the A3 pool had | KILL_GATE.md gate 3 section, `docs/E16_PREREGISTRATION.md` section 3 | `artifacts/GATE3_POOL.json` | E16 | 2026-08-23 |
| The verdict does not turn on the one researcher degree of freedom | `TRACE_Q75_MIN` swept over 3.5, 4.0, 5.0, 6.0 and 8.0. The discriminating rate stays between 75.0% and 79.8% while the scored count falls from 289 to 42, so the observation-level verdict that changes at 6.0 changes because the interval widens on a smaller sample and not because the rate moved. The grouped bound clears at no bar. Bars below the one that was scored are reported as NOT SCORED rather than estimated, because a looser bar selects observations this run never scored and a rate over the ones it happens to have is a rate over a biased subset | KILL_GATE.md gate 3 section | `artifacts/GATE3_SENSITIVITY.json` | E16 | 2026-08-23 |
| More than half the pool is unresolved to the mode reader, and the corridor still finds a trace in most of it | of the 289 scored, 136 are UNCORRECTED to `doppler_mode.verdict_from_scores` and 126 of those discriminate at 92.6%; 153 are UNRESOLVED, meaning neither the curved nor the vertical hypothesis cleared an 8 sigma floor, and 98 of those discriminate at 64.1%. The pool rule never reads a mode, so this is a decomposition of the scored set and not a selection within it, and the receipt field says `decides_the_gate: false` beside it. The pooled rate is the gate's | KILL_GATE.md gate 3 section | `artifacts/GATE3_RECEIPT.json` `mode_decomposition` | E17 | 2026-08-23 |
| A MAD floor of 1e-6 turned the emptiest row in an image into its largest z-score | pool B's `trace_q75` came out with a median of 4.72, a 90th percentile of 22,666,664 and a maximum of 89,000,000. A blank or saturated row has MAD exactly 0, and flooring the divisor inverted the statistic precisely where it degenerated, so a mostly-blank waterfall could outrank a 25 sigma detection by six orders of magnitude. Rows below the quantisation step of the luminance mean are dropped now and counted as `n_rows_unmeasurable`, an image with no measurable row is refused rather than scored low, and the pool went from 321 to 303 with the 90th percentile at 10.79. Found by reading the marginal distribution the pre-registration required before the gate was run, and the correction is committed in that document before either pool was scored | `docs/E16_PREREGISTRATION.md` section 7, E17 build log entry | `docs/E16_PREREGISTRATION.md` section 7, `tests/test_gate3_pool.py` | E16 | 2026-08-23 |
| A second divisor collapse was hiding behind the first | fixing the pool statistic zeroed the dead rows it had been inflating, which exposed `spread = median(|z - baseline|) * 1.4826 or 1e-9` in the corridor scorer: Python reads `0.0` as falsy, so a genuinely zero spread became 1e-9 and produced sigmas up to 3.09e10. The numerator and the denominator were also taken over different row sets. A zero spread returns NaN now and the null calibration refuses with `no_measurable_rows`. A3's three-observation receipt reproduces bit for bit after the change, so this moved the degenerate cases and nothing else | E17 build log entry, `pipeline/tracetriage/corridor_fit.py` | `tests/test_corridor_fit.py`, the A3 re-run | E17 | 2026-08-23 |
| An absent p-value in the gate receipt now names its reason | 14 of the 303 are testable, were scored, and produced no p-value. The receipt published a blank for all 14: `NullCalibration.summary()` omitted `not_tested_reason`, so the one consumer a judge reads was the only one that dropped it while the CLI and the live path both printed it. The receipt carries `no_p_value_by_reason` now, and the writer raises rather than bucketing an unnamed branch as unknown | E17 build log entry, KILL_GATE.md gate 3 section | `artifacts/GATE3_RECEIPT.json` `no_p_value_by_reason` | E17 | 2026-08-23 |
| The live path reproduces the gate wherever both score a corridor | the replay used to reach the receipt through A3's 24 observations, so it compared only what the two sets shared: 3 while the receipt was A3's pool, 1 after E16 rebuilt it. It walks the receipt's own observations now, in obs_id order so the sample is not chosen on the result. Where the two part company they part deliberately: `live.measure` measures the mode first and declines on UNRESOLVED, and the gate fits the uncorrected corridor on every pool member because the pool is selected without reference to any annotation. The test asserts the digit-for-digit agreement and that every decline names a reason | E17 build log entry, `pipeline/tracetriage/live.py` | `tests/test_live.py` | E17 | 2026-08-23 |
| Axis-sign agreement is unanimous where a detection exists, and not everywhere | the convention is remeasured on every scored observation by fitting the corridor as shipped and mirrored. 175 of the 289 can orient an axis at all; the rest tie between the two orientations by less than the 2.0x separation this treats as decisive. Of the 175, 173 agree and 2 do not, and among the 171 that both orient and discriminate, agreement is 171 of 171. The two dissenters reach 0.83 and 1.00 sigma in their best orientation against a median of 2.11 among the agreeing, so neither detects anything either way and a ratio of two noise values is decisive-looking arithmetic over nothing. Both are published with their sigmas rather than dropped, and the test fails if a dissenter ever discriminates. Unanimity over everything was the property when this pool was three observations | README results, E17 build log entry | `artifacts/GATE3_RECEIPT.json` `axis_sign_scope.remeasurement`, `tests/test_physics.py::TestAxisSignEvidence` | E17 | 2026-08-23 |
| The offset sweep is published, and its peak is the fitted offset by construction | the matched filter scores every whole-pixel offset inside the frequency bound and kept only the maximum. The curve is now the primitive and the fit is `np.argmax` over it, so the peak the console draws and the number beside it are one quantity rather than two implementations of one. The scoring geometry the sweep and the null calibration share, the smoothed image, the origin, the pixel bound and the horizon row mask, is derived once in `scoring_setup` for the same reason. Around 455 offsets are scored per observation and 121 or so reach the browser, with the peak and both endpoints always kept, because a curve plotted without its maximum shows the detection peaking at whichever neighbouring sample survived | observation pages, E17 build log entry | `apps/web/public/data/cards.json` `corridor.offset_sweep`, `tests/test_console_export.py`, `tests/test_corridor_fit.py` | E17 | 2026-08-23 |
| The README printed two different verdicts for gate 3 on one page | the substantive gate table read `NOT_ESTABLISHED` eleven lines above a closure table reading `PASSED_UNGROUPED_ONLY`. Both generated: the first went through the console's three-state bucket, in a cell whose own docstring says it prints the strings the receipts carry. It reads `verdict_in_the_receipt` now. Fixing it exposed that the headline sentence is built from three buckets and would have counted three of four substantive gates while naming no fourth, so there are four buckets and an assertion that they sum | README status block, E17 build log entry | `scripts/sync_readme_results.py`, `tests/test_readme_claims.py` | E17 | 2026-08-23 |
| Two receipts were not JSON, and only a bundler noticed | `json.dumps` writes the bare tokens `NaN`, `Infinity` and `-Infinity` unless told not to, and `json.loads` reads them back, so a project whose consumers are all Python never finds out. `artifacts/GATE3_POOL.json` carried 93 non-finite values across 31 observations and `artifacts/GATE3_RECEIPT.json` inherited one, which made both unreadable by `jq` and by `JSON.parse`, on the two files the whole argument rests on. Both writers pass `allow_nan=False` now and convert to null first with each conversion logged. The pool was rebuilt to clear it and membership is identical on both pools, 308 and 303, with every count unchanged, so this moved a serialisation and not a selection | E17 build log entry | `tests/test_artifacts_are_json.py`, the before-and-after membership comparison | E17 | 2026-08-23 |
