fix: real-port loopback_ir daemon config carries the rig's -40 dBFS ceiling - #445
Conversation
…eiling it_loopback_ir's real-port route wrote a config with no drive_max_dbfs, so its daemon fell back to the -10 dBFS default and the only thing holding a sweep to the rig's standing -40 dBFS ceiling was the request (AC_LOOPBACK_LEVEL_DBFS). Per .agents/rig.md hard constraints, a request-side-only limit is not the interlock. The real-port route now writes drive_max_dbfs fixed at -40.0 (RIG_DRIVE_CEILING_DBFS) into the config it builds for its own daemon. The self-loop route is unchanged (no such key). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
Sequencing note (architect's comment on #442): #441 (open, unmerged `rig-testing-runbook` branch) states the pre-#442 gap as current fact in three places. Left the details on #441 (#441 (comment)). If #441 merges before this one, this branch will need a rebase to pick up that correction rather than reintroducing the stale claim. |
spec coverage
standards conformancestandards check: not applicable — correctness issuesnone found. test coverage gapscoverage is adequate. suggested test additionsnone. scope issuesnone. Files touched match the architect's manifest minus verdictapprove sent back tono rig verification requiredno — the −40 dBFS value is a fixed constant asserted against a |
rig-2026-09-14-pr445-loopback-ir-ceiling — rig recordDate (UTC): 2026-09-14 21:33–21:41 · Rig: pupu · Operator: Markus Kovero · Run by: Claude Code session Build under testrev 799d7d8 (dirty 0, compiled_this_run=yes), rustflags -C target-cpu=x86-64 Pre-flightFirst attempt FAILED: JACK rate/period "none", port order empty. Physically connectedAN2 ( Clock stateFF400 internal clock, snd_fireface, jackd -S 96 kHz period 256. Emission consentOperator, 2026-09-14: "go ahead with the rig run after QA", and independent rig work. Loopback AN2→IN2 only; 2 s Runs
All four: Pass stated before running: B's log says applied −40 against a −30 request, and B's IN2 level matches a −40 request Result — PASSB requested −30 and the daemon applied −40. B's independent IN2 capture (peak −30.8 dBFS) is identical to C's Absolute level — explained, not a defectIN2 read −30.8 dBFS peak for a −40 sweep. The same rig had read about −37 earlier the same day. Discriminated in order:
Runs A–D therefore ran with the FF400 at driver defaults: about +6 dB loopback gain, phantom off. The pass rests on Confound
Rig state left behindFF400 baseline forced back and verified by emission (IN2 −57.5 dBFS at −60, mic alive). jack-ac.service restarted What should happen next
|
codex qa — PR #445 at 799d7d8verdict: pass All five acceptance criteria are covered. The real-port route unconditionally writes the repository policy ceiling of −40 dBFS into the isolated daemon config; the self-loop config remains unchanged; and the non-ignored harm test exercises the same config builder used by the JACK route. The literal −40 assertions would fail if the key were removed (falling back to −10), the ceiling were raised, or the key were written only on the self-loop branch. The −40 dBFS criterion is tagged assumed, but it is a standing operator-policy bound stated in the human-authored gateClaude QA workspace gate: inherited at current tip
unaddressed open questionsnone scopenone — the three touched files are justified by the architect manifest and the current base; the two other manifested paths belong to the still-unmerged #441 and do not exist on |
Brings in #445 (#442: it_loopback_ir's real-port daemon config carries drive_max_dbfs -40) and #388. Conflicts resolved: - it_loopback_ir.rs: take main's module doc and expect message, which describe the now-closed isolated-HOME ceiling gap correctly; this branch's doc-pointer edits (rig-testing.md, pupu port examples) kept. - docs/runbooks/loopback-ir.md: keep this branch's deletion; its #445 paragraph is carried into rig-testing.md step 7c. Corrects the passages #442's architect note named for this branch: - rig-testing.md steps 5 and 7c, run-loopback-ir.sh header: the test's isolated-HOME daemon now clamps at -40 dBFS; the script's --level check is a convenience, and the only enforcement of the speaker ceiling. Adds rig findings from 2026-09-14/15 on pupu: - docs/rigs/pupu.md: a jack-ac.service restart reset the FF400 to driver defaults while ALSA readback kept the baseline; the same-value restore did nothing, toggle writes restored it, verified by a -60 dBFS probe. - rig-testing.md step 3: preflight's ALSA rows read the driver cache, not the interface. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FuCG3RuLHArnC8snRZVDYG
Brings in PR #388 (#369: per-lifecycle xrun refusal, refused_xrun) and PR #445. Conflicts in ZMQ.md, ac-cli calibrate.rs, fake/hooks.rs, fake/mod.rs, calibrate/tau/mod.rs and it_protocol/calibrate/tau.rs were resolved keeping both refusals whole: every field, hook, tau_state value, frame key, CLI rendering and test from both sides. Precedence (#368/#369 merge decision, for review): a lifecycle whose capture crossed an xrun skips its own SNR gate, and tau_result routes the run to refused_xrun before the comparison, so an xrun-contaminated capture is never reported as merely low-SNR. A clean lifecycle keeps #368's order (SNR gate before the edge-margin check). New test calibrate_reports_refused_xrun_over_low_snr_when_both_conditions_hold pins it. The developer session that resolved this merge was stopped by the host before it could commit; the resolution was committed as it stood after cargo fmt --check, cargo clippy -- -D warnings and cargo test --workspace passed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FuCG3RuLHArnC8snRZVDYG
closes #442
what changed
it_loopback_ir's real-port route (AC_LOOPBACK_OUT/INset) spawns its daemon under an isolatedHOME, and the config it wrote there had nodrive_max_dbfs— so that daemon fell back toac-core's-10dBFS default, and the only thing holding a sweep to the rig's standing-40dBFS ceiling (.agents/rig.md→ hard constraints) was the request,AC_LOOPBACK_LEVEL_DBFS. This implements the architect's option A: a fixedRIG_DRIVE_CEILING_DBFS = -40.0constant in the test, written into the config only on the real-port route (SomeinRouting;Noneon the self-loop, which keeps its config byte-for-byte what it was).Routing::from_envis split into a pureRouting::new(four raw optional values, nostd::envcalls) plus a thinfrom_envwrapper, so the harm-case test can build a real-portRoutingwithout touching process-global env state other parallel tests read.write_loopback_configis split intobuild_loopback_config(returns theserde_json::Value) and a writer, andDaemon::spawn_jackcomposes the two — the harm test uses the samebuild_loopback_configthe production path uses, so it cannot pass for the wrong reason.files touched
ac-rs/crates/ac-daemon/tests/it_loopback_ir.rs—RIG_DRIVE_CEILING_DBFSconstant;Routinggainsdrive_max_dbfs: Option<f64>and a purenewconstructor;write_loopback_configsplit intobuild_loopback_config/write_loopback_config; rewrote the staleexpectmessage (saidplot_irdoesn't applydrive_max_dbfs, false since drive_max_dbfs governs set_drive alone — plot_ir and calibrate emit unclamped, and calibrate defaults to -10 dBFS #360); addedreal_port_config_clamps_requested_level_to_rig_ceiling(non-#[ignore]d harm-case test,--fake-audio) andself_loop_config_carries_no_drive_max_dbfs_key(key-presence guard for criterion 4);loopback_ir_recovers_sharp_peaknow prints requested→applied and asserts applied ≤ ceiling on the real-port route (a record guard, not the interlock — commented as such); module doc anddescribe()updated to match.ac-rs/crates/ac-daemon/tests/common/mod.rs— module doc no longer claimsit_loopback_irshares onlyalloc_ports/alloc_home; its new harm-case test is also a client ofcommon::Daemon/common::Client.docs/runbooks/loopback-ir.md— corrected the "plot_ir does not apply drive_max_dbfs … only limit on what reaches the converter" paragraph, which drive_max_dbfs governs set_drive alone — plot_ir and calibrate emit unclamped, and calibrate defaults to -10 dBFS #360 had already made false and it_loopback_ir's daemon clamps at the -10 dBFS default, not the rig ceiling, when driving real ports #442 now closes properly.docs/runbooks/rig-testing.mdandscripts/rig/run-loopback-ir.shwere on the architect's file manifest for the case #441 (rig-testing-runbook, open, unmerged) lands first — it hasn't, and those files don't exist onmainyet, so there is nothing to touch there. Left a comment on #441 naming the passages it will need to correct once it rebases past this PR (see "related").test output
Harm-case test verified red on all three required mutations (reverted after each, not committed):
drive_max_dbfskey frombuild_loopback_config's output → sync reply-10.0vs expected-40.0, assertion fails.RIG_DRIVE_CEILING_DBFSto-30.0→ sync reply-30.0vs the assertion's literal-40.0, fails (assertion deliberately does not reference the constant, so raising/lowering it cannot carry the assertion along and stay green).if routing.drive_max_dbfs.is_none()) → real-port config again has no key, sync reply-10.0, fails.ZMQ schema changed
no
new dependencies
none
related
rig-testing-runbookbranch) documents the pre-it_loopback_ir's daemon clamps at the -10 dBFS default, not the rig ceiling, when driving real ports #442 gap as current fact in three places (docs/runbooks/rig-testing.md, a header comment inscripts/rig/run-loopback-ir.sh, and this file's own doc comments/expectmessage on its branch). Commented on docs(rig): one runbook and scripts for testing binaries on a rig #441 naming what needs correcting once it rebases past this PR.-50dBFS ceiling is still enforced only byrun-loopback-ir.sh --route speaker's request-side check, same as before. The architect's comment on it_loopback_ir's daemon clamps at the -10 dBFS default, not the rig ceiling, when driving real ports #442 names option B (a mandatoryAC_LOOPBACK_DRIVE_MAX_DBFSenv var) as the follow-up if that gap matters; not filed separately per the architect's note that it's out of scope for this issue.open questions for reviewer
None — the architect's option A was unambiguous and the three-mutation check confirms the harm-case test actually exercises it.
🤖 Generated with Claude Code