# Kill gate: status board

The plan sets six thresholds that must pass before TraceTriage is worth building. Three were pre-measured on 2026-08-16 before Bob's first task, using live read-only probes. Three remain and can only be settled with the frozen snapshot in hand.

**Thresholds were fixed before any measurement. Do not move one after seeing a result.** If a gate fails, record the failure here with its number and stop. A documented honest failure beats a concealed one.

---

## Status summary

| # | Gate | Threshold | Status |
|---|---|---|---|
| 1 | Dataset volume and entity spread | ≥2,000 mature waterfalls, ≥12 transmitters, ≥30 stations | **PRE-PASSED on feasibility** |
| 2 | Metadata coverage for the corridor | ≥80% of the sample computable | **PRE-PASSED** |
| 3 | Corridor intersects a visible trace | ≥70% of reviewed positives | **PASSED_UNGROUPED_ONLY. 224/289 testable discriminate (78%), the exact one-sided 95% lower bound on that rate is 0.731, which clears the 70% bar. Over 68 independent (station, date) episodes the bound is 0.366 and does not, and the plan groups before it decides. 0 of 303 not testable; the 289 span 68 stations on 1 night** |
| 4 | Blinded human decidability | ≥80% of a balanced sample decidable | **PASSED** |
| 5 | Physics beats image-only on Brier | strict improvement, chronological split | **NOT ESTABLISHED. Margin +0.02079, 95% CI -0.01301 to +0.05036 on 88 test observations across 88 episodes, on the union of the episode-grouped and station-clustered intervals. A narrower arm (image + corridor) leads on both metrics: its risk-coverage margin survives correction over the 21 comparisons the ablation rule reads and its Brier margin does not. The gate as worded does not pass.** |
| 6 | Queue lift over random | ≥1.5x actionable conflicts at equal budget | **NOT_ESTABLISHED. Point lift 1.582×, 95% CI [1.353, 1.740] on 87 decisive test observations across 87 episodes (chronological split, budget 50). The interval contains the 1.5× threshold; cold_station PASSED at 2.253×.** |

---

## Gate 1: dataset volume and entity spread — PRE-PASSED on feasibility

**Threshold:** at least 2,000 mature waterfalls across at least 12 transmitters and 30 stations, including decisive positive, decisive negative and unknown examples, with raw responses, retrieval times, hashes, schema version and CC BY-SA terms preserved.

**Measured** on 600 consecutive observations ending before 2026-07-15:

| Quantity | Result | Floor | Margin |
|---|---|---|---|
| Unique transmitters | 197 | 12 | 16x |
| Unique stations | 211 | 30 | 7x |
| Unique NORAD IDs | 179 | — | — |
| Waterfall URL present | 92.3% | — | — |
| Decisive `waterfall_status` | 29.0% | — | — |

Entity spread passes with enormous margin in a sample less than a third of the required size. Volume is a download, not a research question: ~2,170 fetched records yield ~2,000 waterfalls, about 87 cursor pages and roughly 3.4 GB of PNG.

**Why "on feasibility" and not "passed":** the gate also requires the preservation obligations, hashes, retrieval times, licence terms. Those are satisfied by the snapshot builder, which is Bob's Wave A work. **Gate 1 closes when the snapshot exists, not when the counts are known.**

### The constraint that is easy to miss

Decisive negatives ran at **10.2%** (61/600), against 18.8% positives, a **1.85 : 1** positive-to-negative imbalance among decisive labels.

A 2,000-waterfall snapshot yields roughly 380 decisive positives and 200 decisive negatives, which is too thin to hold up a cold-entity claim.

### DECIDED, 2026-08-16 15:20 IST: snapshot target is 10,000 observations

The operator approved a **20 GB** data budget for exactly this reason. At the measured rates:

| | |
|---|---|
| Observations fetched | 10,000 |
| Waterfalls at 92.3% | ~9,230, about **15.7 GB** at 1.7 MB mean |
| Decisive negatives at 10.17% | **~1,017** |
| Decisive positives at 18.83% | ~1,883 |
| Cursor pages at 25/page | ~400, about **3.0 hours** end to end at the measured rate |

That row read "roughly 15 minutes at 0.4 s spacing" until 2026-08-19, which was wrong by a
factor of twelve: 400 pages at 0.4 s is 2.7 minutes of spacing, and the spacing is not what
sets the wall clock. Measured over the 110 pages of the stage-1 snapshot, the interval
between finished pages has a median of 27.1 s (mean 45.5, tenth percentile 23.7, ninetieth
33.1) and the whole fetch took 82.6 minutes. Each page carries 22.7 waterfall downloads at a
median of 0.98 s, so the images are the cost and the API spacing is a rounding term. At 27.1
s per page, 400 pages is 3.0 hours. The budget conclusion does not change, because the
binding constraint was disk rather than time, but a 15-minute figure would have made a
resumable fetch look optional.

That clears roughly 1,000 decisive per class on the negative side, which is the binding one. Task A1 carries this number and must not scale it down.

Report the 1.85:1 imbalance in the results; do not silently rebalance it.

### AMENDED, 2026-08-23: the shipped snapshot is 27% of that target, and this is where that is recorded

The 10,000-observation decision above was not honoured and until today the reduction
appeared nowhere in this repository. It is recorded here, beside the decision it
overrides, rather than in a later section where a reader who has just read "must not
scale it down" would not find it.

What shipped, read off `artifacts/DATASET_MANIFEST.json` and
`artifacts/BASELINE_RECEIPT.json`:

| Quantity | Decided above | Shipped | Fraction |
|---|---|---|---|
| Observations stored | 10,000 | 2,727 | 27% |
| Waterfalls stored | ~9,230 | 2,500 | 27% |
| Decisive negatives | ~1,017 | 277 | 27% |
| Decisive positives | ~1,883 | 462 | 25% |
| Cursor pages fetched | ~400 | 110 | 28% |

Gate 1's own threshold is at least 2,000 mature waterfalls and it is met at 2,500, so
gate 1 passes on its own wording. What was not honoured is the power decision layered on
top of that threshold, and the decisive-negative count the same section called the
binding one came in at 28% of its target.

**The consequence, stated where it belongs rather than left for a reader to assemble.**
Every inconclusive result in this document is short of observations in the way this
decision was written to prevent:

- The chronological test partition holds 88 observations at a positive rate of 0.7045
  (`artifacts/FUSION_RECEIPT.json`), so 62 positives and 26 negatives. The standard error
  of an AUC near 0.87 on 62 against 26 is about 0.04, which is wider than the 0.033 AUC
  difference the same receipt reports between the shipped arm and image-only.
- Gate 5's Brier interval on that partition is -0.01301 to +0.05036, a width of 0.0634 on
  a margin of +0.02079. The interval spans zero by less than a third of its own width.
- Gate 6 ranks 87 decisive test observations holding 22 conflicts against a budget of 50
  (`artifacts/QUEUE_RECEIPT.json`), so no ordering whatever can score above 87/50 =
  1.740x against a 1.5x bar. The published interval is [1.353, 1.740] and its upper end
  is that ceiling.

**So gates 5 and 6 are inconclusive on sample size, not on effect.** Both point estimates
sit on the favourable side of their bars and neither interval excludes the bar. That is
the reading the numbers support and it is not a defence: a snapshot at the pre-registered
size would have settled both questions one way or the other, and this one cannot.

Why it was reduced is not recorded anywhere, so no reason is offered here. The stage-1
snapshot was built once, at 110 pages, and every unit downstream ran on it. The record
carries the target, the outcome and the gap; it does not carry a decision that closed
them, and inventing one would be worse than the gap.

---

