Skip to content

fix(slice): follow the radio's nrl_level / nrs_level / anfl_level status. Principle I. - #6449

Merged
rfoust merged 2 commits into
mainfrom
fix/6447-dsp-level-status-keys
Oct 14, 2026
Merged

rfoust merged 2 commits into
mainfrom
fix/6447-dsp-level-status-keys

Conversation

@ten9876

@ten9876 ten9876 commented Oct 13, 2026 •

Copy link
Copy Markdown
Collaborator

Fixes #6447. Split out of the FlexLib v4.3.16 tracker #6437, §1 "DSP level status key names".

Summary

The radio reports the NRL, NRS and ANFL levels in slice status as nrl_level, nrs_level and anfl_level (FlexLib v4.3.16.42034 Slice.cs:3678, :3791, :2904). FlexBackend::decodeSliceStatus read only the command names (lms_nr_level, speex_nr_level, lms_anf_level), which never appear in status. So those three level controls never followed the radio: not at connect, not after a profile load, and not when another client changed them. The 4.2.20 status already used these names, so this predates 4.3.

Constitution principle honored

Principle I: the status keys come from FlexLib v4.3.16.42034 and are confirmed on the live radio. Principle II: radio status is the truth for the NRS level, as for every other DSP level, and no status path writes back.

Test plan

  • Local build passes (Qt 6.12, RelWithDebInfo)
  • aetherd_slice_decode_test: each status name and command-name fallback; precedence and zero for all three levels; malformed/overflowing primary values remain dropped even with valid aliases; valid primary values ignore malformed aliases.
  • receive_intent_routing_test::nrsReadbackIsPassive: after an operator NRS level of 72, a reported 50 is adopted and asks for nothing. It replaces the old profileRestore case, which pinned the removed heuristic.
  • Mutation check: restoring fallback-on-parse-failure makes the six malformed/overflow alias assertions fail. With absent-only guards restored, the decoder test passes.
  • Full local suite on original head 6aa6d28: 790/794 pass. The same 4 environmental failures (peripheral_auth_dialog_* ×3, connection_panel_size_test) reproduce on clean main here with Qt Keychain disabled; see feat(transmit): expose PTT Mic Override and document the v4.3 hardware-PTT default. Principle I. #6445.
  • Live, FLEX-8600 on 4.3.16.42034, through the agent automation bridge (the operator closed their own client first; receive-only DSP toggles, restored; TX never involved):
    • Slice 0 status at connect: nrl_level=100 nrs_level=50 anfl_level=100, with no lms_nr_level / speex_nr_level / lms_anf_level keys at all.
    • Before (a build of main without this change): switching NRL on shows the shared DSP level slider at 50, the model's compiled-in default.
    • After (this branch): the same slider reads 100 for NRL and 100 for ANFL, the radio's values.
    • Enabling sends only slice set 0 lms_nr=1 (no level), so the radio's levels were never written. A reconnect read back nrl=0 anfl=0, with levels unchanged at 100/50/100.

Agent automation bridge: gaps found

  • get slice exposes only nr / nrLevel among the leveled DSPs. Reading NRL/NRS/ANFL took toggling the feature on so the shared slider would appear, then finding an unnamed QSlider in dumpTree by geometry. Proposed: add each leveled DSP's on/level to the slice snapshot.
  • The shared DSP level slider has no objectName or accessibleName. Proposed: give it a name that follows the active target (for example "NRL level").

🤖 Generated with Claude Code

