Skip to content

fix(gps): report Flex fix state independently of lock - #6505

Merged
jensenpat merged 3 commits into
aethersdr:mainfrom
rfoust:codex/gps-fix-status
Oct 18, 2026
Merged

jensenpat merged 3 commits into
aethersdr:mainfrom
rfoust:codex/gps-fix-status

Conversation

@rfoust

@rfoust rfoust commented Oct 15, 2026 •

Copy link
Copy Markdown
Collaborator

Flex GPS reports now include a position-fix state independently of receiver and oscillator lock. The backend dropped fix=, so the model could not retain it and AetherGPS's GPS FIX card displayed receiver status such as Locked even for a time-only report.

Fixes #6500.
Refs #6437 (parent integration tracker; remains open).

The protocol authority is the official published FlexLib v4.3.16.42034 source, FlexLib/Radio.cs:155-165,12300-12328,12409-12412. The official archive was downloaded and inspected locally (SHA-256 60978db51dac6ea781899dd80832ad4e7876ce96ed72532b57106376fc26463f). The implementation is clean-room C++ using that public enum/parser contract.

  • Carry 3D, 2D, TIME_ONLY, NO_FIX and Unknown through the existing typed GPS delta and model notification. Recognized tokens are case-insensitive; unsupported/malformed values become Unknown. Absent fields preserve prior state in partial reports. A fresh session without this field remains Unknown.
  • Clear GPS state on disconnect, a new LAN/WAN session, retry and backend replacement. Rebind existing GPS/raw-message subscriptions with a GPS session generation so queued prior GPS cannot repopulate cleared state. Non-GPS raw dispatch retains its existing behavior. Remove the unused raw-dispatch wrapper so all production GPS dispatch carries the captured session generation. Empty resets still advance/rebind the generation but emit no duplicate GPS or clock-settings notification.
  • Keep fix state, independently reported validity, receiver status and reference lock separate. No fix and Time only veto position use. A fix label alone never establishes position/time validity or lock. Unknown retains legacy validity for older firmware and IC-705; Time only retains independently valid GPS time for clock agreement. The status-bar clock also honors time validity and falls back to the computer clock on No fix even while the frequency reference remains locked.
  • Show the fix state in the existing AetherGPS GPS FIX card. Preserve receiver status in its tooltip and accessible description, with live accessibility description notification. Define Time only as a reported mode without claiming time/reference availability. Resume address lookup when a valid fix returns at the same coordinates after Time only or No fix. Update user/bridge documentation and moved Log Analyzer source references. clearfixture also clears position validity, source and UTC date, preventing a cleared report from remaining usable. No layout, theme, GPS configuration or NTP configuration change.

Maintainer presentation review: the GPS FIX card explicitly shows Unknown when no recognized fix state has been reported, including older firmware and other feeds. Receiver status remains available in the tooltip/accessibility description rather than replacing Unknown.

Validation

  • Full native macOS AetherSDR application build, RelWithDebInfo, dedicated ARM64 toolchain, RADE enabled. Host/system processors and executable are ARM64; nested Opus is ARM64; no RNNoise x86 sources in the Ninja graph. The sandbox's iconutil packaging failure passed on the same native build outside the sandbox without source changes.
  • Four focused CTests pass: gps_fix_state_test, aetherd_residual_decode_test, icom_gps_readback_test, backend_family_switch_test. The new socket-free test covers exact/case/malformed/unknown tokens, partial and old-firmware reports, mixed fix/clock notification, independent validity, session resets and queued prior GPS across disconnect/LAN/WAN/backend replacement. Queued and fresh non-GPS profile status still dispatch exactly once. A repeated already-empty reset emits no duplicate telemetry or time-settings notification.
  • Mutation checks: removing fix decoding fails the new suite; bypassing GPS session guards and removing fix from telemetry notification fails the expected stale-state/notification assertions. Restoring each mutation returns the suite to green. Restoring the old UI readout assignment also makes the real-window bridge probe fail; the corrected build passes. Removing the address-query reset also makes the expanded bridge probe fail on 3D → TIME_ONLY → 3D; restoring it re-enables address refresh. The recovery proof uses an invalid HTTP service URL for a deterministic local error, so it sends no address request. Restoring duplicate reset notifications fails the new model assertion; removing the status-bar time-validity gate fails the bridge clock assertion. Correct sources are restored and rebuilt afterward.
  • npm ci && npm run build succeeds in docs/user. Engine-boundary/capability/command-plane findings are identical to the base; touchpoint manifest, test registration, frozen CI gate, generated bridge/reference docs and Log Analyzer rules pass. GPS UI accessibility lint reports zero findings; color ratchet passes.
  • Physical GPS acquisition, live 4.3 firmware convergence and Windows/Linux execution were not tested. The implementation-stage hosted checks passed; current rebased-head status is recorded below.

Agent automation bridge proof

The agent automation bridge ran native Cocoa builds with isolated settings, radio autoconnect disabled, TX automation disabled, and the shared FIFO radio/app lock. Each owned app was terminated and the lock released. Location is the pre-existing public Mount Wilson Observatory fixture, and the NTP address is reserved TEST-NET 192.0.2.80.

