Repository navigation
fix(dvk): a later licensed status clears a refusal, Import re-checks admission when it starts, and transfers stay honest across disconnects. Principle VIII. - #6262
Conversation
…admission when it starts, and transfers stay honest across disconnects. Principle VIII. Second remediation pass on #6247 (#6244): an automated review of b8bac9d plus my own pass over those fixes. - A `license feature name=digital_voice_keyer enabled=1` status now clears an earlier 50004001 refusal, so a subscription activated mid-session re-enables DVK without a reconnect. - Import re-checks DVK admission when the upload starts. The menu was enabled before a modal file dialog, and the DVK may have started recording or playing while it was open. - DvkModel::reset() leaves the transfer flag to DvkWavTransfer, which clears it when its own transfer ends; a transfer can outlive the connection by its timeout. - The file server's busy window starts only after an upload that actually connected. A refused `dvk upload` no longer turns the next try into "file server still busy". - A named upload port that never answers also gets the single 42607 retry, at the connect timeout; an unparseable port reply is logged. - Export says why it turned a connection away ("Ignored a connection from X; waiting for the radio at Y…") instead of timing out silently. - STOP before the radio's echo tags its reply with the pending start's slot, not -1. - A quoted value left open stops at the next `key="` token, so it can no longer swallow the keys after it. - F-key name changes are announced only one slot at a time; slot loads and disconnect resets (many slots at once) stay silent. - The four ATU/AGC menu actions in MainWindow.cpp put their disabled reason on statusTip as well as the tooltip (maintainer: fold in). check_a11y on the PR's GUI files: 4 findings -> 0. Maintainer rulings in this pass: admin-merge when green (the PR author is the acting account); accept core/TxKeyingMarker.h's new includer (DvkPanel.cpp, for the bridge TX markers); keep export strict to the radio's address with the reason shown; keep the WAV encoder local. Tests: dvk_model_status_test 47 (adds RadioModel licence-status clearing, the pending-slot stop tag, the transfer flag surviving reset, and the quote-join stop); dvk_wav_transfer_generation_test adds Import refused while the DVK is busy, a refused upload leaving no busy window, and the timeout retry on 42607. Each new guard was checked by inverting it: the licence clear, the quote stop and the busy-window guard each turn their test red. The full ctest suite passes: 667 tests. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
ten9876
left a comment
There was a problem hiding this comment.
Issue fit
Partially. This follow-up to #6247/#6244 implements the intended license recovery, late Import admission, transfer ownership, connected-upload busy window, timeout fallback, export explanation, and pending STOP tag. The new shared-parser guard also truncates a valid quoted name, and the screen-reader change reduces rather than silences bulk name notifications. Reviewed head a02a4405a, merge base 5f86d2faa. The PR merged during verification at bc1d7b80 (2026-10-07 22:11:49 UTC); the parser condition is confirmed in that landed commit. This is a post-merge review; the blocking finding needs a follow-up fix.
Scope
Everything in the diff is explained by the body and DVK remediation, including the explicitly disclosed maintainer-approved MainWindow accessibility fold-in.
| File/group | Change | Claimed? | Verdict |
|---|---|---|---|
DvkWavTransfer.{cpp,h} |
Late Import gate, connected-only busy window, single timeout retry, transfer diagnostics | Yes | In scope |
DvkModel.{cpp,h}, RadioModel.cpp |
Clear refusal on licensed status; pending STOP slot; retain owner-controlled transfer flag | Yes | In scope |
CommandParser.cpp |
Stop malformed quote joins at another quoted key | Yes | In scope; valid-value regression below |
DvkPanel.{cpp,h} |
Debounce/coalesce name announcements | Yes | In scope; partial improvement below |
MainWindow.cpp |
Four disabled reasons copied to statusTip | Yes; maintainer fold-in | In scope; verified |
Two DVK tests, tests.cmake |
Socket-free regression assertions and isolated settings | Yes | In scope |
The single commit's author date/message fits this remediation and its signature is verified. No new settings keys, protocol verbs, capability fields, dependencies, visual redesign, or CHANGELOG entry. The four architecture gates have identical normalized findings on base/head: 102 engine findings (zero blocking), 70 capability booleans, 368 raw commands, and a current unchanged touchpoint manifest. No new #5554 seam coupling found.
Blockers
- [P2] Preserve a legal quoted value whose closing token ends in
=—src/core/backends/flex/CommandParser.cpp:24. Forid=1 name="CQ a=" duration=1000, the closing tokena="contains=", so the new guard mistakes it for another quoted key and refuses the join. The real sanitizer acceptsCQ a=; FlexLib'sDVKRecordingStatusextracts the name between the quote pair, and its forbidden-character list does not prohibit=. Compiling the actual base/head parser and model against Qt 6.12 gives:
base: sent=CQ a= parsed="CQ a=" model=CQ a= duration=1000 (exit 0)
head: sent=CQ a= parsed="CQ model=CQ duration=1000 (exit 1)
The running head panel also reports Play slot 1: CQ after that status. Ordinary CQ Contest and CQ a=b controls remain intact. This reintroduces the name-truncation problem #6244 addressed for a valid user name; because parseKVs is shared, the same boundary affects other quoted fields.
Recognize a closing quote at the token's end before treating =" as a new key. Add the valid-name round trip to tests/dvk_model_status_test.cpp:103 beside the malformed-next-key case, keeping that existing case green. This drop-in replacement for the join block was compiled and passed both the valid CQ a= case and the existing malformed name="abc foo=1 bar="x" baz=2 case:
if (end < tokens.size()
&& (!tokens[end].contains(QLatin1String("=\""))
|| tokens[end].indexOf(QLatin1Char('"')) == tokens[end].size() - 1)) {
for (qsizetype j = i + 1; j <= end; ++j) {
token += QLatin1Char(' ') + tokens[j];
}
i = end;
}Nits (non-blocking)
- The bulk-notification silence claim is not established —
src/gui/DvkPanel.cpp:405,src/gui/DvkPanel.cpp:420.setAccessibleName()itself emitsNameChanged, as the Qt 6.12 QWidget implementation confirms. With accessibility active, a real running panel andQAccessible::installUpdateHandlerfiltered to F-keyNameChangedevents produced:
single rename 12-slot load disconnect reset
merge-base panel 2 24 24
head panel 2 12 12
The timer halves the bulk event count, but the setters bypass its suppression and a single change still emits twice. These are observed Qt notifications, not a claim about what each native screen reader speaks. Carry an event-count test in the panel coverage and revise the implementation or the body's silence claim accordingly. The announcement queue can be disabled while the existing panel admission/theme tests still pass, so those tests do not demonstrate this behavior.
Verification and attacks
- Built exact head and merge-base
AetherSDRon Linux/Qt 6.12.0 using RelWithDebInfo-O2 -g1and shared core. Built and ran only the focused targets:dvk_model_status_test(47 cases),dvk_wav_transfer_generation_test,dvk_panel_admission_test, anddvk_panel_theme_test: 4/4 passed. The modified transfer test is genuinely socket-free: its null model makes the deferred connect unreachable, and no download listener is started. Inert socket objects are not firmware peers. - Reverted seven guards individually in the scratch checkout: license clearing, malformed-quote stopping, pending STOP slot, transfer ownership on reset, connected-only busy window, late Import admission, and timeout retry. Each relevant test failed for the intended assertion. Restored original source bytes, rebuilt, and the model/transfer tests passed again. The separate valid-name probe fails only on the head. The running panel and accessibility event handler independently verified the name truncation and the notification counts above.
- Drove only isolated offscreen demo instances with fresh settings, explicit sockets
/tmp/pr6262-demo/{base,head,head2}/bridge.sock, identitiespr6262-{base,head,head2}-review, andAETHER_AUTOMATION_NO_TX=1. Connected only toDEMO-0001; settled base/head2 state included{"connected":true,"serial":"DEMO-0001","sliceCount":1,"panCount":1,"transmitting":false}.menu open ToolsplusdumpTreeshowed the three disabled SWR/ATU actions gaining astatusTipequal to their existing tooltip. For example, head Pre-tune returns{"enabled":false,"statusTip":"ATU memory controls are unavailable for this radio"}, while base has no statusTip. The first head snapshot also confirmed the disabled AGC-T reason; the settled head clears it when AGC-T becomes enabled. All owned instances were closed; no live radio or TX was used. - Attacked disconnect while an upload awaits its first command reply using a cold, socket-free model probe. The real
onDisconnectedcallback drain finishes the transfer (error 0x50000063), yieldingtransfer=0, admission=1; the suspected stranded-admission case does not survive that lifecycle check. Reviewed stale-generation, one-retry, connected-error, strict-peer and refusal paths. Real firmware Import/Export and a foreign-peer connection were not exercised: demo does not implement licensed DVK, and this review used no live hardware or new socket test. - Accessibility lint reproduces 4 → 0 on the two touched GUI files. Test registration, CI-gate integrity, QSettings and whitespace checks pass. An additional automated code-review skill was unavailable; this was a manual audit with empirical checks. The full-suite claim was not independently rerun; #6248 records the disclosed pre-existing
control_receive_testfailure. Required CI checks passed for pull-request runs created2026-10-07T21:52:55Z. Main subsequently advanced to this PR's squash merge at2026-10-07T22:11:48Z, but both head and landed commit have tree1a655602and parent5f86d2faa: the merge did not add untested tree changes.
Recommendation
Request changes in a follow-up. Fix and cover the valid quoted-name regression now present on main. The other seven guarded fixes survive targeted mutation, and the MainWindow accessibility reasons are verified. The remaining announcement issue is non-blocking partial remediation. The PR merged while this review was running; no merge or additional GitHub comment was performed by this review.
GitHub rejected REQUEST_CHANGES because the authenticated account is the PR author. This single review is therefore posted as COMMENT with the Request changes recommendation above.
…ssibility events stand alone. Principle VIII. Landing pass on #6264. Parser (maintainer: keep the token rule, tighten it) - The closing-quote test is a named predicate: a token closes the open value unless it opens another quoted field, and a token whose only quote is its last character always closes (endsWith && count == 1, which states what the first-quote-position test meant). - The function comment now describes that rule, including the one shape it cannot tell apart: on a malformed line a lone `label="` reads as the close, exactly as a valid name ending in '=' does. A new test pins that case, and that the keys after it survive. - The looped `=`-name assertions carry the name in their label, so a failure says which name broke. Accessibility (maintainer: fold the #6262 review finding into #6264) - setAccessibleName() and QLabel::setText() emit NameChanged themselves. The panel's extra coalesced F-key event and its manual status events are gone; F-key and row names are set only when they change. - The status label's accessible name carries the last announced state, so each state change is one NameChanged and the 10 Hz elapsed-time text and transfer progress add none. - dvk_panel_admission_test counts NameChanged through QAccessible::installUpdateHandler: a load of unchanged slots emits none on the F-keys, a rename emits one, a state change is one status event with no tick events after it, and disconnect emits one status event plus one for the renamed slot. Against the previous panel, three of those checks fail. dvk_model_status_test 56/56, dvk_panel_admission_test 11/11, dvk_panel_theme_test 33/33. Full ctest passes except control_receive_test, whose fix (#6263) landed on main after this branch's base. Architecture gates read the same on base and head. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
The review's blocker (a quoted name ending in |
…ibility events. Principle VIII. (#6264) ## Summary Fixes the quoted-name regression introduced by #6262, which its [review](#6262 (review)) found, and folds in that review's accessibility finding (maintainer call). **Parser.** Given `id=1 name="CQ a=" duration=1000`, the parser read `a="` as the start of another quoted field, and the DVK model displayed `CQ`. - A token now closes the open quoted value unless it opens another quoted field. - A token whose only quote is its last character always closes. So `CQ a=`, `CQ =` and `CQ a= b=` stay whole, and the following `duration` survives. - One shape is genuinely ambiguous. On a malformed line, a lone `label="` reads as the close, because it looks exactly like a valid name ending in `=`. A test pins that case and checks that the keys after it survive. - The #6247 guard still holds: an unterminated value does not swallow a later `bar="x"`. **Accessibility.** `setAccessibleName()` and `QLabel::setText()` emit `NameChanged` themselves, and the panel was adding events of its own on top. The review measured 2 events for a single rename and 12–24 for a slot load or disconnect. - The panel no longer adds any events of its own. - F-key and row names are set only when they change. - The status line's accessible name holds the last announced state. Each state change is now one event, and the 10 Hz elapsed-time text and transfer progress add none. ## Constitution principle honored **Principle VIII.** Each claim is backed by a test that fails without the change. FlexLib's DVK status parsing keeps everything between the quotes, equals signs included (Principle I). ## Validation - **Parser:** against the merged `bc1d7b80f`, the new assertions fail 6 of 55, one per name ending in `=`. With this fix, `dvk_model_status_test` passes 56/56, with each looped assertion naming its value. - **Accessibility events:** `dvk_panel_admission_test` (11/11) counts `NameChanged` events through `QAccessible::installUpdateHandler`: - reloading unchanged slots: no F-key events - a rename: exactly one - a state change: one status event, and nothing from the following ticks - a disconnect: one status event plus one for the renamed slot Against the previous panel, three of these fail. - `dvk_panel_theme_test` passes 33/33. - **Full `ctest`:** passes except `control_receive_test`. Its fix (#6263) landed on `main` after this branch's base. - **Static gates:** engine boundary, capability records, command plane, touchpoint manifest, test registration and the CI gate read identically on the merge base and the head. `check_a11y` reports nothing on `DvkPanel`. - **Not verified:** socket-free and offscreen only. No live radio, no transmitting, and no real screen reader. ## Checklist - [x] Signed commits - [x] Socket-free regression coverage, and each guard checked by inverting it - [x] No new settings keys, dependencies or ratchet changes - [x] No CHANGELOG change Authored with OpenAI Codex. Landed with Claude Code. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… Principle VIII. An audit of every page against the 106 PRs in v26.10.2, each fact checked against the source at the release commit: - Menu paths: AetherMap, AetherModem, Callsign Lookup and FreeDV Reporter are on View, and Waveforms is on Tools. - New coverage: Windows Firewall rules and the Network Diagnostics page (#6289), live CW contacts (#6268), NB2 on the HL2 and ANAN-G2 (#5966), ANAN-G2 AGC-T and per-radio memory (#6282, #6129), HL2 state restore and its level changes (#6259, #6254, #5975), DVK import (#6247, #6262, #6251), Copy Assist from unprocessed audio (#6216), keyboard-operable status-bar indicators (#6267), the KPA1500 (#5905), CTR2 extensions and the AetherKnob (#6338), live PPM (#6082), and Icom COMP and the squelch scale (#6174, #6184). - Known issues whose GitHub issues are closed are removed. - The style guide now asks for US spelling, and every page follows it. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… and re-take the Stable snapshot. Principle VIII. (#6396) ## Summary This brings the user guide (docs.aethersdr.com) up to date with everything in v26.10.2 and re-takes the Stable snapshot so the default site shows it. Docs only, apart from the TCI page prose in `tools/docs/gen_reference.py`; no application source, test or CI changes. Every page was audited against the 106 PRs in the release. Every fact that was added or kept was checked against the source at `f753dbfc4`, the v26.10.2 release commit, not taken from the PR bodies alone. ## What changed - **Menu paths.** - AetherModem, Callsign Lookup, AetherMap and FreeDV Reporter are on **Tools**. They are created on View and then moved to Tools (`MainWindow_Menus.cpp:1519-1520`, `:1569-1575`); the style guide's example says **Tools → AetherMap...**. - **Show live CW contacts** is on **View**. - Waveforms is on **Tools**, not File. - The VUDU applet button was called TXDSP. - Labels now match the source's `...` vs `…`. - **Gaps from this cycle.** - **Windows and Network Diagnostics:** Windows Firewall rules and the Network Diagnostics Windows Firewall page (#6289); the `aether.network` and `aether.nvafx` log categories (#6289, #6308). - **Remote access:** Tailscale shim revocation semantics (#6325). - **Sleep, App Nap and power throttling (#6195):** per-OS notes. - **Hermes-Lite 2:** state restore, RX audio at unity, CW at TUNE level, multi-interface discovery and a discovery troubleshooting entry (#6259, #6254, #5975, #6009, #6293). - **ANAN-G2:** AGC-T range, a "What is remembered" section, and supply voltage and link status (#6282, #6129, #6137, #6211). - **NB2** on the HL2 and ANAN-G2 (#5966). - **DVK:** WAV import requirements, the slot menu without Delete, status text and licensing (#6247, #6262, #6251). - **Copy Assist** from unprocessed audio (#6216). - **Live CW contacts** replace the decoder's contact card (#6268). - **Status-bar indicators** work from the keyboard (#6267). - **Volume:** controllers, hotkeys and TCI `volume` follow the active output (#6129, #6260). - **Peripherals:** the KPA1500 (#5905), CTR2 link extensions and the AetherKnob (#6338), TMate 2 udev (#6250), MIDI RADE Modem and FDV modes (#6031, #6038). - **RTL-SDR:** live PPM (#6082). - **Icom:** COMP and the MK2 squelch scale (#6174, #6184). - **Known issues.** Entries whose GitHub issues are closed are removed (each checked with `gh issue view`). As decided, this includes three closed today without a demonstrated fix: #5107, #4824 and #5665. - **US spelling.** `docs-style-guide.md` asked for British spelling; it now asks for US, by the maintainer's decision, and every page is converted. The generated TCI pages are converted at their source, the prose strings in `tools/docs/gen_reference.py`. Code spans, links and URLs were left alone. UI labels were checked against the source strings, and none of them are British. Two in-page anchors and two Log Analyzer rule anchors (`#bnr-is-grayed-out-…`) follow the renamed headings. - **Stable snapshot.** `snapshot_stable.py 26.10.2` was re-run on the release tree with these corrections, before `main` was merged in, so Stable carries no post-release docs. `docs/` also has `main`'s post-release pages, in US spelling. Stable alone keeps the IC-9700 waterfall-rate entry (#5996): #6402 fixed it after the 26.10.2 tag, so 26.10.2 still has it. The PDF manual already attached to the v26.10.2 release keeps the tag's docs. ## Validation - `npm ci && npm run build` in `docs/user`: success. This is the broken-link and broken-anchor check. - `node tools/docs/test_log_rules.mjs`: 707 checks passed for 45 rules. - `python3 tools/docs/gen_reference.py --check`: up to date (8 pages). - `git diff --check`: clean. - Menu paths on a build of this branch (offscreen, isolated settings, no radio), `menu list`: `Tools → AetherModem...`, `Tools → Callsign Lookup...`, `Tools → AetherMap...`, `Tools → FreeDV Reporter...`, `Tools → Waveforms...`, `View → Show live CW contacts`. ## Screenshots now out of date (not recaptured) These need a recapture pass on the radio: - **Old panadapter title strip (#6386):** `panadapter`, `main-window`, `first-session-*`, `multi-slice-two-slices`, `split-pair`, `tnf-markers`, `offscreen-vfo-indicator`, `pan-3d-stacked-trace`, `default-light-theme`. - **Pre-canon windows (#6334, #6339, #6341, #6372):** `aetherrx*`, `aethertx*`, `aethermodem-aprs`, `bnr-panel`, `connect-dialog`, `connect-by-ip-card`, `connect-icom-rows`, `connect-smartlink-card`, `rx-agc-calibration`. - **Other:** - `title-bar` (lowercase tab states, #6281); - `menu-help` (#6294); - `display-panel` (#6371); - `support-diagnostics-dialog`; - `waveforms-dialog` and `tailnet-remote-access` (show shim 0.4.0); - `about-window` (v26.10.1); - `radio-setup-qrz`. ## Found during the audit, not fixed here - **App string bug:** `TailnetShimDialog.cpp:628` tells the user to "Connect remotely with Connect → Manual"; the real path is **File → Connect to Radio... → Connect by IP**. - **ANAN AGC-T tooltips:** the RX applet and VFO tooltips show the slider position as dB, which is wrong on ANAN. #6282 lists this as a follow-up. - **MIDI Master Volume on non-Flex radios:** it is refused while PC Audio is off, whereas hotkeys, FlexControl and HID drive the line out. The docs describe the code as it is. - **RTL mode names:** `rtl-sdr.md` writes "FM-N" and "CW-R", but the source strings are `FMN` and `CWR`. Left as is until someone confirms what the mode menu displays. - **IC-705 COMP readback:** inferred from the MK2 (#6174) and not verified on an IC-705. - **Not yet documented:** the #6230 Radio Health row "Forward responses skipped", and the Store/MSIX package. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Summary
Follow-up to #6247 (#6244). A second remediation pass on #6247 produced these fixes, but #6247 merged before they could be pushed. Each fix is below. All of them are on the same DVK surface, and the a11y fold-in is a maintainer call.
license feature name=digital_voice_keyer enabled=1status clears an earlier50004001refusal, so a subscription activated mid-session re-enables DVK without a reconnect. The radio's licensed status outranks the earlier refusal.DvkModel::reset()leaves the transfer flag toDvkWavTransfer, which clears it when its own transfer ends, because a transfer can outlive the connection by its timeout.dvk uploadno longer turns the next try into "file server still busy".key="token, so it can't swallow the keys after it.m_swrScanAction,m_preTuneAction,m_clearAtuAction,m_agcTCalibrationMenuAction) put their disabled reason onstatusTipas well as the tooltip.check_a11yon the touched GUI files goes from 4 findings to 0.Constitution principle honored
Principle VIII. Each fix comes with a test that fails without it.
Test plan
cteston this branch: 671 of 672 pass. The one failure iscontrol_receive_test, which already fails onmain(CI Full Suite on66f393220, before any DVK change); it is tracked in [full-suite] test suite failing on main (fp a1c5df639be3) #6248.dvk_model_status_test(47 cases), which adds:enabled=1)reset()dvk_wav_transfer_generation_testadds three cases:check_engine_boundary(same 102 findings asmain),check_capability_records,check_command_plane,check_test_registration,check_ci_test_gateandgen_touchpoint_manifest --checkall pass.Checklist
AppSettingscalls🤖 Generated with Claude Code