Skip to content

feat(devices): surface Core's background failures in the app log - #809

Merged
tylerkron merged 2 commits into
mainfrom
feat/surface-core-background-failures
Aug 4, 2026
Merged

feat(devices): surface Core's background failures in the app log#809
tylerkron merged 2 commits into
mainfrom
feat/surface-core-background-failures

Conversation

@tylerkron

@tylerkron tylerkron commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Closes #805

Core 1.4.0 made two previously invisible failure classes observable, and the desktop subscribed to neither — so they still vanished silently. This subscribes to both and routes them to AppLogger with a severity chosen so they do not become Sentry noise.

Observability only. No retry or recovery behavior — auto-reconnect is #804.

What was silent

  • IDevice.ErrorOccurred (daqifi-core#378) — faults on a device's read loop, parse, subscriber dispatch, or per-frame stream decode. A stream that could not be read and a stream that could not be decoded both presented to the user as a device that had simply stopped sending.
  • DaqifiDevice.SendFailed (daqifi-core#413) — sending is fire-and-forget, so a SCPI command could fail to reach the device with no error, no log, and no user-visible signal, leaving the app's idea of device state silently diverging from the device's.

Existing silent-failure symptom this would have surfaced: Core does log both internally (_logger.LogWarning), but neither Core device the desktop builds is given an ILoggerSerialStreamingDevice.CreateCoreDevice constructs new CoreStreamingDevice(name, transport) bare, and the WiFi path builds DeviceConnectionOptions without setting Logger. Both therefore went to Core's null sink. So today these events are the only way either failure can be observed at all, and any past report of "the device ignored my command" (e.g. #589's neighbourhood) had no evidence trail whatsoever. Wiring Core's ILogger into the device options would be a reasonable follow-up, but it is out of scope here.

Severity mapping and reasoning

Default Warning. Error is reserved for DeviceErrorSource.Unknown.

DeviceErrorSource Level Why
MessageConsumer Warning A failed transport read, parse, or subscriber dispatch. The dominant cause by far is a link that is dying or gone — which Core independently escalates to ConnectionStatus.Lost — so Error here would file a Sentry event on every unplug.
StreamDecode Warning One malformed streaming frame. Core drops the frame and the stream survives; this is firmware or link noise, not an app fault.
Reconnect Warning Core exhausted its reconnect attempts. Terminal, but the cause is a device that is unplugged, powered off, or off the network — and the user already gets the ConnectionLost teardown and its dialog.
Unknown Error No Core 1.4.0 path raises it (verified by grepping the raise sites at tag v1.4.0: only MessageConsumer, StreamDecode, Reconnect are ever passed). One arriving means Core hit a failure it could not classify — expected volume zero, high signal. This is deliberately the same call already made for SerialPortConnectFailure.Unknown in #801.
unrecognised value Warning Means this build is behind Core, not that the device misbehaved. A Core bump introducing a chatty new source must not blanket-escalate into Sentry.

SendFailed is always Warning, timeout or not: a write fails because the port closed, the device went away, or the device stopped draining its receive buffer (IsTimeout). All three are conditions of the link. The timeout/hard-failure distinction is still written into the message, because "busy device" and "gone device" are diagnosed differently.

Known under-reporting, accepted deliberately. Core folds "an exception thrown while dispatching a parsed message to a subscriber" into MessageConsumer. That subcase is an app bug (a desktop handler threw). Core does not separate it, and the only way to split it here would be an exception-type allow-list — which is precisely the mechanism that went stale three times (#775, #779, #801). It is still written to DAQiFiAppLog.log with its stack trace; escalating it needs a Core-side source split, not a brittle type check here.

Throttling is Core's. SuppressedCount is reported in the message ("N further like failure(s) suppressed by Core's throttle") rather than adding a second throttle on top.

What MessageSendFailedEventArgs<string> actually exposes

Read at v1.4.0:src/Daqifi.Core/Communication/Producers/MessageSendFailedEventArgs.cs:

  • IOutboundMessage<string> Message — the message whose write failed; Message.Data is the SCPI command string
  • Exception Error — the exception the write threw
  • bool IsTimeout — precomputed error is TimeoutException
  • DateTime Timestamp — UTC, set at construction

No SuppressedCount and no throttle on this one; the producer raises per failed message.

Security note: Message.Data cannot be logged verbatim. ScpiMessageProducer.SetNetworkWifiPassword produces SYSTem:COMMunicate:LAN:PASs "<password>", so a failed send of that command would put the user's plaintext WiFi password into DAQiFiAppLog.log. Only the SCPI verb (everything before the first space, capped at 64 chars) is logged, which is what a diagnosis actually needs. There is a test asserting the password does not appear in the log message.

Wiring and the leak

Both events are attached in ConnectionManager.Connect at the same point ConnectionLost is, and detached at the same two teardown sites (Disconnect, Reboot). Those three call sites now go through a mirrored SubscribeDeviceEvents / UnsubscribeDeviceEvents pair, so a newly wired event cannot be attached at connect and forgotten at teardown — the shape fixed in #795.

AbstractStreamingDevice.cs is owned by a concurrent PR, so the wrapper's re-exposure lives in a new partial file, AbstractStreamingDevice.Diagnostics.cs. It forwards with this as the sender so a log line can name the device the way the user sees it (DeviceDisplayName) rather than the Core-internal object. The Core subscription is attached on the first desktop subscriber and released on the last, and the attached Core instance is remembered separately from CoreDevice so the release cannot miss it if a reconnect replaced it.

Tests

18 new tests; suite goes 874 → 892 passing, 0 failing. dotnet build -tl:on is clean with zero new warnings.

ConnectionManager gained an internal test constructor taking an IAppLogger; the existing AppLogger.Instance.X calls in that file became AppLogger.X against the new injected property. That is required for the level assertions — the singleton is process-wide and MSTest parallelizes test classes, so a shared sink would collect other classes' logging.

Not bench-tested

Per instruction, no hardware was touched — no COM port opened, no app launched, no UI harness run. The "unplug mid-command" bench check from the issue's verification list is still outstanding.

Rebase note

Stacked on #807. #806 already merged; this branch is rebased onto it.

When #807 squash-merges, this branch will go CONFLICTING because it carries #807's pre-squash commits. Do not merge main in and resolve by hand — rebase off the parent instead:

git fetch origin
git rebase --onto origin/main bda8634 feat/surface-core-background-failures
git push --force-with-lease

where bda8634 is the tip of chore/delete-wmi-unplug-watcher this branch is currently cut from.

🤖 Generated with Claude Code

@tylerkron
tylerkron requested a review from a team as a code owner August 3, 2026 17:48
@qodo-code-review

Copy link
Copy Markdown
Contributor

PR Summary by Qodo

Surface Core background device failures in the app log

✨ Enhancement 🐞 Bug fix 🧪 Tests ⚙️ Configuration changes 🕐 40+ Minutes

Grey Divider

AI Description

• Subscribe to Core background-failure events and route them into AppLogger.
• Map severities to avoid Sentry noise; preserve stack traces and suppressed counts.
• Upgrade Daqifi.Core to 1.4.0, remove WMI unplug watcher, and add regression tests.
Diagram

graph TD
  A["Daqifi.Core 1.4.0"] --> B["Core device"] --> C["Desktop device wrapper"] --> D["ConnectionManager"] --> E["AppLogger (noisy-safe)"]
  D --> F["UI disconnect dialog"]
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Wire Core's ILogger into device construction
  • ➕ Captures Core's internal warnings/errors without new event plumbing
  • ➕ Preserves Core log context and categories exactly as emitted
  • ➖ Still risks Sentry noise unless filtering is added at the logging layer
  • ➖ Does not directly tie logs to the desktop wrapper sender/device display name
2. Promote some failures to user-visible notifications
  • ➕ Users get immediate feedback when commands fail to send
  • ➕ Reduces support back-and-forth for 'device ignored my command' reports
  • ➖ Higher UX risk: could spam dialogs for transient link issues
  • ➖ Requires product decisions on rate limiting and messaging
3. Centralize severity policy in AppLogger (rule-based filtering)
  • ➕ One place to control what reaches Sentry across the app
  • ➕ Can evolve without touching connection/device code
  • ➖ Harder to include domain-specific context (device display name, SCPI verb-only redaction)
  • ➖ Requires consistent log message structure to classify accurately

Recommendation: The PR’s approach (subscribe to the new Core events and map to Warning/Error with careful context and redaction) is the best minimal-scope observability fix: it makes previously silent failures visible while explicitly avoiding Sentry noise. A follow-up to pass an ILogger into Core device options could complement this by recovering Core-internal logs, but it should be paired with filtering to avoid reintroducing noise.

Files changed (12) +1088 / -138

Enhancement (3) +411 / -131
ConnectionManager.csSubscribe to Core background-failure events and remove WMI unplug watcher +216/-129

Subscribe to Core background-failure events and remove WMI unplug watcher

• Removes WMI ManagementEventWatcher unplug detection and IDisposable plumbing, relying on Core 1.4.0 ConnectionLost instead. Adds per-device event wiring helpers to ensure subscribe/unsubscribe symmetry and subscribes to ErrorOccurred/SendFailed to log failures with Sentry-safe severity mapping, device/source context, suppressed count, and SCPI verb-only redaction. Introduces an internal test constructor that injects an IAppLogger sink for log-level assertions.

Daqifi.Desktop/ConnectionManager.cs

AbstractStreamingDevice.Diagnostics.csRe-expose Core ErrorOccurred/SendFailed via the desktop wrapper +161/-0

Re-expose Core ErrorOccurred/SendFailed via the desktop wrapper

• Adds thread-safe event re-exposure for Core’s diagnostics events so ConnectionManager can subscribe on the desktop device abstraction. Attaches Core subscriptions on the first desktop subscriber and detaches on the last, tracking the exact Core instance attached to handle CoreDevice replacement during reconnects.

Daqifi.Desktop/Device/AbstractStreamingDevice.Diagnostics.cs

IDevice.csExtend desktop IDevice interface with Core diagnostics events +34/-2

Extend desktop IDevice interface with Core diagnostics events

• Adds ErrorOccurred and SendFailed events to the desktop device interface, documenting threading, throttling, and subscribe/unsubscribe lifetime expectations. Aligns the desktop abstraction with Core 1.4.0’s newly observable background failure surfaces.

Daqifi.Desktop/Device/IDevice.cs

Bug fix (2) +39 / -1
BootloaderSessionStreamingDeviceAdapter.csImplement Core 1.4.0 IDevice.ErrorOccurred on bootloader adapter +14/-1

Implement Core 1.4.0 IDevice.ErrorOccurred on bootloader adapter

• Adds the new ErrorOccurred event to satisfy Core 1.4.0’s IDevice contract. The event is a no-op because the bootloader adapter runs no background read/decode pipeline.

Daqifi.Desktop/Device/Firmware/BootloaderSessionStreamingDeviceAdapter.cs

SerialStreamingDevice.csClassify Core SerialPortConnectException reasons to avoid Sentry noise +25/-0

Classify Core SerialPortConnectException reasons to avoid Sentry noise

• Adds early match arms for Core 1.4.0’s SerialPortConnectException and logs NotFound/InUse/AccessDenied as warnings. Intentionally leaves Unknown to fall through to the Error path to preserve high-signal investigation cases, while keeping legacy exception-type matches as fallback.

Daqifi.Desktop/Device/SerialDevice/SerialStreamingDevice.cs

Refactor (1) +0 / -2
App.xaml.csRemove ConnectionManager disposal at app exit +0/-2

Remove ConnectionManager disposal at app exit

• Stops calling ConnectionManager.Instance.Dispose() on exit because the WMI watcher and IDisposable lifetime management were removed from ConnectionManager. Leaves AppLogger shutdown behavior unchanged.

Daqifi.Desktop/App.xaml.cs

Tests (5) +637 / -2
ConnectionManagerBackgroundFailureTests.csAdd tests for routing Core background failures to AppLogger +359/-0

Add tests for routing Core background failures to AppLogger

• Introduces a comprehensive suite covering ErrorOccurred and SendFailed severity mapping, message contents (device/source, suppressed count), SCPI argument redaction, and subscription lifetime (connect/disconnect/reboot). Uses an internal ConnectionManager test constructor and reflection to validate behavior without singleton cross-test interference.

Daqifi.Desktop.Test/ConnectionManagerBackgroundFailureTests.cs

ConnectionManagerFirmwareGateTests.csExpand unplug-teardown tests to assert user notification surface +73/-2

Expand unplug-teardown tests to assert user notification surface

• Adds coverage for OnDeviceConnectionLost setting NotifyConnection/LastDisconnectReason, suppressing dialogs during firmware update, and ignoring already-disconnected devices. Updates cleanup to reset notification state because the WMI unplug watcher path is removed.

Daqifi.Desktop.Test/ConnectionManagerFirmwareGateTests.cs

AbstractStreamingDeviceDiagnosticsTests.csAdd tests for AbstractStreamingDevice forwarding of Core diagnostics events +138/-0

Add tests for AbstractStreamingDevice forwarding of Core diagnostics events

• Verifies that Core ErrorOccurred is forwarded with the desktop wrapper as sender, that the Core subscription is released when the last subscriber unsubscribes, and that multiple subscribers share a single Core subscription correctly. Uses reflection to assert attachment state via the private diagnostics source field.

Daqifi.Desktop.Test/Device/AbstractStreamingDeviceDiagnosticsTests.cs

SerialStreamingDeviceLogConnectFailureTests.csAdd tests for Core 1.4.0 SerialPortConnectException classification +61/-0

Add tests for Core 1.4.0 SerialPortConnectException classification

• Adds tests ensuring NotFound/InUse/AccessDenied reasons log as Warning (not Sentry) while Unknown remains Error. Also adds a tripwire asserting SerialPortConnectException derives from IOException rather than legacy platform exception types.

Daqifi.Desktop.Test/Device/SerialStreamingDeviceLogConnectFailureTests.cs

TimestampProcessorSerializationTests.csUpdate timestamp processor test fake for Core 1.4.0 interface +6/-0

Update timestamp processor test fake for Core 1.4.0 interface

• Implements the new ITimestampProcessor.HasTimestampFrequency method by forwarding to the inner implementation. Keeps existing serialization/order assertions intact while satisfying the updated Core interface contract.

Daqifi.Desktop.Test/Device/TimestampProcessorSerializationTests.cs

Other (1) +1 / -2
Daqifi.Desktop.csprojBump Daqifi.Core to 1.4.0 and drop System.Management dependency +1/-2

Bump Daqifi.Core to 1.4.0 and drop System.Management dependency

• Upgrades the Core package reference from 1.3.0 to 1.4.0. Removes System.Management since WMI-based unplug watching was deleted from the desktop app.

Daqifi.Desktop/Daqifi.Desktop.csproj

@tylerkron
tylerkron force-pushed the feat/surface-core-background-failures branch from 42a2499 to 2c3e35a Compare August 3, 2026 17:51
@qodo-code-review

qodo-code-review Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📜 Skill insights (0)

Context used
✅ Compliance rules (platform): 49 rules

Grey Divider


Remediation recommended

1. WIFI_PASSWORD stored as string ✓ Resolved 📘 Rule violation ⛨ Security
Description
The new test stores a WiFi password in a string constant, rather than a secure type like
SecureString. Plain string passwords can be copied and remain in memory longer than necessary,
violating the password-handling requirement.
Code

Daqifi.Desktop.Test/ConnectionManagerBackgroundFailureTests.cs[R37-38]

+    // A password-carrying SCPI command is the reason send-failure logging reports the verb only.
+    private const string WIFI_PASSWORD = "hunter2-not-in-the-log";
Relevance

●● Moderate

No history requiring SecureString; repo focuses on redaction/masking sensitive values (e.g., serial
logging).

PR-#242

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance ID 244805 requires password values to be stored using a secure type (e.g.,
SecureString) rather than plain string. The added code introduces WIFI_PASSWORD as a `const
string`, which is a plain-string password value.

Rule 244805: Store passwords using SecureString types instead of plain strings
Daqifi.Desktop.Test/ConnectionManagerBackgroundFailureTests.cs[35-39]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`WIFI_PASSWORD` is currently stored as a `string` constant in a test. The compliance rule requires passwords (and similar secrets) to be held in a secure type (e.g., `SecureString`) and not persisted as plain strings.

## Issue Context
This test exists to ensure password-bearing SCPI commands are not logged verbatim. You can keep the intent of the test while avoiding storing a password as a long-lived `string` constant (e.g., assert that only the SCPI verb is logged, not the arguments), or by using a `SecureString` and converting only at the narrow call site if absolutely required.

## Fix Focus Areas
- Daqifi.Desktop.Test/ConnectionManagerBackgroundFailureTests.cs[35-41]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

To customize comments, go to the Qodo configuration screen, or learn more in the docs.

Qodo Logo

Comment thread Daqifi.Desktop.Test/ConnectionManagerBackgroundFailureTests.cs Outdated
Base automatically changed from chore/delete-wmi-unplug-watcher to main August 3, 2026 18:14
@tylerkron
tylerkron force-pushed the feat/surface-core-background-failures branch from 94e7f31 to 32f8e63 Compare August 3, 2026 18:23
claude added 2 commits August 3, 2026 15:02
Core 1.4.0 made two previously invisible failure classes observable and the
desktop subscribed to neither, so they still vanished silently:

- IDevice.ErrorOccurred (daqifi-core#378) - faults on a device's read loop,
  parse, subscriber dispatch, or per-frame stream decode. A stream that could
  not be read and a stream that could not be decoded both presented to the user
  as a device that had simply stopped sending.
- DaqifiDevice.SendFailed (daqifi-core#413) - sending is fire-and-forget, so a
  SCPI command could fail to reach the device with no error, no log, and no
  user-visible signal, leaving the app's idea of device state silently
  diverging from the device's.

Neither Core device the desktop builds is given an ILogger (the serial one is
constructed bare, the WiFi one via DeviceConnectionOptions with Logger unset),
so Core's own warnings for both went to a null sink. These events are currently
the only way either failure can be seen at all.

Both are wired in ConnectionManager where ConnectionLost is already attached,
and detached at the same two teardown sites. Subscribe/unsubscribe now go
through a single mirrored pair of methods so a newly wired event cannot be
attached at connect and forgotten at teardown (the leak fixed in #795).

Severity: default Warning; Error is reserved for DeviceErrorSource.Unknown.
Every source Core actually raises describes the link or the device, not the
app - a read that failed because the cable came out, a frame the device
garbled, a reconnect that ran out of attempts against a powered-off unit - and
routing those to Error would file a Sentry event on every unplug, which is how
#775, #779 and #801 buried real bugs. Unknown is unreachable from any Core
1.4.0 path, so one arriving means Core hit a failure it could not classify:
expected volume zero, high signal, the same call already made for
SerialPortConnectFailure.Unknown in #801. A source value this build does not
recognise means the desktop is behind Core, not that the device misbehaved, and
stays a Warning. Core's SuppressedCount is reported in the message rather than
adding a second throttle on top of Core's.

Send-failure logging reports the SCPI verb only. Core's SetNetworkWifiPassword
embeds the user's WiFi password in the command payload, and DAQiFiAppLog.log
must never contain it.

The desktop wrapper re-exposes both Core events on IDevice from a new partial
file, forwarding with itself as sender so a log line can name the device the
way the user sees it. The Core subscription is attached on the first desktop
subscriber and released on the last, and the attached instance is remembered so
the release cannot miss it across a reconnect.

Observability only - no retry or recovery behavior; auto-reconnect is #804.

Closes #805

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Qodo rule violation: a `string` constant named WIFI_PASSWORD read as a stored
credential. It never was one - it is a synthetic marker fed to
SetNetworkWifiPassword purely so the test can assert those argument bytes never
reach the log. Renamed to SENTINEL_COMMAND_ARGUMENT and documented why
SecureString does not apply: nothing secret is being protected, and Core's
SetNetworkWifiPassword takes a plain string.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@tylerkron
tylerkron force-pushed the feat/surface-core-background-failures branch from 32f8e63 to 8f64b97 Compare August 3, 2026 21:04
@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown

📊 Code Coverage Report

Summary

Summary
Generated on: 8/3/2026 - 9:08:42 PM
Coverage date: 8/3/2026 - 9:06:54 PM - 8/3/2026 - 9:08:37 PM
Parser: MultiReport (2x Cobertura)
Assemblies: 2
Classes: 142
Files: 166
Line coverage: 57.5% (6058 of 10527)
Covered lines: 6058
Uncovered lines: 4469
Coverable lines: 10527
Total lines: 35211
Branch coverage: 43.5% (1626 of 3730)
Covered branches: 1626
Total branches: 3730
Method coverage: Feature is only available for sponsors

Coverage

DAQiFi - 57.6%
Name Line Branch
DAQiFi 57.6% 43.7%
Daqifi.Desktop.App 2.9% 0%
Daqifi.Desktop.Channel.AbstractChannel 74.7% 57.1%
Daqifi.Desktop.Channel.AnalogChannel 46.8% 25%
Daqifi.Desktop.Channel.ChannelColorManager 100% 100%
Daqifi.Desktop.Channel.DataSample 91.6%
Daqifi.Desktop.Channel.DigitalChannel 86.5% 61.1%
Daqifi.Desktop.Configuration.FirewallConfiguration 90.6% 66.6%
Daqifi.Desktop.Configuration.WindowsFirewallWrapper 63% 64.2%
Daqifi.Desktop.ConnectionManager 86.7% 75.7%
Daqifi.Desktop.Converters.BoolAndToVisibilityConverter 100% 100%
Daqifi.Desktop.Converters.BrushColorMatchConverter 100% 100%
Daqifi.Desktop.Converters.ConnectionTypeToColorConverter 88.8% 83.3%
Daqifi.Desktop.Converters.InvertedBoolToVisibilityConverter 100% 100%
Daqifi.Desktop.Converters.ListToStringConverter 100% 94.4%
Daqifi.Desktop.Converters.NotNullToVisibilityConverter 100% 100%
Daqifi.Desktop.Converters.OxyColorToBrushConverter 100% 100%
Daqifi.Desktop.Device.AbstractStreamingDevice 70.5% 62.5%
Daqifi.Desktop.Device.ConnectionLostEventArgs 100%
Daqifi.Desktop.Device.DeviceMessage 92.8%
Daqifi.Desktop.Device.Firmware.BootloaderDiscoveredEventArgs 100% 50%
Daqifi.Desktop.Device.Firmware.BootloaderHoldDroppedEventArgs 100% 50%
Daqifi.Desktop.Device.Firmware.BootloaderHoldService 82.7% 83.3%
Daqifi.Desktop.Device.Firmware.BootloaderSessionStreamingDeviceAdapter 10% 8.3%
Daqifi.Desktop.Device.Firmware.BootloaderWatcher 90.7% 72.6%
Daqifi.Desktop.Device.Firmware.FirmwareFailureClassifier 90% 83.3%
Daqifi.Desktop.Device.Firmware.FirmwareUpdateCoordinator 74.2% 63.4%
Daqifi.Desktop.Device.Firmware.FirmwareUpdateServiceConfig 100%
Daqifi.Desktop.Device.Firmware.HeldBootloader 100% 50%
Daqifi.Desktop.Device.Firmware.HidBootloaderDiscovery 0% 0%
Daqifi.Desktop.Device.NativeMethods 100%
Daqifi.Desktop.Device.SerialDevice.SerialStreamingDevice 56.5% 55.4%
Daqifi.Desktop.Device.WiFiDevice.DaqifiStreamingDevice 59.2% 52.5%
Daqifi.Desktop.DialogService.DialogService 0% 0%
Daqifi.Desktop.DialogService.ServiceLocator 0% 0%
Daqifi.Desktop.DiskSpace.DiskSpaceCheckResult 100%
Daqifi.Desktop.DiskSpace.DiskSpaceEventArgs 100%
Daqifi.Desktop.DiskSpace.DiskSpaceMonitor 88.2% 86.6%
Daqifi.Desktop.DiskSpace.DiskSpaceMonitorCoordinator 100% 100%
Daqifi.Desktop.DiskSpace.DiskSpaceStartDecision 100%
Daqifi.Desktop.DuplicateDeviceCheckResult 100%
Daqifi.Desktop.Exporter.LoggingSessionSampleSource 96.3% 75%
Daqifi.Desktop.Exporter.OptimizedLoggingSessionExporter 68.9% 54.5%
Daqifi.Desktop.Helpers.BooleanConverter`1 100% 100%
Daqifi.Desktop.Helpers.BooleanToInverseBoolConverter 100% 100%
Daqifi.Desktop.Helpers.BooleanToVisibilityConverter 100%
Daqifi.Desktop.Helpers.EnumDescriptionConverter 100% 91.6%
Daqifi.Desktop.Helpers.IntToVisibilityConverter 100% 100%
Daqifi.Desktop.Helpers.MinMaxDownsampler 100% 96.4%
Daqifi.Desktop.Helpers.NaturalSortHelper 100% 100%
Daqifi.Desktop.Helpers.OxyPlotDarkTheme 100%
Daqifi.Desktop.Helpers.ReferenceComparer`1 100%
Daqifi.Desktop.Helpers.TileBrushes 100%
Daqifi.Desktop.Helpers.UiThreadHelper 26.6% 25%
Daqifi.Desktop.Logger.DatabaseLogger 0% 0%
Daqifi.Desktop.Logger.DatabaseMigrator 0% 0%
Daqifi.Desktop.Logger.DeviceLegendGroup 100% 100%
Daqifi.Desktop.Logger.InitialSessionLoad 100%
Daqifi.Desktop.Logger.LoggedSeriesLegendItem 94.1% 44.4%
Daqifi.Desktop.Logger.LoggingContext 100%
Daqifi.Desktop.Logger.LoggingContextDesignTimeFactory 0%
Daqifi.Desktop.Logger.LoggingManager 25.8% 26.7%
Daqifi.Desktop.Logger.LoggingSession 36.5% 13%
Daqifi.Desktop.Logger.MinimapPlotComponents 100%
Daqifi.Desktop.Logger.PlotLogger 68.8% 63.6%
Daqifi.Desktop.Logger.PlotModelFactory 99.4% 62.5%
Daqifi.Desktop.Logger.SessionChannelInfo 100%
Daqifi.Desktop.Logger.SessionDataRepository 97.9% 89.5%
Daqifi.Desktop.Logger.SessionDeviceMetadata 80%
Daqifi.Desktop.Logger.SessionSampleWriter 96% 91.3%
Daqifi.Desktop.Logger.SummaryLogger 88.2% 71.7%
Daqifi.Desktop.Logger.TimestampGapDetector 94.7% 83.3%
Daqifi.Desktop.Loggers.AppLoggerLoggerProvider 59.5% 56.6%
Daqifi.Desktop.Loggers.ImportOptions 66.6%
Daqifi.Desktop.Loggers.ImportProgress 0% 0%
Daqifi.Desktop.Loggers.ImportTimestampQuality 100% 100%
Daqifi.Desktop.Loggers.SdCardDownloadStalledException 92.3% 100%
Daqifi.Desktop.Loggers.SdCardImportResult 100%
Daqifi.Desktop.Loggers.SdCardSessionImporter 70.3% 64.4%
Daqifi.Desktop.MainWindow 0% 0%
Daqifi.Desktop.Migrations.AddSamplesSessionTimeIndex 97.8%
Daqifi.Desktop.Migrations.AddSessionDeviceMetadata 98.6%
Daqifi.Desktop.Migrations.AddSessionSampleCount 98.1%
Daqifi.Desktop.Migrations.DropChannelTable 65.5%
Daqifi.Desktop.Migrations.InitialSQLiteMigration 97.4%
Daqifi.Desktop.Migrations.LoggingContextModelSnapshot 0%
Daqifi.Desktop.Models.DaqifiSettings 83.7% 100%
Daqifi.Desktop.Models.DebugDataHistory 6.6% 0%
Daqifi.Desktop.Models.DebugDataModel 0% 0%
Daqifi.Desktop.Models.FirmwareOption 61.5% 37.5%
Daqifi.Desktop.Models.Notifications 80%
Daqifi.Desktop.Models.Profile 100%
Daqifi.Desktop.Models.ProfileChannel 100%
Daqifi.Desktop.Models.ProfileDevice 100%
Daqifi.Desktop.Models.SdCardFile 42.8% 0%
Daqifi.Desktop.Models.SdCardLogFormatInfo 94.7% 87.5%
Daqifi.Desktop.Services.NoOpMessageBoxService 0%
Daqifi.Desktop.Services.WindowsPrincipalAdminChecker 0%
Daqifi.Desktop.Services.WpfMessageBoxService 0%
Daqifi.Desktop.UpdateVersion.VersionNotification 84% 54.1%
Daqifi.Desktop.View.ConnectionDialog 0% 0%
Daqifi.Desktop.View.DebugWindow 0% 0%
Daqifi.Desktop.View.DeviceLogsView 0% 0%
Daqifi.Desktop.View.DuplicateDeviceDialog 0% 0%
Daqifi.Desktop.View.ErrorDialog 0% 0%
Daqifi.Desktop.View.ExportDialog 0% 0%
Daqifi.Desktop.View.FirmwareDialog 0% 0%
Daqifi.Desktop.View.Flyouts.LiveGraphFlyout 0% 0%
Daqifi.Desktop.View.Flyouts.NotificationsFlyout 0% 0%
Daqifi.Desktop.View.Flyouts.SummaryFlyout 0% 0%
Daqifi.Desktop.View.MigrationStatusWindow 0% 0%
Daqifi.Desktop.View.MinimapInteractionController 0% 0%
Daqifi.Desktop.View.ProfilesPane 0% 0%
Daqifi.Desktop.View.Prototype.ChannelsPanePrototype 0% 0%
Daqifi.Desktop.View.Prototype.DevicesPanePrototype 0% 0%
Daqifi.Desktop.View.Prototype.LiveGraphPane 0% 0%
Daqifi.Desktop.View.Prototype.LoggedDataPanePrototype 0% 0%
Daqifi.Desktop.View.SuccessDialog 0% 0%
Daqifi.Desktop.ViewModels.ChannelsPaneViewModel 57.8% 34.8%
Daqifi.Desktop.ViewModels.ChannelTileViewModel 72.6% 66.1%
Daqifi.Desktop.ViewModels.ConfirmOverlayViewModel 100% 100%
Daqifi.Desktop.ViewModels.ConnectionDialogViewModel 66.1% 55.7%
Daqifi.Desktop.ViewModels.DaqifiViewModel 19.4% 14.3%
Daqifi.Desktop.ViewModels.DeviceLogsViewModel 80.4% 60.8%
Daqifi.Desktop.ViewModels.DevicesPaneViewModel 0% 0%
Daqifi.Desktop.ViewModels.DeviceTileViewModel 35.7% 15.2%
Daqifi.Desktop.ViewModels.DuplicateDeviceDialogViewModel 0%
Daqifi.Desktop.ViewModels.ErrorDialogViewModel 0%
Daqifi.Desktop.ViewModels.ExportDialogViewModel 65.5% 50%
Daqifi.Desktop.ViewModels.FirmwareDialogViewModel 55.1% 46.8%
Daqifi.Desktop.ViewModels.ImportAllOutcome 100% 100%
Daqifi.Desktop.ViewModels.LoggingSessionListViewModel 95.6% 88.8%
Daqifi.Desktop.ViewModels.NewProfileChannelItem 0%
Daqifi.Desktop.ViewModels.NewProfileDeviceItem 0% 0%
Daqifi.Desktop.ViewModels.ProfilesPaneViewModel 0% 0%
Daqifi.Desktop.ViewModels.SdCardFailure 100%
Daqifi.Desktop.ViewModels.SdCardFailureClassifier 100% 88.8%
Daqifi.Desktop.ViewModels.SettingsViewModel 0% 0%
Daqifi.Desktop.ViewModels.SuccessDialogViewModel 0%
Sentry.Generated.BuildPropertyInitializer 100%
Daqifi.Desktop.Common - 46%
Name Line Branch
Daqifi.Desktop.Common 46% 30.9%
Daqifi.Desktop.Common.AppDataPaths 84.2% 50%
Daqifi.Desktop.Common.Loggers.AppLogErrorException 0%
Daqifi.Desktop.Common.Loggers.AppLogger 40.9% 28.9%

Coverage report generated by ReportGeneratorView full report in build artifacts

@tylerkron

Copy link
Copy Markdown
Contributor Author

Rebased onto main (post-#812), and a bench-status note

Rebased after #812 merged, with the unit gate re-run after the rebase rather than before. All three PRs were already reported textually mergeable, but #808 and #810 modify the same file #812 touched, so "no conflict" was not sufficient reason to skip re-testing.

Hardware gate: partially blocked, and NOT on anything in these PRs

Device-gated FlaUI run on the chain-1 tip: 19/23. Four failures, none attributable to this work:

  • 2 SD imports (SdCardImport_ExistingFileOnCard_ImportsToSession, SdCardLogging_LogsToSdCard_ThenImportsToSession) — both report -1 samples on files dated 2026-06-23 and 2026-07-06. Known firmware SD-heap defect (firmware#703); the bench card has been wedged since June and writes no new files.
  • 2 connect/discovery timeouts (ConfigureLogging_SetsFrequencyAndChannels, RestartStreamingAfterGap_TimeAxisAnchorsOnNewSession) — No device discovered on Serial within 60s and Expected at least 1 connected device tile(s) but found fewer within 60s.

The last two are device state, not code. Established by control, not assumption:

Run Code Result
Earlier today #812 branch RestartStreamingAfterGap PASSED (twice)
Now main, which contains #812 FAILS — same code
Now chain-1 tip FAILS identically

Since the identical code passed earlier and fails now, the variable is the bench, not the branch. A Core-level probe on the same port confirms the device is still alive at the transport layer — connects, initializes, firmware 3.7.2, reports analog_in_port_enabled = FFFF, channel enables survive — so it responds to a direct port open while the app's discovery probe times out. That is consistent with the SD subsystem being wedged by the SD tests that ran immediately before, which per firmware#703 recovers only on a true USB re-enumeration.

This needs a physical power-cycle of the bench device, which I can't perform. I deliberately did not force re-enumeration via pnputil/Device Manager — this hardware has known recovery problems and that is not a risk to take unprompted.

Once the device is power-cycled the device-gated suite should be re-run against these branches to close out the gate. Everything else — build, unit gate, Qodo — is green.

@tylerkron

Copy link
Copy Markdown
Contributor Author

Hardware gate result

Run with parallelism disabled (-- MSTest.Parallelize.Workers=1), which is required for this suite to be deterministic at all — see #814.

Branch Device-gated result
chain-1 (#808 + #810) 21/23 — only the 2 known firmware SD failures
chain-2 (#809) 20/23 — the 2 SD failures + RestartStreamingAfterGap

The extra failure on #809 is not #809

RestartStreamingAfterGap_TimeAxisAnchorsOnNewSession failed with Expected at least 1 connected device tile(s) but found fewer within 60s. Checked rather than assumed:

Identical code passing earlier and failing now, plus main failing the same way, rules out #809. The variable is the bench: discovery degrades as the SD tests accumulate, which is the same behaviour a power cycle cleared earlier today (firmware#703 — recovers only on true USB re-enumeration).

Status

Everything else on all three: build clean, unit gates green, Qodo clean.

@tylerkron

Copy link
Copy Markdown
Contributor Author

Hardware gate: cleared

Re-run on a freshly power-cycled device with -- MSTest.Parallelize.Workers=1 (required — see #814).

The one outstanding question was RestartStreamingAfterGap_TimeAxisAnchorsOnNewSession, which failed in #809's full-suite runs but not #810's. Settled with a controlled back-to-back triplet — same test, run alone, same device state, one after another:

Branch Isolated result
main PASS
#810 tip PASS
#809 PASS

All three pass. The full-suite failure is a harness defect, not a branch one: a preceding test leaves its app running (Application failed to exit in the captured output), that app holds the COM port, and the next test is the one that reports the timeout. The same marker appears in runs on main. Now recorded on #814.

I was wrong twice on the way to this, and both are worth recording so the next person does not repeat them: I first attributed these failures to an SD-induced device wedge (excluding the SD tests made it worse), and then to device degradation across the session (a fresh device reproduced it identically). Neither held up.

Final state, all three PRs

#808 #809 #810
Build clean clean clean
Unit 876 896 872
Qodo Bugs 0, Rules 0 Bugs 0, Rules 0 Bugs 0, Rules 0
Device-gated 21/23 20/23 21/23

Every remaining device-gated failure is accounted for: the two SD imports are the known firmware SD-heap defect (firmware#703) on files dated June/July, and #809's third is the leaked-app harness issue above, disproved by isolated re-run.

@tylerkron
tylerkron added this pull request to the merge queue Aug 4, 2026
Merged via the queue into main with commit 429d460 Aug 4, 2026
2 checks passed
@tylerkron
tylerkron deleted the feat/surface-core-background-failures branch August 4, 2026 02:30
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.

reliability: subscribe to Core's ErrorOccurred and SendFailed so background failures stop vanishing

2 participants