Skip to content

fix(monitoring): harden ONT/CM/Cellular Disable/Enable toggle (review follow-ups) - #1048

Draft
Optic00 wants to merge 2 commits into
Ozark-Connect:devfrom
Optic00:feature/monitoring-toggle-hardening
Draft

fix(monitoring): harden ONT/CM/Cellular Disable/Enable toggle (review follow-ups)#1048
Optic00 wants to merge 2 commits into
Ozark-Connect:devfrom
Optic00:feature/monitoring-toggle-hardening

Conversation

@Optic00

@Optic00 Optic00 commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Pattern-wide robustness follow-ups to the device-monitoring Disable/Enable toggle (#1044 ONT, #1046 CM, #1047 Cellular), applied across all three device types. From an adversarial review of the toggle; two of these (#1, #4) were flagged as good catches.

#1 - Make the anti-resurrection guard atomic

UpdateXxxPollResultAsync read Enabled and then called SaveChanges separately - a TOCTOU where a Disable committing in between let an in-flight poll still overwrite the paused row's frozen LastError/LastPolled. It now does a single conditional ExecuteUpdate(...).Where(x => x.Id == id && x.Enabled) (returns rows > 0), so the check and the write are one atomic statement. Repo tests moved from the EF InMemory provider to SQLite in-memory (InMemory can't execute ExecuteUpdate), plus a relational mid-poll-disable regression per type.

#2 - Don't contact / falsely report a disabled device on the manual + dashboard paths

The public poll entry points fetched the device before the guard ran, so refreshing a disabled device still hit the network - and Cellular returned (true, "polled successfully") while nothing persisted. PollCmAsync, PollModemAsync, and the ONT manual poll now short-circuit when the config is disabled (Cellular returns a clear "modem is disabled" message), and the CM/Cellular/ONT stats panels skip auto-polling a disabled config. The non-persisting Test/Probe buttons are unchanged.

#3 - One control for enable/disable (UX change)

Each edit form still had its own "Enable polling" checkbox, competing with the new row toggle - open the edit form, toggle the row off, click Update, and the stale form value re-enabled it. Removed that checkbox from the ONT/CM/Cellular edit forms (the row Disable/Enable button is now the single control) and stopped SaveXxxConfigurationAsync from writing Enabled on update, so SetXxxEnabledAsync is the sole writer. Starlink's edit form is untouched (it has no toggle). New tests assert Save preserves the DB Enabled value.

#4 - A persistence exception is no longer mistaken for "disabled"

The CM/Cellular poll success helpers caught every repository exception and returned false - the same value the guard uses for a disabled row - so a transient DB write failure on an enabled device silently dropped valid stats (and Cellular reported success). They now rethrow, so a real failure surfaces via the poll error path; false only ever means "disabled" (matching how ONT already behaved).

Tests

NetworkOptimizer.Storage.Tests 183/183 and NetworkOptimizer.Web.Tests 933/933 green; solution builds clean (0 warnings).

Off dev after #1044/#1046/#1047 merged.

@Optic00
Optic00 marked this pull request as draft July 22, 2026 17:43
@Optic00

Optic00 commented Jul 22, 2026

Copy link
Copy Markdown
Contributor Author

Moving this to draft / on hold — no rush, syncing with what's already in dev.

  • Update dependency Lextm.SharpSnmpLib to 12.5.7 - autoclosed #2 here (don't contact / false-report disabled devices on the manual + dashboard paths) is already covered by 618c2e90 ("Dashboard ONT/CM/Cellular cards: respect Device Monitoring disable toggle"), so that part will be dropped — it was duplicate work.
  • dev has since moved (Starlink toggle, etc.), so this needs a rebase and currently conflicts.

Plan for the next dev cycle: rebase onto fresh dev, drop the duplicated #2, resolve the remaining #1 (atomic guard) / #3 (single Enable control) / #4 (exception handling) conflicts, and likely extend #1/#3/#4 to Starlink too now that it has the same toggle — so the hardening is pattern-wide across all four types.

@Optic00
Optic00 force-pushed the feature/monitoring-toggle-hardening branch from 1251b77 to a9561c7 Compare July 25, 2026 15:21
Optic00 added 2 commits July 27, 2026 19:18
… follow-ups)

Pattern-wide robustness fixes from the adversarial review of the toggle,
applied across all three device types.

Ozark-Connect#1 Atomic anti-resurrection guard: UpdateXxxPollResultAsync now performs a
single conditional ExecuteUpdate WHERE Id == id && Enabled instead of a
read-then-SaveChanges, closing the TOCTOU where a Disable committing between
the read and the write could still overwrite a paused row's frozen state.
Repo tests moved from the EF InMemory provider to SQLite in-memory (InMemory
cannot execute ExecuteUpdate) + a relational mid-poll-disable regression per type.

Ozark-Connect#2 Disabled devices are no longer contacted or falsely reported on the service
paths: PollCmAsync / PollModemAsync / the ONT manual poll short-circuit when the
config is disabled (Cellular returns a clear 'disabled' message instead of a
false 'polled successfully'), and the Cellular card skips auto-polling a disabled
config on initial load and when paging between modems. The dashboard-card side of
this (Refresh button state, server-side refresh guard, timer config reload) is
already covered by 618c2e9 and is not duplicated here. The non-persisting
Test/Probe buttons are unchanged.

Ozark-Connect#3 Removed the redundant 'Enable polling' checkbox from the ONT/CM/Cellular edit
forms (the row Disable/Enable toggle is the single control) and stopped
SaveXxxConfigurationAsync from copying Enabled on update, so a stale open edit
form can no longer resurrect a row the toggle just disabled. Starlink untouched.

Ozark-Connect#4 A persistence exception is no longer swallowed into the same 'false' the guard
uses for a disabled row: the poll success helpers rethrow, so a real DB write
failure on an enabled device surfaces via the error path instead of silently
dropping valid stats. Both call sites already run inside a catch that records the
failure on the config, so the poll loops stay alive.
Starlink got its own Disable/Enable row toggle after the ONT/CM/Cellular
hardening was written, and it shipped with the same four gaps this branch
already closed for the other three device types.

Ozark-Connect#1 UpdateStarlinkPollResultAsync is now a single conditional ExecuteUpdate
WHERE Id == id && Enabled instead of a read-then-SaveChanges, so a Disable
committing between the read and the write can no longer let an in-flight poll
overwrite the paused terminal's frozen state. StarlinkRepositoryTests moved to
SQLite in-memory (the EF InMemory provider cannot execute ExecuteUpdate) plus a
relational mid-poll-disable regression.

Ozark-Connect#2 PollStarlinkAsync short-circuits on a disabled config, so the manual Refresh
path no longer reaches the dish of a paused terminal. The stats panel already
guards this client-side; this is the server-side half.

Ozark-Connect#3 Removed the 'Enable polling' checkbox from the Starlink edit form (the row
toggle is the single control) and stopped SaveStarlinkConfigurationAsync from
copying Enabled on update, so a stale open edit form can no longer resurrect a
terminal the toggle just disabled. SetStarlinkEnabledAsync is the sole writer.

Ozark-Connect#4 UpdateConfigSuccessAsync rethrows a persistence failure instead of returning
the same false the guard uses for a disabled row, so a real DB write failure
surfaces via the poll error path. The call site already runs inside a catch that
records the failure, so the poll loop stays alive.
@Optic00
Optic00 force-pushed the feature/monitoring-toggle-hardening branch from a9561c7 to 2dda7b0 Compare July 27, 2026 17:26
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