The before capture temporarily restores the exact old readout assignment in the otherwise patched worktree; it is a reconstruction of the old display behavior, not an untouched-main application build. The model reports fixState=TIME_ONLY, positionValid=false, timeValid=true, referenceLocked=true, but GPS FIX displays Locked. The same regression probe rejects it. The old parser's dropped field is independently demonstrated by the parser mutation test.

On the corrected build, gps fixture 8000, gps fixfixture, get gps, invoke AetherGPS... trigger, dumpTree and grab prove:

Reported fix GPS FIX readout Position valid Time valid Independent reference lock
Unknown Unknown true (legacy validity) true true
3D 3D true true true
2D 2D true true true
TIME_ONLY Time only false true true
NO_FIX No fix false false true

After clearing the fixture, reporting 3D alone displays 3D but leaves position/time validity and reference lock false. The probe also verifies the latitude/longitude presentation, that Time only retains clock agreement, that NO_FIX resumes a ticking status-bar computer clock while cached GPS time and reference lock remain unchanged, and that address lookup resumes after TIME_ONLY/NO_FIX → 3D at unchanged coordinates. TIME_ONLY alone after fixture cleanup leaves time validity and reference lock false and its tooltip describes only the reported mode. Fixtures inject typed GPS deltas; the socket-free tests separately validate raw Flex parsing.

Before → after, with the same Time-only fixture:

Old GPS FIX readout displays Locked
Corrected GPS FIX readout displays Time only

Additional captures: Unknown, 3D, 2D, No fix, Initial JSON evidence. Proof assets live on a separate signed fork branch and are absent from this PR's diff.

The GPS fix-state snapshot and disconnected gps fixfixture action were added because existing fixtures could not select those five states. tools/automation_gps_fix_probe.py provides repeatable real-window assertions and saves JSON/PNGs. The bridge is sufficient for this fixture proof; physical GPS acquisition and live firmware convergence are unverified. No RF transmission was performed or authorized.

Landing verification

Reviewed and rebased onto e8106f56bb81765d36c6c5afbec2dcdd12dd40e5; final head a5494eff26f9556d1882b27feecfc238be4edb80. Conflict resolution preserves main's generation-guarded radio/transmit/notch bindings, the interlock bridge entry and all settings-test registrations; Log Analyzer source references were regenerated. Original authorship is preserved and all three rewritten commits are verified by GitHub.

  • Independent native ARM64 RelWithDebInfo app build passed with RADE enabled and Qt 6.12.0. Four current-head socket-free CTests passed (4/4): gps_fix_state_test, aetherd_residual_decode_test, icom_gps_readback_test, backend_family_switch_test; run headlessly, with the Qt 6.12 runtime pinned.
  • Independent offscreen bridge probe passed all five readouts, independent validity/reference lock, fixture cleanup, No-fix ticking computer-clock fallback, same-coordinate address recovery, and Time-only without independently valid time/reference. The instance used isolated settings, disabled autoconnect/TX, an explicit socket, and the shared app lock; it remained disconnected, ended TX-idle, was terminated, and released the lock. The address resolver used an invalid HTTP URL to exercise a deterministic local refusal. These are fixture/model/UI observations, not physical GPS or live firmware evidence.
  • Engine-boundary and command-plane findings match main exactly after line-number normalization (101 tracked boundary findings; 221 tracked commands). Capability freeze, manifest, registration, frozen CI gate, generated reference/bridge docs, accessibility and all 707 Log Analyzer checks pass. All four required checks (build, check-windows, check-macos, Static checks) and hosted Docs site/PDF passed on the rebased head.
  • No independent live acquisition, native screen-reader, or RF/TX test. Prior author mutation evidence was inspected, not rerun during landing.

Generated with OpenAI Codex (GPT-6 Astra)

@rfoust
rfoust marked this pull request as ready for review October 15, 2026 02:15
@rfoust
rfoust requested a review from a team as a code owner October 15, 2026 02:15
Copilot AI balanced review requested due to automatic review settings October 15, 2026 02:15

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

An organization owner can choose the repository organization as the billing source for reviews requested by members with a paid Copilot license. Learn about Copilot code review billing.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 15, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-15T02:25:28.652930Z 69cb783 Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 69cb78365c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/gui/GpsLocationDialog.cpp Resolved
Comment thread src/models/RadioModel.h Resolved

@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.

Reviewed against #6500, project canon, and the PR head checkout. Findings below are reasoned from code — I am headless, so nothing here was reproduced at runtime.

1. Issue fit

Yes. All six scope bullets in #6500 map onto hunks:

