Skip to content

feat(gui): AGC-T Calibration is a CanonWindow dialog, and every close path stops a running sweep and restores AGC-T. Principle VIII. - #6372

Merged
ten9876 merged 1 commit into
mainfrom
canon-agc-sweep
Oct 11, 2026
Merged

ten9876 merged 1 commit into
mainfrom
canon-agc-sweep

Conversation

@ten9876

@ten9876 ten9876 commented Oct 11, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Part of #6239. The AGC-T Calibration window (AgcCalibrationDialog) moves from PersistentDialog to a CanonWindow dialog (Kind::Dialog), as the AetherRX and AetherTX Settings dialogs did:

  • Chrome: no title bar, the ambient ground, the canon header and the corner close button. It closes with Esc or Ctrl+W and opens centered on the main window. It no longer follows View → Frameless Window, so showAgcCalibrationDialog() creates it directly instead of through showOrRaisePersistent().
  • Title: unchanged, "AGC-T Calibration", reached as before from Tools → Calibrate AGC-T... or the AGC-T slider's right-click menu.
  • Size: 460×460 (minimum 420×400), about 40 px taller to make room for the header.
  • Look: it sits under the canon/agcCalibration theme scope:
    • the mode line is ink, the recommendation line cyan and the hint muted;
    • the buttons and the target field are canon controls.
  • Plot: AgcCurveWidget paints from color.canon.* instead of hard-coded colors, and repaints on a theme change:
    • a nested card with a grid-colored plot frame;
    • a cyan curve with aqua points;
    • the knee marker in sparkGold, the current-value marker in ink and muted axis labels.

Close-path fix. On main, dismissing the window during an Auto Sweep (the close button, Esc or Ctrl+W) deleted the dialog, which stopped the sweep's timer but left the slice's AGC-T at whatever intermediate value the sweep had reached. Every close path reaches done(), which now stops a running sweep first. Stopping puts back the AGC-T the sweep started from, as Stop does. The destructor does the same for a dialog destroyed without a close, while the slice still exists. A sweep that has already finished is left alone, so an applied recommendation is never reverted.

