The library
Rendered from docs/reports/2026-09-26_channel-robustness-plan.md; the markdown file is the record.
Plan: channel robustness (A, E, B; C gated) — Sat Sep 26, 2026, 12:35
Run spec: docs/specs/2026-09-26_channel-robustness.md (design decisions D1–D10, acceptance criteria, failure-mode table). Hard stop 16:00.
Ground truth read before writing
hearsay.compression.features(x, crop_s, seed, crop_mode, band_match)(src/hearsay/compression.py:46) callshearsay.handcrafted._crop→band_limit(handcrafted.py:43) →prepare_segment(embed.py:80: band match off since already filtered,trim_silence, optional crop, 8 s cap) → RMS normalize; returns 19 features incl.bw_hz, four band levels,hf_slope_db_per_khz, hole statistics,floor_p2_db.hearsay.compression.launder(x, codec, kbps, sr_enc)(compression.py:105): FFmpeg encode/decode, 16 kHz float32 out, raisesDecodeError.hearsay.audio.trim_silence(audio.py:90),load_audio(audio.py:22).hearsay.pipeline.Models(pipeline.py:~390): CPU only;m1_logit(x)=prepare_segment→embed_segment→ fp16 round →Probe.llr;spectra_logit(x)=prepare_input→score_clip(zero pad, 16 windows)→ spoof − bonafide;m5_logit(x)withload_m5=True. Loading its handcrafted detector is part of the constructor (harmless, no LightGBM import).- Exports
outputs/detector_scores/{m1b_v3,spectra_aasist,m5_xlsr_ft}.csv:path, fold, split ∈ {inner_oof, holdout, test, stress/itw}, score, logit. - Manifests:
splits/nsa_folds.csv(path, label, generator, speaker, source, group, fold),outputs/manifests/itw_stress.csv(path relative, label, speaker),outputs/manifests/nsa_test.csv(filename, path),outputs/manifests/mlaad_en.csv(path, label, generator, model_name, …). - Shipped draft TSV:
submissions/20260926-0914_M4_sweep_A3_w0.2_E_CANDIDATE_our_direction.tsv(log row 12:03). - Test pattern for scripts:
importlib.util.spec_from_file_location(astests/test_spectra.py:115).
Steps
- Codex plan review in the background on this file (
.claude/review-plan.sh); findings folded in when they land, without blocking A. scripts/channel_lambda.py(A). Pure functions:channel_stats(x) -> dict(D1: callscompression.features(x, None, None, "segment", True)and adds the six statistics on the same prepared signal, obtained by the same_cropcall);grouped_oof(X, y, groups, k=5, seed=0);lambda_mean_posterior(p);lambda_acc(q, tpr, fpr)(NaN below a 0.2 margin);feature_position(med_test, med_clean, med_wild);bootstrap_ci(fn, *arrays, n=1000, seed=0).main(): build the three row sets, extract with a 6-process pool (resumable cache atoutputs/channel/lambda_features.csv), fit, estimate, writeoutputs/channel/lambda.jsonand print the readout.- Tests for A in
tests/test_channel_robustness.py(the A rows of the spec's failure-mode table). Run them. - Run A (~5 min extraction). Send λ̂ to the oversight chat immediately.
scripts/m3_probes.py(E). Pure:perturb(x, kind, rng)fornone | mp3 | noise20 | speed+2 | speed-2 | shift1;delta_auc(...);spearman_gap(a_hold, b_hold, a_test, b_test, n=1000);m3_verdict(m3_dauc, m1_dauc, gap, gap_lo, mlaad_below)(D9).main()with subcommandsperturb,agreement,mlaad; each loadsModels(device="cpu", load_m5=True, threads=3)where needed, caches rows tooutputs/channel/m3_*.csv(resumable), writes a JSON summary. Theperturbandmlaadruns go in the background, two processes at 3 threads each.- Tests for E (the E rows of the table). Run them.
scripts/channel_codec.py(B) while E runs. Pure:highband_stats(x) -> dict(D4 statistics, no filtering of its own);variant_grid();match_distance(var_stats, test_stats);codec_match(table, kaiser_row)(D5).main(): 100 inner clips × grid, 1,671 test files, writesoutputs/channel/codec_grid.csvandcodec_match.json.- Tests for B. Run them.
- Gate C. If B matches, message Nathan here with the time left and what C would cost (local MPS ~60 min extraction vs a vast box), and stop there for his answer. If B is negative, record C as not run.
- Report
docs/reports/2026-09-26_channel-robustness.md: A, E, B tables with sources, C and D dispositions, verdicts.docs/STATUS.mdrow,CLAUDE.mddisclosure bullet. Spec "Results" section filled. Doc-sync grep for anything this resolves (e.g. "M3 leakage probes" as open, "λ unmeasured"). - Regression:
uv run pytest -q,uv run ruff check .,uv run pytest -q tests/test_docs_consistency.py. - Commit only this rung's files (
git add <paths>), conventional prefix, no push. - Claude critique against
.claude/codex-audit-prompt.md(loop until Acceptable), then Codex audit (.claude/review-audit.sh docs/specs/2026-09-26_channel-robustness.md); record both in the spec; fix and re-commit. - Report to the oversight chat at each milestone (λ̂; E verdict; B verdict; done).
Time budget (from 12:35)
A code + tests 30 min, run 10 → λ̂ ~13:15. E code + tests 35 min, runs ~40 min in the background → ~14:30. B code + tests 25 min during E's run, run 10 → ~14:15. Report, docs, commit, audits 14:30–15:45. Slack 15 min.
Risks
- Domain classifier learns content, not channel (LJ is one speaker; ITW is celebrities): the per-feature positions and the grouped CV make it visible; the report states it.
- E runtime on CPU (three deep models, ~3,600 clips): capped by sampling; if it runs long, MLAAD drops to 300 clips and the report says so.
- Shared working tree: other lanes have uncommitted files; stage by path only.
- External drive: remounted 12:15; a dropout fails extraction loudly (resumable caches).