## Gate 2: metadata coverage for the corrected corridor — PRE-PASSED

**Threshold:** enough stored TLE, timing, station, transmitter-frequency and artifact metadata to compute a corrected centre corridor for at least 80% of the sample.

**Measured** on the same 600:

| Field group | Coverage |
|---|---|
| `tle1` + `tle2` | **100.0%** |
| `client_metadata` (rx-freq, sample rate, client version) | **94.0%** |
| `waterfall` URL | 92.3% |
| Station lat/lng/alt, start/end, observation_frequency | 100% (always present on the record) |

Binding coverage is the intersection: an observation needs TLE **and** client metadata **and** a waterfall. Worst case that intersection is bounded below by 1 - (0 + 0.06 + 0.077) = **86.3%**, above the 80% floor even if every gap is disjoint.

One correction to the plan's assumption: **`center_frequency` is null in practice**, on every record inspected. The receiver truth is `client_metadata.radio.parameters.rx-freq`, inside a JSON-encoded string. An ingestion that depends on `center_frequency` will find 0% coverage and wrongly conclude this gate fails.

---

<!-- generated by scripts/sync_kill_gate.py: gate 3 heading, do not edit -->
## Gate 3: corridor intersects a visible trace, PASSED_UNGROUPED_ONLY on 303 testable observations
<!-- end gate 3 heading -->

**Threshold:** on a blinded check, the expected corridor intersects a visible target-like trace in at least 70% of reviewed positive examples.

**First claimed by A7 on 2026-08-17, withdrawn the same day, re-measured and closed by `scripts/run_gate3.py`.** Receipt: `artifacts/GATE3_RECEIPT.json`.

### What the withdrawn A7 result was

A7 computed `trace_half_width_hz = 3 * hz_per_px / 2` and compared it against the corridor half-width. Both sides are constants: the left one a matched-filter kernel width (116 to 192 Hz across the two known client layouts), the right one a hardcoded 1200 or 2000 Hz. Nothing in the comparison depended on where the trace sat, so it returned True for all seven of A3's decisive observations and could not return False for any waterfall with a normal axis scale. It was reported as 1/1 = 100%, and a 70% rate cannot be measured on one observation in any case.

A7 also left `freq_offset_hz` at its `0.0` default, so the corridor it checked sat at rx-freq. A3's stored `curved_offset_hz` for this same observation is -13,985 Hz against a corridor whose outer edge is 10,303 Hz from rx-freq, which puts the trace 3,682 Hz outside the band entirely. On the one position number the artifacts held, the unshifted corridor missed by 7x its own half-width.

### The correct measurement

The absolute downlink frequency is not known to better than tens of ppm: a cubesat oscillator drifts, and the SatNOGS transmitter frequency a station tunes to is community-maintained. So one constant frequency offset is fitted per observation, bounded at **50 ppm of the downlink**, which is about 20 kHz at 400 MHz and 6.9 kHz at 137 MHz. A3's own scan bounded this at plus or minus 76.9 kHz, 9.3x the Doppler swing, which lets the curve land anywhere and is why A3's sigma establishes shape rather than position.

A fitted offset is a free parameter, so an absolute score carries no evidence. The statistic is the true corridor against **200 null corridors built by permuting its own Doppler samples in time**, which preserves every frequency value and the whole swing while destroying the monotone shape. Both get the identical bounded fit.

**Read the margin column, not the p-value.** The permutation p-value is a necessary condition and a very weak one. Scrambled paths collapse into noise around sigma 0.40 to 0.57, so anything smooth beats them: a corridor with the frequency axis inverted also reaches `0 of 200` and `p = 0.005` on two of these three observations. What separates the physics from its own sign error is the margin over the best null, stated below in standard deviations of that observation's own null distribution, and the reversal control. Both are now part of the `discriminates` criterion at a floor of **5.0 null standard deviations**, fixed before this run and recorded in `THRESHOLD_RATIONALE`. Until 2026-08-19 the criterion was the p-value, the scaled-swing controls and the at-bound check, and the margin was computed, published in this table, and not consulted.

**Time reversal is a control, and dropping it was an error.** This document and `corridor_fit.py` both said reversal was excluded because a Doppler curve is near odd-symmetric about closest approach, so a reversed curve still fits. The premise is right and the conclusion inverts it. If `D` is odd about closest approach then `D(1-f) = -D(f)`, so time reversal **is** the sign flip. The two errors cancel when applied together, which is why no visual check finds them, and that is exactly why each one alone is maximally wrong. Measured here, the reversal lands at or below the maximum of 200 scrambled corridors on all three observations, which makes it the strongest null available and the one that tests `AXIS_SIGN_CONVENTION` directly. The odd-symmetry residual, `max |D(f) + D(1-f)|` as a fraction of swing, is now measured per observation and carried in the receipt rather than asserted once in a comment.

| obs | corridor | fitted offset | sigma | 200-null max | null sd | **margin (null sd)** | margin | nulls >= true | p |
|---|---|---|---|---|---|---|---|---|---|
| 14740031 | uncorrected | +13,985 Hz (+32.0 ppm) | 2.02 | 0.57 | 0.0077 | **+188.8** | +1.45 | 0 of 200 | 0.005 |
| 14745664 | uncorrected | -7,149 Hz (-16.4 ppm) | 1.54 | 0.41 | 0.0076 | **+148.6** | +1.13 | 0 of 200 | 0.005 |
| 14745929 | uncorrected | -7,149 Hz (-16.4 ppm) | 1.65 | 0.40 | 0.0077 | **+161.8** | +1.25 | 0 of 200 | 0.005 |

**The two wrong-sign variants, scored under identical rules.** Each was built by negating the corridor (`AXIS_SIGN_CONVENTION` inverted) or reversing it in time, then refitting the offset and rescoring against fresh nulls:

| obs | variant | sigma | margin (null sd) | p | beats reversal | beats scaled | discriminates |
|---|---|---|---|---|---|---|---|
| 14740031 | inverted | 0.590 | +2.9 | 0.005 | no | no | **no** |
| 14740031 | reversed | 0.585 | +3.1 | 0.005 | no | no | **no** |
| 14745664 | inverted | 0.398 | -1.4 | 0.050 | no | no | **no** |
| 14745664 | reversed | 0.397 | -2.0 | 0.070 | no | no | **no** |
| 14745929 | inverted | 0.411 | +1.3 | 0.005 | no | no | **no** |
| 14745929 | reversed | 0.413 | +0.9 | 0.005 | no | no | **no** |

Two of the six clear the p-value at exactly the published `0.005`, which is the finding: the p-value cannot tell these apart from the truth. The margin can, by a factor of about 50, and the reversal criterion rejects all six outright, because the reversal of a wrong-sign corridor is the true curve and it outscores them. Odd-symmetry residuals for the three observations are 0.11%, 1.35% and 1.59% of swing, so reversal is the sign flip to within about one part in sixty at worst.

The fitted offsets reproduce A3's independently measured `curved_offset_hz` for all three, which is a cross-check between two separately written estimators.

**Scaled-swing controls.** The obvious objection is that this rewards any smooth bright path rather than the predicted physics. So the same curve was rescaled to 0.25x, 0.5x, 2x and 4x its predicted swing, holding shape and smoothness exactly fixed and varying only magnitude:

| obs | true (1x) | 0.25x | 0.5x | 2x | 4x |
|---|---|---|---|---|---|
| 14740031 | **2.02** | 0.65 | 0.74 | 0.62 | 0.31 |
| 14745664 | **1.54** | 0.52 | 0.56 | 0.47 | -3.49 |
| 14745929 | **1.65** | 0.49 | 0.53 | 0.49 | -1.53 |