Docs:

  • The RX controls page says how to close the window and what closing during a sweep does.
  • docs/style/* list AGC-T Calibration among the canon windows.
  • The touchpoint manifest counts one more core/ThemeManager.h includer (ui-support, no radio state).
  • Stale screenshot, to recapture after merge: rx-agc-calibration.png shows the old window.

Constitution principle honored

Principle VIII (Evidence Over Assertion) covers the checks below. The visual direction is RFC #6226's.

Test plan

  • Local build passes
  • Driven offscreen with scratch settings on the demo simulator (DEMO-0001, transmitting: false), opened via Calibrate AGC-T.... Captures show the canon header, the plot idle, mid-sweep, and finished with the gold knee marker.
  • Close path: AGC-T was 65 before an Auto Sweep and 44 mid-sweep. After the bridge's close on the window it read 65, and 65 again three seconds later. The bridge cannot send an Esc key, but close reaches done() through QDialog::closeEvent() → reject(), the same route Esc takes. canon_window_test covers Esc → reject(), and the review's probe of the production dialog covered Esc, close, reject, accept, explicit done() and direct destruction.
  • canon_window_test passes locally.
  • The static-check scripts pass locally; the touchpoint manifest was regenerated.
  • embed_screenshots.py --check and gen_reference.py --check pass.
  • Not exercised: live radio. No test covers this window, and this PR adds none.

Separate from this PR: after a full sweep finishes, the slice is not left on the recommended value. On the demo, the window showed "knee 96" and AGC-T read 44. A build of main does the same ("knee 64", AGC-T 84). It lives in AgcTCalibrator, which this PR does not touch.

Checklist

  • Commits are signed
  • No new flat-key AppSettings calls. As a centered dialog it no longer saves its geometry.
  • Code is clean-room
  • All meter UI uses MeterSmoother (unchanged)
  • Documentation updated: docs/user/docs/rx-controls.md, docs/style/*
  • Security-sensitive changes: n/a

🤖 Generated with Claude Code

… path stops a running sweep and restores AGC-T. Principle VIII.

AgcCalibrationDialog moves from PersistentDialog to CanonWindow
(Kind::Dialog), as the AetherRX and AetherTX Settings dialogs did:
- Chrome: no title bar, the ambient ground, the canon header and the
  corner close button. It closes with Esc or Ctrl+W, opens centred on the
  main window, and no longer follows View -> Frameless Window, so
  showAgcCalibrationDialog() creates it directly instead of through
  showOrRaisePersistent().
- Size: 460x460 (minimum 420x400), about 40 px taller for the header.
- Look: under the canon/agcCalibration theme scope; the mode line is ink,
  the result line cyan, the hint muted, and the buttons and the target
  field are canon controls.
- Plot: AgcCurveWidget paints from color.canon.* instead of hard-coded
  colors: a nested card with a grid-colored plot frame, a cyan curve with
  aqua points, the knee marker in sparkGold, the current-value marker in
  ink and muted axis labels. It repaints on a theme change.

Close path: Esc and Ctrl+W go through QDialog::reject(), which hides the
window without a close event, so on main a sweep kept stepping the slice's
AGC-T behind a hidden window, and closing mid-sweep left AGC-T wherever the
sweep had got to. done(), which every close path reaches, now stops a
running sweep, which puts back the AGC-T it started from, as Stop does. The
destructor does the same for a dialog destroyed without a close.

The RX controls page says how to close the window and what closing during
a sweep does. docs/style/* list AGC-T Calibration among the canon windows.
The touchpoint manifest counts one more ThemeManager.h includer.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@ten9876
ten9876 requested a review from a team as a code owner October 11, 2026 04:11
@ten9876 ten9876 self-assigned this Oct 11, 2026

@ten9876 ten9876 left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Review of 58acb76e71e5f44140278e46c7405f89b69a3649: Approve with nits, subject to the remaining required CI checks. The PR addresses the AGC-T Calibration item explicitly picked in #6239 under approved RFC #6226: CanonWindow chrome and plot tokens, plus restoration when dismissing an active sweep. I reproduced the restoration defect on the merge base and its fix on the head. No unaddressed scope requirement or demonstrated correctness blocker found.

Scope audit

Files/group Change Claimed? Verdict
src/gui/AgcCalibrationDialog.{cpp,h} Canon chrome, controls and plot tokens; stop active sweep in done/destructor Yes In scope
src/gui/MainWindow.cpp Lazy-create Canon dialog outside persistent frameless integration Yes In scope
Three docs/style/* files Register the migrated window and theme scope Yes In scope
docs/user/docs/rx-controls.md Closing shortcuts and sweep restoration Yes In scope
docs/architecture/aetherd-touchpoints.md ThemeManager includers 172→173 Yes In scope

All eight files are explained by the body and #6239. The single relevant commit has a verified good GPG signature. The removal of saved geometry and Frameless Window following is disclosed and authorized by the canon migration ruling. No unrelated hunks, new config/protocol surface, changed shared token defaults or baseline weakening found.

Blockers

None.

Non-blocking nits

  1. Pin the derived dialog's restoration behavior in a durable regression test. src/gui/AgcCalibrationDialog.cpp:338–351: canon_window_test tests generic CanonWindow Escape/reject and Ctrl+W, but never constructs AgcCalibrationDialog or its calibrator. It cannot detect deletion of either new stop guard. A socket-free behavioral seam is available: an unattached SliceModel, null radio/audio dialog dependencies, an active sweep, then close/reject/Escape/direct destruction; assert the original threshold and no later timer-driven change. Also pin completed/applied recommendation preservation and slice removal.

    My temporary actual-production-dialog probe passed all six dismissal/destruction variants on the head and failed all six on the merge base. That and the before/after demo evidence demonstrate this fix; the nit is retaining the protection in the repository. No synthetic firmware peer is needed, and no per-PR CI allow-list expansion is requested.

  2. Correct the PR body's Escape/background-sweep explanation. src/gui/MainWindow.cpp:4740 sets WA_DeleteOnClose, as the merge-base showOrRaisePersistent() already did. With that production attribute, Escape's QDialog::done path deletes the old dialog; its timer does not keep stepping behind a hidden window. On the merge base, an actual-dialog probe with the attribute set returned:

    productionDeleteOnClose escape alive=0 during=92 later=92
    

    Without DeleteOnClose, the hidden-dialog continuation does reproduce (92 immediately after Escape, 80 later, still running), but that is not the MainWindow-created dialog. The important production bug remains real: dismissal leaves AGC-T at the sweep's intermediate value. Please replace the body's main-window explanation with: “On main, dismissing the window during an Auto Sweep stopped the dialog's timer on deletion but left AGC-T at the intermediate sweep value. The new done() path restores the starting value before dismissal, and the destructor covers direct destruction while the slice still exists.”

What I tried to break

  • Build/test: exact head and actual merge base e56cb0ca664db9b5c4c12a36d8810faa8e40c8fd built in the review-owned scratch checkout using lean RelWithDebInfo, Qt 6.12 and shared core. Only AetherSDR and the inspected socket-free canon_window_test were built; the latter passed. No added/modified tests and no socket-owning test target were found in preflight. No broad suite was run.

  • Socket-free production-dialog attacks: temporary probe linked the actual application object graph with a custom main and invoked the actual dialog/model/timer methods. Close, reject, Escape, accept, direct destruction and explicit done all restored 65 from 92, remained 65 after 850 ms and stopped the sweep on the head. On the base, all six restoration assertions failed; non-deleting dismissal continued to 80, and destruction stayed at 92. Completed/applied recommendation 50 survived closing; deleting the slice during a sweep then destroying the dialog survived. This is local state-machine evidence, not a claim about firmware convergence.

  • Before/after demo bridge: fresh isolated settings, offscreen, explicit sockets aethersdr-pr6372-base and aethersdr-pr6372-head, DEMO-0001 only, TX disabled. Both started at 65 and reached 60 mid-sweep. Base close AgcCalibrationDialog left the following response three seconds later:

    {"model":"slice","ok":true,"property":"agcThreshold","value":60}

    Head corner-close restored and retained:

    {"model":"slice","ok":true,"property":"agcThreshold","value":65}

    A separate head bridge close also restored 65. Reopening worked; idle, mid-sweep and completed captures showed the 460×460 canon header, uncut controls, cyan curve and gold knee marker. Apply set the completed recommendation to 12; closing retained 12. Both owned instances (head PID 3380557, base PID 3385834) exited. No live radio was selected or driven.

  • Source/governance attacks: QPointer guards cover slice destruction; stop emits while dialog controls still exist; completed sweeps are deliberately excluded from rollback. Theme changes connect to plot update, and painted colors use scoped canon tokens. Raw radio/audio pointers are not accessed by the new destructor. Base/head engine findings are byte-identical (102), capability records remain 70, command-plane output remains 32 files/368 commands. Both touchpoint manifests are current; only the declared ThemeManager includer grows. Dialog accessibility checker finds zero on both sides. Generated reference check (8 pages) and screenshot embedding check (119 shots) pass. The stale rx-agc-calibration.png is explicitly disclosed for recapture, as project guidance permits.

No model-invocable automated code-review skill was available; this was a manual adversarial pass with independent governance and lifetime audits. Live firmware, native window-manager placement and theme switching were not exercised. Plot screenshots were captured offscreen; Escape was exercised in the production-dialog probe, while Ctrl+W was covered by the existing generic CanonWindow test plus the inspected shared reject/done dispatch.

CI freshness/status: main remained e56cb0ca664db9b5c4c12a36d8810faa8e40c8fd (2026-10-11 02:02:48 UTC). Pull-request CI/static/docs runs were created at 04:11:50 UTC and include that current main. At the final status read, six applicable checks passed, Linux/Windows/macOS builds were still running, and release-only PDF attachment was skipped. No completed check failed; this does not claim pending builds passed.

Recommendation: Approve with nits. The before/after behavior and lifecycle probes support the fix, and the visual changes follow the explicitly approved scope. Add durable close-path coverage and correct the body's Escape explanation; complete the required build checks and normal CODEOWNERS review before merge. This is a COMMENT review; formal approval remains the operator's call.

@ten9876 ten9876 removed their assignment Oct 11, 2026
@ten9876
ten9876 merged commit d0dfae1 into main Oct 11, 2026
10 checks passed
@ten9876
ten9876 deleted the canon-agc-sweep branch October 11, 2026 04:29

@aethersdr-agent aethersdr-agent Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Issue fit

Partially. #6239's picked list names this window with exactly this scope ("a dialog with the plot in color.canon.*; every close path stops a running sweep and restores AGC-T"), and the chrome/palette half lands cleanly: CanonWindow(Kind::Dialog), canon/agcCalibration container, header, applyCanonSheet on bodyWidget(), and AgcCurveWidget repainting from color.canon.* on themeChanged. The close-path half is real but incomplete: done() only acts while the sweep is running, and the post-sweep state — the one the operator hits most — is left on an unconfirmed value with no way back to the original. Detail in Blockers/Needs-decision below.

Scope

File / group What it changes Claimed? Verdict
src/gui/AgcCalibrationDialog.{h,cpp} PersistentDialog → CanonWindow, canon sheet + tokens in the plot, done()/dtor sweep stop Yes In scope
src/gui/MainWindow.cpp:4733-4746 direct construct + WA_DeleteOnClose instead of showOrRaisePersistent Yes In scope; matches the NetworkDiagnosticsDialog sibling at MainWindow.cpp:4700-4717 exactly
docs/style/{aethersdr-style-guide,dialog-patterns,theme-style-guide}.md add AGC-T Calibration to the canon lists Yes In scope
docs/user/docs/rx-controls.md close keys + what closing mid-sweep does Yes In scope, and accurately scoped to "during an Auto Sweep"
docs/architecture/aetherd-touchpoints.md core/ThemeManager.h 172 → 173 Yes In scope and required: python3 tools/gen_touchpoint_manifest.py --check exits 0 on the head checkout, so the hand count is right. Only core/+models/ headers are rows, so dropping gui/PersistentDialog.h needs no row

No unrelated files, no new public/protocol surface, no settings keys added, no CHANGELOG.md entry. Nothing in the diff is unexplained by the issue.

Two disclosed, non-hidden behavior changes, both stated in the body: geometry persistence is dropped (the old AgcCalibrationDialogGeometry key is now unused and the window centres on the parent), and the window no longer follows View → Frameless Window. Both are CanonWindow's documented defaults (docs/style/dialog-patterns.md:236-240), and Kind::Dialog for a short sweep tool is defensible — unlike AetherGPS (#6364), which kept its key via setGeometryKey().

Blockers

None — nothing here regresses main, and the only correctness gap below was equally broken before this PR.

Needs maintainer decision

  1. src/gui/AgcCalibrationDialog.cpp:345-352 — "every close path … restores AGC-T" does not hold for the finished-sweep path, which is the common one.

    void AgcCalibrationDialog::done(int result)
    {
        if (m_engine.isRunning()) {
            m_engine.stop(); // restores the original value
        }
        CanonWindow::done(result);
    }

    AgcTCalibrator::finishSweep() (src/core/AgcTCalibrator.cpp:202-214) clears m_running/m_auto and emits finished(false) — deliberately not confirmed: its own comment says "They still confirm with Apply (keep) or Stop (restore original)." But onFinished() (src/gui/AgcCalibrationDialog.cpp:565-566) resets the button to "Auto Sweep", so Stop no longer exists after a sweep finishes — Apply or closing the window are the only exits, and closing now takes the isRunning() == false branch and does nothing.

    Your own test plan supplies the evidence that this is not merely "an applied recommendation is never reverted": "the window showed 'knee 96' and AGC-T read 44". 44 is a sweep intermediate (100 → 0 in steps of 4), not the recommendation and not the operator's original. So the sequence open → Auto Sweep → let it finish → close leaves the slice on a value that is neither what the operator had nor what the window recommended, and the original (your 65) is unrecoverable from the UI. The body's defence conflates the two states the engine keeps separate: finished(true) from applyRecommendation() versus finished(false) from finishSweep().

    This is a maintainer call, not a defect I can rule on: does closing an unconfirmed calibration mean keep or cancel? What I'd ask either way is that the title and body stop claiming a guarantee that has this hole. If the answer is cancel, the mechanical shape is a dialog-side flag — stop() when not running does still restore (m_originalValue >= 0 → applyValue), but calling it unconditionally would also revert a genuinely applied recommendation, since applyRecommendation() never clears m_originalValue:

    // In onFinished(bool applied):
    m_resultUnconfirmed = !applied && m_engine.hasRecommendation();
    
    // In onStarted(): m_resultUnconfirmed = false;
    
    void AgcCalibrationDialog::done(int result)
    {
        if (m_engine.isRunning() || m_resultUnconfirmed) {
            m_engine.stop(); // restores the original value
        }
        CanonWindow::done(result);
    }

    Separately: thank you for disclosing the knee-96/AGC-T-44 discrepancy as pre-existing in AgcTCalibrator — I confirmed finishSweep() does call applyValue(m_recommended), so something downstream is not sticking. That deserves its own issue rather than a paragraph in this PR body; no issue is linked for it.

Nits (non-blocking)

  • src/gui/AgcCalibrationDialog.cpp:338-352 — no test covers the new close path. AgcCalibrationDialog.cpp is in no test target and links RadioModel + AudioEngine, and the nearest precedent (#6340's "every close path cancels an armed WSPR beacon") added none either — psk_reporter_map_behavior_test is a pure-core test. I'm not asking for a fake peer or a new GUI target; the honest statement is the one you already made ("No test covers this window, and this PR adds none"). If the restore semantics above get settled, the resulting rule is a socket-free seam worth pinning at the AgcTCalibrator level.
  • #6239's "Picked" list — the item naming this PR was added to the tracking issue body at 04:11:54Z, eight seconds after this PR opened, by you rather than by a maintainer comment; the thread's only rulings are the #6226 approval and the #6273 one. Not a scope problem in practice — e56cb0ca6 (#6364) merged under exactly this pattern — but worth one line of maintainer confirmation.
  • docs/user/docs/rx-controls.md:121 — the body notes rx-agc-calibration.png is now stale; please make sure the recapture issue exists so the screenshot doesn't sit wrong indefinitely.

What I tried to break

  • Close-path plumbing. ✕ → QDialog::close (src/gui/CanonWindow.cpp:83) → closeEvent → reject(); Ctrl+W/⌘W → QDialog::reject via the QKeySequence::Close shortcut (:88); Escape → QDialog::reject. All three land in the done() override, and main's AgcCalibrationDialog has no closeEvent/hideEvent/reject() of its own (only the m_engine.stop() inside onStartStop), so the PR's description of the pre-existing Esc behavior checks out.
  • Use-after-free in the override. m_engine.stop() runs before CanonWindow::done(result), which is the ordering that matters: with WA_DeleteOnClose the base call can destroy this, and nothing after it touches a member. Reversed, this would be a real UAF.
  • Double restore / dtor reentrancy. ~AgcCalibrationDialog re-guards on isRunning(), so the close path cannot restore twice. stop() emitting finished(false) from inside the derived destructor body reaches onFinished(), which writes m_startStopBtn/m_recLabel/m_curve — those children are still alive there (~QWidget deletes them later), so it is safe, if subtle.
  • Reentrancy of the restore write. stop() → applyValue() → agcThresholdChanged → onValueChanged (m_auto is false, so the manual timer arms) and refreshFromSlice(). Harmless on a hiding window; on the ✕ path the timer dies with the dialog. m_slice is a QPointer and every engine call site null-checks, so a slice removed mid-sweep does not dangle through the restore.
  • Manual mode. onValueChanged never sets m_running, so closing the window after hand-moving the AGC-T slider does not revert the operator's own setting — the restore is correctly confined to sweeps.
  • Theme tokens and scope. All twelve tokens used by kAgcCalibrationStyle and the plot (inkSoft, ink, cyan, aqua, muted, control, nested, line, lineHi, grid, onAccent, sparkGold) exist under scopes/root/tokens/color/canon in both default-dark.json and default-light.json. canon/agcCalibration is absent from the theme JSON, but so is every sibling (canon/aetherGps, canon/aetherRx, …): theme::setContainer → registerDeclaredContainer → scopeOrCreate builds it at runtime and lookupRaw's parent walk falls back to canon then root. House pattern, not a gap. Dropping WA_StyledBackground on AgcCurveWidget is consistent with it now painting its own card.
  • Manifest and CI. gen_touchpoint_manifest.py --check → "manifest up to date". All 10 checks on 58acb76 are success/skipped, including check-windows (the -DENABLE_ASR=OFF lane) and Static checks.
  • CodeGuard. All 28 findings on MainWindow.cpp refuted — credit to security-audit (CodeGuard): none falls in this PR's changed range (4733-4746), the CMDI-AMP/CMDI-BACKTICK hits are C++ & and Markdown backticks in // comments, PATH-PROC is /proc/self/status and the app's own bundle path, CS-PII-IP is the WSJT-X multicast default 224.0.0.1, and role_override is the word "override" in a QRM-classifier comment. Nothing to carry forward.
  • Could not check: anything runtime. I have no build, no test run and no way to drive the GUI, so every finding above is reasoned from the code at 58acb76, not reproduced. In particular I could not reproduce your knee-96/AGC-T-44 observation, and I could not verify the visual result of the canon sheet.

Recommendation

Needs maintainer decision, then approve. The chrome, palette and call-site conversion are clean and match the merged AetherGPS/Network Diagnostics precedent down to the WA_DeleteOnClose + QPointer shape, the manifest is honest, and the mid-sweep restore is a genuine improvement over main. The one open question is whether closing an unconfirmed finished sweep should also restore — your own evidence shows that path leaves the slice on a sweep intermediate, so the title's "every close path" currently overstates what done() does. Next step: get the maintainer's call on keep-vs-cancel, adjust done() (or just the title and body) accordingly, and file the AgcTCalibrator knee/value discrepancy as its own issue. Nice work on the close-path diagnosis and on disclosing the pre-existing bug rather than folding it in.


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

@aethersdr-agent

Copy link
Copy Markdown
Contributor

Cisco CodeGuard — static analysis of this PR (33 finding(s))

  • [MEDIUM] CG-PATH-001 — Potential path traversal in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6372/src/gui/MainWindow.cpp:9693
  • [MEDIUM] CG-PATH-001 — Potential path traversal in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6372/src/gui/MainWindow.cpp:9805
  • [MEDIUM] CG-PATH-001 — Potential path traversal in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6372/src/gui/MainWindow.cpp:9927
  • [MEDIUM] CG-PATH-001 — Potential path traversal in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6372/src/gui/MainWindow.cpp:9928
  • [MEDIUM] CG-PATH-001 — Potential path traversal in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6372/src/gui/MainWindow.cpp:9929
  • [HIGH] CS-VLN-PATH-PROC — Vulnerability: CS-VLN-PATH-PROC in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6372/src/gui/MainWindow.cpp:6224
  • [HIGH] CS-VLN-PATH-PROC — Vulnerability: CS-VLN-PATH-PROC in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6372/src/gui/MainWindow.cpp:6303
  • [CRITICAL] CS-VLN-CMDI-AMP — Vulnerability: CS-VLN-CMDI-AMP in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6372/src/gui/MainWindow.cpp:526
  • [CRITICAL] CS-VLN-CMDI-AMP — Vulnerability: CS-VLN-CMDI-AMP in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6372/src/gui/MainWindow.cpp:556
  • [CRITICAL] CS-VLN-CMDI-AMP — Vulnerability: CS-VLN-CMDI-AMP in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6372/src/gui/MainWindow.cpp:559
  • [CRITICAL] CS-VLN-CMDI-AMP — Vulnerability: CS-VLN-CMDI-AMP in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6372/src/gui/MainWindow.cpp:581
  • [CRITICAL] CS-VLN-CMDI-AMP — Vulnerability: CS-VLN-CMDI-AMP in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6372/src/gui/MainWindow.cpp:3660
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6372/src/gui/MainWindow.cpp:1280
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6372/src/gui/MainWindow.cpp:1804
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6372/src/gui/MainWindow.cpp:1896
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6372/src/gui/MainWindow.cpp:1973
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6372/src/gui/MainWindow.cpp:2015
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6372/src/gui/MainWindow.cpp:2050
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6372/src/gui/MainWindow.cpp:2345
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6372/src/gui/MainWindow.cpp:3602
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6372/src/gui/MainWindow.cpp:3693
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6372/src/gui/MainWindow.cpp:6166
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6372/src/gui/MainWindow.cpp:7972
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6372/src/gui/MainWindow.cpp:8037
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6372/src/gui/MainWindow.cpp:8111
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6372/src/gui/MainWindow.cpp:8196
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6372/src/gui/MainWindow.cpp:8377
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6372/src/gui/MainWindow.cpp:8580
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6372/src/gui/MainWindow.cpp:9794
  • [LOW] CS-PII-IP — PII detected: ip_address in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6372/src/gui/MainWindow.cpp:6916
  • [CRITICAL] CS-INJ-role_override — Prompt injection: role override attempt in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6372/src/gui/MainWindow.cpp:11745
  • [CRITICAL] CS-INJ-role_override — Prompt injection: role override attempt (detected after NFKC normalization) in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6372/src/gui/MainWindow.cpp:11745
  • [HIGH] CS-INJ-homoglyph — Mixed Unicode scripts detected (possible homoglyph attack) in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6372/src/gui/MainWindow.cpp:0

Automated static scan by Cisco DefenseClaw CodeGuard on the changed files. Advisory — some may be false positives; the review above verifies them.


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

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>
ten9876 added a commit that referenced this pull request Oct 12, 2026
… 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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant