Repository navigation
fix(radio): route receive intents through RadioModel::isConnected(), not the backend's own LAN-only link - #6125
Conversation
…not the backend's own LAN-only link Fixes aethersdr#6121. Every slice-level receive intent (tune, mode, filter, AGC) has been silently dropped for the entire lifetime of any WAN/SmartLink session since PR aethersdr#5907 landed (142f1d6). RadioModel::receiveCommandSource() gated dispatch on m_backend->isConnected(), which for Flex reports only on FlexBackend's own internally-owned, LAN-only RadioConnection. RadioModel::connectViaWan() never dials that object -- a WAN session is driven entirely through the separate m_wanConn, bypassing the backend abstraction -- so the gate reported "not connected" for the whole session even though the radio was genuinely connected, and silently dropped every intent with no warning. - Routes receiveCommandSource()'s connectivity check through RadioModel::isConnected(), which already accounts for both m_connection (LAN) and m_wanConn (WAN), instead of m_backend->isConnected() alone. - Fixes a latent bug in isConnected() itself, found during triage: the m_wanConn check was nested inside the m_connection branch, making it unreachable once a non-Flex family switch left m_connection null. Hoisted so m_wanConn is checked unconditionally. - Makes the drop loud: a connectivity failure at this gate now emits the same qCWarning + commandDropped() shape sendCmd()'s hasCommandPlane() guard already uses for the equivalent dead-control case, so this class of bug surfaces in the log instead of requiring a bisect next time. - Adds a regression test (tests/receive_intent_routing_test.cpp, wanFallback()) that reproduces the exact shape: a real WanConnection reporting connected while the backend's own link reports disconnected. The fixture never calls connectToRadio(), so no socket is opened -- only the connection's already-private m_connected flag is set directly, through a new test-only friend (WanConnectionTestAccess) matching the existing *TestAccess pattern in this file. Root cause, bisection, and the diagnosis this PR implements are in aethersdr#6121 and the triage comment on it. Principle VIII -- Evidence Over Assertion. The root cause was confirmed by building and testing both sides of PR aethersdr#5907's merge commit directly, with aether.smartlink debug logging enabled for ground truth (not inference from its absence, which cost real time during triage): zero slice tune commands at 142f1d6, correct dispatch at its direct parent e48a242, same radio, same WAN session. The new test reproduces the same shape without a live radio. Test plan: - Local build passes (cmake --build build -j4, macOS arm64, Qt 6.12) - receive_intent_routing_test passes, including the new wanFallback() case (and fails without the RadioModel.cpp fix, confirmed before adding it) - Full ctest suite: 620/621. The one failure, hl2_backend_seam_test, is pre-existing on current upstream/main -- introduced by aethersdr#6104, touches HL2 TX-power/TUNE logic this diff never touches, and reproduces identically with this diff reverted. - Reverified on two physical radios post-fix, confirmed by log in both cases (slice tune commands dispatched matching every TunePolicy event): FLEX-6500 firmware 3.9.18 over WAN/SmartLink (the originally broken configuration), and a separate FLEX-6500 firmware 4.2.20 over LAN. No crossover tested (3.9.18/LAN and 4.2.20/WAN were not available). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
K5PTB
left a comment
There was a problem hiding this comment.
Review: PR #6125 (independent pass via .claude/skills/pr-review)
Verdict: Request changes (posted as a comment). There are 2 blockers, each with a verified drop-in fix below.
Issue fit. #6121 reports that every slice receive intent (tune/mode/filter/AGC) is silently dropped over WAN/SmartLink because receiveCommandSource() gates on m_backend->isConnected(). For Flex, that call only checks FlexBackend's own LAN RadioConnection, which connectViaWan() never dials. The core fix is correct and solves the issue: src/models/RadioModel.cpp:9801 now gates on RadioModel::isConnected(), which sees m_wanConn. Both blockers are in the parts added around that fix: the new "loud drop" and the regression test.
Scope
| File / hunk | Claimed in body? | Verdict |
|---|---|---|
RadioModel.cpp:9801 gate → RadioModel::isConnected() |
Yes | In scope. This is the fix for #6121. |
RadioModel.cpp:2691-2707 isConnected() hoist |
Yes (requested in the #6121 triage) | In scope. It also changes one more case than the body describes (nit 1). |
RadioModel.cpp:9801-9808 qCWarning + commandDropped |
Yes (requested in triage) | In scope, but the commandDropped half is blocker 1. |
RadioModel.h:1732 / .cpp:9777 drop const |
No | Mechanical. Only needed for the emit, and goes away with the blocker 1 fix. |
WanConnection.h:101 test friend |
Yes | In scope. Matches the existing *TestAccess pattern. |
tests/receive_intent_routing_test.cpp |
Yes | In scope, but blocker 2. |
Everything in the diff is explained by the issue. There's no new public or protocol surface, no settings keys and no CHANGELOG.md edit. The body's checklist holds: commit signature verified (verified: true), and no flat keys or CHANGELOG.
Blockers
1. Tuning while disconnected now tells the operator "This radio doesn't support that control." Reproduced in the app.
src/models/RadioModel.cpp:9805-9806 emits commandDropped whenever the radio isn't connected. That signal's only consumer is src/gui/MainWindow_Session.cpp:781-782 → showUnsupportedControlNotice() (:2918-2921). It was built for the no-command-plane case (HL2/Icom), per the comment at :769-772. Slices stay active between a disconnect and the next reconnect (m_stagingReceiveModels is only set inside stageSessionModelsForReconnect(), :6879), so an ordinary tune on a disconnected session now gives a false "unsupported control" message. The body says this is "the same shape" as sendCmd()'s hasCommandPlane() guard. The logging shape is the same, but the cause is different, and the UI message is wrong for this cause.
Bridge evidence: PR head, offscreen, isolated settings, DEMO-0001 only:
connect local serial DEMO-0001 / connect wait → connected, serial DEMO-0001
(after connect) statusMessage: ""
disconnect → connected:false, slices still [(0, 14.1)]
(after disconnect) statusMessage: ""
tune 14.250 → {"ok":true,"sliceId":0,"tune":14.25}
(after tune) statusMessage: "This radio doesn't support that control — nothing was sent to the radio. Further unsupported controls are logged."
log tail → "RadioModel: not connected, dropping slice 0 receive intent" (exactly one, after the tune)
Fix: keep the warning, which is what makes this class of bug visible in a log, and drop the emit. This also lets receiveCommandSource() go back to const (RadioModel.h:1732, RadioModel.cpp:9777).
if (!isConnected()) {
qCWarning(lcProtocol).noquote()
<< "RadioModel: not connected, dropping slice" << source->sliceId()
<< "receive intent";
return nullptr;
} SliceModel* receiveCommandSource() const;If a UI notice for disconnected drops is wanted, it needs its own signal and wording. That would be a separate change.
2. The regression test doesn't exercise the #6121 shape, and a mutant that brings the bug back passes it.
Fixture installs the stub with setBackendForTest() (tests/receive_intent_routing_test.cpp:128), which tears down without aliasing a RadioConnection. So in wanFallback() (:344), m_connection is null. The test only reaches the hoisted m_wanConn branch, the non-Flex shape the body itself calls latent. In production #6121, m_connection is FlexBackend's own RadioConnection: it exists, but it's down. The body's "reproduces the exact shape" claim doesn't hold, and the #6121 triage asked specifically for "a RadioModel whose vended m_connection reports disconnected while a WAN connection reports connected".
Mutation results (rebuilt from a clean git checkout of RadioModel.cpp each time):
RadioModel.cpp mutant |
PR test result |
|---|---|
revert the gate to m_backend->isConnected() |
FAIL (good) |
| revert the hoist to the old nesting | FAIL. The test depends on the null-m_connection branch. |
if (m_connection) return m_connection->isConnected(); before the WAN check. This brings #6121 back for every Flex WAN session. |
PASS. The regression is not pinned. |
A socket-free seam for the real shape already exists: rebuildBackendForTest("flex") builds a real FlexBackend whose RadioConnection is never dialed. tx_operation_integration_test.cpp:1887 already uses it. Drop-in, added after wanFallback() and called from main() after wanFallback();:
void wanOverUndialedFlexConnection()
{
RadioModel radio;
check(radio.rebuildBackendForTest(QStringLiteral("flex")), "RadioModel builds a FlexBackend");
WanConnection wan;
RadioModelSliceLifecycleTestAccess::setWanConnection(radio, &wan);
WanConnectionTestAccess::setConnected(wan, true);
check(radio.backend() && !radio.backend()->isConnected(), "the FlexBackend's own link is down");
check(radio.isConnected(), "a live WAN link connects the model despite the undialed LAN link");
WanConnectionTestAccess::setConnected(wan, false);
RadioModelSliceLifecycleTestAccess::setWanConnection(radio, nullptr);
}Verified: 43/43 pass on the PR head. The third mutant above now fails at "a live WAN link connects the model despite the undialed LAN link". Keep wanFallback() as well, since it is what pins the gate change.
Nits (non-blocking)
- The
isConnected()rewrite changes one case the body doesn't mention (RadioModel.cpp:2702-2706). Whenm_connectionexists but is down and there's no WAN, the old code returnedfalse. The new code falls through tom_backend->isConnected(). That's identical for Flex (FlexBackend.cpp:428-430), butSimBackendcan diverge:connectRadio()setsm_connectedwithout itsRadioConnection(SimBackend.cpp:380-392). This has many callers, so either say so in the body or keep the old semantics and hoist only the WAN term. The version below passes bothwanFallback()and the drop-in above:Also, the hoist's "latent bug" doesn't look reachable through the UI.if (m_wanConn && m_wanConn->isConnected()) return true; if (m_connection) return m_connection->isConnected(); return m_backend && m_backend->isConnected();
startWanRadioConnect()declines when there's noPanadapterStream(MainWindow.cpp:888), andm_connection/m_panStreamare aliased and cleared together (RadioModel.cpp:831-832,855-856,1747-1748). It's a fine defensive change, but the body overstates it. - Comments narrate history (AGENTS.md §Comments: "History does not go in code… Aim for five lines or fewer"). Examples:
RadioModel.cpp:2694-2698("the old nesting made that combination unreachable"), the 10-line block at:9791-9800("died here silently for the whole lifetime…", "loud, it is caught the first time…"), andreceive_intent_routing_test.cpp:3-6, 26-28, 34-37, 338-343. For example,:9791-9800could be:// RadioModel::isConnected(), not m_backend->isConnected(): a WAN session // never dials FlexBackend's own RadioConnection (#6121).
- Nothing asserts the new warning. With blocker 1 applied,
QTest::ignoreMessage(QtWarningMsg, ...)in the "neither link connected" step would pin it (the target already linksQt6::Test). Separately, VFO-wheel tuning while disconnected logs one warning per step. Consider whetherqCWarningis the right level for an expected state.
What I tried to break that held
- Core gate fix: verified. Reverting only the gate fails the PR's test. The send path below the gate routes WAN first (
RadioModel.cpp:9683-9684). - LAN and HL2 paths: reasoned from code. LAN is unchanged because
m_connection->isConnected()still decides. HL2 is unchanged becausem_connectionandm_wanConnare both null, so the backend still decides. - Test-layer boundary: the new fixture constructs a
WanConnection(it holds aQSslSocketmember), butconnectToRadio()is never called. The destructor'sdisconnectFromRadio()writes only when the socket isn'tUnconnectedState(WanConnection.cpp:180). So this is an inert value, not a socket-owning test, and no notify is needed. - Fixture lifetime:
wanis a stack object destroyed beforeFixture, and the test nullsm_wanConnfirst. No dangling pointer. - Engine boundary / touchpoints:
tools/check_engine_boundary.pygives identical per-file output on the merge basefe1fec51and the head (102 findings, 0 blocking).gen_touchpoint_manifest.py --checkis up to date. #5554 notice: no family branch, nodynamic_cast, no new capability. - TX safety (Principle VI): the change only re-enables receive intents and adds no keying path.
- CI: the runs were created at 18:16:35Z, after
main's tipa3f7b625(18:12:08Z), so they test the current merge. At review time, Static checks and Sanitizer had passed, and build / check-macos / check-windows were pending.
Not verified: the real-radio WAN/LAN results in the body (needs hardware), the full-suite 620/621 claim, and the hl2_backend_seam_test attribution to #6104. The automated code-review pass wasn't run; the equivalent checks above were done by hand. The bridge session used a scratch build of the PR head, offscreen, with isolated AETHER_SETTINGS_DIR, AETHER_AUTOMATION_NO_TX=1, connected only to DEMO-0001, and was closed afterwards. The merge-base GUI wasn't driven. That blocker 1 is new in this PR is reasoned from the diff: the base gate returned nullptr with no emit.
…I notice, pin the real aethersdr#6121 shape Two blockers from the independent review on aethersdr#6125: 1. receiveCommandSource() emitted commandDropped() on a disconnected-but- unowned tune, which MainWindow_Session.cpp's only consumer surfaces as "This radio doesn't support that control" -- correct wording for the no-command-plane case that signal was built for, wrong for this one (slices stay active between a disconnect and the next reconnect, so an ordinary tune on a disconnected session now gave a false notice). Keeps the qCWarning, which is what makes the drop visible in a log; drops the emit. receiveCommandSource() goes back to const now that it no longer needs to emit. 2. wanFallback() only reached the hoisted m_wanConn branch through the null-m_connection shape (RadioModel::setBackendForTest()'s stub never aliases a RadioConnection), not aethersdr#6121's actual shape: a real FlexBackend whose own RadioConnection exists but is never dialed. A mutant that re-nests the WAN check inside `if (m_connection)` -- bringing aethersdr#6121 back for every Flex WAN session -- passed the test suite before this fix. Added wanOverUndialedFlexConnection(), built the same socket-free way tx_operation_integration_test.cpp's rebuildBackendForTest() cases are: a real FlexBackend, never dialed, alongside an undialed WanConnection reporting connected. The same mutant now fails at "a live WAN link connects the model despite the undialed LAN link" -- verified by hand before reverting it back to the fix. Kept wanFallback() too, since it still pins the gate change itself. Nits from the same review (comment length/history, no warning assertion, the isConnected() case the body didn't mention) are left as-is, per request. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
Both blockers addressed in Blocker 1 (false "unsupported control" notice). Dropped the Blocker 2 (test didn't pin the real shape). Added Reproduced the review's own counter-evidence before trusting the fix: re-applied the exact mutant from the review (re-nesting the WAN check inside Kept Left the three nits as-is per request — not re-litigating those here, but happy to take another pass at them if preferred. Verified after the fix:
🤖 Generated with Claude Code |
ten9876
left a comment
There was a problem hiding this comment.
Review of becd2e9f8f5fbf2aefce7d4280aa05e594e01a08: the fix addresses #6121; no remaining blockers found. The receive gate now accepts the session's WAN connectivity, and the existing Flex command sink routes the admitted intents through WAN. Disconnected requests remain refused and logged without the incorrect unsupported-control signal. The two findings in the earlier review are resolved.
Scope
| File | Change / claimed? | Verdict |
|---|---|---|
src/models/RadioModel.cpp |
WAN-aware admission, combined connectivity, disconnected warning; yes | In scope |
src/core/backends/flex/WanConnection.h |
Friend for injected test state; yes | In scope; no network peer or public protocol addition |
tests/receive_intent_routing_test.cpp |
Stub and real-Flex WAN coverage, exact commands, refusal and diagnostics; yes | In scope |
Everything in the PR diff is explained by the issue and updated body. The merges from main introduce no unrelated PR-specific changes. No settings keys, capabilities, protocol verbs, dependencies, transmit paths, UI preferences, or CHANGELOG entries were added. The updated body accurately describes the broader connected-backend fallback. Commit dates show implementation, review remediation, and subsequent main merges rather than an unrelated cherry-pick.
Blockers
None.
Nits — non-blocking
-
src/models/RadioModel.cpp:2697–2700: add braces to the two editedifstatements, per AGENTS.md's “Braces on all control flow.” Drop-in replacement:if (m_connection && m_connection->isConnected()) { return true; } if (m_wanConn && m_wanConn->isConnected()) { return true; }
Verification and attacks
-
Built the full Linux/Qt 6.12 app at
b0c11b43; after the subsequent push, built and ran the expanded focused test atbecd2e9f. Production changes between these heads are comment shortening; the other upstream change is to an HL2 test. The expanded receive-routing test passes. Preflight confirmed the WAN/Flex socket objects remain undialed: no bind, connect, or synthetic firmware peer is executed. -
Independently rebuilt mutations: restoring the backend-only gate failed WAN dispatch; allowing a disconnected LAN object to mask WAN failed the real-Flex connectivity assertion. Those were checked on
b0c11b43, with the same production logic and assertions retained in the latest head. Onbecd2e9f, removing the warning faileda disconnected receive edit emits a protocol warning; restoringcommandDroppedfaileddisconnected receive edits never emit an unsupported-control notice. Restored the exact head and reran successfully. -
Drove the
b0c11b43app offscreen through explicit socket/tmp/aether-pr6125-codex, isolated settings,DEMO-0001only, andtxAllowed:false.tune 14.25,slice mode LSB,slice filter -2400 -100, andslice agc slowproduced this readback (selected fields):{"frequency":14.25,"mode":"LSB","filterLow":-2400,"filterHigh":-100,"agcMode":"slow"}After
disconnect,get radioreportedconnected:false; tuning to 14.26 produced no unsupported-control text in the widget tree. After reconnect and waiting for slice creation, the readback wasfrequency:14.27. All owned demo processes were stopped. No live radio was connected and no transmitter was keyed. -
Existing focused coverage also survived stale/reused slice IDs, reconnect staging, off-thread intents, reentrant edits, and disconnected refusal. Source inspection confirmed the Flex sink still routes WAN first and preserves the existing TX-inhibit checks.
-
All four boundary gates passed with byte-identical output on latest merge base
2394c232and headbecd2e9f: engine boundary 102 tracked findings/zero blockers, capability count 71, command plane 401 across 32 files, and current touchpoint manifest. No baseline or manifest expansion. An independent parallel audit agreed. The separate automatedcode-reviewskill was unavailable.
Limits: demo cannot reproduce the real SmartLink transport failure; the WAN proof here is injected-state/command-sink coverage and mutation evidence. I did not repeat the submitter's hardware tests, full-suite result, or macOS build, and did not drive a merge-base GUI. The bridge verifies the unchanged production behavior from before the final test/comment-only push, not a newly linked final-head GUI.
Recommendation
Approve with nits, subject to required CI and owner review. Posting COMMENT rather than an approval, as requested by the review workflow. At the final check, Static checks and sanitizer configuration passed; the three build jobs were pending. CI was created at 19:21:16–17 UTC, before main advanced to 579efd73 at 19:25:50 UTC, so it does not validate that newer main; the non-strict branch policy does not force a rerun. Check that gap during landing.
ten9876
left a comment
There was a problem hiding this comment.
Review: PR #6125 at becd2e9f
Verdict: no blockers. Posted as a comment; approval is the maintainer's call.
Issue fit. #6121: since #5907, every slice receive intent (tune, mode, filter, AGC) is dropped silently for the whole of a WAN/SmartLink session. receiveCommandSource() gated on m_backend->isConnected(), and for Flex that only reports FlexBackend's own LAN RadioConnection, which connectViaWan() never dials. The PR moves the gate to RadioModel::isConnected() (src/models/RadioModel.cpp:9786), which also checks m_wanConn. I checked that the send path below the gate reaches the WAN link. FlexBackend::requestSliceTune / setSliceMode / requestSliceFilter / requestSliceAgc all go through sendSlice() → the RadioModel sink → sendCmd(). sendCmd() routes to m_wanConn first, and none of those methods checks connectivity again. wanOverUndialedFlexConnection() now captures the actual Flex command strings, which pins that whole path. The PR solves the issue. Both earlier blockers are fixed, and since c025c8fb the history comments are trimmed, the body is current, and the warning is asserted.
Scope
| File / hunk | Claimed in body? | Verdict |
|---|---|---|
RadioModel.cpp:9786 gate → RadioModel::isConnected() |
Yes | In scope. This is the fix for #6121. |
RadioModel.cpp:2691-2701 isConnected() checks each link separately |
Yes, including the fall-through to the backend | In scope. Not needed for the Flex fix (nit 1). |
RadioModel.cpp:9786-9790 qCWarning on a disconnected drop, no commandDropped |
Yes | In scope. |
WanConnection.h:101 friend class WanConnectionTestAccess |
Yes | In scope. Test-only and private. Matches the existing *TestAccess pattern. |
tests/receive_intent_routing_test.cpp |
Yes | In scope. |
Merge commits b0c11b43, becd2e9f |
n/a | Bring in main only. |
Everything in the diff is explained by the issue. There's no new public or protocol surface, no settings key, no CHANGELOG.md edit and no CI file change. The body's checklist holds; head commits are signed (verified: true).
Blockers
None.
Nits (non-blocking)
- The
isConnected()change isn't needed for #6121, andwanFallback()asserts the one state it changes (src/models/RadioModel.cpp:2697-2701,tests/receive_intent_routing_test.cpp:334).- Not needed for the Flex fix. In the real Flex WAN shape,
m_connectionis FlexBackend's undialed connection: non-null, not connected. The old nestedm_connection->isConnected() || m_wanConn->isConnected()already returned true there. Mutant 3 below (the merge base'sisConnected()) still passes all ofwanOverUndialedFlexConnection(), including the command-string check. It fails onlywanFallback(). - Where it does change behavior. It only matters when
m_connectionis null and WAN is live. In production that means a non-Flex backend is still loaded, so dispatching intents to it is the wrong result, andwanFallback()asserts exactly that. I couldn't reach that state:startWanRadioConnect()refuses when there's noPanadapterStream(src/gui/MainWindow.cpp:888), and only Flex and Sim provide one. So it causes no harm today, but the test pins behavior nobody wants. - Suggestion. Either drop the
isConnected()hunk andwanFallback()(the gate change pluswanOverUndialedFlexConnection()is the whole fix), or changewanFallback()to assert that intents don't reach a non-Flex backend. - Related, and not caused by this PR.
connectViaWan()(RadioModel.cpp:3704) never switches to the Flex family. A SmartLink connect right after a demo session leavesSimBackendloaded with its own syntheticm_connection. The gate then hands intents toSimBackend, which ignores them because it's disconnected (SimBackend.cpp:493). Flex status decoding is missing there too, becauseFlexBackendis never built. It existed before this PR, so it's worth its own issue, not this PR.
- Not needed for the Flex fix. In the real Flex WAN shape,
- The warning fires on every intent while disconnected (
RadioModel.cpp:9787). Slices are kept for reclaim after an unexpected drop. So a held VFO wheel, or a CAT/TCI client such as WSJT-X writing frequency during the reconnect window, logs oneW aether.protocolline per write at default log level. In the app, threetunes afterdisconnectgave three lines. That's fine for finding the bug; consider rate-limiting it, or logging once per disconnect.
What I verified, and what I tried to break
-
Mutation testing at
becd2e9f(receive_intent_routing_testrebuilt each time; restore confirmed by sha256 againstgit show HEAD:). Unmutated head: 50/50 pass.RadioModel.cppmutantResult 1. Only the gate reverted to m_backend->isConnected()FAIL: wanFallbackand "WAN receive edits reach the real Flex command sink…"2. if (m_connection) return m_connection->isConnected();before the WAN checkFAIL: "a live WAN link connects the model…" and the Flex sink check 3. The merge base's original isConnected()FAIL: wanFallbackonly. The Flex shape passes (nit 1).4. qCWarningremovedFAIL: "a disconnected receive edit emits a protocol warning" -
App session (scratch build of
f670dd82; everyRadioModel.cppchange since then is a comment, checked withgit diff f670dd82 becd2e9f). Offscreen, isolatedAETHER_SETTINGS_DIR/HOME,AETHER_AUTOMATION_NO_TX=1, explicit socket. Connected only toDEMO-0001(get radio→"model": "AetherSDR Demo", familysim), closed afterwards.tune 14.250 (connected) → slice frequency 14.25, no drop logged disconnect → connected:false, connectState "idle", slice 0 retained tune 14.100/14.101/14.102 → ok; log seq 742/745/748: W aether.protocol RadioModel: not connected, dropping slice 0 receive intent dumpTree → no "doesn't support that control" text anywhere reconnect, tune 7.150 → slice frequency 7.15, no drop logged -
The fall-through to the backend (now disclosed in the body): ran 8 sim-family
RadioModeltests on the PR head, all pass:demo_backend_swap_test,backend_family_switch_test,radiomodel_audio_mute_test,control_receive_test,backend_receive_contract_test,radio_setup_recording_mode_dim_test,qso_record_start_policy_test,automation_boundary_core_test. In productionSimBackend's ownconnected/disconnectedhandlers updatem_connectedbeforeRadioModelsees the edge, because they are connected first. -
Sibling gates: in
src/,m_backend->isConnected()appears only inRadioModel::isConnected()'s own fallback and inDroopCalibration.h:14, which is ANAN-only. That's still true on currentmainafter #6111, which adds no new gate on the LAN-only link. -
Engine boundary / touchpoints:
tools/check_engine_boundary.pyreports 102 findings, 0 blocking.gen_touchpoint_manifest.py --checkis up to date. #5554 notice: no family-string branch and no new capability. The test'sqobject_cast<FlexBackend*>is test-only. -
Test-layer boundary (step 0): the tests construct a
WanConnectionand, throughrebuildBackendForTest("flex"), aRadioConnection. Neither is dialed: no bind, listen orconnectToHost. Commands are captured throughsetSliceCommandSink. These are inert values, so no operator notification was needed. -
Principle VI: the change only re-enables receive intents and adds no keying path.
-
Automated pass: I ran
/code-reviewat medium. I checked its point that theisConnected()change isn't needed for Flex and thatwanFallbackpins the null-m_connectionshape; it's in nit 1. It also claimed intents would route to a stale HL2 backend after an HL2 to SmartLink switch. I dropped that becauseMainWindow.cpp:888refuses that connect. Its log-flooding point is nit 2. -
CI: the runs on
becd2e9fwere created at 19:21:16Z. Since thenmainhas moved to579efd73f(19:25:50Z, #6111, TX seam gates, which touchesRadioModel.cpp).git merge-treeagainst currentmainis clean, and #6111 doesn't touch the receive gate orisConnected(). Static checks have passed; build / check-macos / check-windows were still running when I posted this.
Not verified: a real SmartLink session. The demo has no WAN path, so the WAN shape is proven by the unit tests and by reading the send path. The real-radio WAN and LAN results in the body need hardware. I didn't run the full suite.
Ozy311
left a comment
There was a problem hiding this comment.
Reviewed the final candidate at 70b11dca9544cda9e1dcab13ede1a92295bbb849 against main 579efd73f65dbba0bc96eef93dc24f34a9a25060. The change addresses #6121: receive intents use the model's combined connection state, so an active WAN session is not rejected because FlexBackend's LAN connection is undialed. No remaining functional blocker was found.
Scope
| File/group | Claimed in PR | Verdict |
|---|---|---|
src/models/RadioModel.cpp |
Combined connection predicate and logged disconnected drops | In scope; no new protocol, settings or TX path |
src/core/backends/flex/WanConnection.h |
Test-only access to connected state | In scope; no runtime behavior change |
tests/receive_intent_routing_test.cpp |
WAN routing and disconnected regression coverage | In scope; injected state and command sink, no opened socket or firmware peer |
Findings and disposition
-
The false unsupported-control notice from the earlier review is absent at
f670dd82. Commitc025c8fbadditionally asserts that disconnected edits never emitcommandDroppedand that the protocol warning remains. Reintroducing the signal or removing the warning each fails its assertion. -
The real undialed Flex connection case added at
f670dd82is retained.c025c8fbextends it through the real Flex encoder, asserting the exact tune, mode, filter, AGC mode, AGC threshold and AGC off-level commands, plus refusal when both links are down. Both the backend-only gate and a LAN-first early return fail the strengthened suite. -
The historical-comment nit is addressed with concise invariant comments. The PR body now accurately describes warning-only behavior and the backend fallback when a vended LAN link is down. No demonstrated non-WAN regression was found; the simulator bridge check passes.
-
The later braces nit is fixed by
70b11dca; both edited connection checks now follow the project style. The complete app, both focused tests and the 24-request demo check were rerun on that commit. -
The later non-blocking scope observation is valid: the model connectivity refactor is broader than the minimal Flex gate fix. The PR explicitly discloses and tests the combined-link predicate; no reachable new regression was demonstrated. A non-Flex-to-SmartLink family switch is a pre-existing session lifecycle concern, not established as caused by this change, so this PR does not expand into that redesign.
-
Per-intent disconnected warnings are retained as the chosen diagnostic behavior. Rate limiting is a possible follow-up; it is not needed to restore WAN commands or eliminate the false unsupported-control notice, and the reviewer classified it as non-blocking.
Verification
- Final macOS arm64 / Qt 6.12.0 app build passes. Focused CTest: 2 selected, 2 passed (
receive_intent_routing_test,transmit_seam_drop_notice_test); receive executable: 50 checks passed. - Four separately rebuilt mutants all fail: backend-only gate, LAN masking WAN, false unsupported-control notice, missing warning. Restored source rebuilt and retested before publication.
- Four architecture checks pass. PR-only output matched the original merge base before the main refresh; the final checks include the upstream #6111 capability ratchet decrease to 70: engine boundary (102 tracked findings, 0 blockers), generated touchpoints, capability records and command plane. No new family branch, concrete-backend cast in production, vendor command above the seam, or capability is introduced.
- Final agent automation bridge run used isolated settings, offscreen
DEMO-0001, explicit local socket andtxAllowed:false. At 14.250 MHz: LSB, filter -2400/-100, AGC slow/42. At 14.260 MHz: USB, filter 200/2600, AGC fast/55. After disconnect and a tune request, the widget tree contains no unsupported-control status message. All 24 requests completed successfully; owned PID 65475 was stopped. - All seven PR commits are GitHub Verified. Main was incorporated through
579efd73; there are no unresolved review threads or outstanding changes-requested reviews.
The automated code-review skill was unavailable; this was a manual adversarial pass. Live WAN/LAN hardware, RF/TX and the full CTest suite were not rerun by this reviewer. The submitter's physical-radio evidence remains separately attributed in the PR body. The demo cannot reproduce the SmartLink TLS transport itself; the WAN gate is demonstrated by socket-free injected connectivity and command capture.
Landing state
Approval is for the reviewed code and demonstrated local behavior; required GitHub CI is still pending and is not claimed green. Linux and Windows are building, while macOS and Static Checks are queued. Auto-merge is armed and must wait for those required checks. The final head's earliest runs were created at 2026-10-03 19:41:21 UTC, after the current main tip 579efd73 at 19:25:50 UTC; main is included in the verified head. No unresolved threads or changes-requested reviews remain. The operator explicitly authorized taking assignment and enabling auto-merge.
Reviewed with Codex using the repository's PR review and fb workflows.
…lay, Qt 6.12 (#6159) ## Summary Six-file release prep for **v26.10.1**, metadata and docs only. No source, test or CI changes. Cutoff: `origin/main` at `18c5245e5` (#6155, merged 2026-10-04). Anything that merges after it is folded in before the tag. **95 merged changes** (95 PRs, no direct commits) from **12 human contributors**, plus AetherClaude (2) and Dependabot (0). There are no first-time contributors this cycle. That was checked for each login by looking for a merged PR before v26.9.5's tag, not from `author_association`. ## Changelog Built from `v26.9.5..18c5245` mapped through the commit-to-PR API (`repos/…/commits/<sha>/pulls`), not from commit subjects. All 95 PRs are cited in the section. The notes lead with three things: - the sweep that makes every control on a radio without a Flex command plane either work or refuse with a reason (#6044, #6049, #6015, #6111); - the CTR2 relay (#6090, #6114, #6155) and authenticated 4O3A connections with a Peripherals page (#6008, #6027); - the Hermes-Lite 2's squelch, APF, AGC-off, RIT/XIT and FFT AVG. The structural half covers Qt 6.12 for every binary and source build (#5842, #6055) and the slice receive controls moving behind the seam (#5892, #5907, #5919; raw Flex commands above the seam 413 → 369, confirmed with `check_command_plane.py` at the cutoff). Upgrade-visible changes are called out with **On upgrading** inline: the Ulanzi default and removed MOX/TUNE pills (#5964), auto RF gain arming and law (#5976, #6062), DIGU/DIGL opening with AGC off (#6119), HL2 squelch calibration and the Auto SQL fallback (#6127), and the macOS 14.4 floor (#5842). The previous tag v26.9.5 is an ancestor of `main`, so nothing is excluded from the range. ## Docs refresh, not just version strings - **README:** Highlights "Broad hardware control" gains the CTR2 relay. Supported Hardware says direct TGXL/PGXL/Antenna Genius connections authenticate. The Other radio families paragraphs gain the HL2's squelch, APF, RIT/XIT, FFT averaging and CL1 reference, the ANAN-G2's receiver audio and radio speaker, and RTL-SDR's confirmed receive state, capture browsing and zoom FFT. In the Roadmap section, the aetherd line gains the receive controls and a CTR2 relay line is added. No maturity label changed. The six backends and seven NR engines were re-checked against `src/core/backends/` and `AetherRxProfiles.cpp`. - **ROADMAP, In flight:** - aetherd: the receive controls behind the seam. - Icom: TUNE power live, no false drop notices, the one-panadapter correction. The stale "VOX and CW break-in" leaves Remaining: `IcomCivBackend` has implemented both since #4799/#5113. - ANAN-G2: receiver audio and radio speaker. - RTL-SDR: M1, with Remaining restated from #5924's body. - Hermes-Lite 2: the v26.10.1 control parity, and the CW latency cost updated to 4096 taps. - A new **CTR2 controller relay** entry. - Nothing left In flight as finished. - **ROADMAP, Open RFCs:** diffed against `label:rfc`. #5893 leaves (closed). #6106 and #6079 join under Radios, protocol and devices. - **ROADMAP, Recently shipped:** 8 new `(v26.10.1)` entries. The list is trimmed to the v26.10.x cycle at the month rollover, and its intro names v26.10.x. Everything trimmed is still in CHANGELOG. ## Judgment calls flagged for the maintainer - **Recently shipped trimmed to v26.10.x at the rollover:** maintainer's call. This makes the validator's "only the four current-version lines lose `26.9.5`" check FAIL. The lines it lists are exactly the trimmed `(v26.9.5)` entry markers, and there is no historical mention elsewhere. That is the one FAIL below. One side effect: the trim removes ROADMAP's last mentions of #5630 and #5684, approved RFCs that shipped in v26.9.4. Both are now closed as completed, and both remain in CHANGELOG. - **A CTR2 relay In-flight entry:** maintainer's call. Wi-Fi works today. USB's host side shipped and has carried the radio's status stream to the firmware author's development firmware; it waits on a CTR2 USB firmware release. - **Intro wording with zero Dependabot updates:** "AetherClaude and no dependency updates within that total" keeps the house shape and passes the count check. The Contributors block has no Dependabot sentence. - **#6125 is filed under the seam section.** It fixes a WAN/SmartLink receive regression introduced by #5907 inside this range, so released builds never had the regression. The section states what ships: receive intents gate on `RadioModel::isConnected()`. - Ready for review, not draft: maintainer's call. ## Scope | file | change | |---|---| | `CMakeLists.txt` | `project(AetherSDR VERSION 26.10.1)` | | `README.md` | Current version line, plus the prose refresh above | | `AGENTS.md` | Current version line | | `CHANGELOG.md` | new `[v26.10.1] — 2026-10-04` section; `[Unreleased]` stays and stays empty; history below byte-identical | | `packaging/linux/io.github.aethersdr.aethersdr.metainfo.xml` | `<release version="26.10.1" date="2026-10-04"/>` first in `<releases>` | | `ROADMAP.md` | cycle heading → post-v26.10.1, plus the prose refresh above | ## Validation `check_release_prep.py --mapping mapping.json`: 29 checks, **1 FAIL** (explained above), 0 WARN. - All six version spots say 26.10.1. The CHANGELOG heading date equals the metainfo date, 2026-10-04. - `appstreamcli validate` passed. The `<release>` list is date-descending with unique versions (22 entries). - `[Unreleased]` stays empty, `[v26.10.1]` sits directly under it, and the CHANGELOG diff is exactly the inserted section, with history byte-identical to `origin/main`. - All 95 PRs in the range are cited. The only other numbers cited are #3849 (RFC), #5262 (tracking issue), #5468 (RFC), #6091 (RFC, approved and closed) and #6142 (open follow-up issue for #6119). - The intro (95/12), the Contributors counts (13 entries) and "first-time: none" are re-derived from the mapping, and they match. - ROADMAP `(vX)` markers lead with v26.10.1. Every local link in README and ROADMAP resolves. No removed heading is linked elsewhere. Code fences balance, with no duplicated table headers or doubled `---`. - **FAIL:** "only the four current-version lines lose `26.9.5`". The lines are the trimmed Recently-shipped markers; see the judgment calls above. - `git diff --check` is clean. The diff touches no `src/`, `tests/`, `third_party/` or `.github/workflows/` path. - Configure: `cmake -S . -B <scratch>` reports `CMAKE_PROJECT_VERSION=26.10.1`. No build or test was run, because this is metadata only; CI builds it. - `main` CI at the cutoff: `CI` passed on `18c5245e5`. After merge: tag the resulting **main** commit, not this branch. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Summary
Fixes #6121. WAN/SmartLink receive edits reached a gate that checked only FlexBackend's undialed LAN connection, so tuning, mode, filter and AGC requests never reached the radio.
RadioModel::isConnected(), which checks LAN, WAN and backend connectivity. The WAN check also works without a LAN connection object. If a vended LAN connection is down, a connected backend can still establish connectivity; this includes the simulator's direct backend path.aether.protocol. Do not emitcommandDropped, whose UI consumer reports an unsupported control rather than a disconnected session.No new protocol verb, capability, setting, dependency, or transmit path is added.
Constitution principle honored
Principle VIII — Evidence Over Assertion. The reporter bisected the regression to #5907 (
142f1d63versuse48a242f). Local mutation checks independently demonstrate that the regression suite detects the broken connectivity gates and diagnostics.Validation
receive_intent_routing_testpass; 50 assertions. After incorporating main fix(radio): route VOX, monitor, speech processor, CW speed and break-in through the seam gate. Principle VIII. #6111, both the receive test and neighboringtransmit_seam_drop_notice_testpass (2/2 CTests).DEMO-0001, TX disabled. Tune/mode/filter/AGC readbacks pass for LSB and USB; disconnected tuning produces no unsupported-control status notice. Owned demo process stopped after verification.Checklist
Original implementation generated with Claude Code. Additional review and regression coverage by Codex.