The predicted swing beats every rescaling on every observation. The measurement is confirming the magnitude SGP4 predicted, not the presence of a smooth line.

<!-- generated by scripts/sync_kill_gate.py: gate 3 verdict, do not edit -->
**Gate 3 is PASSED_UNGROUPED_ONLY.** 224 of 289 testable observations discriminate (78%) and 32 of 68 independent (station, date) groups do. The exact one-sided Clopper-Pearson 95% lower bound is `0.7309` over observations and `0.3662` over groups. The observation-level bound clears the 70% threshold and the grouped one does not, so the gate is recorded as clearing on the weaker grouping only. The plan's rule is to group, so this is reported rather than claimed as a pass.
<!-- end gate 3 verdict -->

<!-- generated by scripts/sync_kill_gate.py: gate 3 pools, do not edit -->
| pool | scored | discriminating | rate | 95% lower bound | verdict | selected by |
|---|---|---|---|---|---|---|
| **B, pre-registered** | 289 | 224 | 78% | 0.7309 | PASSED_UNGROUPED_ONLY | a corridor-free presence statistic |
| A, corridor-selected | 307 | 292 | 95% | 0.9258 | PASSED | A3's `UNCORRECTED` label, which reads the corridor |

Only pool B decides the gate, at a threshold of 0.70. Pool A is selected on `sigma_curved - sigma_vertical >= 3`, so asking whether the corridor discriminates on it is asking whether the thing it was selected for is true of it. The two rates are published together because the gap between them is the size of that circularity, and a reader cannot judge it from one number. Measured here it is +18 percentage points.

The pools are drawn from 2,727 observations, the whole snapshot. 2,412 were measured and the rest were dropped: 227 had no waterfall image, 39 have a predicted Doppler swing too small to tell the two shapes apart, 37 failed physics (STALE_TLE), 12 had no image row with the spread to carry a z-score. Pool B took 303 of them and pool A 308, sharing 137. Every observation's own `trace_q75` is in `artifacts/GATE3_POOL.json` whether it was selected or not, so the pool can be recut at another bar from that file.
<!-- end gate 3 pools -->

<!-- generated by scripts/sync_kill_gate.py: gate 3 estimands, do not edit -->
### Two estimands, and why the pre-registered one is the verdict

Gate 3's threshold is worded as a rate over reviewed positive examples. Two different statistics can be built from that sentence, and both are reported here because they answer different questions.

| | pre-registered, decides the gate | cluster-corrected, decides nothing |
|---|---|---|
| Question | does the corridor discriminate on **every** scored capture at a station on a date | how often does the corridor discriminate, with the dependence between captures paid for rather than collapsed |
| Statistic | all-or-nothing indicator over 68 (station, UTC date) groups | rate over 289 scored observations, cluster bootstrap over the same 68 groups |
| Value | 0.4706 | 0.7751 |
| One-sided 95% lower bound | 0.3662 | 0.7104 |
| Clears 0.70 | no | yes, by 0.0104 |

**The verdict stays PASSED_UNGROUPED_ONLY and the status board above still counts this gate as unmet.** The cluster-corrected analysis in the right-hand column was written on 2026-08-23, after the pre-registered statistic had been seen to return 0.3662 against the 0.70 bar. A rule adopted after seeing the result it is applied to is not the rule that was tested. That is the whole reason the pre-registered verdict is the one that stands here, and not the one that clears.

**The margin is narrow.** 0.7104 against 0.70, so 0.0104 of room on a bound that four bootstrap seeds place between 0.7104 and 0.7108, with the lowest of the four published. Read at two sides instead of one the interval is [0.6963, 0.8381], whose lower end does **not** clear 0.70. Two other routes to the same bound agree with the bootstrap and are in the receipt: a design-effect normal approximation gives 0.7217 and an exact binomial bound at an effective n of 165.2 gives 0.7151. Nothing here is a comfortable pass.

**The clustering the grouping exists to answer is real.** One-way ICC 0.2304 on the discriminating indicator over 68 station-days holding 4.25 observations each, so a design effect of 1.7489. The rate still clears the bar after paying for it. This is the same one-way ICC gate 6 uses, from `queue.intraclass_correlation`, so the two gates cannot disagree about what clustering means.

**What the pre-registered statistic is measuring, measured.** An all-or-nothing indicator over groups of mean size 4.25 and maximum 20 falls as groups grow whether or not any dependence exists, so its value is not evidence about dependence. Holding the per-observation rate at its observed 0.7751 and drawing every outcome independently, so with zero clustering, the all-pass group rate over these same sizes comes out at 0.5043 with a 95% range of 0.3971 to 0.6029 over 20,000 draws. The published 0.4706 sits inside that range. And the bar is out of reach of this statistic rather than merely unmet: 0 of 20,000 draws reached 0.70, the largest reaching 0.6912, and in expectation the collapsed statistic needs a per-observation rate near 0.8975 before it can touch 0.70 at these group sizes. So the collapse is a stricter question rather than a corrected version of the gate's own one, and its value is set mostly by the group-size distribution.

Both are in `artifacts/GATE3_RECEIPT.json`: the pre-registered one under `entity_grouping`, the second under `cluster_corrected_estimand`, which carries `decides_the_gate: false` so a consumer cannot read it as the verdict.
<!-- end gate 3 estimands -->

This gate read PASSED until 2026-08-18 because `clears_threshold` compared the point estimate against the bar: `1.0 >= 0.70` is True, and the identical comparison would have passed 1 of 1. This document had already made that exact argument 28 lines above, when the earlier one-observation version of the gate was withdrawn with the note that a 70% rate cannot be measured on one observation in any case, and then the three-observation version was accepted on the same logic. Gates 5 and 6 publish NOT_ESTABLISHED when an interval fails to exclude a threshold; gate 3 now reads from the same register.

**What did not change.** The per-observation evidence stands and is the strong part of this gate: each of the three beats 200 scrambled corridors with none reaching it, `p = 0.005`, and each beats all four scaled-swing controls. The correction is to the cross-observation rate claim, not to the measurements. Re-running `scripts/run_gate3.py` after the change reproduced every sigma to six decimal places, so only the verdict and the two new bound fields moved.

### What this establishes, and what it does not

Precision matters here, because the gate's own name is looser than the measurement.

**Established.** After fitting one constant frequency offset bounded at 50 ppm, the predicted Doppler **shape** fits the observed trace significantly better than corridors built by permuting the same Doppler values in time, and better than the same curve rescaled to 0.25x, 0.5x, 2x or 4x its swing. Both the shape and the magnitude of the SGP4 prediction are doing work.

**Not established.** That the corridor sits where physics places it with no fitted offset. The three fitted offsets are 40 to 84 percent of their own predicted swing, so each needed a substantial slide before it fit. This is a **shape** test, not an absolute-position test, and "corridor intersects a visible trace" reads as a position claim. The per-row position diagnostic is `null` on all three scored observations, so no observation has a measured `coverage >= 0.70`.

**Why the offset is not a fudge.** A cubesat oscillator drifts and the SatNOGS transmitter frequency a station tunes to is community-maintained, so an absolute-position test would be testing the database rather than the orbital mechanics. The offset is a real physical quantity, reported per observation as `fitted_offset_ppm`, and it is one of the project's findings rather than a nuisance parameter to hide.

This distinction is machine-readable in the receipt's `claim` block, not left in prose.

### The scope limit, which is a real finding and not a pass

**Only 3 of A3's 7 decisive observations are testable at all.** The corrected corridor is **identically 0 Hz across the whole pass**, a bare vertical line with a free horizontal offset. It predicts no shape, so there is nothing to confirm, and every null built from it reproduces it exactly. Measured before this was guarded: on obs 14745602 the true corridor and the flat null both scored coverage 1.000, and on obs 14746118 true and scrambled sigmas agreed to every decimal place. That reads as "the physics has no discriminating power" and is really "the control was the same corridor".

So the physics-conditioned part of TraceTriage has predictive content **only on uncorrected captures**. A3 found 3 uncorrected among 24 vetted with-signal observations. Excluding the corrected four is a limit on the gate's scope, recorded as `observations_not_testable` in the receipt. It is not a pass, and any claim about physics value has to carry it.

### n = 3, and the three are not independent

Each observation carries its own p-value at the 1/201 floor and beats all four scaled-swing controls, so the **per-observation** evidence is strong. The **cross-observation rate does not carry three independent samples**, and the receipt now records why under `entity_grouping`:

| obs | NORAD | station | window (UTC, 2026-08-09) |
|---|---|---|---|
| 14740031 | 63214 | 91 | 23:50:08 to 23:54:09 |
| 14745664 | 63218 | 1696 | 23:32:49 to 23:40:58 |
| 14745929 | 63217 | 1696 | 23:43:40 to 23:51:28 |

2 ground stations, 3 satellites, **1 UTC night**, all inside a 22-minute window. The NORAD IDs are consecutive, so these are almost certainly one deployment cluster. Two of the three share station 1696 three minutes apart, which is exactly why they fit an identical -7,149 Hz offset: the same receiver carries the same local-oscillator error and the same stale transmitter frequency, so that is **one systematic offset measured twice**, not two independent confirmations.

**That identity is the whole of the argument, so here is the measurement it rests on.** A3's `curved_offset_hz` is `+7148.936170212766` on 14745664 and `+7148.936170212766` on 14745929, equal to every digit stored, against `-13985.148514851486` on 14740031 from station 91. This gate's own fitted offsets are the same three values sign-flipped by `AXIS_SIGN_CONVENTION`, which is the -7,149 / -7,149 / +13,985 in the table above. Two receivers, two offsets; one receiver, one offset measured twice. Read the two smaller numbers off `vertical_column_offset_hz` instead, which is what `docs/DOPPLER_CORRECTION_FINDING.md` did until 2026-08-23, and they come out as 2.4 and 1.8 kHz, unequal, and the dependence argument this section makes stops following from the data.

The plan requires bootstrapping "by orbital episode or day, not by image row" and keeping each transmitter and orbital revolution in one split. By that rule this is closer to 2 station-days on 1 night than to n=3. Gate 3 is not established, and this is the second reason why: even if the rate cleared its bound, 2 station-days on 1 night would not support the generalisation. A later wave re-running it at snapshot scale across stations, bands and dates is what would settle it.

### Diagnostics that are reported but are not the gate

Per-row residuals and corridor coverage are in the receipt as `fit.*` and are **not** the gate statistic. These traces integrate to significance along the path while individual rows stay below the 4.0 robust-z detection floor: on obs 14740031, 2.1% of rows carry a per-row detection, so `fit.degraded` reads `TRACE_NOT_MEASURABLE` and `residual_hz` is null. A per-row instrument reports nothing on a trace A3 localised at high sigma, which is why the gate uses the path-integrated statistic. **A3's sigmas and this gate's sigmas are different statistics and cannot be read against each other**: A3 normalised per column band, `corridor_fit` normalises against the median and MAD of the whole image, and the ratio between them runs from 0.87 to 12.4 across the seven decisive observations, so it is not a rescaling. On obs 14740031 A3's *vertical* sigma of 2.83 exceeds this gate's *curved* sigma of 2.02, which inverts a comparison both artifacts otherwise agree on. Every observation in the receipt carries the ratio at `a3_reference.sigma_scale_ratio_to_fit`. Reporting a null residual honestly is the point; A7's 185.6 Hz was a constant standing in for this missing measurement.

### Thresholds

Every threshold is a constant in `pipeline/tracetriage/corridor_fit.py`, fixed before any observation was scored, with its reasoning in `THRESHOLD_RATIONALE` and pinned by `tests/test_corridor_fit.py::test_thresholds_are_the_documented_values`: `z_min=4.0`, `min_detect_frac=0.30`, `coverage_threshold=0.70`, `offset_ppm_limit=50.0`, `n_nulls=200`, `p_value_max=0.05`, `swing_scale_factors=(0.25, 0.5, 2.0, 4.0)`, `min_swing_hz=3000.0`, `exclude_at_bound=True`, `seed=42`.

Two of those close paths that were silent rather than wrong:

- **`min_swing_hz=3000.0`.** Only `span > 0` was checked before, so a grazing low-elevation pass with a technically nonzero but tiny swing would have been marked testable. Permuting nearly-equal values gives nearly the same path, so truth and null both collapse toward noise and a p-value can come out significant on pixel quantisation alone. A3 refuses a verdict below the same 3 kHz for the same reason. Not live in this receipt: the three swings are 16.6 to 19.5 kHz.
- **`exclude_at_bound=True`.** `offset_at_bound` was computed and then consulted nowhere. A fit that saturates its ppm bound may not have found the true optimum, so its sigma is a lower bound and the observation is now excluded rather than merely flagged. Not live here either: the three offsets are +32.0, -16.4 and -16.4 ppm against a 50 ppm limit.

One documentation correction. The fitted offsets and A3's `curved_offset_hz` agree to six significant figures **in magnitude and are always opposite in sign**, because A3's value is a raw column-shift-to-Hz conversion while ours carries `AXIS_SIGN_CONVENTION`. The magnitude agreement is a real cross-check between two separately written estimators; describing them as reproducing each other would invite someone to "fix" one of them. Pinned by `test_a3_offset_relates_by_exactly_minus_one_not_by_identity`.

### Physics verification (Trap 1 and 2 guard)

Time runs **bottom to top** (top row = end of pass). The frequency axis runs **against the Doppler sign** (`AXIS_SIGN_CONVENTION = -1`). Both are encoded in `physics.corridor_columns` and guarded in `tests/test_physics.py`. These two errors cancel visually, which is why the verdict is a measurement with a stated margin rather than a visual read.

That same sign convention broke the first version of this measurement: the offset was searched in column space and handed back as `off_px * hz_per_px`, so it was re-applied to the opposite side of the axis, displaced the curve by twice the fitted 113 px, and detected nothing while every intermediate number looked plausible. Conversion now goes through `px_to_offset_hz`, guarded by three tests.

The Hz/px derivation was verified: **123.76 Hz/px** from axis-tick OCR maps the 17,290 Hz swing to ~140 px of the 621 px plot, about 22% of the frequency axis. Assuming `samp-rate-rx` (2.5 MHz) would compress the corridor to ~7 px and fail this gate for a reason that is purely a wrong constant.

---

## Gate 4: blinded human decidability — PASSED

**Threshold:** a balanced sample reviewed with API labels and model output hidden; at least 80% must support a decisive artifact or target-consistency judgment.

Split assignment must be frozen **before** review begins. Separate axes: artifact usability, visible signal, target consistency. Relabel a fixed subset after a delay and report intra-rater agreement.

If below 80%, the labelling protocol is the problem, not the model. Fix the protocol before training anything.

**The instrument exists and has been run once, by something that is not a person.**
`scripts/build_gate4_worksheet.py`
builds the blinded bundle: 20 observations per label class chosen by a fixed seed, one image
per opaque item, a form with three axes and no label anywhere in it, and 12 observations
repeated under a second item id so intra-rater agreement can be measured without telling the
reviewer which items are repeats. That is 72 items over 60 observations.
`scripts/score_gate4.py` reads the filled form and writes `artifacts/GATE4_RECEIPT.json`.