…tus. Principle I. (#6447)

The radio reports the NRL, NRS and ANFL levels in slice status as
nrl_level, nrs_level and anfl_level (FlexLib v4.3.16.42034 Slice.cs:3678,
3791, 2904). FlexBackend read only the command names (lms_nr_level,
speex_nr_level, lms_anf_level), which never appear in status, so those
three level controls never followed the radio. Confirmed live on a
FLEX-8600 on 4.3.16 in #6437; 4.2.20 status used the same names.

- Parse the status names; keep the command names as a fallback when the
  status name is absent.
- Remove the NRS profile-restore heuristic (#2849 / #2917). It re-sent the
  cached operator level whenever the radio reported 50, but had never run
  because no NRS level was ever parsed. Parsing nrs_level would have made
  it override any client setting 50, which on 4.3 is also the maximum, with
  no wire evidence behind it. SliceDspRequest loses the restore-only Origin
  with it.
- Tests: decode of each status name, the fallback, precedence and a
  malformed value; a reported NRS level is adopted passively.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

@aethersdr-agent aethersdr-agent Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Issue fit

Yes for the decoder half. #6447 asks for nrl_level / nrs_level / anfl_level to be parsed into the existing SliceDelta fields with the command names kept as a fallback, plus decoder tests for each status name, the fallback, and a malformed value. src/core/backends/flex/FlexBackend.cpp:1258-1267 does exactly that, and tests/aetherd_slice_decode_test.cpp:36-73 covers all four cases. nrf_level is correctly left alone — the live capture on #6437 says it is absent from 4.3 status.

The second half (removing the NRS profile-restore heuristic) is not in #6447's requirements — the issue body lists "the NRS 0..50 range and restore heuristic" under Out of scope, each its own §1 item. It was added to scope by your 23:28 comment on #6447, i.e. disclosed before the PR, and the PR body states it plainly. See the maintainer-decision section.

Scope

File What it changes Claimed in the body? Verdict
src/core/backends/flex/FlexBackend.cpp:1258-1267 Carries the three status key names, command names as fallback Yes In scope — this is #6447
tests/aetherd_slice_decode_test.cpp:36-73 Four new decoder cases Yes In scope
src/core/backends/ReceiveCommand.h:51-80 Deletes SliceDspRequest::Origin and its valid() clause Yes Scope addition, disclosed on #6447 before the PR
src/models/SliceModel.cpp:638-642, 1537-1545, 1943-1946, 2201-2206 + SliceModel.h:754-760 Deletes the restore heuristic and m_nrsLevelUser{,Override} Yes Same addition
src/core/backends/icom/IcomCivBackend.cpp:4530-4532 Drops the origin != Operator gate Yes Mechanical consequence of the enum removal; verified no-op (below)
src/models/RadioModel.cpp:9902-9918 Comment reflow; passes true for operatorOrigin Yes Mechanical consequence
tests/receive_intent_routing_test.cpp:420-432, 676 Replaces profileRestore() with nrsReadbackIsPassive() Yes Same addition

Nothing in the diff is unexplained — no build/CI churn, no formatting-only files, no CHANGELOG.md entry (correct), no new public or protocol surface (nrl_level/nrs_level/anfl_level are keys the radio already sends; nothing new goes on the wire). The template self-certification holds.

Socket tests: none added, modified or removed. Both touched tests link Qt6::Core / Qt6::Test only (tests/tests.cmake:590-593, 6706-6710) and neither source contains a socket, bind, listen or fake peer.

Blockers

None. I could not construct an input, ordering or lifecycle event that makes the decoder hunk wrong, and the removal is canon-aligned rather than a regression (reasoning below).

Needs maintainer decision

  1. src/core/backends/ReceiveCommand.h:51 + src/models/SliceModel.cpp:1943 — the heuristic removal is correct under canon but the recorded ruling is self-authored. The triage on #6447 offered (a) fold in the sibling §1 range item or (b) "land the key-name fix with the heuristic explicitly gated off", and recommended (b). The PR does neither verbatim — it deletes. I think deletion is the better of the three: Principle II (CONSTITUTION.md:84-87, "radio status is read as truth, client commands are only requests") makes a status-path write-back a wrong model, and a gated-off wrong model still sits in the tree waiting to be re-enabled. I also confirmed your "never ran in production" claim independently — pre-PR, SliceDelta::nrsLevel had exactly one writer, the speex_nr_level carry (grep over src), and the live capture shows that key absent from status, so the v == 50 branch was unreachable from the wire. What a human should ratify is only that the "maintainer's call, recorded on #6447" is your own comment, not an independent ruling, and that this PR is where the (a)/(b) choice gets settled.

  2. src/models/SliceModel.cpp:1943-1946 — #2849 is closed as fixed, and this PR's own body says the fix never ran. The deleted comment named the symptom:

    The radio's profile global snapshot does not persist speex_nr_level. On recall the firmware reports its default of 50 even when the user previously set a different value.

    What now prevents that symptom: nothing — and under Principle II that is right, because the client should show what the radio reports. But #2849 ("NRS setting value not persistent goes to 50 each time", reproduced on a 8400/4.2) is closed against PR #2917, whose fix this PR deletes as never-effective. "If a profile global recall really does reset NRS, that's a separate issue to file with a capture" leaves a closed-but-unfixed issue in the tracker. Please reopen #2849 or file the successor and link it from this PR body now rather than conditionally, so the bookkeeping does not depend on the original reporter noticing again. This is the one thing I'd want landed alongside the merge.

Nits (non-blocking)

  • src/core/backends/flex/FlexBackend.cpp:1261-1267 — radio-reported levels land unclamped, and this PR is what makes the path live. Flagged by security-audit (CodeGuard, codeguard-0-input-validation-injection) and confirmed: carry() on std::optional<int> is ok-guarded but not range-guarded (FlexKvCarry.h:50-53), and SliceModel::applyChanges writes the value straight through — src/models/SliceModel.cpp:1944: if (m_nrsLevel != v) { m_nrsLevel = v; emit nrsLevelChanged(v); }. Every operator-side setter clamps (SliceModel.cpp:629-663, std::clamp(v, 0, 100)) and the transmit decoder uses carryClamp(..., 0, 100) for exactly this reason. A garbled or hostile nrs_level=99999 reaches the model: harmless in the slider (QSlider::setValue self-clamps) but the label at VfoWidget.cpp:4808 prints the raw v, and RadioModel.cpp:13159-13163 serialises it into the automation snapshot. Pre-existing for nr_level / anf_level / nb_level, which is why I'm filing it as a nit rather than a blocker — but if you want the decoder to match the "Flex slice validation boundary" its own comment at FlexBackend.cpp:1190 claims, the six carries are a one-word change:
        carryClamp(kvs, "nrl_level", d.nrlLevel, 0, 100);
        if (!d.nrlLevel) carryClamp(kvs, "lms_nr_level", d.nrlLevel, 0, 100);
        carryClamp(kvs, "nrs_level", d.nrsLevel, 0, 100);
        if (!d.nrsLevel) carryClamp(kvs, "speex_nr_level", d.nrsLevel, 0, 100);
        carry(kvs, "nrf_level", d.nrfLevel);
        carryClamp(kvs, "anfl_level", d.anflLevel, 0, 100);
        if (!d.anflLevel) carryClamp(kvs, "lms_anf_level", d.anflLevel, 0, 100);
  • tests/receive_intent_routing_test.cpp:420-432 — the commandReady spy went away with the old case. profileRestore() asserted raw.isEmpty(), i.e. that no path emits raw wire text. nrsReadbackIsPassive() keeps the receiveDspRequested count but drops that spy. Since "no status-path write-back on NRS" is precisely the property this PR establishes, consider re-adding QSignalSpy raw(&slice, &SliceModel::commandReady); … check(raw.isEmpty(), …) so a future reintroduction is caught. (Lines 179, 208 and 263 cover other paths, not this one.)
  • src/models/SliceModel.cpp:638 — the 0..100 clamp is unchanged while 4.3's NRS maximum is 50. Explicitly out of scope per #6447 and disclosed in the body; noting only that the user-visible consequence changes shape now that the radio's value is adopted — drag to 100, the radio holds 50, and the next status re-reports 50.

What I tried to break

  • Status → signal → slider → setter → wire echo loop. Parsing these three keys fires nrlLevelChanged / nrsLevelChanged / anflLevelChanged from the status path for the first time, and all three feed the shared DSP level slider. VfoWidget.cpp:4803-4818 wraps setValue in a QSignalBlocker, and the slider's own handler additionally bails on m_updatingFromModel (:2109). No round-trip to the wire. Consistent with your capture showing slice set 0 lms_nr=1 with no level write.
  • std::optional truthiness on a legitimate zero. if (!d.nrlLevel) carry(...) on a level of 0: operator bool is has_value(), so a reported nrl_level=0 is not overwritten by the fallback. Checked all three.
  • The Icom gate removal. A ProfileRestore request could only ever be Feature::Nrs, and IcomCivBackend::requestSliceDsp's switch has no Nrs case — default: return ReceiveDispatch::Unsupported (IcomCivBackend.cpp:4545). Dropping the gate is a true no-op. RadioModel.cpp:9917's literal true matches the header default (RadioModel.h:1811); the Kiwi-suppression path at :9926 still computes its own operatorOrigin from SliceAudioRequest::Origin, so the reflowed comment stays accurate.
  • Orphaned state after the deletion. No surviving reference to ProfileRestore, SliceDspRequest::Origin or m_nrsLevelUser* anywhere in src or tests; the sibling Origin enums on SliceAudioRequest / SliceControlRequest / SliceAgcRequest / SliceFilterRequest are untouched and still read. m_dspIntentRevisions is still consumed (SliceModel.h:632, SliceModel.cpp:2208), so the removal did not leave it write-only.
  • Whether the tests discriminate. Against unfixed code, decode blocks 1 and 3 fail (no status-name carries) and nrsReadbackIsPassive fails on both assertions (2 requests, level 72). That is 4 failures, matching your mutation check exactly. Neither test is in any ctest -R filter in ci.yml, so the 8 green checks on 6aa6d28 prove compile + the static lane, not these cases — coverage here rests on your local run, which is internally coherent.
  • The body's bridge-gap claim. Verified rather than taken: sliceSnapshot (AutomationServer.cpp:1561+) emits only nr / nrLevel / anf among the leveled DSPs. The fuller dsp object with lms_nr / speex_nr / lms_anf levels lives in the resource dump (RadioModel.cpp:13146-13164), a different surface — so the claim holds and the QSlider-by-geometry workaround was genuinely necessary.

Everything above is reasoned from the head checkout at 6aa6d28; I am headless, so I built nothing, ran no test, and drove no GUI. The live-radio evidence in the body is the only runtime evidence either of us has, and I cannot independently confirm it.

CodeGuard: all 40 listed hits are outside the changed ranges — the FlexBackend.cpp hunk is 1255-1270 while the flags sit at 367-1623 with the SSRF-HEX cluster at 1429-1472 — so this is a whole-file lexical scan, not a diff scan. security-audit refuted each family with evidence: zero QProcess/system/popen/exec* matches across all 14 files (the CMDI-* hits are C++ operators and Markdown backticks inside comments, e.g. `using namespace flexkv` at FlexBackend.cpp:1195), zero QUrl matches (no SSRF sink for the hex protocol constants), and the CS-SEC-PASSWORD hits are the Icom keychain path, untouched here. Its one surviving lead is the unclamped-level nit above.

Recommendation

Approve with nits, contingent on the #2849 bookkeeping. The decoder fix is the right shape, matches the issue's protocol table, keeps the command names as a fallback, and has tests that genuinely fail without it. The heuristic deletion is a disclosed scope addition that I believe is more canon-correct than the gating the triage proposed — but it is the author's own recorded ruling, so a human should sign off on it here. Concrete next step: reopen #2849 (or file the successor) and link it from the PR body, then optionally take the carryClamp one-liner. Nice work on the live before/after capture and the mutation check — they made this reviewable in a way most protocol PRs aren't.


🤖 aethersdr-agent · cost: $9.2734 · model: claude-opus-5

@aethersdr-agent

Copy link
Copy Markdown
Contributor

Cisco CodeGuard — static analysis of this PR (299 finding(s))

  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/core/backends/RadioCapabilities.h /tmp/aetherclaude/pr-6449/src/core/backends/RadioCapabilities.h:108
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/core/backends/RadioCapabilities.h /tmp/aetherclaude/pr-6449/src/core/backends/RadioCapabilities.h:255
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/core/backends/RadioCapabilities.h /tmp/aetherclaude/pr-6449/src/core/backends/RadioCapabilities.h:390
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/core/backends/RadioCapabilities.h /tmp/aetherclaude/pr-6449/src/core/backends/RadioCapabilities.h:525
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/core/backends/RadioCapabilities.h /tmp/aetherclaude/pr-6449/src/core/backends/RadioCapabilities.h:548
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/core/backends/RadioCapabilities.h /tmp/aetherclaude/pr-6449/src/core/backends/RadioCapabilities.h:611
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/core/backends/RadioCapabilities.h /tmp/aetherclaude/pr-6449/src/core/backends/RadioCapabilities.h:648
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/core/backends/RadioCapabilities.h /tmp/aetherclaude/pr-6449/src/core/backends/RadioCapabilities.h:711
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/core/backends/RadioCapabilities.h /tmp/aetherclaude/pr-6449/src/core/backends/RadioCapabilities.h:788
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/core/backends/RadioCapabilities.h /tmp/aetherclaude/pr-6449/src/core/backends/RadioCapabilities.h:850
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/core/backends/RadioCapabilities.h /tmp/aetherclaude/pr-6449/src/core/backends/RadioCapabilities.h:908
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/core/backends/anan/AnanBackend.cpp /tmp/aetherclaude/pr-6449/src/core/backends/anan/AnanBackend.cpp:1067
  • [MEDIUM] CS-VLN-SSRF-HEX — Vulnerability: CS-VLN-SSRF-HEX in src/core/backends/flex/FlexBackend.cpp /tmp/aetherclaude/pr-6449/src/core/backends/flex/FlexBackend.cpp:1429
  • [MEDIUM] CS-VLN-SSRF-HEX — Vulnerability: CS-VLN-SSRF-HEX in src/core/backends/flex/FlexBackend.cpp /tmp/aetherclaude/pr-6449/src/core/backends/flex/FlexBackend.cpp:1464
  • [MEDIUM] CS-VLN-SSRF-HEX — Vulnerability: CS-VLN-SSRF-HEX in src/core/backends/flex/FlexBackend.cpp /tmp/aetherclaude/pr-6449/src/core/backends/flex/FlexBackend.cpp:1466
  • [MEDIUM] CS-VLN-SSRF-HEX — Vulnerability: CS-VLN-SSRF-HEX in src/core/backends/flex/FlexBackend.cpp /tmp/aetherclaude/pr-6449/src/core/backends/flex/FlexBackend.cpp:1472
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/core/backends/flex/FlexBackend.cpp /tmp/aetherclaude/pr-6449/src/core/backends/flex/FlexBackend.cpp:367
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/core/backends/flex/FlexBackend.cpp /tmp/aetherclaude/pr-6449/src/core/backends/flex/FlexBackend.cpp:496
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/core/backends/flex/FlexBackend.cpp /tmp/aetherclaude/pr-6449/src/core/backends/flex/FlexBackend.cpp:547
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/core/backends/flex/FlexBackend.cpp /tmp/aetherclaude/pr-6449/src/core/backends/flex/FlexBackend.cpp:777
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/core/backends/flex/FlexBackend.cpp /tmp/aetherclaude/pr-6449/src/core/backends/flex/FlexBackend.cpp:782
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/core/backends/flex/FlexBackend.cpp /tmp/aetherclaude/pr-6449/src/core/backends/flex/FlexBackend.cpp:805
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/core/backends/flex/FlexBackend.cpp /tmp/aetherclaude/pr-6449/src/core/backends/flex/FlexBackend.cpp:1195
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/core/backends/flex/FlexBackend.cpp /tmp/aetherclaude/pr-6449/src/core/backends/flex/FlexBackend.cpp:1550
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/core/backends/flex/FlexBackend.cpp /tmp/aetherclaude/pr-6449/src/core/backends/flex/FlexBackend.cpp:1623
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/core/backends/hl2/Hl2Backend.cpp /tmp/aetherclaude/pr-6449/src/core/backends/hl2/Hl2Backend.cpp:178
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/core/backends/hl2/Hl2Backend.cpp /tmp/aetherclaude/pr-6449/src/core/backends/hl2/Hl2Backend.cpp:257
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/core/backends/hl2/Hl2Backend.cpp /tmp/aetherclaude/pr-6449/src/core/backends/hl2/Hl2Backend.cpp:593
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/core/backends/hl2/Hl2Backend.cpp /tmp/aetherclaude/pr-6449/src/core/backends/hl2/Hl2Backend.cpp:699
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/core/backends/hl2/Hl2Backend.cpp /tmp/aetherclaude/pr-6449/src/core/backends/hl2/Hl2Backend.cpp:1949
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/core/backends/hl2/Hl2Backend.cpp /tmp/aetherclaude/pr-6449/src/core/backends/hl2/Hl2Backend.cpp:2039
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/core/backends/hl2/Hl2Backend.cpp /tmp/aetherclaude/pr-6449/src/core/backends/hl2/Hl2Backend.cpp:2295
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/core/backends/hl2/Hl2Backend.cpp /tmp/aetherclaude/pr-6449/src/core/backends/hl2/Hl2Backend.cpp:2455
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/core/backends/hl2/Hl2Backend.cpp /tmp/aetherclaude/pr-6449/src/core/backends/hl2/Hl2Backend.cpp:2567
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/core/backends/hl2/Hl2Backend.cpp /tmp/aetherclaude/pr-6449/src/core/backends/hl2/Hl2Backend.cpp:3159
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/core/backends/hl2/Hl2Backend.cpp /tmp/aetherclaude/pr-6449/src/core/backends/hl2/Hl2Backend.cpp:4082
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/core/backends/hl2/Hl2Backend.cpp /tmp/aetherclaude/pr-6449/src/core/backends/hl2/Hl2Backend.cpp:4181
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/core/backends/hl2/Hl2Backend.cpp /tmp/aetherclaude/pr-6449/src/core/backends/hl2/Hl2Backend.cpp:4499
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/core/backends/hl2/Hl2Backend.cpp /tmp/aetherclaude/pr-6449/src/core/backends/hl2/Hl2Backend.cpp:4921
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/core/backends/hl2/Hl2Backend.cpp /tmp/aetherclaude/pr-6449/src/core/backends/hl2/Hl2Backend.cpp:5488

Automated static scan by Cisco DefenseClaw CodeGuard on the changed files. Advisory — some may be false positives; the review above verifies them.


🤖 aethersdr-agent · cost: $9.4074 · model: claude-opus-5

@ten9876 ten9876 left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Issue fit: yes. Approve with nits. Reviewed 6aa6d28e4 against merge base 7d82707eb. The three status-name carries fix #6447's missing NRL/NRS/ANFL readback. Removing NRS cached-level restoration prevents this decoder fix from activating unsolicited status-path writes. That removal is explicitly authorized in the maintainer's scope comment; it is not an unapproved preference change. The NRS range and NRF items remain outside this PR as specified.

Scope

File/group Change Claimed in body? Verdict
src/core/backends/flex/FlexBackend.cpp Parse three status names, preserve aliases Yes In scope
src/models/SliceModel.{h,cpp} Remove cached NRS level and status-path restoration Yes; issue thread authorizes it In scope
src/core/backends/ReceiveCommand.h Remove restore-only origin and validation Yes Consequence of heuristic removal
src/core/backends/icom/IcomCivBackend.cpp, src/models/RadioModel.cpp Remove obsolete origin checks and refusal classification Yes Mechanical consequence; Icom still rejects NRS
tests/aetherd_slice_decode_test.cpp Status, alias, precedence and malformed decoding Yes In scope
tests/receive_intent_routing_test.cpp Replace restoration expectations with passive readback Yes In scope

All eight changed files are explained by the issue/thread and body. The single commit's author date fits the work; no unrelated default, visual, build/CI, dependency, persistence or public daemon surface changes. No CHANGELOG or stable-doc edits. Both modified tests are socket-free: the WAN fixtures only inject connectivity and never dial a peer. No test-boundary notification/approval was needed.

Blockers

None confirmed.

Nits — explicitly non-blocking

  1. Absent-only fallback is not what the code implements — src/core/backends/flex/FlexBackend.cpp:1262,1264,1267. The body says aliases are used when the status name is absent. These conditions instead use an alias when the primary name fails parsing. Production-decoder probes for all three features returned:

    REVIEW primary=nrl_level malformed+alias engaged=1 value=13
    REVIEW primary=nrs_level malformed+alias engaged=1 value=13
    REVIEW primary=anfl_level malformed+alias engaged=1 value=13
    

    Each input had the primary set to junk and its alias set to 13. The alias is valid input, and no real-firmware failure from this combination was established, so this is a contract/test nit, not a claimed user regression. Either disclose fallback-after-invalid and add mixed malformed/valid precedence tests, or make the promised absent-only policy explicit. The latter replaces the three added conditions with:

        if (!kvs.contains(QStringLiteral("nrl_level"))) {
            carry(kvs, "lms_nr_level", d.nrlLevel);
        }
        if (!kvs.contains(QStringLiteral("nrs_level"))) {
            carry(kvs, "speex_nr_level", d.nrsLevel);
        }
        if (!kvs.contains(QStringLiteral("anfl_level"))) {
            carry(kvs, "lms_anf_level", d.anflLevel);
        }

    If retaining the current policy, still brace the conditions: AGENTS.md says “Braces on all control flow.”

  2. Add the matching user-doc update — docs/user/docs/dsp-noise-mitigation.md:40 (untouched). The changed amount controls now follow radio reports, and NRS adopts profile/other-client values instead of attempting cached restoration. AGENTS.md's user-documentation workflow says a change to what the user sees updates the matching page “in the same PR, never in a follow-up.” Add a short current-behavior explanation to the radio-side DSP section, for example:

    The NRL, NRS and ANFL amount controls show the radio's reported levels. Profile recalls and changes made by another client update the displayed values. AetherSDR does not restore a previously chosen NRS amount over the radio's readback.

What I tried to break

  • Built the named AetherSDR, aetherd_slice_decode_test, receive_intent_routing_test, and backend_receive_contract_test targets in my owned scratch worktree (Qt 6.12, Linux, shared core, -O2 -g1). All three focused CTests passed. Restoring the exact merge-base decoder yielded four failures in the PR's new decoder assertions. A separate mutation retaining the old model value on reported NRS=50 failed the passive-readback assertion. Restored head sources and reran all three successfully. No full-suite run; the body's 790/794 claim was not independently reproduced.
  • Added temporary socket-free decoder probes: a primary zero beat alias 99 for all three features; integer overflow was dropped; mixed malformed-primary/valid-alias behavior is recorded above. The original malformed-without-alias case passes. Removed all probes/mutations afterward; scratch sources are clean.
  • Checked FlexLib v4.3.16 setters and parsers directly: the cited command/status spelling split is real, and integer parse failure is rejected. Status parsing in FlexLib itself accepts successfully parsed integers without clamping. I have not promoted the explicitly excluded NRS range work or the bot's range advice into a new blocker here.
  • Audited the deleted restoration's remaining callers, lifetime/reentrancy guards, and sibling dispatch. No surviving SliceDspRequest::Origin, ProfileRestore, or m_nrsLevelUser* references. Icom's switch still returns Unsupported for NRS. Readback signals reach the existing signal-blocked slider updates, so the changed model path does not inherently feed a command back to the radio.
  • Drove the clean PR-head GUI through /tmp/aethersdr-pr6449-review, isolated settings, offscreen, TX disabled: connect list → explicit DEMO-0001 → connect wait → get radio/get slice → tune 14.225 → readback frequency:14.225 → disconnect → confirmed connected:false, transmitting:false. Closed the instance. The ordinary demo lacks these firmware DSP capabilities and its slice snapshot omits their levels, so this session checks receive lifecycle only; it does not independently prove the body's live before/after NRL/ANFL slider or profile/MultiFlex convergence claims. Those require the reported hardware evidence; I did not connect to hardware. The “never ran in production” historical claim also exceeds what a current source audit alone can establish.

Governance, bot review and CI

All four boundary gates pass on merge base and head, with identical per-file findings: 102 tracked engine warnings, 297 command-plane sites, 70 capability booleans. The touchpoint manifest is unchanged. No #5554 additions or enforcement weakening found.

The prior review's demand for a separate ruling because the scope comment is by the author is not a governance defect: GOVERNANCE.md names that author, @ten9876, as the Project Maintainer with final scope authority. Independent CODEOWNERS approval remains required for merging, as usual. #2849's profile-persistence diagnosis was asserted without a capture; deleting its non-authoritative heuristic does not establish that a firmware persistence bug remains. A new wire capture can settle that separately; I am not making tracker reopening a merge condition without that evidence.

No invocable code-review skill was available. I performed the manual pass and an independent parallel audit. The CodeGuard comment's backtick/hex alarms are outside this diff's changed ranges; spot-checks identify comment/protocol constants rather than shell or URL execution introduced by this PR. No verified security finding from that scan is retained.

All eight reported CI checks passed. Earliest head run creation: 2026-10-13T23:57:28Z (CI/Static pull-request runs: 23:57:30Z). Current main 709093e2 was committed at 23:37:44Z, before those runs, so the stale-merge-snapshot concern did not apply at review completion. The focused CTests above were run locally; the green per-PR checks do not establish a full-suite pass.

Recommendation: Approve with nits. Align or disclose/test the fallback policy and add the matching user documentation. The key-name fix and passive NRS readback survived focused tests and mutation checks; hardware convergence remains supported by the author's evidence rather than my reproduction.

@rfoust rfoust self-assigned this Oct 14, 2026
Keep a malformed or overflowing primary status value dropped even when a command-name alias is valid. Cover precedence, zero, invalid primaries and invalid aliases for all three DSP levels. Document radio-authoritative amount readback and passive NRS reconciliation.

@rfoust rfoust left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Issue fit: yes. Approve. The status-name decoder fixes #6447, and the maintainer-authorized removal of cached NRS restoration keeps radio readback passive. The follow-up commit e0b80845a resolves both review nits: command-name aliases apply only when their primary status key is absent, and the user guide describes radio-authoritative amount readback.

Scope

File/group Change Claimed in body? Verdict
src/core/backends/flex/FlexBackend.cpp Parse the three radio status names; use aliases only for absent primary keys Yes In scope
src/models/SliceModel.{h,cpp} Remove cached NRS restoration and unsolicited writeback Yes; maintainer scope comment on #6447 In scope
src/core/backends/ReceiveCommand.h, src/core/backends/icom/IcomCivBackend.cpp, src/models/RadioModel.cpp Remove restore-only origin and obsolete dispatch handling Yes Necessary cleanup
tests/aetherd_slice_decode_test.cpp, tests/receive_intent_routing_test.cpp Decoder edge cases and passive NRS readback Yes In scope
docs/user/docs/dsp-noise-mitigation.md Describe amount readback after profile/other-client changes Yes Required matching documentation

All nine changed files are explained by the issue, its authorized scope addition, and review remediation. No unrelated defaults, visual design, dependencies, persistence keys, protocol verbs, or CHANGELOG edits.

Blockers

None confirmed.

Nits

Both prior nits are resolved. No remaining actionable nits from this pass.

Verification and attacks

  • Built the three focused ARM64 targets with Qt 6.12, RelWithDebInfo, the prescribed ARM64 toolchain, and RADE enabled. All three CTests passed: aetherd_slice_decode_test, receive_intent_routing_test, and backend_receive_contract_test. Tests are socket-free; the WAN fixtures inject connectivity without dialing a peer.
  • Tested the constructed merge of head e0b80845ab8f788ae85b1fe8af856ed20e4c1e93 with main 9fdb616d4ed63a523e2ce730f76ef20cfc78a9e4, tree f7e81a86b8ff9f8e108fed4fd779b7dd7d804691. No conflicts. The final restored tree matches that snapshot exactly.
  • Malformed and overflowing primary values remain dropped even alongside valid aliases, for all three features. Zero and valid-primary precedence survive; absent-primary aliases still work. Mutation back to fallback-on-parse-failure produced six assertion failures. Restored the guards and reran all three CTests successfully.
  • npm ci and npm run build passed. Engine boundary, touchpoint manifest, capability-record, command-plane, generated-reference and log-rule checks passed; no boundary growth.
  • The preceding review independently checked the official FlexLib 4.3.16 parser/setter spellings and mutation-checked the original decoder and passive-NRS fixes. Its isolated, offscreen, TX-disabled simulator session read frequency 14.245, then connected:false and transmitting:false after injected disconnect; the owned instance was closed. That lifecycle session used the pre-follow-up source. This decoder/doc follow-up was verified through the production socket-free seam; no additional GUI run or hardware connection was performed.
  • Flex hardware/profile/Multi-Flex convergence and the author's original full-suite count remain supported by the author's evidence, not an independent reproduction. The simulator lacks these firmware DSP controls. NRS range and NRF work remain explicitly outside this PR.

Fresh static, CodeQL, and docs checks are green; the three platform build checks are still running at approval time. Squash merge remains subject to the required checks.

@rfoust
rfoust enabled auto-merge (squash) October 14, 2026 04:16
@rfoust
rfoust merged commit 704bf82 into main Oct 14, 2026
10 checks passed
@rfoust
rfoust deleted the fix/6447-dsp-level-status-keys branch October 14, 2026 04:39
@rfoust rfoust removed their assignment Oct 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

DSP levels: parse the radio's nrl_level / anfl_level / nrs_level status keys

2 participants