#6500 requirement Where
Normalize all five states behind the existing GPS seam, case-insensitive, unknown→Unknown, absent preserves src/core/backends/GpsDelta.h:10-17 (enum), src/core/backends/flex/FlexBackend.cpp:1703-1717 (decode), src/models/RadioModel.cpp:12268 (if (d.fixState) present-only apply)
New sessions / disconnect / reconnect / radio replacement start Unknown, including firmware that never reports fix RadioModel.cpp:12233 resetGpsState(), called from 2400 (retry), 3460 (LAN), 3584 (WAN), 8003 (disconnect), 10078/10101 (backend replacement)
Show fix state in the existing AetherGPS readout without deriving validity or lock from the label src/gui/GpsLocationDialog.cpp:769-787; RadioModel.h:274-283 keeps the veto one-directional (a label never grants validity)
Preserve other backends and legacy position handling RadioModel.h:280-283 — Unknown is a no-op veto, so Icom/HL2/RTL and pre-fix= Flex keep their reported validity verbatim
Socket-free parser/model/UI/session regression coverage tests/gps_fix_state_test.cpp, tests/tests.cmake:6786-6792
Matching user documentation docs/user/docs/aetherclock-and-gps.md:54-66, docs/automation-bridge.md

I verified all twelve refreshed rules.json source pointers against the head checkout (7634 → else if (key == "model"), 8318 → consecutive pings unanswered, 12716 → DAX TX create failed, 1335 → dax_rx stream create, etc.). Every one is accurate; the generated reference is not stale.

Test layer is correct per AGENTS.md "Test-layer boundary": wire parsing, model tables and session/lifecycle behaviour → socket-free CTest. No synthetic firmware peer. Positive convergence is routed to the bridge probe and explicitly disclaimed.

2. Scope

File / group What it changes Claimed in body? Verdict
src/core/backends/GpsDelta.h GpsFixState enum + std::optional<GpsFixState> fixState Yes In scope
src/core/backends/flex/FlexBackend.cpp decode fix=; rewrite the positionValid rationale comment Yes In scope
src/models/RadioModel.{h,cpp} m_gpsFixState, gpsFixState(), gpsTimeValid(), vetoing gpsPositionValid(), GPS session generation fence, resetGpsState/wireGpsSessionReceivers, m_wanConn = nullptr moved to the top of connectToRadio Yes In scope
src/gui/GpsLocationDialog.cpp GPS FIX card shows fix state; receiver status moves to tooltip + accessible description; address-query reset on invalid position; clock-agreement gate → gpsTimeValid() Yes In scope
src/core/AutomationServer.cpp fixState/timeValid in get gps; available widened; new gps fixfixture verb; clearfixture now also clears positionValid, source, date Verb yes; the clearfixture widening is not called out In scope — see nit 5
tests/gps_fix_state_test.cpp, tests/tests.cmake socket-free regression target, joins AETHER_SETTINGS_CONSUMERS Yes In scope
tools/automation_gps_fix_probe.py new bridge probe Yes In scope
docs/automation-bridge.md, docs/user/docs/aetherclock-and-gps.md, rules.json docs + generated refs Yes In scope

No unrelated files, no formatting churn, no CHANGELOG.md entry (correct — it must not have one), no deleted guard, no settings keys, no capability-record change. Every hunk is explained by #6500.

One maintainer call: gps fixfixture is new public bridge surface (AutomationServer.cpp:8176-8200, registered at 3330) arriving as a side effect of a bug fix. It is disconnected-only, double-gated, and modelled on the existing gps fixture, so I am not calling it a violation — but third parties bind to the bridge registry, so per GOVERNANCE.md it is your ruling, not mine. Same posture as the interlock probe verb in #6493.

3. Blockers

None.

4. Nits (all non-blocking)

1. src/models/RadioModel.cpp:9209 — onMessageReceived is now dead code, and three comments still name it as the live dispatcher.

void RadioModel::onMessageReceived(const ParsedMessage& msg)
{
    handleMessageReceived(msg, m_gpsSessionGeneration);
}

Both connect(..., RadioConnection::messageReceived, ...) and connect(..., WanConnection::messageReceived, ...) were deleted from setupBackend/connectViaWan; the only two remaining subscriptions (RadioModel.cpp:12217, 12224) bind lambdas that call handleMessageReceived directly. A grep across src/ and tests/ finds no connect site and no invokeMethod("onMessageReceived"), so this slot is unreachable. The stale references are RadioModel.cpp:9307 ("handled by onMessageReceived"), 11051 ("is handled by onMessageReceived()") and 12557 ("via onMessageReceived()"). Worth deleting the wrapper plus its declaration at RadioModel.h:1448 and retargeting those three comments — and note that the wrapper passes m_gpsSessionGeneration (the current generation), so if it is ever reconnected by name the fence it is supposed to honour becomes a no-op.

2. src/gui/GpsLocationDialog.cpp:777 — the TIME_ONLY sentence asserts availability rather than defining the state (confirmed; CodeGuard/Codex flagged this).

        fixDescription += tr(" Time and frequency are available without a position fix.");

Your own test asserts fix=TIME_ONLY alone leaves gpsTimeValid() false (tests/gps_fix_state_test.cpp, "TIME_ONLY alone never sets time validity true"), and oscLocked() can be false at the same time — yet the tooltip and accessible description still tell the operator both are available, while the same dialog's clock-agreement row and 10 MHz REFERENCE card say otherwise. Definitional wording fixes it without adding a gate:

        fixDescription += tr(" Time only means the receiver supplies time and"
                             " frequency without a position fix; see the clock"
                             " and reference readouts for what is actually valid.");