The sample is committed to rather than promised. A random 32-byte salt and the mapping from
item to observation are written **outside** the repository, and what is committed is one
sha256 per item over the salt, the item id, the observation id and the digest of the image
file. Before the review nobody can invert that; after it, the scorer re-hashes every image
from disk, recomputes every commitment, refuses outright if one fails, and publishes the salt
and the mapping in the receipt. So the commitment binds the sample, its order **and** the
pictures, and the claim that all three were fixed in advance is checkable by anyone rather
than resting on the word of whoever built it.

**Why 60 observations and not 36.** The verdict reads the interval rather than the point
estimate, so the sample size decides which rates the study can establish at all. At 36
observations the exact 95% lower bound only reaches 0.80 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 of 60, and a true rate of 0.90 gives a lower bound of 0.8121,
which clears it. The manifest publishes those numbers as
`what_this_sample_size_can_establish`, computed from the same bounds the verdict uses, so the
sizing is a number a reader can check rather than a judgment they have to accept.

**The receipt says PASSED, and a person produced the answers.** The generated block below
is the current state and this paragraph is about the machinery that got it there, which is
worth keeping because the gate spent most of its life in the other outcome. This paragraph
said "the receipt says NOT_RUN" for weeks after the human arm landed fourteen lines below it,
which is the drift a document with a generated region invites: the region moved and the prose
around it did not.

NOT_RUN is a distinct outcome from FAILED on purpose: a worksheet no person has answered is a
fact about the world, and folding it into a failure would manufacture the measurement the
gate would then be missing. `scripts/score_gate4.py` will not publish a rate without a
`REVIEWER.json` saying who produced it, and for a reviewer that is not a person it puts every
number under `arm` and leaves the gate's own verdict alone. That is deliberate: the console's
gate table, `scripts/sync_kill_gate.py` and the MCP `gate_status` tool all read `verdict` and
none of them has to know this distinction exists in order to avoid publishing a model's
answers as a person's. The three measured verdicts follow the interval, exactly as gates 3, 5 and 6 do:
PASSED when the exact 95% Clopper-Pearson lower bound reaches 0.80, FAILED when the upper
bound is below it, NOT_ESTABLISHED when the interval contains it. On the committed 60, that is
PASSED at 54 or more decisive, FAILED at 42 or fewer, and NOT_ESTABLISHED in between.

<!-- generated by scripts/sync_kill_gate.py: gate 4 arm, do not edit -->
**A person has answered this worksheet and the gate is decided.** 60 of 60 first-occurrence observations were decidable, a rate of 1.0000, exact one-sided 95% [0.9513, 1.0000], which clears the 0.80 threshold, so the gate is met. Intra-rater agreement is 8 of 12 repeated pairs answered identically on all three axes, which bounds how far the first number can be trusted. Agreement with the network's own label is 23 of 40 on the visible-signal axis and 17 of 40 on the target-consistency axis, and neither of those is the network's question. Those two numbers should be read together and they are not the same claim. Every plate supported a judgment, and on 4 of 12 repeated plates the same reviewer reached a different judgment the second time. Decidability at 1.0000 is what this gate asked for and what it establishes. Reliability at 0.6667 is a separate property, it is the weaker of the two, and no claim here rests on it. The reviewer was **human**: Kesav Jayakumar (Kesav2k04), the author of TraceTriage, reviewing on 2026-08-22. Not an independent reviewer: the same person built the instrument. What the commitment guarantees instead is that the sample, its order and the images were fixed before the review and cannot have been chosen around the answers. The reviewer could not see the network's waterfall_status, any model prediction, the item-to-observation mapping or the salt, none of which is in the bundle: the key file was never opened before scoring. The reviewer knows the corpus and built the pipeline, so this is blinded but not independent, and the receipt says so rather than implying a stranger did it. Repeat pairs are at least six items apart and are not marked, so the intra-rater figure is still two reads that could not see each other. A model answered the same committed sample before a person did, and that review is kept in `prior_review` rather than deleted: 57 of 60 decidable at 0.9500, by A general-purpose commercial language model, not IBM Bob and not a person, run as twelve independent sessions on 2026-08-21. It is not this gate and never was. What the two together allow, which neither allows alone, is a comparison of a model reviewer against a person on identical blinded plates, and it does not go the way a reader would guess: the model was less decisive (0.9500 against 1.0000) and more self-consistent (11 of 12 repeated pairs against 8 of 12). Neither arm is the other's control and this is one reviewer of each kind, so it is published as an observation and not as a result.
<!-- end gate 4 arm -->

Two numbers come out beside the gate and neither decides it: intra-rater agreement on the
repeated items, which bounds how far the first number can be trusted, and agreement between
the blinded reviewer and the network's own `waterfall_status`, which is the measurement that
would say whether the silver labels this project trains against are what a careful human sees
in the same image. The second excludes items the network left unknown and items the reviewer
answered `unsure` on, and the receipt publishes both counts, because the second exclusion
conditions the rate on the reviewer's own confidence.

**What the blinding does not cover**, stated in the manifest rather than left to be assumed:
the image files are byte-distinct, so hashing the bundle no longer recovers the repeat pairs,
but a reviewer who decodes them and hashes the pixels still can; and a bundle built with
`--source console` is made of re-encodes of waterfalls this repository tracks, so a determined
reader could match pixels against `apps/web/public/waterfalls` and recover the label. The
committed build used the snapshot, which has no such path.

---

## Gate 5: physics improves probability quality — NOT ESTABLISHED

**Threshold:** the physics-conditioned model lowers Brier score against a calibrated image-only baseline on a temporary chronological split.

**Measured 17 Aug 2026 in B2-B6.** Receipt: `artifacts/FUSION_RECEIPT.json`. Run:
`.venv/Scripts/python.exe scripts/run_fusion.py --n-boot 4000`.

> The physics-conditioned arm has the lower Brier score by 0.02080, but the 95% interval
> (-0.01271 to 0.05022) spans zero on 88 test observations across 88 episodes. A point
> estimate in the right direction with an interval containing zero is not a gain, and
> reporting it as one would be the same error unit A7 made. The gate is not met.

The wording was not changed to fit the result, and the challenger was not redefined to
manufacture a pass. `physics_conditioned` is the arm the gate names, and that arm is what
was tested.

**What was established instead, on the same 88 observations, and what was withdrawn on
2026-08-19.** Removing the geometry block leaves `image_corridor`, which beats calibrated
image-only on both metrics. One of the two margins survives the multiplicity correction:

| comparison | margin | 95% CI | Bonferroni CI (21 comparisons) |
|---|---|---|---|
| Brier | +0.02026 | +0.00678 to +0.03631 | -0.00050 to +0.04874, **does not survive** |
| risk-coverage area | +0.05736 | +0.02605 to +0.09312 | +0.01192 to +0.11887, survives |

`image_corridor` was nominated after reading the ladder, which is why the corrected
interval travels with it.

Until 2026-08-19 both rows read "survives", over a family of 7 and on an interval that
resampled episodes. Two things changed in D7. Both make the correction stricter, and
neither was chosen after seeing what it did to this result:

- **The family is the 21 comparisons the rule can read**, 7 on each of the 3 splits above
  the 300-row training floor, rather than the 7 on the split being reported. The ablation
  rule is a disjunction across splits: it retains a block if an arm containing it wins on
  any eligible split. Correcting at the split boundary corrects over a family narrower
  than the search, and the receipt's own justification for using a corrected rule already
  said the ladder runs comparisons on each of four splits.
