The library
Rendered from docs/handoffs/2026-09-26_channel-robustness-handoff.md; the markdown file is the record.
On this page
Handoff: channel robustness on the frozen path (Sat Sep 26, 2026, ~08:30)
Paste this into an idle chat (the M5 chat is the best fit: GPU-literate, owns the codec-augmentation code, has the In-the-Wild gate machinery) and start it with /plan-review.
Purpose of this chat: run the four measurement-and-refit actions the fusion consult ranked "DO" for the frozen deep path, and deliver either a better M1b column for the final TSV or a documented negative result. Nothing here touches the 14:00 draft: the draft ships the rule frozen at 08:13. Time box: about 4 hours of work, hard stop 16:00, so that any new column can go through the same pre-declared sweep (scripts/fuse_sweep.py) and parity before the fusion freeze at 22:00.
Read first: CLAUDE.md, docs/STATUS.md, docs/consults/2026-09-26_fusion-strategy_RESPONSE.md (items 3, 4, 6, 8 and 9 of "What to do"), docs/reports/2026-09-26_fusion-sweep-predeclared.md, docs/reports/2026-09-26_m1b-asv19-bonafide.md, docs/architecture.md sections 4 and 8.
Why
Both deep models sit at 0.26–0.34 minDCF on In-the-Wild against 0.014–0.07 on the NSA holdout. The memo's diagnosis: the training real speech is all clean read speech, and the test set's uniform 7.2 kHz wall looks like a codec round-trip, whose artifacts on real test speech can masquerade as vocoder artifacts. The memo also corrected our own premise: once band-matched, adding VCTK real speakers helped on both axes (ITW 0.380 → 0.342, holdout 0.159 → 0.072), so real-speech diversity is a live lever.
The four actions, in order
A. Estimate λ, the test set's wild-domain weight (30 min, CPU, no labels). Label-free channel statistics per clip: 95% spectral roll-off, long-term spectral tilt, noise-floor level and its stationarity over time, a reverberation proxy (e.g. energy-decay slope after offsets). Most exist already as detector features: bw_hz, the four band levels and floor depths from hearsay.compression.features, flatness, energy_db_std and pause_frac from hearsay.handcrafted.features. Train a small logistic classifier to separate holdout-real (splits/nsa_folds.csv, fold holdout, label bonafide) from In-the-Wild real (outputs/manifests/itw_bonafide.csv or the real rows of itw_stress.csv), then average its predicted P(wild) over the 1,671 test files. Report λ̂ with a bootstrap interval. The memo's crossover is 0.32 and its prior is "low" (one curated re-encode, not field audio). This number decides how much In-the-Wild should weigh in every later selection; write it into the report first.
B. Diagnose the 7.2 kHz edge as a codec (45 min, CPU). Take 50 LJ clips; run hearsay.compression.launder(x, codec, kbps, sr_enc) over a grid (mp3 at 32/48/64/96 kbps, aac at 32/48/64, at 16 and 44.1 kHz), then band-match with hearsay.handcrafted.band_limit; compare each variant's roll-off slope across 6.5–8 kHz, high-band spectral flatness and the compression detector's hole statistics (deep_hole_frac, local_hole_frac, floor_p2_db) against the same statistics on the test files (outputs/inventory/nsa_test_highband.csv has per-file band levels already). Pick the closest match; if nothing is closer than the plain Kaiser low-pass, stop here and record the negative.
C. Symmetric codec round-trip refit of M1b (1.5 h, GPU then CPU). Only if B found a match. Apply the matched round-trip to every training clip of both classes (never one class: that recreates the 7.2 kHz shortcut in reverse), decode back to 16 kHz, then extract with the shared crops: uv run python scripts/extract_embeddings.py --manifest <laundered manifest> --name nsa_train_sample_v4c --segment --crop test --seed 0 and the same for asv19_addon (seed 100). Do not touch embed.py, the extraction script, train_probe.py or the fold files; write a new manifest whose paths point at the laundered copies (put them under outputs/laundered/, not data/). Refit with uv run python scripts/train_probe.py --train nsa_train_sample_v4c,asv19_addon_v4c --folds splits/nsa_folds_plus_asv19.csv --stress itw_stress_v3. Compare with M1b v3 (models/m1_wav2vec2-xls-r-300m_L7_20260926-0521/meta.json: holdout 0.072, ITW 0.342, ITW P_FA 0.8%) on inner OOF, holdout, per-real-source, ITW under both cost weightings. Clean-only readouts beside the augmented ones (CLAUDE.md rule). Export scores in the fusion format as outputs/detector_scores/m1b_v4c.csv if it wins on ITW without losing the holdout beyond noise (0.15); otherwise record the numbers and stop.
D. One wild, non-read bona fide corpus (30-minute download rule). If a public corpus of a few thousand real, non-read, channel-diverse clips downloads within 30 minutes (VoxCeleb1 test needs a request form, so probably not; SpoofCeleb's bona fide is enhanced and the authors warn about it; say what you tried), band-match it, group by speaker, add it to the inner folds only via scripts/extend_folds.py (a new fold file name; never edit splits/nsa_folds.csv), extract, refit as in C, compare. One corpus, not three. If nothing downloads in 30 minutes, abandon and say so.
E. M3 leakage probes (1 h, CPU, can run in parallel with C). Three, from the memo. (1) Perturbation sensitivity: 500 inner clips, four label-preserving perturbations (MP3 round-trip via launder, +20 dB SNR white noise, ±2% time-stretch, a one-sample circular shift); score with M3 (scripts/score_spectra.py functions, zero-pad, CPU) and with M1b; report ΔAUC per perturbation for each; a memorising model degrades far more. (2) Spearman of M1b vs M3 ranks on the holdout versus on the test set (both exports exist under outputs/detector_scores/); a large drop means holdout agreement is memorisation. (3) M3 on MLAAD spoof (outputs/manifests/mlaad_en.csv, on disk): miss rate at M3's inner threshold; near-perfect on the classic sets but mediocre here is consistent with memorisation. Ten minutes on the model card for a training-data list, then stop. The result gates whether M3's FA-suppression role in the shipped rule is kept for the final.
Deliverables
docs/reports/2026-09-26_channel-robustness.md: λ̂ with interval; the codec-match table; the refit comparison table (or the negative); the corpus attempt; the three M3 probe results and a one-line verdict on M3's role.- Any winning column exported in the fusion format and announced to the main chat, which reruns
scripts/fuse_sweep.pyunder its pre-declared rule. - A STATUS line and a disclosure bullet in
CLAUDE.md.
Rules
Never train or select on the holdout, In-the-Wild or the test set. No pseudo-labels. Augment both classes or neither. Stage only your own files (git add <paths>, never -A); do not push. Files you may create: scripts/channel_*.py, scripts/m3_probes.py, the report, new manifests under outputs/manifests/, laundered audio under outputs/laundered/. Do not edit src/hearsay/embed.py, probe.py, spectra.py, the fold files, scripts/extract_embeddings.py, train_probe.py, fuse.py, fuse_sweep.py. The local GPU is free (M3 and the M1 re-extractions are done); the runner agent and the Docker chat use CPU only. Report to the oversight chat at each milestone; Nathan ratifies anything that changes the shipped column.