Skip to content

feat(radio): route ordinary RX slice lifecycle through the backend seam - #5471

Open
Ozy311 wants to merge 3 commits into
mainfrom
codex/rtl-slice-lifecycle
Open

feat(radio): route ordinary RX slice lifecycle through the backend seam#5471
Ozy311 wants to merge 3 commits into
mainfrom
codex/rtl-slice-lifecycle

Conversation

@Ozy311

@Ozy311 Ozy311 commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

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 IRadioBackend lifecycle methods and routes ordinary callers through RadioModel. 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

  • Omitting an add frequency requests default placement. An explicit malformed, non-finite or non-positive value returns "slice add requires a finite positive frequency in MHz" after the capacity pre-check; it never becomes a default-frequency request.
  • Model refusal returns "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>".
  • Accepted operations can remain pending; re-poll get slices for confirmed ownership. Pending/refused removal preserves center lock. Confirmed removal uses the existing onSliceRemoved rebind 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 0ac8721a

Merged 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 655d6922

  • Rebuilt the Linux engine library, changed production MainWindow_Wiring object and ten focused test targets on Nobara-Dell with the same toolchain and RTL configuration listed below.
  • All ten CTests passed; the lifecycle fixture passed 103 assertions. Removing only the new immediate failure signal compiled and failed both intended diagnostic assertions. A separate disconnect mutation replaced the connected device serial with the already-changed next target; it compiled and failed the new ownership assertions. Exact source restoration, engine/GUI-object/test rebuild and a restored ten-test run passed. The earlier diagnostic mutation remains tied to its unchanged production fix.
  • Current-source strict engine boundary (102 tracked findings, zero blockers), registration, frozen CI gate, touchpoint manifest, generated bridge docs, changed-file accessibility lint and whitespace checks pass.
  • Six-file source digest 8676e50192e2b1e664c05cb37992efd8ecaf740c4b587ad4ae23eef6d42c7f22 matches both hosts after mutation restoration. Engine artifact SHA-256 62ecb16b4158bbad722b938fad6e71666030b3c0be723932500d173085aa68c3; lifecycle-test SHA-256 d67aaddeeb7984fae92e18f56a418ae9321fc8318734a5c8eb34918b0c74db95.
  • Signed follow-up commit 655d69224d12d5225ab5988252ec8b9d436f8f78; local SSH signature verification passes.
  • All seven PR checks subsequently passed at 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 a25935952ae9e4ce04e57c46613ae8ea4dec7765

  • Full Linux application and selected targets built on Nobara-Dell x86-64 with GCC 16.2.1, Qt 6.11.1, CMake 4.3.0 and Ninja 1.13.2 (RelWithDebInfo, RTL enabled). The fresh signed-head rebuild passed all ten focused tests again in 1.80 seconds before live smoke.
  • The ten tests were 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_test and anan_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.
  • Three deliberate mutations compiled and failed the lifecycle test: disabled replacement-generation checks, removed independent-creation capability guard, and Flex-only routing substituted for command-plane ownership. Exact restoration and the restored test passed. Strict engine-boundary, registration, frozen CI-gate, touchpoint manifest, bridge docs, GUI accessibility lint and whitespace checks passed; tracked boundary warnings remained.
  • Native Wayland receive-only smoke compared baseline e60c3bea2ac8e5b762be9a3c6f52aa1bab8fb9ad against 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.
  • All seven PR checks subsequently passed at this published head, including Linux, macOS and Windows builds. Those checks apply only to that historical head. macOS/Windows physical-dongle checks remain staged; macOS arm64 is the only available ARM host and Linux aarch64 requires an external tester.

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 head a25935952ae9e4ce04e57c46613ae8ea4dec7765. Prior tested Linux artifact SHA-256: 537bb51e536dfad3732c61426c458762a6590dac5501c7465b54c98e4a49349d. That commit passed local SSH-signature verification and GitHub Verified.

Scope checklist

  • Clean-room code; preserved Flex encoding.
  • No new flat-key AppSettings calls or persistence ownership.
  • No meter UI change, new smoothing requirement or CHANGELOG edit.
  • Capability/bridge documentation updated; no security advisory applies to this lifecycle foundation.

73, Ozy K6OZY · GPT-6 Astra-Ultra

…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.
@Ozy311 Ozy311 self-assigned this Sep 7, 2026
@Ozy311
Ozy311 marked this pull request as ready for review September 7, 2026 04:37
@Ozy311
Ozy311 requested a review from a team as a code owner September 7, 2026 04:38
@Ozy311 Ozy311 removed their assignment Sep 7, 2026

@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

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:5136 returns false for 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.
  • canCreateSlices is unread on exactly the two families whose values differ from the rule. The check at RadioModel.cpp:5110 sits below if (!hasCommandPlane()), so Flex's true (FlexBackend.cpp:146) and Sim's false (SimBackend.cpp:276) are both dead values — on Sim, addSliceOnPan creates 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 at radio-capabilities-map.md:38 half-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 remove on 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, but docs/automation-bridge.md:1461-1462 still documents add | 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 on onSliceRemovedclearCenterLockForSlice(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_sliceLifecycleCommandSinkForTest is a test hook on production RadioModel, cleared in teardownBackend(). setBackendForTest is 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.cpp hunk. Rather than trust the "extract to wireBackendReceiverState()" framing, I took a comment-stripped, whitespace-normalized multiset diff of the whole file between HEAD~1 and HEAD. The only differences are the generation captures/guards, the new sliceLifecycleFailed connect, ++m_backendReceiverGeneration in teardownBackend, and the rewritten addSlice*/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 no PanadapterModel (base sent the command anyway), no longer accepts freqMhz <= 0, and no longer falls back to the hardcoded 14.1 MHz / 0.2 MHz geometry at RadioModel.cpp:5044/5072 when the pan is missing. All three move toward refusal, which is the PR's stated direction.
  • Seq-0 false negative. dispatchSliceLifecycleCommand treats sendCmd(...) != 0 as "dispatched". If the sequence counter started at 0, the first command of a session would report a spurious refusal. RadioModel.h:1584 initialises m_seqCounter{1} and WanConnection.h:111 does 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 at RadioModel.cpp:5136, and the removed center-lock pre-clear is genuinely covered by onSliceRemoved (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 remove producer: 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_backendReceiverGeneration is captured at the top of wireBackendReceiverState and incremented at teardownBackend:1784 before the QObject::disconnect calls, and disconnect(m_connection, ...) runs before m_backend.reset() — so the fixture's backend-owned RadioConnection cannot be disconnected after free. testReplacement drives this from a producer thread, which is the right shape.
  • The tests themselves. They are not self-agreeing: testCommandAdapter sets canCreateSlices per family and then asserts creation succeeds for both, which is what exposed that the field is unread on the command-plane path; testNeutralLifecycle asserts 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 the dropped.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 on a259359 is green across all seven checks, which per ci.yml's -R filtering 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)

@aethersdr-agent

Copy link
Copy Markdown
Contributor

Antares Detector — candidate vulnerable file(s)

  • src/core/backends/anan/AnanBackend.cpp

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

@Ozy311

Ozy311 commented Sep 7, 2026

Copy link
Copy Markdown
Contributor Author

Review follow-up in 655d6922:

  • Fixed the blocking silent creation refusal: capability denial/backend refusal now logs and emits the existing sliceLifecycleFailed("create", -1, reason) signal. No Flex-command fallback is introduced.
  • Corrected both local-removal messages, clarified that canCreateSlices only gates the neutral hook, documented strict bridge arguments/refusal strings, and disclosed center-lock persisted-intent behavior. Flex/Sim capability values and behavior remain unchanged.
  • Added RTL → other family → RTL coverage plus real disconnect-handler/reconnect-staging tests for same-device reclamation, different-device pruning, pending-request preservation and single intent forwarding. This addresses bounded disconnect model-state coverage; transport wiring, automatic retry and same-backend asynchronous cancellation are not claimed and remain separate F4/M1 work.
  • The test-only command sink is deliberately narrow and cleared at teardown, following the existing injection seam.

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: AnanBackend::invokeExtension ignores the namespace/verb/argument and emits an unsupported-extension error; it does not dispatch an operation. The original PR's only ANAN change was the explicit false capability declaration. No unrelated security change was made.


73, Ozy K6OZY · GPT-6 Astra-Ultra

rfoust added a commit that referenced this pull request Sep 8, 2026
## 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>
@Ozy311 Ozy311 self-assigned this Sep 9, 2026
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.
@Ozy311 Ozy311 removed their assignment Sep 9, 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.

1 participant