- **The published interval is the union of two groupings.** Episodes are inert as clusters
  on this test set: 88 episodes over 88 observations, a mean group size of 1.0, and an
  intraclass correlation that cannot be estimated at all. The same paired differences give
  a station ICC of 0.2471 and a design effect of 1.3741, so the interval is now measured
  under both and the published bound is the worse end of each.

Of those two changes, the family size is the one that withdraws the claim, and the
measurement says so rather than the argument. Corrected over 21 comparisons the
observation-level interval is -0.00050 to +0.04364 and the station-clustered interval is
+0.00297 to +0.04874, so the station grouping alone would still clear zero. The clustering
did not widen this comparison in the direction the review expected: the two nominal
intervals are 0.02816 and 0.02823 wide, near enough identical, and the station one sits
about 0.0013 higher rather than being wider. The union is used because it is conservative
whichever way the groupings fall, not because clustering was the decisive term here. The
normal-theory design-effect widening is reported beside it and also fails to clear zero
(-0.00408 to +0.04767), so the two accountings agree on the verdict while disagreeing about
which grouping drives it.

Both intervals are read at 50,000 draws, and the draw count is part of the result rather
than a setting. A 21-comparison correction reads the 0.119th percentile, so 4,000 draws put
that endpoint at the fifth-smallest resample of the whole distribution: a quantile the
bootstrap does not have the resolution to report, returned as an interval that looks like
any other interval. The minimum that puts 20 draws in the tail is 16,800, and 50,000 puts
59.5 there. Every corrected interval in the receipt now carries `percentile_resolution`
beside it, so an unresolvable endpoint is visible instead of implied.

**What the retraction changes downstream.** The corrected ablation rule reads Brier
comparisons, so it now retains no block beyond image and recommends `image_only`. The
queue is still ranked by `image_corridor`, and the receipt states that disagreement at
`ablation_conclusion.shipped_arm_vs_recommendation` rather than settling it silently in
either direction. Two measured reasons are recorded there. The narrower arm is not
established as better either, so swapping the shipped arm on this result would be a change
made for the appearance of consistency rather than for a finding. And the same arm's
risk-coverage margin does survive the same correction on the same split, which is the
metric closest to what the queue actually does, because selective review is the queue's
whole job. That comparison was not promoted into the ablation rule after the fact.

**Why the gate's own challenger fails.** The geometry block
carries no usable signal on this corpus. Its seven features measured marginal AUC between
0.466 and 0.567 before any head was fitted, and `physics_only` scores Brier 0.2136 against
the 0.2085 prior-only floor with a calibration slope of -0.07. Adding it to image widens
the combined interval past zero.

**What this does not license.** The corridor block is physics, so "physics helps" is
supported in the specific form measured: the fitted Doppler corridor helps, the orbital
geometry summary does not. The claim is weaker after D7 than before it. What survives
correction over the family the rule reads is a ranking gain on the metric the queue uses,
not a calibration gain: the Brier interval no longer clears zero. Any claim broader than
that is not in evidence here.

The comparison was against a **calibrated** image-only baseline, calibrated by the same
method on the same partition, so the improvement is not calibration wearing a physics
costume. The frozen test set was not touched: all four splits report against their own
test partitions and the chronological split is the one the gate names.

---

## Gate 6: queue lift over random — NOT ESTABLISHED

**Threshold:** the top of the review queue surfaces at least 1.5x as many manually actionable conflicts as random ordering at the same budget.

**Measured 2026-08-17 in C1, remeasured 2026-08-18 in C2 after a defect in the
interval.** Receipt: `artifacts/QUEUE_RECEIPT.json`. Run:
`.venv/Scripts/python.exe scripts/run_queue.py --seed 42 --n-boot 4000`

> The queue's point lift is 1.582 on the chronological split (20 conflicts in 50
> examined, expected 12.644 by random). The governing 95% interval is 1.353 to 1.755,
> which contains the 1.5 threshold. A point estimate above the bar whose interval
> straddles it does not establish the claim, for the same reason gate 5 was recorded as
> NOT_ESTABLISHED. The bootstrap median is 1.589, so the point estimate is not the
> product of a skewed resample; the interval is simply wide on 87 decisive observations.

**Corrected in D15g.** The upper bound is 1.740, not 1.755. D15 stopped the bootstrap
rounding each draw's review budget and made it take the ceiling instead, which put the
bound exactly on the 87 over 50 cap. The block above is what C2 measured and is left as
it was written, because a dated record that is quietly edited is not a record.

**The C1 interval was wrong, and this is what it was.** C1 published 1.00 to 1.20
against a point estimate of 1.60, and every split showed the same shape: the
interval lying entirely below its own point estimate. That is not a property any
resample of a consistent statistic can have four times out of four, and it was
recorded at the time as expected behaviour of percentile intervals on ratio
statistics. It was not. The bootstrap deduplicated its own draw
(`pool_set = set(pool)`), and a draw of 88 episodes with replacement covers only
about 63% of them, so the drawn population fell to a mean of 55.8 rows while the
budget stayed at 50. Selecting 50 of 55.8 is not selection, the drawn conflict
rate converged on the population rate, and lift was driven towards 1.0 by
construction: 65 of 2000 draws returned exactly 1.0, which is where the 1.00
lower bound came from. Reproducing the old loop on synthetic data with the same
proportions returns [1.0000, 1.2200], against the published [1.00, 1.20].

The measurement now carries `point_in_ci`, and a point estimate outside its own
interval is reported as NOT_MEASURABLE with both numbers and the gap, rather than
as a verdict about the gate. `n_boot_effective` records surviving resamples
(4000 of 4000 on all four splits) because dropping draws with no conflict
conditions the interval, and a reader cannot see that from the interval alone.
Tests: `tests/test_queue_lift_bootstrap.py`, 26 of them, and the regression case
fails against the old loop rather than merely passing against the new one.

**Conflict definition (fixed before measuring):**

1. `MODEL_LABEL_DISAGREE`: shipped arm (`image_corridor`) predicts with prob ≥ 0.75 that the label should be the opposite of the current `waterfall_status`.
2. `STALE_CATALOGUE_FREQ`: fitted offset ≥ 20 ppm and `offset_at_bound = false`.
3. `DEAD_CAPTURE`: `flat_row_frac ≥ 0.15`.

**Every ordering's lift over random (chronological split, budget = 50, n_decisive = 88,
random expects 12.644 conflicts):**

| Ordering | Conflicts at budget | Lift over random |
|---|---|---|
| Review-value queue | 20 | 1.582 |
| Image uncertainty | 15 | 1.186 |
| FIFO | 14 | 1.107 |
| Physics-only | 13 | 1.028 |
| Random | 12.644 expected | 1.000 by definition |

All five orderings are on one scale, so they are comparable. These are point
estimates. C4 adds the paired interval for each comparison, drawn from the same
episode resample under both orderings, because a ratio between two orderings
needs the same draw under both to mean anything.

**Per-split results, on the shipped queue, measured 2026-08-18 in C2.** The
governing interval is the union of the episode-grouped and station-clustered
intervals, which is conservative in both directions.

<!-- generated by scripts/sync_kill_gate.py: per-split gate 6 table, do not edit -->
| Split | Verdict | Point lift | Episode CI | Station CI | Governing (union) | n_decisive |
|---|---|---|---|---|---|---|
| chronological | NOT_ESTABLISHED | 1.582 | [1.353, 1.740] | [1.368, 1.735] | [1.353, 1.740] | 87 |
| cold_station | PASSED | 2.253 | [1.920, 3.011] | [2.020, 3.859] | [1.920, 3.859] | 217 |
| cold_transmitter | NOT_ESTABLISHED | 1.656 | [1.462, 1.834] | [1.336, 1.894] | [1.336, 1.894] | 95 |
| cold_combined | NOT_ESTABLISHED | 1.292 | [1.073, 1.520] | [1.130, 1.500] | [1.073, 1.520] | 76 |
<!-- end per-split gate 6 table -->

