Skip to content

RTL: analog multi-receiver audio, meters and release packaging - #6083

Merged
ten9876 merged 13 commits into
mainfrom
codex/rtl-multirx-delivery
Oct 11, 2026
Merged

ten9876 merged 13 commits into
mainfrom
codex/rtl-multirx-delivery

Conversation

@Ozy311

@Ozy311 Ozy311 commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

Analog RTL receivers gain independent squelch, receive-audio exports, meters and release packaging. This is the remaining analog delivery increment under RFC #5468; the WFM foundation, applet and live PPM changes are already in main.

Scope and separation

Rebased onto main d6f6a4f00e40e292d575c40cad7d0848406d89f5. Only the five remaining analog commits were replayed, followed by the release-control and user-documentation cleanup. The merged WFM/PPM changes and newer main changes are retained.

  • Independent manual/Auto SQL; selected-receiver APRS; stable TCI mapping; native Linux/macOS DAX receive feeds; relative receive meters with a per-device switch; averaging preserved during monitor-volume edits.
  • WFM controls follow the selected receiver and reject stale bindings. The normal launch remains one receiver. Explicit process-only 2/4/8 receiver evaluation remains separate from production capacity qualification; no new Linux ARM capacity result is claimed.
  • Official AppImage, macOS DMG and Windows packages require ENABLE_RTL=ON and REQUIRE_RTL=ON. Configuration fails if RTL or float-FFTW is missing. Jeremy approved required RTL and bundling pinned libusb. The completed AppImage is extracted and audited after the final linuxdeploy pass, including a byte comparison of its libusb payload. Windows dependency validation follows normal and delay imports recursively.
  • No ENABLE_HD_FM declaration, placeholder, release flag or decoder dependency remains in this analog increment. The actual optional switch and all explicit OFF release/build flags belong to deferred draft RTL: add opt-in native digital broadcast reception and local metadata #6002 / RFC [RFC] Optional Digital FM reception and program metadata #6079. That feature is not a prerequisite for this PR.
  • User documentation now covers RTL packaging, evaluation limits, squelch, meters, selected-slice AetherModem and receive-only DAX/TCI routing.

Shared behavior for review

Auto SQL uses the existing typed receive dispatcher and confirmed-state guards. RTL's 2048-bin detector scale is relative dBFS, not calibrated dBm or directly comparable with the display FFT; no misleading threshold line is drawn. An accepted capture revision retires a receiver's relative-level sample only when that receiver's detector input changes (passband, mode, membership, front end, capture placement or DC suppression); monitor gain, pan, mute and squelch edits keep it, so the meter does not blank during an AF gain drag. Other adapters retain their manual behavior and refuse the automatic request.

TCI initialization is batched as one worker job for every family. AETHER_SETTINGS_DIR suppresses legacy-profile migration and normal log pruning. The activeSliceCommandIssued connection uses receiveCommandSource() identity and Qt::DirectConnection; this shared-family guard remains part of the increment. Receive-only PipeWire routing drains stale audio while preserving the legacy Flex FIFO's sole-reader/EPIPE behavior. Dependency-prefix and Qt deployment fixes remain in packaging.

Validation

Native Nobara / GCC 16.2.1 / Qt 6.12 / Release application and daemon builds pass at a3458b09. 53/53 selected CTests pass, zero skips/failures, 221.35 seconds. This covers RTL capture/control/receiver banks, FM/WFM, SQL, spectrum/averaging, APRS, DAX/TCI, meters, applet lifecycle and actual PipeWire FIFO behavior. The analog CMake cache contains no Digital switch.

Platform CI passes on Linux, macOS and Windows. Static Checks and Docs pass.

Full Suite passes: 691 passed, five skipped, zero failures, 696 registered tests, 932.37 seconds. The skipped cases are four AppSettings safety scenarios and hl2_probe_spectrum_panadapter; they are not claimed as exercised.

All branch release-package workflows pass at exact head a3458b09: AppImage x86-64 and ARM, macOS DMG Apple Silicon and Intel, and Windows Installer. Both completed AppImages pass RTL runtime closure and libusb byte comparison. Both DMGs pass signing/notarization; Windows passes actual DLL closure and installer/artifact packaging. Artifact identities and checksums are retained. Release/Store publication jobs were skipped.

Already passed on this revision: 16 required-RTL configuration combinations, 9 package-helper tests, engine/routing/capability gates, generated manifests and references, frozen-test-gate and registration checks, build-options checks, LogAnalyzer checks, and the user-documentation production build. Commits are signed.

Historical operator multi-RX acceptance and earlier package/full-suite results remain in the PR discussion and original #6077. They are not substituted for this head's results. The intermittent historical Mac WFM receiver withdrawal during a filter/stereo transition remains unexplained; passing reruns did not establish a cause. No new live-RF, audible, sanitizer or production-capacity qualification is claimed.

This PR requests its own maintainer/code-owner review. #6002 remains draft. No release or Store publication is part of this validation.


73, Ozy K6OZY · Codex

@Ozy311
Ozy311 requested review from a team as code owners October 2, 2026 02:19
@Ozy311 Ozy311 self-assigned this Oct 2, 2026
@github-actions

github-actions Bot commented Oct 2, 2026

Copy link
Copy Markdown

Accessibility check findings

This PR touches src/gui/ files with 17 accessibility issue(s).
Full details appear as inline annotations on the diff above.

Files scanned : 26
Files with findings: 5
Total findings : 17

See docs/a11y.md for the canonical
Qt patterns (setAccessibleName, QAccessibleValueChangeEvent,
QAccessibleInterface subclasses, etc.). Structural remediation
(keyboard navigation, custom QAccessibleInterface subclasses)
is tracked in #3288.

Findings are warnings only — the build is not blocked.

@aethersdr-agent aethersdr-agent Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Issue fit

Yes, with one decision deferred. There is no Fixes #NNNN; the governing document is RFC #5468, whose own review-scope table names this PR: "#6083 — Analog multi-RX, SQL, meters, DAX/TCI/APRS routing and RTL packaging … Official builds require RTL; optional Digital remains OFF." Every feature group in the body maps to hunks in the diff, and I found no body claim the code contradicts. The RFC also rules that "Release packaging is an infrastructure/code-owner review" — so packaging is in scope here but routed to a different reviewer than the DSP work. That text lives in the RFC body (author-maintained), so whether a code owner has signed off on making RTL a hard release dependency is still open, not settled.

Reviewed increment only: 5e39790a..4b1816ac (124 files, +6655/-503), the single commit on top of #6082. The upstream stack (#5924 → #5973 → #5974 → #6082) is not re-reviewed here. Merge-base with main is 68cdf661.

CI on 4b1816ac: Full test suite — success, Static checks — success, Sanitizer option configures — success. The body said the full suite had "no completed verdict yet"; it has now passed, which is stronger evidence than the 39 local tests. I also re-ran the Python gates in the head checkout: touchpoint manifest up to date, test registration OK, capability records at the frozen 71, engine boundary 0 blocking, command plane 413/413 baseline, CI test gate matches the frozen list.

Scope

Grouped by feature — 124 files across ten concerns don't read usefully one row per file.

Feature group Representative files Claimed? Verdict
2/4/8 RX bank + evaluation admission RtlSdrBackend.cpp, RtlSdrWorker.*, RtlReceivePipeline.*, RtlCaptureTransaction.* yes in scope
Manual/Auto SQL + schema-1 automatic/marginDb RtlSquelchGate.h, RtlSliceSettings.cpp, SliceModel.*, AutomationServer.cpp yes in scope; new persisted + bridge surface → maintainer decision
Relative meters + receiveMetersEnabled + rtl/receive_meters.set MeterModel.*, MeterDef.h, SMeterWidget.*, SmartMtr*, VfoWidget.*, RtlDeviceSettings.cpp yes in scope; new persisted key + new extension verb → maintainer decision
Native DAX receive DaxReceiveModel.*, DaxApplet.*, PipeWire*, VirtualAudioBridge.*, hal-plugin/AetherSDRDAX.cpp yes in scope; see finding 2
Selected-receiver APRS / AX.25 receive Ax25ReceiveModel.*, Ax25HfPacketDecodeDialog.*, MainWindow_DigitalModes.cpp yes in scope
Stable TCI receiver mapping TciServer.* yes in scope
Spectrum averaging across monitor edits SpectrumTemporalAverage.h, SpectrumWidget.* yes in scope
WFM applet follows selected receiver WfmApplet.*, RxApplet.*, AppletPanel.* yes in scope
Release packaging 4 workflows, CMakeLists.txt, cmake/AetherRequireRtl.cmake, 5 setup scripts, rtl-dependencies.json, check_rtl_package.py, test_require_rtl.py, build-installer.sh, build-windows-local.ps1 yes (title) in scope per RFC #5468, routed to code-owner review
ENABLE_HD_FM reserved option whose only effect is FATAL_ERROR CMakeLists.txt yes in scope; nit
AETHER_SETTINGS_DIR now suppresses profile migration + log pruning src/main.cpp no explained by the isolated-evaluation requirement in rtl-multirx-analog.md; please add a body line
PortAudio -Wno-error=implicit-const-int-float-conversion; *_LINK_LIBRARIES prefix preservation; macdeployqt added to the local macOS installer script setup-macos-deps.sh, CMakeLists.txt, packaging/macos/build-installer.sh no consequences of the new pinned-prefix setup; correct, but undisclosed

Nothing here is unrelated to the issue; the three undisclosed rows are all downstream of changes the body does claim. No scope blocker. Two rows add durable third-party-visible surface (a persisted schema field pair, a persisted device key, an extension verb) — flagged for a maintainer, not as a violation.

Blockers

1. Every packaging change in this PR has never executed, anywhere. All three release workflows are on: push: tags: ['v*'] + workflow_dispatch (appimage.yml:3-5, macos-dmg.yml:3-5, windows-installer.yml:3-5), and get_check_runs on 4b1816ac returns only Static checks, Sanitizer option configures and Full test suite. So REQUIRE_RTL=ON configure, setup-rtl-deps.sh/.ps1, the check_rtl_package.py invocations, the source/notice staging and the Windows POST_BUILD DLL loop will all run for the first time on a release tag. The body concedes "No new current-head Windows package … is claimed," but the consequence isn't stated: a mistake in any of those steps surfaces as a failed release, not a failed PR.