3. src/gui/MainWindow.cpp:2401-2414 — the status-bar clock does not get the NO_FIX veto, so it now disagrees with the dialog (confirmed; Codex flagged this).

        if (!utcTime.isEmpty()
            && (usablePositionTime
                || (normalizeOscillatorValue(m_radioModel.oscState()) == "gpsdo"
                    && m_radioModel.oscLocked()))) {

On a Flex GPSDO, caps.hasGpsFrequencyReference is true so usablePositionTime is false, and the second arm accepts utcTime purely because the oscillator is locked. A GPSDO in holdover reports fix=NO_FIX while staying locked, and a fix-only delta re-emits the preserved m_gpsTime — so the status bar re-asserts a frozen GPS clock. This is pre-existing (there was no fix state to consult before), but this PR tightened the dialog's gate to gpsTimeValid() at GpsLocationDialog.cpp:980 and left this one alone, so the two surfaces now contradict each other for the same report. One-line fix:

        if (!utcTime.isEmpty()
            && m_radioModel.gpsTimeValid()
            && (usablePositionTime
                || (normalizeOscillatorValue(m_radioModel.oscState()) == "gpsdo"
                    && m_radioModel.oscLocked()))) {

(Worth checking against the IC-705 path before applying — for Unknown fix state gpsTimeValid() reduces to the raw flag, so I believe it is a no-op there, but you own that call.)

4. src/core/backends/flex/FlexBackend.cpp:1704-1706 — fix is matched untrimmed while the adjacent status is trimmed. const QString fix = kvs.value(QStringLiteral("fix")).toUpper(); vs. kvs.value(QStringLiteral("status")).trimmed().toLower() fourteen lines down. Your test pins 3D and 3D → Unknown deliberately, so this is intentional FlexLib parity, and I could not fetch the archive to check ParseGPSFixState myself. Flagging only because the failure mode is silent: a padded token degrades the readout to Unknown with no log line. A .trimmed() would preserve every documented token and cost nothing.

5. src/core/AutomationServer.cpp:8151-8156 — clearfixture gained three new cleared fields and the body does not say so. delta.positionValid = false; delta.source = QString(); delta.date = QString(); are new. This is a real fix (the old clearfixture left positionValid true, so "cleared" state still read as a usable position) and the probe depends on it, but it is a behaviour change to an existing bridge verb. Worth one line in the body / automation-bridge.md changelog prose so a scenario author who relied on the old partial clear is not surprised.

6. src/models/RadioModel.cpp:2400 — the auto-reconnect timer now clears and re-emits GPS state on every retry tick. resetGpsState() inside the m_reconnectTimer lambda bumps the generation, clears every field and emits gpsStatusChanged + gpsTimeSettingsChanged — once per retry, forever, while the radio is unreachable. onDisconnected() already cleared all of it at 8003, so each tick re-emits an identical empty report and re-runs every GPS consumer (including MainWindow's applyCapabilitiesToUi). Rebinding the subscriptions there is the load-bearing part; the clear-and-emit is redundant. Cheap guard: only emit when something was actually non-default, or split the rebind out of resetGpsState for this call site.

7. Naming, no action needed: gpsTimeValid() is derived from m_gpsPositionValid, i.e. "time is valid because position validity was reported". It works because for Flex that flag really means "receiver locked", and GpsDelta has no independent timeValid. The getter's semantics are correct but the name invites a future caller to assume there is a separate time-validity report behind it; a one-line comment at RadioModel.h:275 would prevent that.

Socket-test surfacing (per the #5254 reporting rule)

tests/gps_fix_state_test.cpp is not a socket-owning test, despite appearances. It includes RadioConnection.h/WanConnection.h and constructs WanConnection wan;, but WanConnection's constructor (src/core/backends/flex/WanConnection.cpp) only wires its member QSslSocket's signals and sets a heartbeat interval — nothing binds, listens or dials. The test drives delivery by emitting messageReceived directly from a std::thread so the call is posted rather than delivered, which is the whole point of the fence assertions. No peer process, no Fake* radio class, no bind()/listen()/connectToHost(). It is registered unconditionally and joins AETHER_SETTINGS_CONSUMERS with TestSettingsProfile isolation, matching the CMake contract. No socket test was removed.

tools/automation_gps_fix_probe.py does open the bridge's local socket, but it is an operator-run tool, not a registered CTest target.

5. What I tried to break

  • The subscription rewiring, hardest. Deleting the two permanent messageReceived connects and funnelling raw dispatch through wireGpsSessionReceivers could have orphaned meter/profile/M-message handling. I traced every path: wireBackendReceiverState() is called at RadioModel.cpp:955, inside setupBackend, after the single m_connection = flex->connection() assignment at 887 (grep confirms 887 and the nullptr at 1737 are the only two write sites), so the new binding sees exactly the pointer the old one did. teardownBackend() nulls both m_backend and m_connection before rebuildBackendForFamily's resetGpsState() runs, so that call binds nothing rather than dereferencing a dangling sender, and setupBackend rebinds immediately after. setBackendForTest reaches wireBackendReceiverState() at 10084. The receiveMessages=false arm in onDisconnected still rebinds the backend gpsChanged subscription before its early return. I could not find a path that leaves raw dispatch unbound across a reconnect.
  • The generation fences, for both false negatives and double dispatch. Every rebind captures the current m_backendReceiverGeneration and m_gpsSessionGeneration, so fresh reports always pass; the WAN/LAN arms are if (m_wanConn) … else if (m_connection), so no message can be dispatched twice; QObject::disconnect on a default-constructed QMetaObject::Connection is a safe no-op, so the first wireGpsSessionReceivers call is fine.
  • Moving m_wanConn = nullptr to the top of connectToRadio. This is the kind of reordering that breaks something three functions away. I grepped all 40 m_wanConn sites: none fall inside setupBackend, teardownBackend, ensureOfflineHealth or rebuildBackendForFamily, so nothing between the old and new position reads it. The only behavioural delta is the if (!m_backend) return; early exit (RTL built without librtlsdr), which now leaves m_wanConn cleared instead of stale — strictly better.
  • The address-lookup reset as a Nominatim rate-limit hazard. Clearing m_hasAddressQueryPosition on every invalid-position refresh means a 3D → TIME_ONLY → 3D flap re-enters requestAddress with force=false and no dedupe. LocationAddressResolver turns out to hold a rounded-key in-memory cache and a 1 req/s floor (src/core/LocationAddressResolver.{h,cpp}), so a flapping fix hits the cache and issues no network traffic. The body's "the resolver can reuse its cache" is accurate.
  • Regression risk in the new gpsPositionValid() veto. I enumerated every consumer: GpsLocationDialog.cpp:831, Ax25HfPacketDecodeDialog.cpp:4733 (APRS beacon position), MainWindow.cpp:2314 and 2406, AutomationServer.cpp:1825. All four want "is this position usable", which is what the veto now answers; the APRS one is arguably the most important and is now correctly refused on TIME_ONLY. For every non-Flex backend fixState stays Unknown, which makes the veto a no-op, so the IC-705 and HL2 paths are byte-identical.
  • The tests, for self-confirmation. The parser half cannot pass on unfixed code — fix= is dropped on main, so every FixCase row fails. The radio-a/radio-b loop's checkCleared would pass vacuously if backend gpsChanged delivery were silently unbound, but the earlier deliver(model, decode("status=Fine Lock#lat=…")) → gpsPositionValid() assertion proves the binding first, so the vacuity does not apply. The "queued raw WAN GPS cannot undo same-family session reset" + "preserves queued non-GPS profile dispatch exactly once" pair is the assertion that actually constrains the fence, and it fails if the generation check is removed.
  • Case and malformed-token handling at the parser. kvs[token.left(eq).toLower()] at FlexBackend.cpp:1698 lowercases keys, so the test's FiX= row is legitimate rather than lucky; fix#=3D yields one token with no = and one with eq == 0, both skipped by the pre-existing if (eq < 1) continue;, so the "malformed field leaves fix absent" row is genuinely testing the key path.
  • CodeGuard's 254 findings. A dedicated read-only security audit of all nine listed files confirmed none of them. fixfixture validates against a closed five-token allow-list, never concatenates the token into any sink, and is refused while connected at two independent layers (AutomationServer.cpp:8145 and RadioModel.cpp:2747). gps is absent from kSafe and from every per-verb exception in isReadOnlyRequest, so observe-only mode refuses all three actions — the new action inherits the existing gate rather than sidestepping it. The probe shells out nowhere. The rules.json and PhoneCwApplet.cpp/RxApplet.cpp hits contain no GPS content at all and are whole-file-scan noise against main's tip, not diff content; the 192.0.2.* and password hits are an RFC 5737 documentation address and Icom log-matching prose respectively.

What I could not check: anything requiring execution. Specifically — I could not fetch the FlexLib 4.3.16 archive to verify ParseGPSFixState's exact token set and trimming behaviour, so the protocol claim rests on your stated inspection (nit 4); I could not confirm the accessibility DescriptionChanged event behaves on a real screen reader; and I could not run the new test. Hosted CI did not run it either — all seven checks on 69cb783 are green, which is real evidence the tree compiles and links on macOS, Windows and Linux, but ci.yml's only ctest -R steps name six unrelated tests, so zero of gps_fix_state_test's assertions executed in CI. The local four-CTest run and the documented mutation checks are the only evidence those assertions hold, and the body says so plainly.

6. Recommendation

Approve with nits — with one maintainer call. The protocol change is narrow and correct, the veto is one-directional so no label can manufacture validity, the session fence holds up under the ordering attacks above, and the generated references and docs are genuinely in sync rather than approximately so. Every nit is a follow-up rather than a correctness defect; I would take nits 1 and 3 in this PR (dead code with three comments pointing at it, and two surfaces now disagreeing about the same report) and let the rest ride. The maintainer call is whether gps fixfixture belongs in the permanent bridge registry.

Thanks for the unusually honest validation section — "the before capture is a reconstruction, not an untouched-main build" and "hosted CI is pending" are exactly the disclosures that make the rest of the proof worth reading.


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

@aethersdr-agent

Copy link
Copy Markdown
Contributor

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

  • [MEDIUM] CS-VLN-SSRF-HEX — Vulnerability: CS-VLN-SSRF-HEX in docs/user/src/components/LogAnalyzer/rules.json /tmp/aetherclaude/pr-6505/docs/user/src/components/LogAnalyzer/rules.json:89
  • [HIGH] CS-VLN-PATH-UNC — Vulnerability: CS-VLN-PATH-UNC in docs/user/src/components/LogAnalyzer/rules.json /tmp/aetherclaude/pr-6505/docs/user/src/components/LogAnalyzer/rules.json:744
  • [HIGH] CS-VLN-PATH-UNC — Vulnerability: CS-VLN-PATH-UNC in docs/user/src/components/LogAnalyzer/rules.json /tmp/aetherclaude/pr-6505/docs/user/src/components/LogAnalyzer/rules.json:1003
  • [HIGH] CS-PII-DL-1 — PII detected: drivers_license in docs/user/src/components/LogAnalyzer/rules.json /tmp/aetherclaude/pr-6505/docs/user/src/components/LogAnalyzer/rules.json:88
  • [HIGH] CS-VLN-SSRF-PRIV-192 — Vulnerability: CS-VLN-SSRF-PRIV-192 in src/core/AutomationServer.cpp /tmp/aetherclaude/pr-6505/src/core/AutomationServer.cpp:7000
  • [HIGH] CS-VLN-SSRF-LOCALHOST — Vulnerability: CS-VLN-SSRF-LOCALHOST in src/core/AutomationServer.cpp /tmp/aetherclaude/pr-6505/src/core/AutomationServer.cpp:12293
  • [HIGH] CS-VLN-SSRF-LOCALHOST — Vulnerability: CS-VLN-SSRF-LOCALHOST in src/core/AutomationServer.cpp /tmp/aetherclaude/pr-6505/src/core/AutomationServer.cpp:12294
  • [MEDIUM] CS-VLN-SSRF-HEX — Vulnerability: CS-VLN-SSRF-HEX in src/core/AutomationServer.cpp /tmp/aetherclaude/pr-6505/src/core/AutomationServer.cpp:6210
  • [CRITICAL] CS-VLN-CMDI-SEMI — Vulnerability: CS-VLN-CMDI-SEMI in src/core/AutomationServer.cpp /tmp/aetherclaude/pr-6505/src/core/AutomationServer.cpp:1759
  • [CRITICAL] CS-VLN-CMDI-SEMI — Vulnerability: CS-VLN-CMDI-SEMI in src/core/AutomationServer.cpp /tmp/aetherclaude/pr-6505/src/core/AutomationServer.cpp:7488
  • [CRITICAL] CS-VLN-CMDI-SEMI — Vulnerability: CS-VLN-CMDI-SEMI in src/core/AutomationServer.cpp /tmp/aetherclaude/pr-6505/src/core/AutomationServer.cpp:13084
  • [CRITICAL] CS-VLN-CMDI-AMP — Vulnerability: CS-VLN-CMDI-AMP in src/core/AutomationServer.cpp /tmp/aetherclaude/pr-6505/src/core/AutomationServer.cpp:2412
  • [CRITICAL] CS-VLN-CMDI-AMP — Vulnerability: CS-VLN-CMDI-AMP in src/core/AutomationServer.cpp /tmp/aetherclaude/pr-6505/src/core/AutomationServer.cpp:8481
  • [CRITICAL] CS-VLN-CMDI-AMP — Vulnerability: CS-VLN-CMDI-AMP in src/core/AutomationServer.cpp /tmp/aetherclaude/pr-6505/src/core/AutomationServer.cpp:9507
  • [CRITICAL] CS-VLN-CMDI-AMP — Vulnerability: CS-VLN-CMDI-AMP in src/core/AutomationServer.cpp /tmp/aetherclaude/pr-6505/src/core/AutomationServer.cpp:11528
  • [CRITICAL] CS-VLN-CMDI-AMP — Vulnerability: CS-VLN-CMDI-AMP in src/core/AutomationServer.cpp /tmp/aetherclaude/pr-6505/src/core/AutomationServer.cpp:11891
  • [CRITICAL] CS-VLN-CMDI-AMP — Vulnerability: CS-VLN-CMDI-AMP in src/core/AutomationServer.cpp /tmp/aetherclaude/pr-6505/src/core/AutomationServer.cpp:12450
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/core/AutomationServer.cpp /tmp/aetherclaude/pr-6505/src/core/AutomationServer.cpp:299
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/core/AutomationServer.cpp /tmp/aetherclaude/pr-6505/src/core/AutomationServer.cpp:463
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/core/AutomationServer.cpp /tmp/aetherclaude/pr-6505/src/core/AutomationServer.cpp:840
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/core/AutomationServer.cpp /tmp/aetherclaude/pr-6505/src/core/AutomationServer.cpp:949
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/core/AutomationServer.cpp /tmp/aetherclaude/pr-6505/src/core/AutomationServer.cpp:1306
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/core/AutomationServer.cpp /tmp/aetherclaude/pr-6505/src/core/AutomationServer.cpp:1379
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/core/AutomationServer.cpp /tmp/aetherclaude/pr-6505/src/core/AutomationServer.cpp:1531
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/core/AutomationServer.cpp /tmp/aetherclaude/pr-6505/src/core/AutomationServer.cpp:1608
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/core/AutomationServer.cpp /tmp/aetherclaude/pr-6505/src/core/AutomationServer.cpp:1755
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/core/AutomationServer.cpp /tmp/aetherclaude/pr-6505/src/core/AutomationServer.cpp:1785
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/core/AutomationServer.cpp /tmp/aetherclaude/pr-6505/src/core/AutomationServer.cpp:1789
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/core/AutomationServer.cpp /tmp/aetherclaude/pr-6505/src/core/AutomationServer.cpp:2021
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/core/AutomationServer.cpp /tmp/aetherclaude/pr-6505/src/core/AutomationServer.cpp:2131
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/core/AutomationServer.cpp /tmp/aetherclaude/pr-6505/src/core/AutomationServer.cpp:2190
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/core/AutomationServer.cpp /tmp/aetherclaude/pr-6505/src/core/AutomationServer.cpp:2198
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/core/AutomationServer.cpp /tmp/aetherclaude/pr-6505/src/core/AutomationServer.cpp:2310
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/core/AutomationServer.cpp /tmp/aetherclaude/pr-6505/src/core/AutomationServer.cpp:2408
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/core/AutomationServer.cpp /tmp/aetherclaude/pr-6505/src/core/AutomationServer.cpp:2439
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/core/AutomationServer.cpp /tmp/aetherclaude/pr-6505/src/core/AutomationServer.cpp:2738
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/core/AutomationServer.cpp /tmp/aetherclaude/pr-6505/src/core/AutomationServer.cpp:2761
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/core/AutomationServer.cpp /tmp/aetherclaude/pr-6505/src/core/AutomationServer.cpp:2834
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/core/AutomationServer.cpp /tmp/aetherclaude/pr-6505/src/core/AutomationServer.cpp:2900
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/core/AutomationServer.cpp /tmp/aetherclaude/pr-6505/src/core/AutomationServer.cpp:3371

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: $17.2418 · model: claude-opus-5

@rfoust

rfoust commented Oct 15, 2026

Copy link
Copy Markdown
Collaborator Author

Addressed the review feedback in signed commit 26bd8698c. Both inline findings were reproduced and corrected; the replies carry the before/after bridge evidence.

Review item Disposition
1. Unused raw-message slot Removed the wrapper/declaration and retargeted all three comments. Production dispatch retains its captured session generation.
2. Time-only availability claim Fixed; tooltip/accessibility text describes the reported mode and keeps time validity/reference lock independent.
3. Status-bar No-fix veto Fixed; the computer clock resumes even with a locked frequency reference.
4. Whitespace trimming Kept strict FlexLib parity. The inspected official v4.3.16.42034 Radio.cs:12316-12328 switches on value.ToUpperInvariant() without trimming and maps unmatched values to Unknown. The padded-token cases intentionally pin that contract.
5. clearfixture scope Explicitly documented position validity, source and UTC-date cleanup in both the PR body and bridge documentation.
6. Empty retry/reset notifications Fixed: every reset still advances the epoch and rebinds, but only changed telemetry/time settings emit their corresponding notification. Added a socket-free repeated-empty-reset assertion and mutation-checked it.
7. gpsTimeValid() semantics Added a comment stating that it uses legacy backend validity plus the no-fix veto, rather than a separate reported time-valid bit.

The protocol reference remains the official FlexLib archive; the local archive still hashes to 60978db51dac6ea781899dd80832ad4e7876ce96ed72532b57106376fc26463f. No parser token semantics, defaults, layout or theme changed in this follow-up. All changed file groups (readouts, GPS dispatch/reset, regression probe/tests and matching docs/generated references) belong to #6500. The maintainer call on keeping gps fixfixture as permanent bridge surface remains unchanged.

Validation: full native ARM64 build with RADE ON, four focused CTests, expanded native bridge proof, docs build, accessibility lint, engine-boundary check, registration/manifest/generated-reference checks and Log Analyzer rules pass. The clock and empty-reset mutations fail at the intended assertions; correct sources were restored and rebuilt. Bridge sessions used isolated settings, disconnected public-location fixtures, disabled TX and the shared app lock; only owned apps were closed. Live GPS acquisition and screen-reader behavior remain unverified. Hosted CI is rerunning on this commit; the four CTests were run locally, not added to the frozen per-PR gate.

For the CodeGuard advisory, I mapped the 40 visible entries to the reviewed 69cb78365 source. The visible command-injection hits identify C++ loop punctuation, reference declarations, help strings or backticks in comments; the documentation hits identify error-code prose/regex data. The localhost WebSocket hit is the existing explicit TCI test client, outside this GPS diff. These entries do not establish an introduced vulnerability, so they did not justify source changes here. The comment does not contain the full claimed 254-item report; this is not a verdict on unseen entries.

Generated with OpenAI Codex.

@jensenpat jensenpat self-assigned this Oct 18, 2026
@jensenpat
jensenpat force-pushed the codex/gps-fix-status branch from 26bd869 to a5494ef Compare October 18, 2026 02:24

@jensenpat jensenpat 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.

Review and landing verification

#6500 is addressed: the Flex parser carries a typed fix independently of receiver/reference lock; absent partial fields preserve state, session changes clear it, and old queued GPS cannot repopulate the new session. The AetherGPS readout, clock fallback and same-coordinate address recovery use that state consistently. No remaining blockers or nits.

Scope

File/group Change claimed in body Verdict
GpsDelta.h, FlexBackend.cpp Typed five-state fix and published token decoding In scope
RadioModel.{h,cpp} Separate validity vetoes, session resets and stale-event fencing In scope
GpsLocationDialog.cpp, MainWindow.cpp Fix readout/accessibility, address recovery and valid-time gate In scope
AutomationServer.cpp Diagnostic fields, disconnected-only fix fixture, complete fixture cleanup In scope
gps_fix_state_test.cpp, tests.cmake Socket-free regression target and settings isolation In scope
automation_gps_fix_probe.py Repeatable model/UI assertions In scope
User/bridge docs and Log Analyzer references Matching behavior documentation and generated source pointers In scope

No workflow, dependency, settings-default, capability, theme/layout or CHANGELOG changes. The requested landing accepts the described additive fixture surface; no additional scope was introduced.

What survived review

  • Inspected the local published FlexLib 4.3.16.42034 Radio.cs enum, parser and fix dispatch: exact case-insensitive tokens, untrimmed malformed values -> Unknown, default Unknown. Reviewed partial reports, positive labels without validity, Time-only/No-fix vetoes and older-firmware/Icom fallback.
  • Reviewed subscription ordering and teardown, queued LAN/WAN/old-backend delivery, repeated empty resets and preserved non-GPS raw dispatch. Rebase preserves main's existing generation-guarded radio-status, transmit-answer and notch subscriptions rather than restoring an unguarded connection. Range-diff contains only those integration resolutions and refreshed generated lines.
  • Built ARM64 AetherSDR plus four test targets from this clean head. QT_QPA_PLATFORM=offscreen ctest --test-dir build -j22 --no-tests=error -R '^(gps_fix_state_test|aetherd_residual_decode_test|icom_gps_readback_test|backend_family_switch_test)$': 4/4 passed, including a final run with Qt 6.12 runtime pinned. New/modified regression sources own no socket or synthetic firmware peer. The focused tests are local evidence; they were not added to the frozen PR gate.
  • Ran automation_gps_fix_probe.py against the newly built, isolated disconnected app at /private/tmp/aethersdr-pr6505.sock. Observed TIME_ONLY: positionValid=false, timeValid=true, referenceLocked=true; NO_FIX: positionValid=false, timeValid=false, referenceLocked=true; the No-fix computer clock advanced 02:29:38Z -> 02:29:39Z while cached GPS time remained unchanged. All five actual widget readouts, address recovery after each veto, cleanup, and Time-only with no independent validity/reference passed. Inspected the Time-only screenshot. Final radio snapshot: connected=false, transmitting=false. Only the owned app was closed; shared lock released. No RF or live radio access.
  • Engine-boundary and command-plane diagnostics are unchanged from main (101 tracked boundary findings, 221 tracked commands), and all applicable lightweight static/generated-doc checks pass. Hosted Docs site/PDF and Static Checks passed. All three rebased commits are GitHub-verified.

The prior two inline findings are fixed in the rebased a5494eff2 and already have evidence-bearing replies/resolution. Remaining earlier body nits are fixed or explicitly supported by current source: strict token parsing matches FlexLib, complete clearfixture behavior is documented, empty resets suppress duplicate notifications, and the derived time-validity semantics are documented. Reviewed CodeGuard's visible advisory examples against the diff; they identify existing whole-file syntax/prose rather than a newly introduced sink. The unseen advisory count is not independently audited.

A separate model-invocable code-review skill was unavailable; this was a manual review, including verification of the existing bot findings. Author mutation evidence was inspected but not independently rerun. Physical GPS acquisition/live 4.3 firmware and native screen-reader behavior remain unverified. The initial local launch mixed Qt installations; pinning DYLD_FRAMEWORK_PATH/plugins to Qt 6.12 resolved it without source changes.

Approve at a5494eff26f9556d1882b27feecfc238be4edb80; merge only after all four required platform/static checks are green and live PR gates remain clear.

Generated with OpenAI Codex (GPT-6 Astra)

@jensenpat
jensenpat merged commit 3fc45eb into aethersdr:main Oct 18, 2026
7 checks passed
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.

GPS: carry Flex fix state separately from receiver lock

3 participants