Taking the union rather than "the wider interval" matters, and cold_station is the
case that shows why. Its station interval is wider than its episode interval and
yet has the higher lower bound, so quoting the wider one would report 2.020 when a
defensible grouping supports only 1.920. No verdict changes under the union here,
which is a robustness result rather than a reason to have chosen it.

cold_combined is NOT_ESTABLISHED rather than FAILED, and that is a rule change
rather than a number change. C1 called any point estimate at or below 1.5 a
failure. An interval of [1.073, 1.520] contains the threshold, so it does not
refute the gate any more than it establishes it. FAILED is now reserved for an
interval lying entirely below the bar, which is the same standard applied in the
other direction.

cold_station passes on 217 decisive observations, and it is the split where a
reviewer meets stations the model has never seen. That is the operating condition
the queue exists for. It does not substitute for the chronological split, which is
the primary and is not established.

**What entity-concentration control cost, per the C2 pre-registration.** The
shipped queue is the capped queue, because not spending a reviewer's budget on one
station is a product requirement. The uncapped numbers are reported as a reference
and were never eligible to be the verdict.

| Split | Displaced from the budget | Capped lift | Uncapped lift | Conflicts capped / uncapped |
|---|---|---|---|---|
| chronological | 4 | 1.582 | 1.582 | 20 / 20 |
| cold_station | 40 | 2.253 | 3.005 | 27 / 36 |
| cold_transmitter | 4 | 1.656 | 1.608 | 34 / 33 |
| cold_combined | 10 | 1.292 | 1.292 | 17 / 17 |

cold_station is where diversity is expensive: capping one station at 5 of 50
displaced 40 candidates and cost 9 conflicts, dropping lift from 3.005 to 2.253.
The split still passes. That is the price of a queue that does not hand a reviewer
40 captures from the same site, and it is reported rather than resolved by quoting
whichever queue scored better.

The station cap bound on all four splits. The transmitter cap displaced nothing on
any of them, so it is inert on this corpus and recorded as inert. A cap is credited
with a displacement whenever it would have blocked an entry, even where another cap
would also have blocked it, so that result is a property of the data and not of the
order the caps are checked in.

### Active-selection replay against every baseline, measured 2026-08-18 in C4

Gate 6 asks only whether the queue beats random. A queue that beats random and
loses to FIFO has not earned a reviewer's attention, because FIFO is what a
reviewer already does. Every ordering is replayed over the same resampled
populations, paired within each draw: one draw produces one synthetic population
and all four orderings are scored on it before the next draw. Each ordering is
re-sorted by its own rank inside the draw, because an ordering's top 50 in a
resampled population is not the same set as its top 50 in the original.

Random is not carried as an ordering. FIFO here is observation-id order, so
carrying random separately would report the same comparison twice under two names;
random enters through its expectation.

**Conflicts found at budget 50, with each ordering's lift over random:**

| Split | Review-value queue | Image uncertainty | FIFO | Physics-only |
|---|---|---|---|---|
| chronological | 20 (1.582) | 15 (1.186) | 14 (1.107) | 13 (1.028) |
| cold_station | 27 (2.253) | 16 (1.335) | 16 (1.335) | 19 (1.586) |
| cold_transmitter | 34 (1.656) | 24 (1.169) | 21 (1.023) | 22 (1.072) |
| cold_combined | 17 (1.292) | 10 (0.760) | 15 (1.140) | 9 (0.684) |

The tested statistic is the difference in conflicts found at the same budget, not
the ratio. A difference is defined in every draw, including draws where a baseline
finds nothing, and its null is exactly zero. The ratio is reported beside it with
a continuity correction of +0.5 on both terms applied in every draw rather than
only where the denominator is zero, because a correction applied selectively
changes the estimator between draws.

**A baseline counts as beaten only when the Bonferroni-widened interval excludes
zero under both the episode and the station resample, and both groupings agree.**
Same principle as the governing interval: where two defensible groupings exist,
the conservative reading governs, and a disagreement is reported as not
established with both directions named rather than resolved in favour of the
answer that helps.

| Split | vs FIFO | vs image uncertainty | vs physics-only |
|---|---|---|---|
| chronological | +6, not established | +5, not established | +7, **beaten** |
| cold_station | +11, **beaten** | +11, not established | +8, not established |
| cold_transmitter | +13, **beaten** | +10, not established | +12, **beaten** |
| cold_combined | +2, not established | +7, not established | +8, **beaten** |

**The limitation this exposes, stated plainly.** The queue is never established as
better than image-uncertainty ordering, on any split, under the both-groupings
standard. It leads on the point estimate every time, by 5 to 11 conflicts, and on
three of four splits the episode-grouped interval alone survives correction, but
the station-clustered interval does not. Image uncertainty is the closest
competitor and it is nearly free: it needs the shipped arm's probability and
nothing else. So the defensible claim from C4 is that the queue beats a physics
ordering and beats what a reviewer does today, and that its advantage over sorting
by the model's own uncertainty is real in point estimate and not established at
this sample size.

The queue does not lose to any baseline on any split under either grouping. That
matters because a loss was representable: `baseline_better` is a reachable state
tested by a constructed case, and survival is tested in both directions.

**Episode deduplication is nearly inert here, and the count is reported for the
same reason.** It removed 3, 0, 1 and 5 observations across the four splits,
because pass episodes hold 1.004 observations each on this corpus and only 8 of
2716 hold more than one. The rule is real and tested against constructed
duplicates; the data barely exercises it.

**Two groupings, because the finer one was measuring nothing.** The 88 decisive
observations of the chronological test partition fall into 87 pass episodes of mean
size 1.000, so the episode intra-class correlation is not computable and is
reported as not measurable with its counts. The episode-grouped bootstrap used
throughout Waves B and C was therefore resampling singleton groups, which is an
ordinary bootstrap. Clustering by ground station is present and material on every
split: ICC 0.0887, 0.0784, 0.1347 and 0.0909, design effects 1.132 to 1.552,
consistent with a receiver and a local-oscillator error shared across a station's
passes. Both intervals are published and the union governs.

The queue's episode key also moved from `(station, satellite, start[:13])` to
`(station, satellite, orbital_revolution)`, the key `splits.py` partitions on. An
hour bucket splits any pass crossing an hour boundary into two groups. Measured
difference on this corpus: 2716 revolution episodes against 2722 hour buckets, 17
observations affected, so the correction is small here and would not be on a
multi-day snapshot.

The cold_station PASSED result is notable: a strong signal that the queue selects observations that a reviewer with no station familiarity finds actionable. The chronological split's narrow CI reflects the small test set (88 decisive obs) and the ratio statistic's known skewness under small samples.

---

## If a gate fails

Record it here: gate number, threshold, measured value, the artifact that produced it, and the date. Then stop.

Do not submit another image-only waterfall classifier under this plan. The plan is explicit: more features cannot repair a false claim.

---

## Failure log