This is cheap to clear and I'd rather see it cleared than argued: workflow_dispatch all three on this branch and link the runs. If any can't be dispatched from a non-default branch, say so and I'll drop it to a nit. Inline on appimage.yml:252.

2. The Windows arm of check_rtl_package.py is materially weaker than the macOS arm, in exactly the place the new pthreads dependency lands. macOS walks every Mach-O's LC_RPATH/otool -L transitively and fails on any dependency not resolvable inside the payload. Linux checks that each rtlsdr/fftw3f/libusb DT_NEEDED name exists in usr/lib. Windows only checks that the three DLLs sit beside the exe and that the app's own import table mentions rtlsdr and fftw3f — it never walks rtlsdr.dll's imports for reachability.

rtl-release-packaging.md asserts "The pthreads source/build is a Windows upstream configure prerequisite; it is not an imported runtime of the library-only RTL target." I can't test that without a Windows build, and the deploy step copies only third_party\rtl-deps\bin\*.dll (rtlsdr.dll + libusb-1.0.dll), so pthreadVC2.dll never ships. If that assertion is wrong, the installer ships an rtlsdr.dll that cannot load, and the validator passes anyway. Either extend the Windows arm to dumpbin /dependents on rtlsdr.dll and assert every non-system import is present, or paste the dumpbin /dependents rtlsdr.dll output as evidence. Inline on check_rtl_package.py.

Nits (non-blocking)

  • drainFd is opened for every RX pipe, not only receive-only ones (PipeWireAudioBridge.cpp:312), so the pre-existing Flex DAX path changes too: AetherSDR now permanently holds a second reader on each FIFO, meaning the write side can no longer observe EPIPE/"no readers" if module-pipe-source goes away — writes fill the kernel buffer and then fail EAGAIN, and the ::write() return at line 459 is unchecked. A drainFd open failure also now aborts a channel that previously opened fine. Reasoned from code; not reproduced. Gating the ::open on m_receiveOnly would confine both to the new path.
  • Same function: if (actual < capacity && m_receiveOnly) returns before the if (actual < 0) debug branch, so that branch is unreachable in receive-only mode.
  • appimage.yml:252 builds libusb + librtlsdr + float FFTW from source on every run with no actions/cache, while windows-installer.yml:137-147 got a cache step and macos-dmg.yml folds into the existing one. Linux release builds pay the compile each time and widen the per-build network surface.
  • HAL plugin AetherSDRDAX.cpp:94: the compare_exchange_strong is a real improvement over the bare store, but it can't detect an ABA reset to the same cursor value. Worth a comment so the next reader doesn't over-trust it.
  • The ENABLE_HD_FM option exists only to FATAL_ERROR when ON. Reserving the name is defensible; a one-line comment saying it is a reservation (not a buildable feature on this branch) would help.
  • GitHub's archive/<sha>.tar.gz tarballs are not guaranteed byte-stable across server-side git/compression changes, so rtl-dependencies.json's rtl hash can break without the pin moving. git clone + git checkout <sha> or a release asset is sturdier.

What I tried to break (and could not)

  • The evaluation-capacity gate. RtlSdrBackend.cpp:131-137 requires AETHER_AUTOMATION == "1" and an exact "2"/"4"/"8", is #if-gated to the three declared platforms, and never persists. rtl_backend_test.cpp:56-71 tests the env var alone (refused), the pair (accepted), and "9" (refused) — it fails if either half of the guard is removed. Doc and code agree; kQualifiedReceiverCapacity stays 1.
  • The automatic/marginDb schema addition. Both fields are optional, marginDb is range-checked 5–20, non-bool automatic is rejected, and receiveMetersEnabled defaults to true when absent — so older documents load unchanged. I then tried to reach the return false on automaticSquelch && !squelchEnabled (RtlSliceSettings.cpp:144) from the UI: setSliceAutoSquelch forces squelchEnabled = true when enabling (RtlSdrBackend.cpp:957) and setSliceSquelch clears automaticSquelch (:940), and both the pipeline (RtlReceivePipeline.cpp:78) and the transaction (RtlCaptureTransaction.cpp:246) reject the combination at admission. The invalid state can't be produced, so the whole-document rejection is defensive only — and whole-document rejection is this schema's pre-existing contract, not a new behaviour.
  • Supply-chain ordering in the new setup scripts. Every URL in rtl-dependencies.json is HTTPS; the RTL pin is an immutable commit SHA. fetch() in setup-rtl-deps.sh and the loop in setup-rtl-deps.ps1 both verify before tar, and verify on a cache hit too, not only after a download. _verify_sha256.sh fails hard even when no hasher exists. setup-fftw.ps1 moves Confirm-Sha256 out of the download-if-absent block, so a cached zip is now verified — a real hardening improvement in this PR. I could not verify the five hashes themselves: this sandbox's proxy blocks fftw.org and codeload.github.com, so treat the pin values as unverified by me.
  • tools/test_require_rtl.py. I expected a tautology — 17 lines claiming 16 combinations. It isn't: it writes a probe script that include()s the real cmake/AetherRequireRtl.cmake and runs cmake -P for all 16 products, asserting the exit code against not required or (enabled and rtl and fftw). It would catch an inverted or dropped condition. I couldn't run it (no cmake on this box; it fails FileNotFoundError, which is also why it must stay out of any environment lacking cmake).
  • CMake ordering. AetherSDR and aetherd are defined at CMakeLists.txt:1614/:1624, well before the POST_BUILD loop at :2733, so the foreach(_rtl_app …) cannot hit "no TARGET has been created". RTL_FFTW3F_TARGET is set at :2697/:2700 in the same directory scope the NR4 hunk reads at :3114, so that hunk is live, not dead. The find_path/find_library fallbacks do set the *_FOUND variables aether_require_rtl() tests.
  • The control_receive_test.cpp edits. The body says two outdated assertions were updated. That is exactly what changed: the filter-mode count/WFM contract and the dBFS/bin → dBFS/2048-bin unit label. Nothing else in that file moved, and the WFM branch is a constexpr follow of kQualifiedWfmEnabled rather than a hardcoded pass.
  • AX.25 teardown. Ax25ReceiveModel::~Ax25ReceiveModel clears state->owner and calls retire() under the mutex before thread.quit()/wait(), so an in-flight worker result finds a null owner and is dropped rather than delivered into a half-destroyed model. Matches the decoder-pcm-contract.md claim.
  • HAL memset bounds. totalSamples = bytesCount / sizeof(float), so both memset(dst, 0, bytesCount) and memset(dst + toRead, 0, (totalSamples - toRead) * 4) stay inside the bytesCount CoreAudio provides, including a non-multiple-of-4 bytesCount.
  • Socket-test sweep (§2 disclosure). I grepped the added and modified tests in this PR's own sources, not the working tree: ax25_receive_model_test.cpp, dax_receive_applet_test.cpp, pipewire_rx_bridge_test.cpp, tci_rx_audio_test.cpp, rtl_backend_test.cpp, control_receive_test.cpp. No test opens, binds, listens or connects. The only hits were a local helper named bind(model, fixture) in the AX.25 test and a QWebSocketProtocol::CloseCode callback on an injected m_io seam in the TCI test. The body's "eight concurrent TCI clients" is driven through that injected worker, not sockets — nothing to record, and no socket test was removed.

Everything above is reasoned from the head checkout, not reproduced at runtime. I have no build, no test execution and no GUI here, so I cannot confirm the 39-test local result, the Apple Silicon build, or the unresolved intermittent WFM-receiver withdrawal the body honestly leaves open. That last item is the one I'd most want settled before a capacity claim, and the body is right not to call three passing reruns a root cause.

The required CodeGuard security audit was launched over this file list; it had not reported when I posted, so I ran the highest-value checks from its brief myself — the SHA-before-extraction ordering, URL schemes, HAL memset bounds, workflow triggers and drainFd lifetime, all above. I did not independently confirm the 1082 bulk CodeGuard hits; the CS-PII-*, CS-SEC-AWS-SECRET, CS-SEC-PASSWORD, CS-VLN-XSS-* and CS-VLN-SQLI-SLEEP categories have no plausible home in a C++/YAML/shell tree like this one and I am not repeating them. The CS-VLN-CMDI-BACKTICK/-PIPE/-SUBSHELL hits I did check (appimage.yml:77,131,258,325,359,445; static-checks.yml:14,36,81,…) land on lines this PR does not touch and carry no untrusted input, so I'm dropping them rather than forwarding noise.

Recommendation

Needs maintainer decision, and specifically two different maintainers. The DSP/model half is the strongest-tested part of this PR — the schema additions are backward-compatible and range-validated, the evaluation gate is genuinely mutation-resistant, the setup scripts verify hashes in the right order, and every static gate plus the full Linux suite is green. I tried the obvious ways to break the capacity gate, the squelch invariant and the AX.25 teardown and the code held.

The packaging half is where I'd hold. Per RFC #5468 it belongs to a code owner, and the two blockers are both about evidence rather than design: a release path that has never run once, and a Windows validator that would not catch the one dependency the docs assert is absent. Concrete next step: workflow_dispatch the three packaging workflows on this branch and link the runs, and either strengthen the Windows arm of check_rtl_package.py or paste dumpbin /dependents rtlsdr.dll. Both are cheap and would turn the largest remaining unknown in this PR into a fact.

Thanks for splitting the digital decoder out — reviewing the analog tree on its own is a great deal easier, and the body's willingness to name what isn't proven (no fresh Windows package, no Linux ARM qualification, the unresolved WFM withdrawal) made this review much faster than it would otherwise have been.


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

Comment thread .github/workflows/appimage.yml
Comment thread tools/check_rtl_package.py Outdated
Comment thread src/core/PipeWireAudioBridge.cpp Outdated
Comment thread src/main.cpp
@aethersdr-agent

