feat(radio): route ordinary RX slice lifecycle through the backend seam - #5471
feat(radio): route ordinary RX slice lifecycle through the backend seam#5471Ozy311 wants to merge 3 commits into
Conversation
…Principle II. Implement P01 of RFC #5468: preserve status-authoritative slice ownership, propagate backend refusal, retain Flex/Sim command routing, and ignore retired-backend lifecycle events. Add socket-free lifecycle regression coverage and explicit per-backend capability checks.
There was a problem hiding this comment.
Issue fit
Partially. RFC #5468's capability section specifies canCreateSlices almost verbatim as implemented ("Independent slices can be created/removed on an existing pan through the model lifecycle API, including its preserved Flex command-plane adapter; separate from maxSlices and pan creation"), and the RFC's obligations — set explicitly in all six backend constructors, a row in docs/architecture/radio-capabilities-map.md, behavioral gating tests — are all met. The seam verbs, the sliceLifecycleFailed diagnostic, the generation guard against retired-session events, and the removal of the slice remove "not supported" special case in the bridge all land as described.
What is not met is the flip side of the same contract: the create path lost its failure reporting entirely. Finding 1 below. On a backend with no command plane, addSliceOnPan now returns false with no signal, no status message and no qCWarning — where before the change it went through sendCmd's drop path and produced both a log line and the #5263 status-bar notice. The removal path got a message; creation did not.
The RFC also asks for gating tests covering "RTL → other-family → RTL swaps and disconnect". testReplacement covers rtl → hl2 → hl2 and the retired-backend queue; there is no disconnect case.
Scope
| File(s) | Change | Claimed? | Verdict |
|---|---|---|---|
src/models/RadioModel.cpp (1064 lines) |
Extract receiver-state connects into wireBackendReceiverState(); add generation guard; addSlice*/removeSlice return bool and route through the seam |
yes | In scope. Verified as pure motion — see "what I tried to break" |
src/models/RadioModel.h |
Signatures, sliceLifecycleFailed, generation counter, test sink |
yes | In scope |
src/core/backends/IRadioBackend.h |
createSlice/removeSlice defaults + sliceLifecycleFailed |
yes | In scope. New virtual surface on the backend seam, RFC-sanctioned |
RadioCapabilities.h + 6 backend capabilities() |
canCreateSlices declared explicitly everywhere |
yes | In scope, matches the RFC's "all six constructors" |
docs/architecture/radio-capabilities-map.md |
One row | yes | In scope — but see Finding 3 |
src/core/AutomationServer.cpp |
slice add frequency validation; slice remove via the model |
yes | In scope. Two undisclosed public-surface behavior changes — nits below |
src/gui/MainWindow_Wiring.cpp |
Two close handlers route through removeSlice |
yes | In scope |
src/gui/MainWindow_Session.cpp |
Status-bar wiring for sliceLifecycleFailed |
yes | In scope |
tests/backend_slice_lifecycle_test.cpp + tests.cmake |
New socket-free target | yes | In scope, registered |
tests/{anan,rtl,sim,icom_control_profile}_*.cpp |
One-line capability assertions each | yes | In scope — the RFC's gating-test obligation |
Nothing in the diff is unexplained by the RFC. No CHANGELOG.md entry (correct). No settings keys, no credentials, no TX surface.
Socket-test disclosure (§2): tests/backend_slice_lifecycle_test.cpp constructs a RadioConnection (tests/backend_slice_lifecycle_test.cpp:280) and installs it as m_connection to model command-plane ownership. I read RadioConnection::RadioConnection (src/core/RadioConnection.cpp:65-68) — it is an empty body; the QTcpSocket and heartbeat QTimer are created in init(), which the test never calls. No socket is created, bound, listened on or connected. The test's own comment says exactly this and it is accurate. It runs in the full suite, not the frozen PR gate.
Blockers
1. Slice creation on a backend without a command plane is now a completely silent dead control (src/models/RadioModel.cpp:5107-5112).
Before this PR, addSliceOnPan on an HL2 / Icom / ANAN / RTL built the Flex wire text and handed it to sendCmd, which hit the no-command-plane drop at RadioModel.cpp:9083-9092:
qCWarning(lcProtocol).noquote()
<< "RadioModel: no command plane for this backend, dropping" << command;
emit commandDropped(command);and MainWindow_Session.cpp:729-738 turned that into the operator-facing notice "This radio doesn't support that control — nothing was sent to the radio." That is the M0/#5263 loud-drop contract, and its comment names the failure mode it exists to prevent.
After this PR the early return fires first, so on those radios "+RX", the empty-pan spectrum click and the Kiwi-spot click produce no status message, no commandDropped, and no log line at all. All four GUI create sites discard the new bool without acting on it (MainWindow_Wiring.cpp:4904, 4916, 5217, 5281). The support-bundle evidence disappears with it.
The asymmetry is the tell: the two close handlers in this same diff gained statusBar()->showMessage(...) on false, and sliceLifecycleFailed — a signal this PR adds and already wires to the status bar at MainWindow_Session.cpp:936 — exists precisely for this. The cheapest fix is to use it, or at minimum restore a qCWarning.
The new test freezes the silence in rather than catching it: check(radio.slices().size() == 1 && dropped.isEmpty(), "creation refusal has no state or Flex fallback") asserts commandDropped is empty. Asserting no Flex fallback is right; asserting no diagnostic is the regression.
(Reasoned from code — I cannot run the GUI. The claim rests on the two code paths and the commandDropped consumer cited above, all read in the head checkout.)
Nits (non-blocking)
- "Radio did not accept slice removal" is untrue for local refusals (
MainWindow_Wiring.cpp:5273,5691).RadioModel::removeSlice:5136returnsfalsefor the last-slice guard and for an unknown id before anything is sent, so closing the only receiver now tells the operator the radio refused when the radio was never asked. Previously that case was a silent no-op. A separate message ("Cannot close the last receiver") or leaving that case silent would both be more honest. canCreateSlicesis unread on exactly the two families whose values differ from the rule. The check atRadioModel.cpp:5110sits belowif (!hasCommandPlane()), so Flex'strue(FlexBackend.cpp:146) and Sim'sfalse(SimBackend.cpp:276) are both dead values — on Sim,addSliceOnPancreates slices through the command plane despite the ❌ in the map. The caps-map doc's own opening line is the argument: "A capability that no consumer reads looks identical, from the backend side, to one that works." The row atradio-capabilities-map.md:38half-covers this ("Sim's existing command-plane behavior is retained") but still prints ❌ against an Effect column describing something that does happen. Worth stating in the row that the field is only consulted on backends with no command plane, so nothing later gates the "+RX" button on it and silently breaks Sim.- Two undisclosed bridge behavior changes.
slice add <unparseable>used to fall back to a default-frequency create and now returns an error;slice removeon a non-Flex radio used to answer"not supported on this radio (no Flex command plane)"and now answers"refused: radio did not accept slice removal". Both are improvements, butdocs/automation-bridge.md:1461-1462still documentsadd | optional <mhz>with no word on strictness, and a script matching the old refusal text breaks. - Center-lock clearing semantics shift slightly. The deleted GUI pre-clear called
clearCenterLockForPan(panId, /*clearPersistedIntent=*/true)unconditionally; the replacement relies ononSliceRemoved→clearCenterLockForSlice(id, !preservePersistedIntent)(MainWindow_Wiring.cpp:2321-2331), which can preserve persisted intent inside a band-recall window. That is almost certainly the better behavior and I could not construct a case where it re-locks the wrong slice, but it is a behavior delta the body does not mention. m_sliceLifecycleCommandSinkForTestis a test hook on productionRadioModel, cleared inteardownBackend().setBackendForTestis precedent, so this is consistent rather than novel — naming it so the maintainer rules deliberately.
What I tried to break
- Code-motion audit of the 1064-line
RadioModel.cpphunk. Rather than trust the "extract towireBackendReceiverState()" framing, I took a comment-stripped, whitespace-normalized multiset diff of the whole file betweenHEAD~1andHEAD. The only differences are the generation captures/guards, the newsliceLifecycleFailedconnect,++m_backendReceiverGenerationinteardownBackend, and the rewrittenaddSlice*/removeSlice/dispatchSliceLifecycleCommand. No behavior is smuggled into the motion. The rewrite also tightens three real cases the base tolerated:addSliceOnPan(panId, freq)no longer accepts a pan with noPanadapterModel(base sent the command anyway), no longer acceptsfreqMhz <= 0, and no longer falls back to the hardcoded 14.1 MHz / 0.2 MHz geometry atRadioModel.cpp:5044/5072when the pan is missing. All three move toward refusal, which is the PR's stated direction. - Seq-0 false negative.
dispatchSliceLifecycleCommandtreatssendCmd(...) != 0as "dispatched". If the sequence counter started at 0, the first command of a session would report a spurious refusal.RadioModel.h:1584initialisesm_seqCounter{1}andWanConnection.h:111does the same, and nothing resets either to 0 — the test is sound. The profile-load suppression path also returns 0, which correctly reads as "not dispatched". - The deleted GUI guards. The removed
if (m_radioModel.slices().size() <= 1) return;reappears atRadioModel.cpp:5136, and the removed center-lock pre-clear is genuinely covered byonSliceRemoved(MainWindow_Wiring.cpp:2321-2331) — so neither deletion resurrects #3854 or the last-slice case. - Sibling call sites. Grepped the head checkout for every remaining
slice removeproducer:RigctlProtocol.cpp:257,TciServer.cpp:1881/1889/1902/2115/3730,MainWindow.cpp:8570. All are split/TX-slice cleanup, which the header comment and the RFC explicitly scope out of "ordinary". No ordinary RX close site was left behind. - Retired-session events.
m_backendReceiverGenerationis captured at the top ofwireBackendReceiverStateand incremented atteardownBackend:1784before theQObject::disconnectcalls, anddisconnect(m_connection, ...)runs beforem_backend.reset()— so the fixture's backend-ownedRadioConnectioncannot be disconnected after free.testReplacementdrives this from a producer thread, which is the right shape. - The tests themselves. They are not self-agreeing:
testCommandAdaptersetscanCreateSlicesper family and then asserts creation succeeds for both, which is what exposed that the field is unread on the command-plane path;testNeutralLifecycleasserts a successful reply "alone cannot invent slice ownership"; the sparse-ID check (removeSlice(2)with slices 0/1/3 live) would fail against a vector-index implementation. The one place a test locks in a defect is thedropped.isEmpty()assertion in Finding 1. - Could not check: any runtime behavior. No build, no test run, no GUI. Every finding above is reasoned from the head checkout at
/tmp/aetherclaude/pr-5471, with the file:line cited. CI ona259359is green across all seven checks, which perci.yml's-Rfiltering does not include the new target.
Recommendation
Request changes, on Finding 1 alone. The architecture, the capability plumbing, the generation guard and the test are all solid work, and the scope is unusually clean for a diff this size — the motion audit came back with nothing hidden in it. But the PR's own thesis is that a refusal must be visible rather than silent, and the create path currently ends up quieter than the code it replaces, on exactly the radios the RFC is about. Emitting sliceLifecycleFailed("create", -1, …) from RadioModel.cpp:5107-5112 reuses the signal and status-bar wiring already in this diff and should be a few lines; the dropped.isEmpty() assertion then wants to become an assertion that the operator was told. The nits can ride along or follow separately.
🤖 aethersdr-agent · cost: $12.9578 · model: claude-opus-5
GitHub rejected the inline anchors (GitHub API 422: Unprocessable Entity); findings below:
src/models/RadioModel.cpp:5107-5111
Blocker — this is now a fully silent dead control on HL2 / Icom / ANAN / RTL.
Before this change, the same call fell through to sendCmd, whose no-command-plane branch (RadioModel.cpp:9083-9092) did two things this early return skips:
qCWarning(lcProtocol).noquote()
<< "RadioModel: no command plane for this backend, dropping" << command;
emit commandDropped(command);MainWindow_Session.cpp:729-738 turns commandDropped into the operator notice "This radio doesn't support that control — nothing was sent to the radio." — the M0/#5263 loud-drop contract. After this hunk, "+RX", the empty-pan spectrum click and the Kiwi-spot click on those radios produce no message, no signal and no log line, because all four GUI create sites discard the new bool (MainWindow_Wiring.cpp:4904, 4916, 5217, 5281).
The close path in this same diff got a status message on false; creation did not. You already added the right signal for it and wired it to the status bar at MainWindow_Session.cpp:936 — using it here costs two lines and keeps the return-value contract intact:
if (!hasCommandPlane()) {
// Refusal is terminal. Paired/fixed receivers do not acquire an
// independent lifecycle just because maxSlices happens to exceed one.
if (m_backend && backendCapabilities().canCreateSlices
&& m_backend->createSlice(backendPanIdFor(panId), frequencyHz)) {
return true;
}
// Never silent: this replaced sendCmd's commandDropped notice (#5263),
// which was the only thing telling an HL2/Icom operator that "+RX"
// reached nothing.
qCWarning(lcProtocol) << "RadioModel::addSliceOnPan: backend declined" << panId;
emit sliceLifecycleFailed(QStringLiteral("create"), -1,
tr("this radio cannot create a slice here"));
return false;
}
The new test currently freezes the silence in — check(radio.slices().size() == 1 && dropped.isEmpty(), "creation refusal has no state or Flex fallback") asserts the diagnostic is absent. Asserting no Flex fallback is right; that assertion should become one that the operator was told.
src/gui/MainWindow_Wiring.cpp:5273
Nit: RadioModel::removeSlice (RadioModel.cpp:5136) returns false for the last-slice guard and for an unknown id before anything reaches the radio, so this message tells the operator the radio refused when the radio was never asked. Closing the only receiver used to be a silent no-op here.
Worth distinguishing the local refusal from a transport/backend refusal — or leaving the last-slice case silent, as it was. Same at line 5691.
src/core/backends/sim/SimBackend.cpp:276
Nit (capability contract): the canCreateSlices check at RadioModel.cpp:5110 sits below if (!hasCommandPlane()), so it is never consulted for Sim — addSliceOnPan creates slices on the demo radio through the command plane regardless of this false. Flex's true at FlexBackend.cpp:146 is dead for the same reason.
That makes the caps-map row (radio-capabilities-map.md:38) read ❌ for Sim next to an Effect column describing behavior Sim does have. The doc's own opening states the risk: "A capability that no consumer reads looks identical, from the backend side, to one that works."
Not a blocker — nothing gates on it today. But if a later PR hides "+RX" behind caps.canCreateSlices, Sim silently loses working functionality. Suggest th
(truncated)
|
Antares Detector — candidate vulnerable file(s)
The AnanBackend implements an internal radio backend that can be invoked via backend()->invokeExtension (seen in AutomationServer). The implementation lacks evidence of any validation or safe dispatch mechanism to prevent invoking unsafe backends, which could lead to unvalidated external input reaching dangerous operations such as unsafely routing RX slices. Localized by Cisco Foundation AI Antares-1B running locally in the AetherClaude sandbox, seeded by the Cartographer security map. Advisory only — please verify before acting. 🤖 aethersdr-agent · cost: $13.3544 · model: claude-opus-5 |
|
Review follow-up in 655d6922:
Linux engine/changed GUI object and ten registered targets build; all ten CTests pass, with 103 lifecycle assertions. Removing the refusal diagnostic failed its assertions in the earlier follow-up run. The new wrong-session-serial mutation failed the disconnect ownership assertions; exact restoration, rebuild and ten-test rerun passed. Static checks pass. Follow-up GUI/hardware validation was not run. New-head CI is pending separately. The separate Antares advisory does not describe an operation this implementation performs: 73, Ozy K6OZY · GPT-6 Astra-Ultra |
## Summary RTL RFC #5468 requires every receiver's complete guarded RF passband to fit the shared capture before tuning, filter/mode changes or sample-rate changes are accepted. Add a pure `SharedCapturePolicy` helper that selects the nearest legal quantized tuner center, validates coherent readback and produces a deterministic restore plan within the current capture. The helper validates asymmetric filter/usable margins, signed carrier/BFO translation, transition guards, disjoint hardware domains, bounded numeric inputs, stable slot IDs and caller-supplied receiver capacity. It keeps an already legal center, refuses an impossible desired set atomically, and restores fitting entries in ascending stable-ID order without recentering or renumbering. Conservative floating-point arithmetic rejects unrepresentable boundary fringes rather than widening acceptance with a tolerance. This is F2 of #5468 and is independent of P01 #5471. The helper compiles into the static `aethercore` library; the registered test compiles that same implementation. No live backend calls it yet. USB/DSP/audio/viewport/UI/persistence integration and supported receiver counts remain later RFC phases. The new contract is documented in `docs/shared-capture-policy.md`. This PR does not close the umbrella RFC. ## Review follow-up An additional regression rejects a center grid whose 1e-5 Hz step is smaller than the double spacing at 2^39 Hz while its grid indices remain within the independent index bound. The capture and slice otherwise fit. Disabling only the resolution guard makes this new check fail, closing the reported mutation-coverage gap. The final readback validation remains, with its defense-in-depth purpose made explicit. The contract now distinguishes adapter-declared logical readback from measured physical RF frequency. In [Osmocom librtlsdr v2.0.2](https://github.com/osmocom/rtl-sdr/blob/v2.0.2/src/librtlsdr.c#L884-L913) and [RTL-SDR Blog commit aed0ea19](https://github.com/rtlsdrblog/rtl-sdr-blog/blob/aed0ea19f3a273370a13c9009b96313c75d54c7b/src/librtlsdr.c#L887-L936), `rtlsdr_get_center_freq()` returns the cached requested frequency stored by successful tuning; it does not measure achieved PLL frequency. Other versions and forks need verification. Actual readback must still satisfy the declared domains and complete-set containment checks. ## Constitution principles honored Principle VII — invalid numeric descriptors, impossible intervals, malformed identities, oversized collections and incoherent readback fail at the policy boundary. Principle II — a selection is only a proposal; the integration layer must validate coherent capture state before publication. Logical library readback does not establish physical RF accuracy. ## Validation - Follow-up on macOS ARM64, Apple clang 21.0.0, standalone C++20 `-O2`: **260 checks, 0 failures**. The test is socket-free and requires no Qt event loop, hardware, DSP or settings. - Follow-up mutation in a temporary source copy: disabling only `stepHz < resolution` produces **260 checks, 1 failure**, specifically the new unresolved-grid assertion. Production guards remain unchanged. - `git diff --check` passes for the follow-up. - Prior reviewed head `5cd78ad10bf3ce86c404cddf7c2aea9b4215c844`: Linux normal engine policy object and registered CTest built with GCC 16.2.1; all 259 then-existing assertions passed. The independent rational oracle matched all 2,000 cases, upper-transition-guard and generation mutations failed as expected, and the exact restored source passed. Strict engine-boundary, test-registration, frozen CI-gate and touchpoint checks passed on that head. Those are recorded prior-head results, not a new Linux run of this follow-up. - Linux follow-up: normal engine policy object and registered CTest rebuilt; **260 checks pass**. Removing only the resolution guard compiled and failed the new assertion. Exact source restoration, rebuild and final CTest pass. Strict engine-boundary, registration, frozen CI gate, touchpoint manifest and whitespace checks pass. - Signed follow-up head `bec2815c605442864fc425133ddd887cb200680d`; full source matches validation base `e60c3bea2ac8e5b762be9a3c6f52aa1bab8fb9ad` plus six-file digest `b2237190f1a875addf40557d39d9e5e8c01fcc171906908861a1c51ac46c4cf5`. All seven PR checks passed on this follow-up head. - Review follow-up `beb054c384c9bb1334c68ab6ecb8bc8ec96ebd93` corrects only the Osmocom source anchor; implementation and tests are unchanged. Independent review of `bec2815c` merged with main `f0ed969b` passed all 260 checks in native macOS ARM64 optimized and ASan/UBSan builds. Grid-resolution, upper-guard, generation and restore-capacity mutations all failed as expected. Engine-boundary, registration, frozen CI-gate, touchpoint and whitespace checks passed. See the live Checks tab for the documentation follow-up status. No live-radio or performance claim is made for this unintegrated policy. The test is registered in the default graph without expanding the frozen per-PR allow-list. No dependencies, threads, settings owner, socket-owning test, UI controls or CHANGELOG entry are added. Full application behavior, physical RF accuracy and supported receiver capacity remain integration gates. --- 73, Ozy **K6OZY** · GPT-6 Astra-Ultra --------- Co-authored-by: Robbie Foust <rfoust@gmail.com>
Resolve additive bridge documentation and test consumer-list conflicts. Preserve upstream RTL identity, squelch and display persistence changes. Full Linux application build and 17 focused CTests pass; the lifecycle fixture passes 103 assertions. Static boundary, registration, CI-gate, touchpoint, bridge-doc and whitespace checks pass. Refs #5468, #5471.
Summary
Ordinary RX slice creation and closure depended on Flex command text, preventing backends with their own receivers from implementing those actions and allowing the bridge to report dropped creation as requested. P01 of RFC #5468 adds default-refusing
IRadioBackendlifecycle methods and routes ordinary callers throughRadioModel. Requests report acceptance or refusal; confirmed normalized state creates/removes models. Backend-replacement generation checks reject late events, and center lock clears only after authoritative removal.Flex and Sim retain their command adapters, encoding and create-reply behavior. All six backends explicitly declare
canCreateSlices; RadioModel consults it only on the neutral path without a command plane, so it must not independently gate +RX in the UI. RTL retains one slice, one pan and its current DSP/WFM path. Shared-capture tuning, additional receivers, audio-rate changes, persistence, startup slice creation and split/TX ownership remain subsequent work. The larger RadioModel diff extracts existing receiver-state bindings into a production/test helper; their bodies retain existing behavior apart from generation guards.Review correction
The six-file follow-up fixes the silent neutral-path creation refusal identified in AetherClaude's review. Capability denial or backend refusal now logs a protocol warning and emits
sliceLifecycleFailed("create", -1, reason)before returning false. The existing status-bar consumer handles this signal. Tests assert both refusals produce a diagnostic, preserve receiver identity/state and never fall back to a command adapter. Replacement coverage now includes returning to RTL after another family through production receiver bindings and teardown. A further socket-free fixture calls disconnectFromRadio, injects completion into the real onDisconnected handler through an existing friend, and uses the production reconnect-staging seam. It proves same-device object reclamation versus different-device pruning, authoritative state refresh, one intent binding, and preservation of unconfirmed lifecycle requests.Both GUI close paths now say "Cannot remove this slice", which also describes local guards that never contact the radio. Header/capability-map documentation clarifies the neutral-hook scope; bridge documentation records the strict arguments and refusal responses below. These corrections touch RadioModel, RadioCapabilities, MainWindow_Wiring, the lifecycle test, and the two documentation pages. Capability values, defaults and center-lock implementation are unchanged.
Observable behavior
"slice add requires a finite positive frequency in MHz"after the capacity pre-check; it never becomes a default-frequency request."refused: radio did not accept slice creation"or"refused: radio did not accept slice removal". The removal response replaces the earlier non-Flex"not supported on this radio (no Flex command plane)"; scripts matching that text must update. Local removal checks retain"refused: cannot remove the last slice"and"no slice with id <sliceId>".get slicesfor confirmed ownership. Pending/refused removal preserves center lock. Confirmed removal uses the existingonSliceRemovedrebind logic, which can preserve persisted center-lock intent during a band-recall window; the prior GUI pre-clear erased that intent unconditionally.Constitution principles
Principle II: request acceptance cannot invent slice ownership or delete a pending receiver. Principle VII: malformed bridge input is rejected at the boundary. Principles VIII/XI: validation is tied to the exact tested source, including the demonstrated refusal-diagnostic regression.
Merge-conflict repair against main
0ac8721aMerged main into the existing branch, preserving both parents. The two textual conflicts were additive: the ordinary-slice request documentation and upstream squelch guidance are both retained, and the settings-consumer list retains the lifecycle test plus all four upstream additions. Recent RTL identity/anonymous scope, reconnect squelch invalidation, and FFT/display persistence changes remain intact. The PR still changes the same 21 files with the same per-file addition/deletion counts relative to the refreshed main.
Signed merge commit:
d6b68a3c0fd9913f00245757127885ecde50d638(local SSH signature verified). Exact repaired source tree:c10afc840eab298437336045d5ee59f91d90bd48, identical on macOS and Nobara-Dell. Full Linux application build and all 17 focused CTests passed on Nobara-Dell (GCC 16.2.1, Qt 6.11.1, CMake 4.3.0, Ninja 1.13.2; RelWithDebInfo; RTL/RADE/DFNR/Vulkan enabled). The lifecycle fixture passed 103 assertions. The selection includes the original ten lifecycle/backend checks plus RTL settings, state memory, shared-capture policy, Band Stack, client display persistence and both squelch regressions. Tests ran with temporary settings, Qt offscreen, and an isolated Linux user/network namespace; CTest completed in 10.68 seconds. Strict engine boundary, registration, frozen CI gate, touchpoint manifest, generated bridge docs and whitespace checks pass.No new behavior or new tests were introduced by this conflict repair, and no new mutation run was performed. Historical mutation evidence below remains tied to its original tested heads. No GUI/hardware, live RX/TX, macOS/Windows build or sanitizer validation was repeated for this merged tree. ONNX Runtime and sherpa-onnx were unavailable. Fresh PR CI is required after publication; earlier green checks apply to
655d6922.Prior follow-up validation at
655d69228676e50192e2b1e664c05cb37992efd8ecaf740c4b587ad4ae23eef6d42c7f22matches both hosts after mutation restoration. Engine artifact SHA-25662ecb16b4158bbad722b938fad6e71666030b3c0be723932500d173085aa68c3; lifecycle-test SHA-256d67aaddeeb7984fae92e18f56a418ae9321fc8318734a5c8eb34918b0c74db95.655d69224d12d5225ab5988252ec8b9d436f8f78; local SSH signature verification passes.655d6922; those checks do not validate the later merge-conflict repair.Follow-up tests used isolated settings, Qt offscreen and a Linux user/network namespace. No full app relink, GUI session, hardware/USB acquisition or live RF validation was performed for the follow-up.
The prior results below validate
a25935952ae9e4ce04e57c46613ae8ea4dec7765, not these six follow-up edits. Follow-up GUI visibility is untested. The new disconnect test covers production handler/model ownership behavior; it does not prove setupBackend signal wiring, real transport delivery, automatic reconnect scheduling or cancellation of earlier-session work on the same backend. Asynchronous backend cancellation remains an F4/M1 obligation. The existing command sink is a narrow production test hook, cleared at teardown, consistent with the existing setBackendForTest seam.Prior validation at signed head
a25935952ae9e4ce04e57c46613ae8ea4dec7765RelWithDebInfo, RTL enabled). The fresh signed-head rebuild passed all ten focused tests again in 1.80 seconds before live smoke.backend_slice_lifecycle_test,rtl_backend_test,hl2_receivers_test,radiomodel_slice_connect_enumeration_test,control_resource_service_test,automation_dsp_backend_readback_test,icom_family_test,icom_control_profile_test,sim_backend_testandanan_backend_test. They used--no-tests=error, isolated temporary settings and a Linux user/network namespace without an external interface. The lifecycle fixture injects state/commands and calls the bridge dispatcher directly, with no synthetic firmware peer, live radio or RTL acquisition.e60c3bea2ac8e5b762be9a3c6f52aa1bab8fb9adagainst that signed head. Physical RTL WFM retained slice 0, center lock and visible spectrum/waterfall after refused add/last-slice-close. Physical FLEX-6700 add-B/visible-close preserved A's ID, frequency, mode, filter and pan; B disappeared and visible center lock changed from B to off. Product Sim retained its one-slice behavior. RTL/Flex three-second captures contained finite, nonzero 24 kHz PCM at raw/post/output/final taps before and after. Proof instances stopped, radio ownership was released, and the RTL interface driver and PC mute state were restored.PC output was muted: captured PCM proves pipeline liveness, not listening quality. Both baseline and candidate recorded underruns; no dropout-free, performance, soak or radiocert claim is made. TX automation was disabled and no TX action occurred. Optional ONNX Runtime and sherpa-onnx features were unavailable; the GCC/Qt generated-MOC incomplete-type warning also occurred on the unchanged baseline.
Prior source-manifest digest:
f43d1068ff2bfa48a9274a4b04e3e4e9e2a3ca586ef83e82b8ef8a7cb1e75f3b, matching signed heada25935952ae9e4ce04e57c46613ae8ea4dec7765. Prior tested Linux artifact SHA-256:537bb51e536dfad3732c61426c458762a6590dac5501c7465b54c98e4a49349d. That commit passed local SSH-signature verification and GitHub Verified.Scope checklist
73, Ozy K6OZY · GPT-6 Astra-Ultra