**17 Aug 2026, gate 5: NOT ESTABLISHED.** The physics-conditioned arm did not lower Brier
score against a calibrated image-only baseline by a margin whose interval clears zero
(+0.02080, 95% CI -0.01271 to +0.05022, n=88 observations across 88 episodes). Cause: the
orbital-geometry feature block carries no usable signal on this corpus (marginal AUC 0.466
to 0.567 across its seven features; `physics_only` Brier 0.2136 against a 0.2085
prior-only floor), and adding it to the image arm widens the interval past zero. Action
taken: the block was dropped by the ablation rule, and the shipped arm is `image_corridor`,
which cleared zero on both Brier and risk-coverage area and survived Bonferroni correction
over the 7 comparisons then being counted. That last clause was corrected on 2026-08-19;
see the entry at the end of this log. The gate's wording was left intact and its challenger
was not redefined.
Recorded rather than retried, because the honest failure of a specific claim is worth more
here than a restated claim that passes. Full detail in the gate 5 section above and in
`artifacts/FUSION_RECEIPT.json`.

<!-- generated by scripts/sync_kill_gate.py: gate 3 failure log, do not edit -->
**2026-08-18, gate 3: withdrawn from PASSED.** `clears_threshold` compared the point estimate against the bar, and 1.0 ≥ 0.70 is True for 1 of 1 as much as for 3 of 3. The gate reads PASSED_UNGROUPED_ONLY as of the receipt in this tree, which is the state described from here on rather than the state on the date above. The expected corridor discriminates on 77.5% of them: 224 of 289, each beating 200 corridors built by permuting its own Doppler samples in time with none reaching it (p = 0.005), and each beating all four scaled-swing controls that hold shape and smoothness fixed and vary only magnitude. The gate asks for a rate of 70%; the measured rate is 77.5% on 289 trials, whose exact one-sided 95% Clopper-Pearson lower bound is 0.7309. That bound clears the 70% bar. Over the 68 independent (ground_station, UTC date) episodes those observations span it is 0.3662 and does not, and the plan groups before it decides, because a ground station's oscillator error is common to every pass it records. So the observation-level pass is reported and not claimed. Receipt: `artifacts/GATE3_RECEIPT.json`.
<!-- end gate 3 failure log -->

**2026-08-18, gate 6: NOT ESTABLISHED.** The review-value queue's point lift is 1.582x over random at budget 50 on the chronological split (20 conflicts against 12.6 expected, 87 decisive test observations across 87 episodes). The 95% grouped bootstrap interval is [1.353, 1.740], which contains the 1.5x threshold, so the claim is not established. Bootstrap median 1.589. cold_station PASSED at 2.253x [1.920, 3.859] on 217 decisive observations, which is the split where a reviewer meets unseen stations, and it does not substitute for the primary split. cold_transmitter 1.656x and cold_combined 1.292x are both NOT_ESTABLISHED on intervals containing the threshold. Receipt: `artifacts/QUEUE_RECEIPT.json`.

**2026-08-18, correction to the gate 6 entry recorded on 17 Aug.** That entry
published the interval [1.00, 1.20] beneath a point estimate of 1.60 and attributed
the gap to small-sample behaviour of percentile intervals on ratio statistics. The
attribution was wrong and the interval was an artefact. The grouped bootstrap
deduplicated its own resample, so the drawn population fell from 88 rows to a mean
of 55.8 while the budget stayed at 50, which removed the selectivity that lift
measures and pushed the statistic towards 1.0. The tell was visible in the
published numbers: the point estimate lay above the interval's upper bound on all
four splits at once, which no resample of a consistent statistic does. Reproducing
the old loop on synthetic data at the same proportions returns [1.0000, 1.2200].
The verdict is unchanged at NOT_ESTABLISHED and the wording was not touched, but it
now fails for the reason stated rather than for a defect. `point_in_ci` was added so
this shape cannot be narrated again: a point estimate outside its own interval is
reported as NOT_MEASURABLE with both numbers and the gap.

*Written while gate 4 had one arm and no human reviewer, and left as written because the
failure log is a record rather than a status board. Superseded on 2026-08-22: a person
answered the committed worksheet, the receipt reads PASSED at 60 of 60 decidable, and the
model arm is kept beside it as `prior_review`. The gate 4 section above is the current state.*

**2026-08-18, correction to this document.** The status summary at the top of this
file and the failure-log entry above both carried gate numbers from an earlier run,
while the per-split table further down carried the current ones. The summary claimed a
95% interval of [1.00, 1.20] for gate 6 and a cold_station lift of 3.00x; the receipt
says [1.353, 1.755] and 2.253x. Two different intervals for one gate in one document
is exactly the drift this project checks for elsewhere, and it was found by reading the
file against `artifacts/QUEUE_RECEIPT.json` rather than by any gate. The summary and
the log entry are now generated from the receipt by
`scripts/sync_kill_gate.py`, so the next re-run cannot leave them behind. The verdict
was NOT_ESTABLISHED before the correction and is NOT_ESTABLISHED after it: no
conclusion changes, only the numbers supporting it.

A second error came out of the same reading. Gate 6 was described as running on "88
decisive test observations across 88 episodes". It runs on 87 in 87: `n_test_decisive`
and `n_groups` in the queue receipt are both 87, and 88 is gate 5's sample size, which
had been copied across. One observation is not a material difference to the verdict,
and that is the reason it survived: a wrong number that changes nothing is the kind
nobody checks. It is now read from the receipt like the rest of the row.

**2026-08-18, correction to the correction above.** The paragraph above says the
summary and the log entry "are now generated from the receipt by
`scripts/sync_kill_gate.py`, so the next re-run cannot leave them behind". That was
not true when it was written. The script replaced one hardcoded old string per row and
asserted the string was present, so its second run died on `AssertionError: gate 5
summary row not found`, and it appended its own correction paragraph unconditionally,
so a second run would have duplicated that too. A generator that cannot run twice is a
one-shot fixup, and it was documented as something to re-run after any pipeline
re-run. The script now anchors on structure rather than on the old
content and is idempotent: `tests/test_kill_gate_sync.py` renders the document twice
and asserts the bytes are identical, asserts every generated row against its receipt,
and fails if this file drifts from them. `--check` reports drift without writing.
Nothing in the measurements changed. The first regenerated table was byte-identical to
the hand-written one it replaced, which is the check that the generator reproduces
what a reader had already been shown.

**2026-08-19, correction to the gate 5 entry recorded on 17 Aug.** That entry said the
shipped arm `image_corridor` "does clear zero on both Brier and risk-coverage area and
survives Bonferroni correction". The Brier half of that no longer holds. The correction it
survived ran over the 7 comparisons on one split, on an interval that resampled episodes.
The ablation rule it feeds reads 7 comparisons on each of the 3 splits above its training
floor, and episodes are inert as clusters on this test set: 88 groups over 88 observations,
where an intraclass correlation cannot be estimated at all, while the same paired
differences give a station ICC of 0.2471. Corrected over the 21 comparisons the rule reads,
on the union of the episode-grouped and station-clustered intervals at 50,000 draws, the
Brier margin of +0.02026 has a corrected interval of -0.00050 to +0.04874 and does not
clear zero. The family size is what does that: over the same 21 comparisons the
station-clustered interval alone is +0.00297 to +0.04874 and would still clear zero, so the
clustering fix is not what withdraws the claim even though both changes landed together. The risk-coverage margin of +0.05736 does, at +0.01192 to +0.11887 over the
same family. Nothing in the measurement changed: the point estimates are identical and the
four nominal interval endpoints all moved by less than 0.002. What changed is the family
the correction runs over and the grouping the interval resamples, both of which were too
narrow, and both were found by reading the receipt against its own justification text
rather than by any gate. The corrected ablation rule now retains no block beyond image and
recommends `image_only`, while the queue is still ranked by `image_corridor`. That
disagreement is published at `ablation_conclusion.shipped_arm_vs_recommendation` with the
two measured reasons the arm was not swapped. Receipt: `artifacts/FUSION_RECEIPT.json`.