Copy link
Copy Markdown
Contributor

Three addenda to my review above — one correction of mine, one question, one piece of reading I finished afterwards.

Correction to my own file:line. I wrote AetherSDRDAX.cpp:94 for the compare-exchange nit. It is line 95, with the failure-path memset at 96. The substance is unchanged (the CAS is a real improvement over the bare store; it just can't detect an ABA reset to the same cursor value).

A question, not a finding, about check_rtl_package.py:23. For Linux the app filter is x.name in {"AetherSDR", "aethersdr"} and line 24 exits non-zero on len(apps) != 1. With CMAKE_INSTALL_PREFIX=/usr the installed binary is AppDir/usr/bin/AetherSDR, and appimage.yml consistently uses lowercase only for the icon (aethersdr.png) and the new source directory (usr/share/aethersdr/), so I expect exactly one match. But linuxdeploy --desktop-file also writes into the AppDir root, and the desktop file is generated rather than committed, so I could not confirm from the checkout whether anything lands as a lowercase aethersdr binary or wrapper. If it does, the validator fails a correct payload. Worth a glance when you run the dispatch builds — it's one more reason those runs are the cheapest way to close out blocker 1.

DaxReceiveModel holds up to a hostile read. I reviewed this one after posting and want it on the record, because it is the part of the change I pushed hardest on and it did not give. I specifically tried to break it with a synchronous listener that disables, rebinds or destroys the model from inside channelReset/audioReady:

  • The reentrancy guard (m_state->processing plus the qScopeGuard) blocks a re-entered receive, and the guard restores through a QPointer so it can't write to freed state.
  • Every emit is followed by a revalidation of self, m_state->enabled, binding.current(), channel.binding.alive and input.current() before anything else is touched.
  • State::Channel& channel is a reference into a fixed std::array<Channel,8>, so a concurrent refreshBindings() assigning channel = {} through the same slot cannot dangle it, and the alive comparison afterwards catches the rebind.
  • In the final if (!accepted && self && channel.converter == converter), self is tested before channel.converter, so && short-circuiting keeps the dereference off a destroyed m_state. That ordering is load-bearing and easy to lose in a later refactor — a one-line comment saying so would be cheap insurance.
  • The converter-identity capture in the process callback means a reset mid-callback stops delivery rather than emitting into a retired route, and the live-producer refusal (pin->owner != binding.alive) plus the kMaxStreams bound are both real, not just documented.

Binding keyed on alive rather than on trx is the right call and is what makes "a live old producer cannot seize a newly created receiver's route" in docs/native-dax-receive.md an accurate claim rather than an aspiration. The only soft spot I found is cosmetic: if two slices ever resolved to the same trx, refreshBindings would silently keep the last one — which shouldn't be reachable if TciServer's map is 1:1.

Finally, on process: the CodeGuard security audit I noted in the review as not-yet-reported is still running. If it surfaces anything I did not already cover, I will verify it against the checkout and post it here rather than opening a second review — credited as "security-audit (CodeGuard)". The two blockers and the recommendation in my review stand as written either way.


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

@aethersdr-agent

Copy link
Copy Markdown
Contributor

security-audit (CodeGuard) has completed. It surfaced two leads I had not covered. I verified both against the diff with git, which that session could not do, and neither is introduced by this PR — so neither is a blocker here. But one of them has a published-GHSA precedent in this very repository, so I don't want it to just evaporate.

Not a blocker for #6083 — but worth its own issue

The macOS DAX transport is POSIX shared memory on a predictable global name, created world-readable and world-writable:

  • src/core/VirtualAudioBridge.cpp:46 — shmName() returns /aethersdr-dax-%1, fully predictable.
  • :52 / :55 — shm_open(name, O_RDWR, 0666), then on failure shm_open(name, O_CREAT | O_RDWR, 0666). No O_EXCL.
  • :66-67 — the "open existing" branch deliberately skips ftruncate ("segment already exists at the right size") and never fstats size or owner before the mmap(sizeof(DaxShmBlock)) at :70 and the header stores at :79-83.
  • hal-plugin/AetherSDRDAX.cpp:113-117 — the consumer half: shm_open(name, O_RDWR, 0666) then mmap(nullptr, sizeof(DaxShmBlock), …) with no fstat. DaxShmBlock is ~384 KB, and this runs inside coreaudiod.

The reason I'm raising a pre-existing issue at all is that src/core/PipeWireAudioBridge.cpp:247-251 — in this file, in this PR's diff neighbourhood — documents the Linux twin of exactly this exposure as a fixed vulnerability:

FIFO lives in the per-user XDG runtime directory with mode 0600. Pre-fix code used /tmp/aethersdr-dax-%1.pipe with mode 0666, which any local user could read (operator RX audio) or write (operator TX audio with the licensed callsign — an FCC liability). See GHSA-x8xf-4g5v-ppf9.

So the project has already ruled that mode-0666 DAX audio transport on a predictable path is a vulnerability, and fixed it for the Linux FIFO path. The macOS shm path appears never to have had the same treatment. The same two consequences read across: a local user can read operator RX audio, or write into the segment on a path that ends up as transmitted audio under the operator's callsign. There is also a pre-creation variant — squat the name at a smaller size and the "reuse" branch wins without any size or owner check, which on the HAL side faults inside a privileged audio daemon.

I'd suggest a separate security issue (plausibly a GHSA, given the precedent), not an expansion of this PR. The shape of the fix mirrors the one already shipped on the Linux side: create with 0600 and O_EXCL, and on any reuse path fstat the fd and require st_uid == getuid() and st_size == sizeof(DaxShmBlock) before mmap. Happy to file it if you'd rather it not sit in a PR thread. Both git diff checks are explicit — git diff 5e39790a..4b1816ac -- src/core/VirtualAudioBridge.cpp and … -- hal-plugin/AetherSDRDAX.cpp return no shm_open/mmap/0666 lines, and the GHSA fix comment is likewise not in this PR.

One small thing that is adjacent to the new code

PipeWireAudioBridge.cpp:287 opens the FIFO write end as O_WRONLY | O_NONBLOCK, with no O_CLOEXEC — while the drainFd this PR adds at :312 correctly has it. Since runPactl spawns pactl children (and unloadModules()/cleanupStaleModules() do so while these fds are live), the write end of the 0600 FIFO leaks into them. The new line is the better-behaved of the two; making the older one match would be a one-token change. Non-blocking.

Correcting myself

In thinking about why the shm surface matters here I assumed this PR widened DAX from four channels to eight, because VirtualAudioBridge.h changes a comment from "per-channel RX gain (1-4)" to "(1-8)". That is wrong: NUM_CHANNELS is already 8 on the base branch and the gain array is already eight wide. The edit is a stale-comment fix, not a capacity change. No claim in my review depended on it, but I'd rather retract it than leave it standing.

What the audit independently confirmed

Useful as a cross-check on my own review rather than as new findings, and all of it matches what I posted:

  • Supply chain clean on every question asked. All URLs HTTPS; hash verification runs after the cache-hit check and before every extraction (setup-rtl-deps.sh:15-19, setup-rtl-deps.ps1:14-16, setup-fftw.ps1:58-59 and 76-86, setup-macos-deps.sh:102-111), so a cached-but-unverified archive is unreachable; _verify_sha256.sh:18-21 fails closed with no hasher. The field helper passes JSON values as argv to python3 -c, never through a shell.
  • No CI injection and no over-broad permissions. Every third-party action SHA-pinned; no github.head_ref or event.*.title/body interpolation; the only two interpolations are an integer PR number and a workflow_dispatch boolean passed via env:. It reached the same trigger finding I did independently.
  • All three new memcpy/memset sites in bounds, including the HAL bytesCount reasoning and the rp % RING_SIZE wrap safety I'd checked, plus RtlRfExtractor.cpp:153-154 and RadioModel.cpp:5085/:6607 which I had not.
  • RtlStartupTrace.h only serializes — the api:deserial triage is a false positive; nothing parses untrusted JSON.
  • No fd leaks on the new PipeWire failure paths, and PipeWireNativeRxSource's ring arithmetic is sound (power-of-two sizes, mask, array declared at kMaximumRingSize).
  • The path: triage on TciServer.* and ClientDisplaySettings.h is noise — no filesystem API in those files at all.
  • The CS-PII-*, CS-SEC-AWS-SECRET, CS-SEC-PASSWORD, CS-VLN-XSS-*, CS-VLN-SQLI-SLEEP, CS-VLN-SSRF-* and CS-MAL-SCRIPT categories were all discarded as scanner noise, independently of my own dismissal. A credential grep over scripts/setup/ turned up only prose about the keychain — no secret values.

My two blockers and the Needs-maintainer-decision recommendation stand unchanged. Nothing the audit found bears on either, and nothing it found is a reason to hold this PR.


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

@github-actions

github-actions Bot commented Oct 3, 2026

Copy link
Copy Markdown

Accessibility check findings

This PR touches src/gui/ files with 17 accessibility issue(s).
Full details appear as inline annotations on the diff above.

Files scanned : 26
Files with findings: 5
Total findings : 17

See docs/a11y.md for the canonical
Qt patterns (setAccessibleName, QAccessibleValueChangeEvent,
QAccessibleInterface subclasses, etc.). Structural remediation
(keyboard navigation, custom QAccessibleInterface subclasses)
is tracked in #3288.

Findings are warnings only — the build is not blocked.

@aethersdr-agent

Copy link
Copy Markdown
Contributor

AppImage packaging is red on both architectures at 4a242e4f

First: dispatching all three packaging workflows was the right call, and it has already paid for itself — this is exactly the break that would otherwise have landed on a release tag. Thank you for running them.

Status at 4a242e4f (run 37096565763)

Check Result
Static checks ✅ success
Sanitizer option configures ✅ success
build-dmg (macos-15, apple-silicon, arm64) ✅ success
build-dmg (macos-15-intel, x86_64) ⏳ still running
build-windows ⏳ still running
build-appimage (ubuntu-22.04, x86_64) ❌ failure
build-appimage (ubuntu-24.04-arm, aarch64) ❌ failure
Full test suite ⚠️ cancelled (see below)

What failed, and what it is not

On the aarch64 leg the failing step is Build AppImage. Everything before it is green:

  • Build pinned RTL dependencies (including float FFTW) ✅ — setup-rtl-deps.sh built libusb, rtl-sdr and float FFTW from source
  • Configure ✅ — so -DENABLE_RTL=ON -DREQUIRE_RTL=ON resolved the pinned prefix correctly
  • Build ✅ and Install to AppDir ✅ — nothing fails to compile or install

So this is not a compile failure and not a missing-RTL configure failure. It is packaging.

It is also not ARM-specific and not CI infrastructure: the x86_64 leg went red too, and the only thing that changed for both legs is this PR's RTL packaging. (My tooling only returns the first failing job's step breakdown, so I can see the step name for aarch64 but not for x86_64 — if x86_64 died somewhere else, that's worth knowing and would change the picture.) The macOS arm64 DMG passing is useful evidence in the other direction: the shared setup-rtl-deps.sh and the macOS branch of check_rtl_package.py are fine. It's the Linux path.

The step now does two things, and I can't tell which one broke

At the PR head, .github/workflows/appimage.yml Build AppImage (≈ L412–426) runs linuxdeploy and then, newly:

python3 tools/check_rtl_package.py AppDir --platform linux

I can't read the step's output from this environment (the log API is blocked here), so I'm not going to guess at an error string. The single most useful thing you can paste is the tail of that step, and specifically: did RTL-PACKAGE-OK: print before the job died?

  • If it printed → linuxdeploy itself failed, or the Wayland assertion/mksquashfs stage did.
  • If it didn't → the new validator refused the AppDir, and its message names the exact library.

Where I'd look first

The Linux branch of tools/check_rtl_package.py is strict about location, not just presence:

libdir = root / "usr/lib"
...
if any(token in name for token in ("rtlsdr", "fftw3f", "libusb")) and not (libdir / name).is_file():
    raise SystemExit(f"Unreachable RTL dependency: {binary}: {name}")

and the earlier rglob gate raises Missing RTL package dependency: libusb-1.0.so* if the library isn't anywhere in the AppDir at all. The PR adds third_party/rtl-deps/lib to LD_LIBRARY_PATH so linuxdeploy can resolve the DT_NEEDED entries — but resolving is not the same as copying. linuxdeploy carries a hardcoded exclude list of libraries it deliberately refuses to bundle (host-provided things it expects to find on the target system), and it matches by soname, not by path — so a library on that list is skipped even when it came from your own prefix. Worth checking whether libusb-1.0.so.0 is on the list for the linuxdeploy build the Download linuxdeploy + Qt plugin step pulls.

If that's the cause, LD_LIBRARY_PATH can't fix it; the lever is naming the libraries explicitly on the linuxdeploy invocation (--library <path>, if the downloaded build has it) so they're staged regardless of the exclude list — or --exclude-library handling, depending on version.

One process suggestion

Split Build AppImage into two steps — linuxdeploy, then check_rtl_package.py. Right now a red there is ambiguous between "bundling broke" and "the new validator rejected the payload", which is the position this comment is in. Two steps makes the next failure self-describing, and it's cheap.

Full test suite is cancelled, not passed

The Linux full suite run linked in the PR body as current validation came back cancelled — probably superseded by a newer push. Cancelled isn't a pass, so that link doesn't yet carry evidence for this head and will need a re-run. Your body is already careful to say "dispatch is not a pass", which is the right instinct; this is just the specific one to redo.

Still open

Windows and the Intel DMG are still running. Copilot's question about whether rtlsdr.dll's own imports resolve without pthreadVC2.dll will get a real answer from the Windows leg — if docs/rtl-release-packaging.md's assertion is right, the installer build and its check_rtl_package.py --platform windows pass; if it's wrong, you'll see it there rather than having to reason about it. I'll hold off on that one until the job finishes.

The nit about no actions/cache for third_party/rtl-deps is real but orthogonal — it only costs wall-clock (libusb + rtl-sdr + float FFTW rebuilt from source on every leg, every run), and while you're iterating on this failure that cost will add up.

Happy to re-check once you've got the step tail or a fresh run.


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

@github-actions

github-actions Bot commented Oct 3, 2026

Copy link
Copy Markdown

Accessibility check findings

This PR touches src/gui/ files with 17 accessibility issue(s).
Full details appear as inline annotations on the diff above.

Files scanned : 26
Files with findings: 5
Total findings : 17

See docs/a11y.md for the canonical
Qt patterns (setAccessibleName, QAccessibleValueChangeEvent,
QAccessibleInterface subclasses, etc.). Structural remediation
(keyboard navigation, custom QAccessibleInterface subclasses)
is tracked in #3288.

Findings are warnings only — the build is not blocked.

ten9876 added a commit that referenced this pull request Oct 4, 2026
…#5924)

## Summary

RTL receive changes now publish only after hardware readback and DSP
adoption. Capture browsing preserves each slice's frequency and
settings, parks receivers outside usable capture, and resumes them when
capture returns. Offscreen markers reveal the receiver through a
confirmed capture transaction. Zoom uses a continuous 65,536-sample FFT
(36.621 Hz bins at 2.4 MS/s), independently of audio and capture
bandwidth.

FM/FM-N have symmetric capability-bounded filters, 48 kHz per-receiver
PCM, squelch, session/revision fences, rollback and accepted-state
persistence. Requested or refused state never becomes saved live state.
Spectrum averaging preserves RF history through display movement, and
coherent capture coverage supports the 3D waterfall. PPM/DC settings use
confirmed backend controls. Production admission at this checkpoint
remains one receiver.

Implements the bounded receive runtime of RFC #5468; respects the
backend seam obligations in #5554/#5262. WFM stereo, optional digital
decoding, live PPM interaction and multi-RX delivery are downstream
increments.

## Integration and validation

Reconciled with main at `a798558ac` (#6118).

The reconciliation preserves main's typed tune/filter/mode/APF intents,
HL2 squelch and WDSP changes. Each slice's SQL intent uses the existing
common audio-intent binding once; the merge does not add a duplicate
backend connection. The confirmed-pan dispatch comment now distinguishes
an accepted request awaiting observation from refusal. Runtime
documentation now describes the implemented 65,536-bin display FFT.

Checkpoint `efc358a1` native Apple Silicon/Qt 6.12 build and CTest:
**5/5 pass**, no skips, in 11.29 seconds (`receive_intent_routing_test`,
`slice_apf_agc_off_seam_test`, `flex_slice_mode_intent_test`,
`rtl_model_acceptance_test`, `slice_model_letter_test`). These are
socket-free model/dispatch regressions; they do not claim live-radio,
GUI or hardware timing qualification. The downstream #6083 application
and broader tests are separately revision-pinned there.

This requests human review and preserves the outstanding in-scope
maintainer decisions; it does not mark the free-pan amendment approved.
HD decoding is outside this PR and HD consent is not a merge condition
here.

Upstream's single spectrum outlet and socket-free test rehoming are
preserved. The real model derives paced waterfall rows from spectrum
observations; retired orphan waterfall signals are not restored. The new
upstream receive-contract test now expects RTL's implemented FM/FM-N
filter capability. Generated seam/touchpoint manifests are regenerated.

Earlier downstream aggregate evidence (not rerun on this individual
head): the aggregate builds AetherSDR with native Apple Silicon/Qt 6.12,
RTL required and the optional decoder OFF. Focused downstream tests
already pass for Flex mode dispatch, confirmed RTL model/capture state,
FFT averaging, WFM, the PPM widget and seam consumers. This is aggregate
evidence, not a claim that every platform was rerun on this individual
head. Fresh hosted checks are required on this PR.

The operator reports that the delivered `7601593f` build ran stably for
days. That is live hardware feedback on the delivered build, not an
instrumented soak of the new upstream integration. Current-head
sanitizer, ARM throughput and full release-artifact qualification are
not claimed.

## CI integration repair

Upstream Radio Setup test targets (including #6008's
`peripheral_auth_dialog_test`) compile `RadioSetupDialog.cpp`, so they
also list the RTL widget and its availability registry. Both repaired
tests build and pass on the native Mac decoder-OFF integration (2/2,
4.00 seconds). This follow-up changes test linkage only; earlier
application/regression measurements retain their stated revisions.
New-head CI is required and remains distinct from those measurements.

## Maintainer decisions

All of the decisions below were made by Jeremy, the maintainer.

- **2026-09-23 rulings.** The [mode-transition, seam and scope
rulings](#5924 (comment))
stand.
- **2026-10-03, free capture browsing.** The RFC #5468 amendment of
2026-09-25 (free capture browsing with parked slices) is **approved as
RTL-only**; other backends keep their existing pan behavior.
- **2026-10-03, later seam additions.** The seam pieces added after
09-23 are **ratified as RTL-owned**, on the same terms as 09-23: a
second backend must justify reuse. They are `SquelchLevelScale`'s `unit`
and `modesExclusive` fields; `requestReceiveTune(view)`,
`recenterReceiveCapture` and `ReceiveCapturePlacement`; and the
`BackendPanAveraging` persistence/description fields. Averaging
persistence and coherent 3D presentation are part of that ratification.
- **2026-10-04, one squelch record.** RTL's squelch is described by
#6127's `RadioCapabilities::squelchLevelScale`, not a second record.
`unit` carries RTL's dBFS/bin, and `modesExclusive` marks the record as
the receiver's own gate.

HL2 FFT-FPS/dBm persistence coordination stays separate, with sibling
ClientDisplay tables preserved.

## New gui/ touchpoints (ratified)

| Header | New includer | Why |
|---|---|---|
| `core/backends/RadioCapabilities.h` | `src/gui/ModeFilterPresets.cpp`
| FM/squelch availability reads the declared
`ReceiveFilterControl`/`SquelchLevelScale` records |
| `models/PanadapterModel.h` | `src/gui/PanFrameGuard.h` | Snapshots the
pan's geometry when a frame arrives, so a confirmed FFT is never painted
against a newer range |
| `models/RadioModel.h` | `src/gui/ReceiveCaptureAction.h`,
`src/gui/RtlReceiverSettingsWidget.cpp` | Capture-recenter action and
the RTL device settings both go through RadioModel's capability-gated
verbs |
| `models/SliceModel.h` | `src/gui/MainWindowHelpers.cpp` |
`requestSlicePanCenter` resolves the slice's pan and sends confirmed
backends a Center tune instead of a pan request |

## Effects on other radios

- **Squelch availability** follows one rule in both the RX applet and
the VFO (`ModeFilters::squelchAvailableInMode`). A `squelchLevelScale`
with `modesExclusive` (RTL only: FM/FM-N, −120 + 1.2·level dBFS/bin,
Auto SQL) lists the supported modes, keeps each receiver's manual/Auto
intent, and has Auto start from the absolute threshold. Otherwise the
#2504 rule disables SQL in DIGU/DIGL/NT/RTTY/CW, unless the radio
declares mode-independent squelch. Every other family's scale leaves
`modesExclusive` false, so its SQL line, Auto SQL and availability are
unchanged.
- **SQL's unavailable reason** stays on the button. Where SQL is
unavailable in the current mode, the RX applet and the VFO no longer
replace the registry's accessible reason with the Auto-SQL description.
- **FM-family passband availability** follows
`ModeFilters::fmFilterAdjustable`. Declared FM rows are authoritative
(RTL). A radio-owned filter with no FM row refuses FM edits (Flex, per
FlexLib `Slice.cs`). HL2, ANAN, Icom and the Demo keep their existing
adjustable passband.
- **WFM/WBFM** use carrier-centered FM edges and no longer inherit the
SSB preset ladder.
- **Cancelled VFO drags** release the drag hold without the final retune
(`SpectrumWidget::sliceDragCancelled`).
- **Off-thread `SliceModel` setters** are refused and logged.

## CI

The Linux `build` job now deletes unused host toolchains (Android, .NET,
GHC, CodeQL) before building. The full RelWithDebInfo tree, which now
includes this PR's RTL tests (about 3.6 GiB of test binaries), outgrew
the hosted runner's free disk. Jeremy authorized the workflow change.

## Integration with #6107

#6107 gated a stranded RTL worker on the `connectRadio()` wiring this PR
replaces. This PR's `startCapture()` already detaches the worker before
stopping it, and gates every relay on producer identity and the capture
session, so that wiring is kept. #6107's read-error guarantee is pinned
in `rtl_capture_worker_test`'s stop-timeout block. That test fails if
the producer gate is removed.

Principles II, V and XIV govern confirmed state and persistence; VIII/XI
govern evidence and regression tests.

---
73, Ozy **K6OZY** · Codex

Generated with OpenAI Codex (GPT-6 Astra)

---------

Co-authored-by: jensenpat <patjensen@gmail.com>
Co-authored-by: Jeremy [KK7GWY] <kk7gwy@aethersdr.com>
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Jeremy [KK7GWY] <105957118+ten9876@users.noreply.github.com>
@ten9876 ten9876 self-assigned this Oct 4, 2026
ten9876
ten9876 previously requested changes Oct 4, 2026

@ten9876 ten9876 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This implements the analog #5468 delivery increment at e03365cd9ee855bb570fce4afc535475efe4cda5: evaluation receiver banks, receiver-specific audio consumers, squelch/meters and required RTL packaging. The analog/Digital split is real, and normal-launch admission remains one receiver. Request changes: the exact-head Linux packaging run fails on both supported architectures because the promised pinned libusb runtime is not bundled.

Scope

Files/group Claimed? Verdict
RTL backend/worker/pipeline, SQL, settings and meter state Yes: 2/4/8 opt-in banks, independent controls, truthful meters In scope; production default remains one
WFM/RX/VFO/meter/settings applets and model wiring Yes: selection, stale-binding protection, relative presentation In scope; existing controls remain capability-shaped
TciServer, DaxReceiveModel, native bridges/HAL Yes: stable TCI mapping and receive-only DAX In scope; preserve existing Flex TX ownership
Ax25ReceiveModel, decoder/dialog wiring Yes: selected-receiver APRS In scope; decoder source/worker epochs are explicit
CMake, dependency recipes/manifests, release workflows and package checker Yes: mandatory RTL, Digital OFF, portable runtime/source/notices In scope, but Linux delivery currently fails
src/main.cpp isolation and other-family audio/build effects Explicitly disclosed In scope; not misrepresented as RTL-only
Tests, generated manifests, runtime/package docs Yes In scope; full Git inventory is 125 paths (API summary stops at 100)

No unrelated Digital decoder or default multi-receiver enablement found. Reviewed the actual parent-branch increment, not the optional Digital follow-on.

Blocker

  1. Bundle pinned libusb before building the AppImage. .github/workflows/appimage.yml:405 relies on automatic dependency deployment, but linuxdeploy blacklists this dependency. Both aarch64 and x86_64 in the exact-head AppImage run 37098245180 say:

    Skipping deployment of blacklisted library .../third_party/rtl-deps/lib/libusb-1.0.so.0
    Missing RTL package dependency: libusb-1.0.so*
    Process completed with exit code 1.
    

    The AppImage itself is assembled before the new validator at line 411 refuses it; neither architecture produces the promised qualified delivery. This is distinct from the earlier Wayland filename failure, which this head has passed. Explicitly stage the pinned SONAME library under AppDir/usr/lib before the linuxdeploy invocation / --output appimage, keeping the validator enabled. The direct staging line is:

           cp -L third_party/rtl-deps/lib/libusb-1.0.so.0 AppDir/usr/lib/libusb-1.0.so.0

    Then rerun both AppImage legs and retain their RTL-PACKAGE-OK/artifact evidence. Do not replace the missing library with a host-only dependency or weaken the presence check.

Non-blocking notes and qualification limits

  • tests/tests.cmake: exact-head Full Suite run 37098243451 stopped while linking tx_applet_power_reconciliation_test: /usr/bin/ld: final link failed: No space left on device. This is runner storage exhaustion, not evidence that this PR's test assertions failed. The body should update its “in progress” statements and obtain a completed qualification run after restoring runner capacity.
  • src/core/backends/rtl/RtlSdrBackend.cpp:131: keeping 2/4/8 behind both AETHER_AUTOMATION=1 and the explicit evaluation count correctly avoids inferring Linux ARM qualification from Mac ARM. No current-head hardware soak, sanitizer or per-architecture integrated CPU headroom was established here; preserve the one-receiver production boundary until the RFC gates are met.
  • src/core/backends/rtl/RtlReceivePipeline.cpp: the disclosed historical intermittent withdrawal still lacks a demonstrated root cause. The bounded-exhaustion test below proves its intended refusal path, not that every earlier real-time withdrawal was explained.

What I verified and tried to break

  • Exact-source standalone production DSP harness, GCC 16.2.1 / Qt 6.11.2, isolated FFTW wisdom: rtl_squelch_test, rtl_receive_pipeline_test, and rtl_wfm_pipeline_test complete without assertion failures. The WFM suite exercises multiple native analog receivers with independent signal content and mono/stereo state, and reports rtl_wfm_pipeline_test: 0 failures; intentional held-consumer exhaustion reports successful_blocks=8 repair=1.
  • Automatic-SQL negative control: replaced its estimated floor-plus-margin threshold with a fixed floor. The test fails; restoring exact production source restores the passing test. This pins actual noise rejection, not merely a settings round-trip.
  • Python Windows dependency-closure tests pass 4/4 (transitive missing DLL, nested-copy refusal, system imports/cycles, parse refusal); REQUIRE_RTL configuration matrix passes 16 combinations. These are injected parser/configuration tests, not native Windows artifacts. Linux deployment failure above is direct hosted evidence.
  • Read source/retirement paths for sparse TCI binding identity, per-receiver DAX producer leases, receive-only bridge TX exclusion, APRS queue/reset generations, parked receiver silence and relative meter units. These paths beyond the DSP probes were inspected, not all replayed locally.
  • Four boundary gates pass against head and parent, with the same 102 tracked engine findings, 71 capability booleans and 401 frozen raw commands; manifests current. The test preflight included the complete changed-file list: TCI sends are injected, and the PipeWire regression uses local FIFOs; no synthetic firmware/network peer was run.

Did not drive the GUI or access hardware: the demo does not provide RTL capture/multi-receiver USB or the platform DAX devices needed for these acceptance claims. Separate automated code-review skill unavailable. Native Mac/Windows artifacts, actual PipeWire/CoreAudio device delivery and fresh radio/ARM performance remain explicitly unverified by this review.

CI freshness: earliest head run 2026-10-03 04:56:21 UTC precedes current main 2026-10-04 18:26:40 UTC. Static checks are green, but strict: false does not make that a current-main build; branch-dispatch packaging tests the branch itself. Recommendation: Request changes for Linux packaging, then rerun the affected qualification and follow the stack merge order.

— Jeremy KK7GWY + Codex

ten9876
ten9876 previously requested changes Oct 4, 2026

@ten9876 ten9876 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Issue fit: the analog #5468 delivery increment is real. It adds opt-in 2/4/8 receive banks, independent manual and Auto SQL, selected-receiver APRS, a stable TCI mapping, receive-only native DAX, relative meters and required RTL packaging. Normal launch still admits one receiver. Request changes: the Linux packaging this PR exists to deliver fails on both architectures, and the PR inherits #5973's stale base.

Reviewed d4ef62d5..e03365cd (this PR's own base..head) at head e03365cd9ee855bb570fce4afc535475efe4cda5. This is an independent second pass after the earlier ten9876 + Codex review on the same head.

Stack: main ← #5973 ← #5974 ← #6082 ← #6083. This PR's base is #6082's head branch.

Scope

Files / group Claimed? Verdict
RTL backend, worker, pipeline, squelch gate, startup trace, settings, meters Yes In scope
WFM/RX/VFO/S-meter/SmartMtr applets and model wiring Yes In scope
TciServer, DaxReceiveModel, VirtualAudioBridge, PipeWireAudioBridge/PipeWireNativeRxSource, hal-plugin/AetherSDRDAX.cpp Yes: stable TCI mapping, receive-only DAX In scope, cross-family: Flex DAX and TCI users run this code. One undisclosed change is listed under nits.
Ax25ReceiveModel, the AX.25 dialog, MainWindow_DigitalModes, plus a new DaxConsumer::Ax25Decoder in flex/PanadapterStream.{h,cpp} Yes: selected-receiver APRS In scope. The Flex DAX-holder enum gains a value.
RadioCapabilities.h (receiveAudioExport) and map row, declarations in every backend Yes In scope. The map row is present (unlike #5974's broadcastFmReceive).
touchpoint-tags.json: two new headers tagged universal Implicit Fine. These are additions, not a re-classification of an existing vendor header. EB counts are unchanged.
CMakeLists.txt, cmake/AetherRequireRtl.cmake, scripts/setup/*, tools/check_rtl_package.py, tools/test_* Yes: mandatory RTL, pinned deps In scope. Maintainer decision (see below).
.github/workflows/{appimage,macos-dmg,windows-installer,static-checks}.yml Yes CI/release edits. Maintainer decision.
src/main.cpp: AETHER_SETTINGS_DIR suppresses legacy-profile migration and moves log pruning Yes, disclosed as cross-family In scope. The default path is unchanged: SettingsPaths::configDir() returns the same GenericConfigLocation/AetherSDR when the variable is unset.
CMakeLists.txt:2733-2736: new option(ENABLE_HD_FM … OFF) whose only effect is FATAL_ERROR when ON Body: "keeps its existing name" Not an existing option on main (git grep ENABLE_HD_FM origin/main finds nothing). See maintainer decisions.
Tests (25 files), generated manifests, docs Yes In scope. See the socket note below.

No CHANGELOG entry.

Step-0 socket preflight: no new or modified test opens a socket.

  • tci_rx_audio_test constructs TciServer but never starts a listener. Its transport is injected, and its tests.cmake block says so.
  • pipewire_rx_bridge_test uses a local mkfifo and does not run pactl.
  • The bind(...) hits in the AX.25 tests are fixture helpers.

Blockers

  1. The Linux AppImage fails on both architectures at this head. Confirmed in run 37098245180. The earlier review's finding holds. Both the aarch64 and x86_64 legs log:

    Skipping deployment of blacklisted library …/third_party/rtl-deps/lib/libusb-1.0.so.0
    Missing RTL package dependency: libusb-1.0.so*
    ##[error]Process completed with exit code 1.
    

    libusb-1.0.so.0 is on the upstream AppImage excludelist (AppImageCommunity/pkg2appimage excludelist, line 189), so linuxdeploy at .github/workflows/appimage.yml:405 will never bundle it, and the new validator at :411 correctly refuses the result. There are two coherent fixes, and they are a packaging-policy call:

    • (a) Stage the pinned SONAME into AppDir/usr/lib before linuxdeploy, as the earlier review suggested. This overrides the excludelist on purpose.
    • (b) Treat libusb as a host library, like the excludelist intends: every supported distro ships libusb-1.0.so.0, and udev/kernel interplay is why it's excluded. Teach check_rtl_package.py to accept a host-resolved libusb on Linux only.

    Either way, a passing AppImage run on this head is the merge gate.

  2. Inherited: rebuild on the rebased #5973/#5974. Every head in the stack descends from the pre-squash #5924 snapshot and lacks fe91a982f (SQL/FM-filter rules for non-RTL radios), which is now on main. A trial merge of this head against main conflicts in 61 paths. This PR edits the same RxApplet and VfoWidget squelch code that fe91a982f rewrote (RxApplet.cpp +50/−, VfoWidget.cpp +71), so the new manual/Auto SQL must be re-expressed on top of ModeFilters::squelchAvailableInMode rather than resolved in this branch's favour. It also has to route through #5919's requestSliceSquelch/ReceiveDispatch path, which landed on main after this stack forked.

For the maintainer (@ten9876)

  • Official builds require RTL. Linux, macOS and Windows release workflows now pass -DENABLE_RTL=ON -DREQUIRE_RTL=ON. A missing librtlsdr/libusb/float-FFTW fails configuration, and every installer ships those libraries. RFC #5468's 2026-10-02 scope note says "Official builds require RTL", but that text is the author's. I found no ruling from you that ratifies it. Your 09-07 conditional approval predates it.
  • ENABLE_HD_FM. It is a new build option whose only behaviour on main is to refuse configuration, and it exists to reserve a name for draft #6002 / RFC #6079. I'd drop it here, together with the three -DENABLE_HD_FM=OFF workflow flags, and let #6002 introduce it if that RFC is approved.
  • CI edits: a new Static Checks step (tools/test_require_rtl.py) and the three release workflows.
  • The libusb packaging choice in Blocker 1.

Non-blocking

  • Body is stale. It says "Intel Mac and Windows remain in progress", but the Windows Installer (37096568845) and macOS DMG (37096567337) runs at 4a242e4f both completed successfully. The "corrected full suite … in progress" run 37098243451 failed at link time with No space left on device (runner storage, not a test assertion). No completed full-suite run exists for this head.
  • Undisclosed cross-family TCI change. TciServer::sendInitBurst (TciServer.cpp:3120-3145) now posts the whole init burst as one worker job instead of one sendClientText per command. The rationale is sound (8 receivers × 8 clients would overflow the 256-job mailbox). Message order and the per-message sendText lifetime/backlog checks are preserved. This is the WSJT-X reconciliation path every Flex TCI user hits at connect, so please name it in the body.
  • Unresolved withdrawal. The intermittent Mac WFM receiver withdrawal is still unexplained. It is disclosed; keep the one-receiver production default until it is understood.

What I verified, and what I tried to break

  • Re-checked the prior review.
    • The AppImage blocker is real: I re-read the failed logs above.
    • The "Full Suite stopped on disk" reading is right.
    • The scope claims hold.
    • The prior review didn't flag ENABLE_HD_FM being new to main, the stale "in progress" statements, the TCI init-burst change, or the #5973 base problem.
  • Receive-only DAX TX safety. PipeWireAudioBridge::open(…, receiveOnly=true) creates no TX pipe and starts no TX poll timer. setTransmitting() returns early when receive-only, and the Flex path (receiveOnly=false) keeps the original sink and timer. openRxDrain is a no-op for Flex, so the sole-reader/EPIPE behaviour is untouched there.
  • Evaluation gate. RtlSdrBackend::RtlSdrBackend (RtlSdrBackend.cpp:123-137) raises capacity only when AETHER_AUTOMATION=1 and AETHER_RTL_EVALUATION_RECEIVERS ∈ {2,4,8}, on the three qualified OS/CPU pairs only. A normal launch, or Linux ARM, stays at one receiver.
  • main.cpp path change. With AETHER_SETTINGS_DIR unset, SettingsPaths::configDir() returns exactly the old literal, so ordinary users' log directory doesn't move.
  • Built this head and ran two focused tests (Arch/GCC, isolated settings): pipewire_rx_bridge_test → PipeWire bridge: 0 failures; rtl_squelch_test → exit 0.
  • Mutation: removed the receive-only early return in PipeWireAudioBridge::setTransmitting (PipeWireAudioBridge.cpp:501). pipewire_rx_bridge_test still passes (0 failures). Low impact — with no TX pipe the only effect would be an RX silence fill if a TX edge ever reached a receive-only bridge — but the receive-only TX exclusion the body leans on is pinned only by construction (loadPipeSink not called), not by a test. Non-blocking; a one-assertion test on isReceiveOnly() + setTransmitting(true) leaving m_transmitting false would pin it. Restored, sha256 matches HEAD.
  • Demo drive (bridge, offscreen, isolated AETHER_SETTINGS_DIR, AETHER_AUTOMATION_NO_TX=1, socket aethersdr-pr6083). Connected by raw {"cmd":"connect","action":"local","value":"serial DEMO-0001"}; get radio → "model":"AetherSDR Demo", "serial":"DEMO-0001". On a non-RTL radio the WFM surface stays out of the way: dumpTree shows the WFM bar button visible:false, ContainerWidget WFM/WfmApplet visible:false, and wfmAudioMode/wfmDeemphasis/wfmBandwidth enabled:false with reason "Broadcast FM receiver: No selected WFM receiver"; RX/TX/PWR/P/CW buttons remain visible. get slice carries the new "automaticSquelch":false,"automaticSquelchMarginDb":10 fields with "squelch":false,"squelchLevel":20 unchanged. Disconnected and closed the instance afterwards. RTL capture, multi-RX, DAX/TCI delivery and APRS cannot be reached from the demo (no USB device, and TCI/CAT are off by default) — those remain reasoned from source and the PR's offscreen tests. No hardware was touched.

Recommendation: Request changes. A passing AppImage is the merge gate, the stack base needs rebuilding on current main, and the RTL-mandatory release policy plus ENABLE_HD_FM need your ruling.

— Jeremy KK7GWY + Claude

@ten9876

ten9876 commented Oct 5, 2026

Copy link
Copy Markdown
Collaborator

Maintainer ruling: official builds require RTL, and the AppImage bundles the pinned libusb-1.0.so.0 despite the upstream excludelist. That resolves the AppImage packaging blocker's policy question; the rebase blocker still stands.

— Jeremy KK7GWY + Claude

@Ozy311
Ozy311 force-pushed the codex/rtl-multirx-delivery branch from e03365c to 50e2659 Compare October 6, 2026 01:53
@Ozy311
Ozy311 force-pushed the codex/rtl-live-ppm branch from d4ef62d to 1e0276f Compare October 6, 2026 01:53
@github-actions

github-actions Bot commented Oct 6, 2026

Copy link
Copy Markdown

Accessibility check findings

This PR touches src/gui/ files with 15 accessibility issue(s).
Full details appear as inline annotations on the diff above.

Files scanned : 24
Files with findings: 4
Total findings : 15

See docs/a11y.md for the canonical
Qt patterns (setAccessibleName, QAccessibleValueChangeEvent,
QAccessibleInterface subclasses, etc.). Structural remediation
(keyboard navigation, custom QAccessibleInterface subclasses)
is tracked in #3288.

Findings are warnings only — the build is not blocked.

1 similar comment
@github-actions

github-actions Bot commented Oct 6, 2026

Copy link
Copy Markdown

Accessibility check findings

This PR touches src/gui/ files with 15 accessibility issue(s).
Full details appear as inline annotations on the diff above.

Files scanned : 24
Files with findings: 4
Total findings : 15

See docs/a11y.md for the canonical
Qt patterns (setAccessibleName, QAccessibleValueChangeEvent,
QAccessibleInterface subclasses, etc.). Structural remediation
(keyboard navigation, custom QAccessibleInterface subclasses)
is tracked in #3288.

Findings are warnings only — the build is not blocked.

@jensenpat
jensenpat dismissed stale reviews from ten9876 and ten9876 October 10, 2026 23:55

Stale on e03365c. Later heads bundled the pinned libusb runtime and rebased onto main; 136cb48 merges current main and its required CI is green. The libusb and base findings do not apply to this head.

@jensenpat

Copy link
Copy Markdown
Collaborator

Landing commit 136cb48b8 is on current main and required CI is green (build, check-macos, check-windows, Static checks).

The three conflicts are resolved:

  • tests/tests.cmake keeps both dax_receive_applet_test and hl2_discovery_logging_test
  • AX.25 MODEM/DIAG lines use logLineHtml and keep the destruction guards
  • aetherd-touchpoints.md was regenerated (ThemeManager 173)

Auto SQL and receive audio still go through RadioModel. Only RTL sets receiveAudioExport.

This account cannot complete the merge: the release workflows are Tier 1, and GitHub is waiting on a code-owner review from @aethersdr/maintainers. The earlier change requests were on e03365cd (missing bundled libusb, and the old parent base). Those are not the current head.

@jensenpat
jensenpat requested a review from ten9876 October 10, 2026 23:58
ten9876 and others added 2 commits October 11, 2026 12:11
The only conflict is the generated touchpoint manifest, regenerated with
tools/gen_touchpoint_manifest.py (ThemeManager includers 174).

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

Every accepted capture revision removed each receiver's SLC:LEVEL sample,
including AF gain, pan, mute and squelch edits that leave the detector bins
untouched. While the operator dragged AF gain, the S-meter and VFO meter
fell back to no reading and dropped peak hold between FFT frames.

A revision now retires a receiver's sample only when its passband, mode or
membership changes, or when the session, front end, capture placement or DC
suppression changes. rtl_backend_test pins both halves.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The only conflict is the generated touchpoint manifest, regenerated with
tools/gen_touchpoint_manifest.py.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@ten9876
ten9876 dismissed stale reviews from jensenpat and jensenpat October 11, 2026 19:30

Each requested change is resolved at head 324c26322:

  • Retarget/rebase onto current main: the base is main. origin/main (d6402657e) is an ancestor of the head, and GitHub reports mergeable: MERGEABLE. WFM/PPM are no longer duplicated, since #6082/#5973 landed first and only the analog increment was replayed.
  • Drop ENABLE_HD_FM until RFC #6079: git grep ENABLE_HD_FM at 324c26322 returns no hits.
  • Check the AppImage after --output appimage: .github/workflows/appimage.yml:512-514 extracts the finished image, runs tools/check_rtl_package.py on it, and does cmp on the libusb payload.
  • Required build/check-macos/check-windows on a main-targeted head: these run on this head now.

You approved 136cb48b8 on 2026-10-10. That approval was dismissed only by the later merge pushes. Locally at 324c26322: 24/24 selected RTL/SQL/meter/DAX/TCI/AX.25 CTests pass, and the engine-boundary, capability-record and command-plane gates match main.

@ten9876 ten9876 self-assigned this Oct 11, 2026
ten9876 and others added 2 commits October 11, 2026 12:54
MainWindow_Wiring.cpp's profile-recovery DAX guard keeps this branch's
hasDaxStreams gate under main's Q_OS_MACOS spelling.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Main retired the deprecated Q_OS_MAC spelling; the RTL evaluation-capacity
guard, its test and the native DAX receive wiring now match it.

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

Copy link
Copy Markdown

Accessibility check findings

This PR touches src/gui/ files with 11 accessibility issue(s).
Full details appear as inline annotations on the diff above.

Files scanned : 24
Files with findings: 3
Total findings : 11

See docs/a11y.md for the canonical
Qt patterns (setAccessibleName, QAccessibleValueChangeEvent,
QAccessibleInterface subclasses, etc.). Structural remediation
(keyboard navigation, custom QAccessibleInterface subclasses)
is tracked in #3288.

Findings are warnings only — the build is not blocked.

1 similar comment
@github-actions

Copy link
Copy Markdown

Accessibility check findings

This PR touches src/gui/ files with 11 accessibility issue(s).
Full details appear as inline annotations on the diff above.

Files scanned : 24
Files with findings: 3
Total findings : 11

See docs/a11y.md for the canonical
Qt patterns (setAccessibleName, QAccessibleValueChangeEvent,
QAccessibleInterface subclasses, etc.). Structural remediation
(keyboard navigation, custom QAccessibleInterface subclasses)
is tracked in #3288.

Findings are warnings only — the build is not blocked.

The only conflict is the generated touchpoint manifest, regenerated with
tools/gen_touchpoint_manifest.py.

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

Copy link
Copy Markdown

Accessibility check findings

This PR touches src/gui/ files with 11 accessibility issue(s).
Full details appear as inline annotations on the diff above.

Files scanned : 24
Files with findings: 3
Total findings : 11

See docs/a11y.md for the canonical
Qt patterns (setAccessibleName, QAccessibleValueChangeEvent,
QAccessibleInterface subclasses, etc.). Structural remediation
(keyboard navigation, custom QAccessibleInterface subclasses)
is tracked in #3288.

Findings are warnings only — the build is not blocked.

@ten9876 ten9876 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Approved 4f3aa39e1aa04207f32180f37e6cf125636bb52d. This approval is the maintainer's review: GitHub was waiting on @aethersdr/maintainers because the PR touches the Tier 1 release workflows.

Fixed in this pass

  • RTL meter blanked during AF gain/pan/mute/SQL edits (automated pass). publishCapture() removed every receiver's SLC:LEVEL sample on every accepted revision. Fixed in 6b1132a10: the new RtlSdrBackend::staleRfMeterIds() retires a sample only when that receiver's passband, mode or membership changes, or when the session, front end, capture or DC suppression changes. Proved by a new block in rtl_backend_test (9 checks). With the old retire-everything rule restored, 5 fail; with the fix, all pass. A kept sample still ages out through MeterModel::relativeLevelForSlice's vitalIsFresh gate.
  • Q_OS_MAC spelling (merge with #6355). c77bb491c renames the PR's three remaining uses to Q_OS_MACOS. The build log has no Q_OS_MAC warning.
  • Merges with main: 84a8436e7, 324c26322, dfe363264, 4f3aa39e1. Each manifest conflict was resolved by regenerating with tools/gen_touchpoint_manifest.py, never by hand. dfe363264 keeps the PR's hasDaxStreams guard under main's Q_OS_MACOS in MainWindow_Wiring.cpp.

Maintainer decisions

  • Touchpoints accepted as part of the increment: models/Ax25ReceiveModel.h (replaces core/tnc/AetherAx25LibmodemShim.h), models/DaxReceiveModel.h, and ThemeManager +1 (SmartMtrWidget.cpp).
  • The AX.25 dialog keeps cancelling a running RX capture on a decoder-source reset (announced in the log).

Refuted or kept from the automated pass

  • VirtualAudioBridge::resetRxChannel racing the HAL plugin: already handled by the PR's compare_exchange_strong in hal-plugin/AetherSDRDAX.cpp.
  • RtlSquelchGate stale threshold in Auto: with no neighbor bins the gate observes NaN and closes, so the old threshold is never evaluated.
  • avoidDc on a bank receiver switched to WFM: the planner protects only the followed receiver, so the suggested change would have no effect. The line is unchanged from main, and an overlapping FM receiver still gets the existing DC warning and the Move-capture action.
  • Auto SQL slider holding the confirmed value: this is the intended confirmed-state behavior (Principle II).
  • Not changed (efficiency and cosmetic nits): per-frame backendCapabilities() in DaxReceiveModel::receive, 100 ms relative-meter poll timers, always-on PCM statistics in drainAudio, and RTL health rows with no value before the first capture.

Verified at this head

  • Locally (Arch, GCC, Qt 6.12, lean RelWithDebInfo, RTL on): 30/30 CTests pass. That covers RTL capture/backend/pipeline/WFM/SQL/APRS/spectrum/settings, meters, DAX/TCI receive and PipeWire, the AX.25 receive model, the RX/VFO/WFM widgets, and the TCI suites including the socket-owning tci_server_review_test and tci_automation_test (run on operator direction; #6365's rx_enable cases pass).
  • check_engine_boundary, check_capability_records and check_command_plane --strict show the same findings as main. The manifest is current.
  • Demo simulator (DEMO-0001, isolated settings): manual SQL on 35 and off reads back correctly.
  • Required CI on this SHA: Static checks passed. build, check-windows and check-macos were still running when this was posted, at the maintainer's direction; auto-merge waits for them. On the previous head (c77bb491c, the same tree minus the manifest-only merge of #6386) all four passed.

Not verified

RTL hardware behavior (live receive, audible SQL, multi-receiver banks, meter feel) and the release packages on this SHA. The demo has no RTL receiver and no LEVEL meter. The disclosed intermittent Mac WFM withdrawal remains unexplained; the one-receiver production default stands.

@ten9876
ten9876 enabled auto-merge (squash) October 11, 2026 20:29
@ten9876
ten9876 merged commit 46943eb into main Oct 11, 2026
10 checks passed
@ten9876
ten9876 deleted the codex/rtl-multirx-delivery branch October 11, 2026 20:56
ten9876 added a commit that referenced this pull request Oct 11, 2026
…remote operation over CGNAT (#6389)

## Summary

Release prep for **v26.10.2**: the six release files plus the Stable
docs snapshot. Metadata and docs only; no source, test or CI changes.

Cutoff: `origin/main` at `46943eb3c` (#6083, merged 2026-10-11).
Anything that merges after it is folded in before the tag.

**106 merged changes** from **15 human contributors**, AetherClaude (2)
and Dependabot (0). First-time contributor: **@dacacioa**. This was
confirmed by checking each login for a merged PR before the previous
tag, not from `author_association`.

## Changelog

The range `v26.10.1..46943eb` was mapped through the commit-to-PR API
(`repos/…/commits/<sha>/pulls`), not from commit subjects. It holds 106
PRs and no direct commits, and every one is cited in the section.
`v26.10.1` is an ancestor of `main`, so nothing is excluded. #6083
merged before the branch was cut, so it is inside the range and needs no
unmerged-PR banner.

The notes lead with:
- the unified 52 px title bar (#6198);
- docs.aethersdr.com (#6286, #6298, #6294);
- remote operation over CGNAT through the in-radio tailnet shim (#6273,
#6318, #6325, #6319);
- the canon windows (#6227, #6334, #6339, #6340, #6341, #6364, #6372,
#6386).

Then come DVK import, FFT Smooth/De-comb, TCI tune and receiver events,
RTL-SDR multi-receiver delivery, HL2 and ANAN-G2 state memory, the
IC-7300MK2 fixes, the first clean sanitizer runs (stated beside #6350's
TSan red) and Authenticode signing.

Numbers come from the PR bodies. "On upgrading" callouts cover the
behavior changes the bodies flagged:
- HL2 receive audio is 12.04 dB quieter (#6254);
- HL2 host CW is +6.02 dB at a fixed drive (#5975);
- ANAN AGC-T is remapped (#6282);
- sleep prevention is on by default (#6195);
- the Tailscale MTU cap has no opt-out (#6319);
- IC-7300MK2 Auto SQL is withdrawn (#6184).

DeepFist (#5953, #6103) is described as applying only to
`ENABLE_DEEPFIST_EXPERIMENT` builds.

## Docs refresh, not just version strings

- **README**:
- Highlights: FFT Smooth/De-comb on the spectrum line; AetherMap naming;
remote over CGNAT and TCI tune/receiver events on the SmartLink/TCI
line; pre-NR Copy Assist; a new docs.aethersdr.com line.
- Supported Hardware: the KPA1500, marked as not yet validated on
hardware.
- Other radio families: HL2 state restore extended; ANAN-G2 gains AGC-T,
NB2, supply voltage/link and per-radio memory; RTL-SDR now "bundled in
every official package", with WFM stereo, PPM, per-receiver SQL/meters
and DAX/TCI feeds, and multi-receiver explicitly an evaluation.
- Roadmap section: the CTR2 line advanced and a visual-canon line added.
  - Maturity labels are unchanged.
- **ROADMAP**:
- In flight: a v26.10.2 sentence each for aetherd, Icom, ANAN-G2,
RTL-SDR (remaining is now production multi-receiver qualification and
digital FM, RFC #6079), Hermes-Lite 2 and the CTR2 relay (RFC #6332
extensions). A new entry for the visual canon (RFC #6226 / #6239).
  - Queued: RFC #4214 (approved 2026-10-09) added.
- Open RFCs, checked against `label:rfc`: #4214 leaves (approved); #4764
and #4861 leave (shipped by #6198 and #6216); #6346, #6232, #6212 and
#6209 join. #6332 is approved and folds into the CTR2 entry.
- Recently shipped: nine new `(v26.10.2)` entries, still within the
v26.10.x cycle, so no month-rollover trim.
- Backends (six: anan, flex, hl2, icom, rtl, sim) and NR engines (seven)
were checked against the tree; both are unchanged.

## Judgment calls flagged for the maintainer

All of these were decided through AskUserQuestion:
- Version **26.10.2**, date **2026-10-11**, opened ready for review.
- **#6350**, the TSan red in the weekly sanitizer lane: proceed, and
name it below.
- RFCs **#4764** and **#4861** come off Open RFCs as shipped. The issues
themselves are left open for you to close.
- RFC **#4214** goes under Queued (next cycle).
- A **visual canon** In-flight entry, plus a README Roadmap line.
- The **KPA1500** is listed in README Supported Hardware with "not yet
validated on hardware".
- Including **#6083**: you asked for it, and it merged before the
cutoff, so it ships as an ordinary part of the range.

## Known failing on `main`

- **`ctr2_hid_thread_port_test`** fails in the weekly TSan lane (runs of
2026-10-08 and 2026-10-10; #6350). `main`'s CI and Full Suite were green
at `414082197`. The CHANGELOG headline and intro say so: the lanes
achieved their first clean runs this cycle, and the test remains failing
under TSan. This prep does not fix the test, and the release ships with
it red in that lane.

## Scope

| file | change |
|---|---|
| `CMakeLists.txt` | `project(AetherSDR VERSION 26.10.2)` |
| `README.md` | Current version line, plus the prose refresh above |
| `AGENTS.md` | Current version line |
| `CHANGELOG.md` | new `[v26.10.2] — 2026-10-11` section; `[Unreleased]`
stays and stays empty; history below byte-identical |
| `packaging/linux/io.github.aethersdr.aethersdr.metainfo.xml` |
`<release version="26.10.2" date="2026-10-11"/>` first in `<releases>` |
| `ROADMAP.md` | cycle heading → post-v26.10.2, plus the prose refresh
above |
| `docs/user/versioned_docs/version-stable/**`,
`docs/user/versioned_sidebars/version-stable-sidebars.json`,
`docs/user/stable-version.json`,
`docs/user/src/components/LogAnalyzer/rules.json` | docs snapshot
(`tools/docs/snapshot_stable.py 26.10.2`), in its own commit. This is
the first snapshot since #6286. |

## Validation

Output of `check_release_prep.py --mapping mapping.json` at the branch
head: 30 checks, 1 FAIL (explained below), 1 WARN (the snapshot files,
listed in the scope table).

```
PASS v26.10.1 is an ancestor of origin/main
PASS all six version spots say 26.10.2
PASS CHANGELOG heading date equals metainfo release date
PASS appstreamcli validate
PASS metainfo <release> list is date-descending with unique versions
PASS `## [Unreleased]` stays and stays empty
PASS `## [v26.10.2]` sits directly under `[Unreleased]`
PASS the previous section is `[v26.10.1]`
PASS CHANGELOG diff is exactly the inserted section
PASS new section opens with a `### <headline>`
PASS intro sentence has the house shape
PASS new section has a `### Contributors` block
PASS release_contents.py --check (citations, intro and Contributors counts)
     PRs in range: 106  cited: 106; intro 106/15 matches; Contributors (16 entries) matches; first-time ['dacacioa']
     citations outside the range: #4764, #4861, #5468, #6192, #6226, #6271, #6332 (RFCs), #6238, #6350 (issues)
PASS code fences balance / no duplicated table headers or doubled `---` (README, ROADMAP, new section)
PASS every local link in README.md and ROADMAP.md resolves
PASS no heading removed from README/ROADMAP is still linked elsewhere
PASS Recently shipped `(vX)` markers run newest-first and lead with the new version
PASS Recently shipped intro names the current cycle (v26.10.x)
FAIL only the four current-version lines lose the `26.10.1` string outside CHANGELOG
     historical mentions must stay: "label": "26.10.1"
PASS diff touches no src/, tests/, third_party/ or .github/workflows/ path
PASS all six release files are in the diff
WARN files beyond the six (the docs snapshot)
PASS git diff --check
```

- **The one FAIL is the snapshot's version label, not a historical
mention.** `docs/user/stable-version.json`'s `"label"` is the docs
site's current Stable version, and `snapshot_stable.py` rewrites it by
design. The validator predates #6286 and knows only the four
current-version lines. Run on the six-file commit alone, the same check
passes (29 checks, 0 FAIL, 0 WARN).
- The docs build passes: `npm ci && npm run build` in `docs/user`. `node
tools/docs/test_log_rules.mjs` reports 707 checks passed for 45 rules.
The line moves it reports were already on `main`.
- Four merged PRs (#6340, #6341, #6364, #6372) named screenshots to
recapture after merge (`psk-reporter-sidebar.png`, `menu-tools.png`,
`connect-dialog.png`, `rx-agc-calibration.png`), and #6386 named the
panadapter shots (`panadapter.png` and the main-window captures, which
still show the old title strip). They were not recaptured, so the Stable
snapshot carries the older images.
- No configure, build or test was run for this prep; it is metadata
only, and CI builds it.
- `main` CI at the cutoff `46943eb3c`: Docs and CodeQL - Code Quality
are green; CI, Full Suite, CodeQL and the System Libraries Canary were
still running when this was opened. At `414082197`, every one of them
was green.

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>
on8st added a commit to on8st/AetherSDR that referenced this pull request Oct 11, 2026
Conflicted file: BUILD-OPTIONS.md (hand-written; tools/check_build_options.py
checks it, nothing generates it). One hunk, two adjacent table rows:

- ENABLE_RTL: main's row, which gained "unless REQUIRE_RTL=ON" with
  aethersdr#6083 (46943eb). This branch never changed that row.
- AETHER_HL2_TX_TXA: this branch's row (SSB, AM, DSB and FM; OFF is
  SSB only). Main never changed that row; it only sat next to the other.

Everything else main added to the file (REQUIRE_RTL, the sanitizer and
RelWithDebInfo rows of aethersdr#6208, aethersdr#6214/aethersdr#6217) merged as text and is kept.
check_build_options.py --strict passes.

No other file conflicted. Hl2TxDsp.cpp/.h merged as text over aethersdr#6245:
the level chain is main's Hl2TxAlc (gain ramped inside the block), and
AM, DSB and FM audio now passes through it like SSB.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.

3 participants