Skip to content

chore(api): move the per-configuration sample-rate cap into Core - #513

Open
tylerkron wants to merge 3 commits into
mainfrom
chore/sample-rate-cap-in-core-481
Open

chore(api): move the per-configuration sample-rate cap into Core#513
tylerkron wants to merge 3 commits into
mainfrom
chore/sample-rate-cap-in-core-481

Conversation

@tylerkron

Copy link
Copy Markdown
Contributor

What was wrong

How fast a DAQiFi will actually stream depends on how many channels you have enabled — a Nyquist that manages 7,746 Hz with one analog channel manages 3,518 Hz with sixteen. Core never told anyone that. StreamingFrequency only checked the board's absolute 22,000 Hz ceiling, so a perfectly ordinary "set the rate, then enable more channels" sequence left an impossible rate live, and the library reported it back as if it were fine. The firmware does not clamp: it rejects the start outright and streams nothing, so the first sign of trouble is a recording session that comes back empty.

The rule for computing that ceiling did exist — but only inside the MCP server, where it had been added for #447. Every other consumer of the library, the desktop application first among them, silently went without it.

How it was fixed

Core owns the rule now. SampleRateCap decides the ceiling, and IStreamingDevice exposes it as MaximumStreamingFrequencyHz together with EnforceStreamingFrequencyCap(), which lowers a live rate that no longer fits and tells you what it was before. The MCP server keeps only the part that was genuinely its own — the operator's --max-sample-rate-hz clamp — and gets the device half from Core, so its behaviour is unchanged.

What a reviewer may want to push back on:

  • Which source wins. The device's own current_max_rate_hz stays authoritative; the published rate model — which until now had no production caller anywhere in Core — is only the fallback for a document that states no cap. The bench numbers below are the argument: the model sits above the device's answer in every configuration measured (15,714 vs 7,746 for one channel; 5,000 vs 3,518 for sixteen), exactly as its own docs warn, because it accounts for channel count and type but not for the transport. Preferring it, or taking the lower of the two, would either over-permit or contradict the documented contract.
  • The setter still validates against the board ceiling only. Making StreamingFrequency reject against the per-configuration cap would break the reasonable ordering of setting a rate before enabling channels, and would be a behaviour break for existing callers. Enforcement is therefore something you ask for.
  • Freshness is a contract, not a guarantee. The device's figure describes the set enabled when the capability document was read, so a caller that changes channels should re-read it — the MCP server already does this after every configure call. This is stated on the type rather than papered over with a heuristic.
  • Two members were added to the public IStreamingDevice. Both have default implementations, so existing implementers keep compiling — the same pattern the interface already uses for StartStreamingAsync.
  • Daqifi.Mcp.SampleRateCapCalculator is gone, its tests ported down to Core with the logic. Daqifi.Mcp ships as a dotnet tool, not a consumable library, so nothing depends on that type. This also makes the MCP README's "all device/protocol logic lives in Daqifi.Core" true again, which it had stopped being.

Verification

Tests — 27 new in Core, 4 left in MCP for the server clamp. Proven to catch regressions by mutating the implementation and re-running: inverting the source precedence fails 5, counting disabled channels fails 4, dropping the hardware-maximum floor fails 2, dropping the write-back in the enforcement path fails 2, counting digital channels fails 1, looking a channel up by id without checking its kind fails 1, and treating a negative reported cap as real fails 1. Full suite green on net9.0 (3,002 Core + 86 MCP) and net10.0 (3,002), 0 failures, 0 warnings in Debug and Release.

Bench (non-destructive), Nq1 fw 3.7.2 on /dev/cu.usbmodem1101. Core's cap was compared against the device's own answer across five channel selections, re-reading the capability document each time:

enabled analog device current_max_rate_hz Core cap model prediction
(none) 0 0 18,333 Hz
0 7,746 7,746 15,714 Hz
0,1,2 6,260 6,260 12,222 Hz
4,8,10,12,14 8,634 8,634 10,000 Hz (5 dedicated of 5)
all 16 3,518 3,518 5,000 Hz

Core agrees with the device exactly in every case, which is what says the MCP server's numbers have not moved. The 7,746 and 3,518 figures are the same ones #447 measured. The right-hand column is the fallback that would have been used had the device stated nothing — consistently optimistic, which is the evidence for the precedence choice above.

The #447 trap itself was then reproduced end to end on hardware: rate set to 7,746 Hz with one channel enabled, all sixteen enabled, cap drops to 3,518 Hz with 7,746 Hz still live, EnforceStreamingFrequencyCap() lowers it to 3,518 and reports 7,746, and a second call reports no change. Channels were restored to none enabled afterwards. Discovery, capability reads and channel enables only — no streaming, no SD, no reboot, no firmware, no LAN writes.

closes #481

Not merging — this is for your review.

 #481)

The rule for how fast a device can actually stream with the channels it has
enabled lived only in the MCP server, so every other consumer of Daqifi.Core —
the desktop application first among them — could command a rate the
configuration cannot deliver and get a session that silently produces nothing.

Core now owns it: SampleRateCap decides the ceiling, IStreamingDevice exposes
MaximumStreamingFrequencyHz and EnforceStreamingFrequencyCap(), and the MCP
server keeps only its own --max-sample-rate-hz clamp. The device's reported cap
stays authoritative; the published rate model — which had no production caller —
is the fallback when the device states none.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@tylerkron
tylerkron requested a review from a team as a code owner August 13, 2026 02:05
@tylerkron

Copy link
Copy Markdown
Contributor Author

/agentic_review

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Move per-configuration sample-rate cap logic into Core

✨ Enhancement 🧪 Tests 🕐 40+ Minutes

Grey Divider

AI Description

• Centralize per-enabled-channel sample-rate ceiling computation in Daqifi.Core.
• Expose the computed ceiling and an opt-in enforcement hook on IStreamingDevice.
• Keep MCP’s operator --max-sample-rate-hz clamp while reusing Core’s device cap logic.
Diagram

graph TD
  A["Library consumers (Desktop/MCP)"] --> B["IStreamingDevice"] --> C["DaqifiStreamingDevice"] --> D["SampleRateCap (Core)"] --> E["CapabilityDocument/RateModel"]
  F["MCP: DaqifiAgent"] --> G["Server rate clamp (--max-sample-rate-hz)"] --> B
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Validate StreamingFrequency setter against per-configuration cap
  • ➕ Prevents impossible rates from ever becoming live
  • ➕ Simplifies consumer logic (no separate enforcement call)
  • ➖ Breaks common ordering: set rate before enabling channels
  • ➖ Behavior change for existing callers that rely on board-ceiling-only validation
2. Always take min(deviceReportedCap, modelCap) when both exist
  • ➕ More conservative; reduces chance of over-permitting if one source is optimistic
  • ➕ Potentially safer if deviceReportedCap is stale
  • ➖ Can contradict the documented contract (device cap is authoritative when present)
  • ➖ May under-permit in valid scenarios and mask transport/interface constraints inconsistently
3. Keep logic in MCP and duplicate into other consumers
  • ➕ No public API surface change in Core
  • ➖ Policy divergence risk across clients
  • ➖ Continues the original bug for non-MCP consumers until each is updated

Recommendation: The PR’s approach is the best tradeoff: centralizing cap computation/enforcement in Core ensures consistent behavior across all consumers while keeping the StreamingFrequency setter semantics stable. Retaining MCP’s operator clamp as a separate step cleanly isolates server-only policy. The explicit EnforceStreamingFrequencyCap() opt-in avoids ordering regressions while providing a straightforward way for callers to recover from the “set rate then enable more channels” trap.

Files changed (6) +738 / -26

Enhancement (3) +253 / -0
SampleRateCap.csCentralize per-configuration sample-rate cap policy in Core +216/-0

Centralize per-configuration sample-rate cap policy in Core

• Adds SampleRateCap to compute an effective streaming ceiling from hardware maximum, device-reported cap, and an optional rate-model fallback. Provides enforcement helpers to lower a live streaming rate when it exceeds the current cap and includes channel counting logic that correctly distinguishes simultaneous analog inputs and ignores digital/output channels.

src/Daqifi.Core/Device/Capabilities/SampleRateCap.cs

DaqifiStreamingDevice.csExpose maximum streaming frequency and enforcement on DaqifiStreamingDevice +17/-0

Expose maximum streaming frequency and enforcement on DaqifiStreamingDevice

• Adds MaximumStreamingFrequencyHz and EnforceStreamingFrequencyCap() members that delegate to SampleRateCap. Keeps StreamingFrequency setter validation tied to the absolute board ceiling to avoid ordering regressions.

src/Daqifi.Core/Device/DaqifiStreamingDevice.cs

IStreamingDevice.csAdd default interface members for cap visibility and enforcement +20/-0

Add default interface members for cap visibility and enforcement

• Extends IStreamingDevice with MaximumStreamingFrequencyHz and EnforceStreamingFrequencyCap() using default implementations that call SampleRateCap. This makes the capability available to interface-only consumers without requiring code changes in existing implementations.

src/Daqifi.Core/Device/IStreamingDevice.cs

Refactor (1) +32 / -26
DaqifiAgent.csSwitch MCP to Core SampleRateCap and retain only server clamp policy +32/-26

Switch MCP to Core SampleRateCap and retain only server clamp policy

• Replaces MCP’s internal cap calculator with Core’s SampleRateCap and IStreamingDevice.MaximumStreamingFrequencyHz, then applies the server’s operator clamp via a new ApplyServerRateClamp helper. Updates revalidation/enforcement paths after channel configuration and maintains StartLogging backstop behavior using the new cap computation path.

src/Daqifi.Mcp/DaqifiAgent.cs

Tests (2) +453 / -0
SampleRateCapTests.csAdd comprehensive tests for SampleRateCap computation and enforcement +419/-0

Add comprehensive tests for SampleRateCap computation and enforcement

• Introduces unit tests covering cap source precedence (hardware/device/model), boundary conditions (0/negative values), and enforcement behavior. Adds device-level scenarios validating MaximumStreamingFrequencyHz and EnforceStreamingFrequencyCap() via a transportless test device and representative capability documents.

src/Daqifi.Core.Tests/Device/Capabilities/SampleRateCapTests.cs

DaqifiMcpTests.csAdd MCP-only tests for operator max sample-rate clamp +34/-0

Add MCP-only tests for operator max sample-rate clamp

• Adds ServerRateClampTests validating that MCP’s --max-sample-rate-hz option only lowers (never raises) the device-provided cap and preserves the zero-cap meaning. This reflects the remaining MCP-specific policy after moving device cap logic to Core.

src/Daqifi.Mcp.Tests/DaqifiMcpTests.cs

@qodo-code-review

qodo-code-review Bot commented Aug 13, 2026

Copy link
Copy Markdown

Code Review by Qodo

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

Grey Divider


Remediation recommended

1. Board-ceiling docs inaccurate ✓ Resolved 🐞 Bug ⚙ Maintainability
Description
IStreamingDevice.MaximumStreamingFrequencyHz’s remarks state the board ceiling is returned instead
of 0 only when no capability document exists, but SampleRateCap.ComputeForDevice also returns the
board ceiling when a capability document exists but contains no usable
Streaming/current-cap/rate-model data. This documentation mismatch can cause callers to misinterpret
why they got the hardware ceiling.
Code

src/Daqifi.Core/Device/IStreamingDevice.cs[R71-74]

+        /// Zero is a real answer: it means no analog input is enabled, so there is no capacity to
+        /// stream. See <see cref="SampleRateCap"/> for where the figure comes from, how fresh it
+        /// is, and the one case that reports the board ceiling instead of zero for an empty
+        /// configuration — a device that has published no capability document at all.
Relevance

●●● Strong

Team often accepts fixing XML/remarks mismatches to match actual behavior and avoid misleading
callers.

PR-#321
PR-#98

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The interface remark limits the board-ceiling fallback to ‘no capability document’. The
implementation uses metadata.CapabilityDocument?.Streaming and only computes a model cap when a
RateModel exists; otherwise it passes nulls into Compute(...) and returns the hardware maximum. A
unit test confirms the board ceiling is returned even when a (minimal) capability document exists.

src/Daqifi.Core/Device/IStreamingDevice.cs[65-76]
src/Daqifi.Core/Device/Capabilities/SampleRateCap.cs[86-92]
src/Daqifi.Core/Device/Capabilities/SampleRateCap.cs[98-130]
src/Daqifi.Core.Tests/Device/Capabilities/SampleRateCapTests.cs[276-283]

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

### Issue description
`IStreamingDevice.MaximumStreamingFrequencyHz` remarks claim the board ceiling is returned for empty configurations only when there is *no* capability document. However, `SampleRateCap.ComputeForDevice(...)` also falls back to the board ceiling when a document exists but does not include usable rate information (no `Streaming`, no `CurrentMaximumRateHz`, no `RateModel`, or model can’t be evaluated).

### Issue Context
There is already a test confirming “document that states no rates” returns the board ceiling, so the docs should reflect that broader condition ("no usable cap/model information") rather than only "no capability document".

### Fix Focus Areas
- src/Daqifi.Core/Device/IStreamingDevice.cs[71-74]
- src/Daqifi.Core/Device/Capabilities/SampleRateCap.cs[86-92]

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


2. Empty set yields nonzero cap ✓ Resolved 🐞 Bug ≡ Correctness
Description
When the device-reported cap is absent and the code falls back to the capability rate model,
SampleRateCap.ComputeForDevice can compute a positive MaximumStreamingFrequencyHz even when no
channels are enabled, violating the documented meaning of 0 (“nothing enabled”). This can mislead
consumers into believing streaming is possible in an empty configuration and undermine “no capacity”
checks built on this value.
Code

src/Daqifi.Core/Device/Capabilities/SampleRateCap.cs[R102-105]

+        if (deviceReportedCapHz is not >= 0 && streaming?.RateModel is { } model)
+        {
+            var (simultaneousCount, totalCount) = CountEnabledAnalogInputs(
+                device.GetChannelsSnapshot(), metadata.CapabilityDocument!);
Relevance

●●● Strong

Correctness edge-case; team historically accepts guards/sanitization around capability-derived
rates/limits.

PR-#349
PR-#404

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The interface contract states that 0 is a real answer meaning nothing is enabled, but the model
fallback path computes predictedHz from the rate model based on enabled analog-input counts. The
model’s computation can still produce a positive ceiling when the analog-input total count is 0
(e.g., PerTickBudgetHz / (PerTickOverhead + 0)), so the fallback can return nonzero for an empty
enabled set.

src/Daqifi.Core/Device/IStreamingDevice.cs[65-83]
src/Daqifi.Core/Device/Capabilities/SampleRateCap.cs[98-114]
src/Daqifi.Core/Device/Capabilities/CapabilityRateModel.cs[94-115]
src/Daqifi.Core/Device/Capabilities/CapabilityStreaming.cs[45-52]

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

## Issue description
`SampleRateCap.ComputeForDevice(...)` falls back to `CapabilityRateModel` when `CurrentMaximumRateHz` is absent. In that path, an empty enabled-channel set (nothing enabled) can still produce a positive predicted rate (e.g., `PerTickBudgetHz / PerTickOverhead`), which contradicts the public `IStreamingDevice.MaximumStreamingFrequencyHz` contract that `0` means “nothing is enabled”.

## Issue Context
- The interface docs explicitly define `0` as the “nothing enabled” result.
- The model computation can return a positive ceiling even with `totalChannelCount == 0`.
- This only manifests when the device-reported cap is absent and a rate model is present/evaluable.

## Fix Focus Areas
- src/Daqifi.Core/Device/Capabilities/SampleRateCap.cs[88-114]
- src/Daqifi.Core.Tests/Device/Capabilities/SampleRateCapTests.cs[122-262]

## Recommended fix
1. In `ComputeForDevice`, when `deviceReportedCapHz` is absent (null/negative), take one snapshot of channels and detect “nothing enabled” (across streamable types).
  - If nothing is enabled, return `0` immediately (or force `modelCapHz = 0`), before attempting model evaluation.
2. Add a regression unit test:
  - Create a device with a capability document that has `Streaming.RateModel` present and `CurrentMaximumRateHz = null`.
  - Ensure no channels are enabled.
  - Assert `device.MaximumStreamingFrequencyHz == 0`.

(Choose the streamable-channel predicate that matches Core’s streaming semantics; at minimum, ensure analog-input enabled count and digital enabled count are both zero.)

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


3. Digital-only treated as none ✗ Dismissed 🐞 Bug ≡ Correctness
Description
SampleRateCap.ComputeForDevice forces the cap to 0 whenever no enabled analog inputs are found,
which makes MaximumStreamingFrequencyHz report “no capacity” even when only digital channels are
enabled. This conflicts with Core’s digital-only stream decoding/parsing support and can mislead
consumers into treating digital-only streaming configurations as impossible.
Code

src/Daqifi.Core/Device/Capabilities/SampleRateCap.cs[R113-116]

+            if (totalCount == 0)
+            {
+                // Nothing to sample, so no capacity — the same answer the device gives for this
+                // case, measured on an NQ1 running firmware 3.7.2 with nothing enabled and again
Relevance

●● Moderate

Semantic disagreement: bench notes claim device returns 0 for digital-only; unsure team will change
this behavior.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The new logic hard-sets the model cap to 0 when no enabled analog inputs are present, and the new
test asserts the same outcome even when digital channels are enabled. Elsewhere in Core,
digital-only streaming frames are explicitly supported/decoded, suggesting that “digital-only” is
not necessarily “nothing to stream,” making the 0-cap semantics ambiguous/misleading.

src/Daqifi.Core/Device/Capabilities/SampleRateCap.cs[108-127]
src/Daqifi.Core.Tests/Device/Capabilities/SampleRateCapTests.cs[260-274]
src/Daqifi.Core/Device/Internal/StreamFrameDecoder.cs[160-166]
src/Daqifi.Core/Device/Internal/StreamFrameDecoder.cs[479-521]
src/Daqifi.Core.Tests/Device/DaqifiStreamingDeviceDecodeTests.cs[778-794]
src/Daqifi.Core.Tests/Device/SdCard/SdCardFileParserTests.cs[809-830]

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

### Issue description
`SampleRateCap.ComputeForDevice(...)` sets `modelCapHz = 0` when `totalCount == 0` (no enabled *analog inputs*), and the new tests assert that “only digital enabled” also yields a 0 cap. This effectively equates “no enabled analog inputs” with “nothing to stream”.

But Core contains explicit support for digital-only streaming frames (decoder logic + tests, SD-card parsing tests). If the public `MaximumStreamingFrequencyHz` is meant to describe overall streaming capacity (not just analog acquisition capacity), returning 0 for digital-only configs is misleading.

### Issue Context
Either:
- the cap is **analog-input-only** capacity (then docs/naming should say so explicitly, and “nothing to stream” wording should be corrected), or
- the cap is **overall stream** capacity (then digital-only configs should not be forced to 0).

### Fix Focus Areas
- src/Daqifi.Core/Device/Capabilities/SampleRateCap.cs[113-124]
- src/Daqifi.Core.Tests/Device/Capabilities/SampleRateCapTests.cs[247-275]
- src/Daqifi.Core/Device/IStreamingDevice.cs[71-74]

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


Grey Divider

Tip of the day
💡 Did you know, you can type 'qodo, fix this' on a finding and the fix lands right on your PR

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Previous review results

Review updated until commit 0218b60 ⚖️ Balanced

Results up to commit eb1e19f ⚖️ Balanced


🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)


Remediation recommended
1. Empty set yields nonzero cap ✓ Resolved 🐞 Bug ≡ Correctness
Description
When the device-reported cap is absent and the code falls back to the capability rate model,
SampleRateCap.ComputeForDevice can compute a positive MaximumStreamingFrequencyHz even when no
channels are enabled, violating the documented meaning of 0 (“nothing enabled”). This can mislead
consumers into believing streaming is possible in an empty configuration and undermine “no capacity”
checks built on this value.
Code

src/Daqifi.Core/Device/Capabilities/SampleRateCap.cs[R102-105]

+        if (deviceReportedCapHz is not >= 0 && streaming?.RateModel is { } model)
+        {
+            var (simultaneousCount, totalCount) = CountEnabledAnalogInputs(
+                device.GetChannelsSnapshot(), metadata.CapabilityDocument!);
Relevance

●●● Strong

Correctness edge-case; team historically accepts guards/sanitization around capability-derived
rates/limits.

PR-#349
PR-#404

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The interface contract states that 0 is a real answer meaning nothing is enabled, but the model
fallback path computes predictedHz from the rate model based on enabled analog-input counts. The
model’s computation can still produce a positive ceiling when the analog-input total count is 0
(e.g., PerTickBudgetHz / (PerTickOverhead + 0)), so the fallback can return nonzero for an empty
enabled set.

src/Daqifi.Core/Device/IStreamingDevice.cs[65-83]
src/Daqifi.Core/Device/Capabilities/SampleRateCap.cs[98-114]
src/Daqifi.Core/Device/Capabilities/CapabilityRateModel.cs[94-115]
src/Daqifi.Core/Device/Capabilities/CapabilityStreaming.cs[45-52]

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

## Issue description
`SampleRateCap.ComputeForDevice(...)` falls back to `CapabilityRateModel` when `CurrentMaximumRateHz` is absent. In that path, an empty enabled-channel set (nothing enabled) can still produce a positive predicted rate (e.g., `PerTickBudgetHz / PerTickOverhead`), which contradicts the public `IStreamingDevice.MaximumStreamingFrequencyHz` contract that `0` means “nothing is enabled”.

## Issue Context
- The interface docs explicitly define `0` as the “nothing enabled” result.
- The model computation can return a positive ceiling even with `totalChannelCount == 0`.
- This only manifests when the device-reported cap is absent and a rate model is present/evaluable.

## Fix Focus Areas
- src/Daqifi.Core/Device/Capabilities/SampleRateCap.cs[88-114]
- src/Daqifi.Core.Tests/Device/Capabilities/SampleRateCapTests.cs[122-262]

## Recommended fix
1. In `ComputeForDevice`, when `deviceReportedCapHz` is absent (null/negative), take one snapshot of channels and detect “nothing enabled” (across streamable types).
  - If nothing is enabled, return `0` immediately (or force `modelCapHz = 0`), before attempting model evaluation.
2. Add a regression unit test:
  - Create a device with a capability document that has `Streaming.RateModel` present and `CurrentMaximumRateHz = null`.
  - Ensure no channels are enabled.
  - Assert `device.MaximumStreamingFrequencyHz == 0`.

(Choose the streamable-channel predicate that matches Core’s streaming semantics; at minimum, ensure analog-input enabled count and digital enabled count are both zero.)

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


Results up to commit 66eade3 ⚖️ Balanced


🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)


Remediation recommended
1. Board-ceiling docs inaccurate ✓ Resolved 🐞 Bug ⚙ Maintainability
Description
IStreamingDevice.MaximumStreamingFrequencyHz’s remarks state the board ceiling is returned instead
of 0 only when no capability document exists, but SampleRateCap.ComputeForDevice also returns the
board ceiling when a capability document exists but contains no usable
Streaming/current-cap/rate-model data. This documentation mismatch can cause callers to misinterpret
why they got the hardware ceiling.
Code

src/Daqifi.Core/Device/IStreamingDevice.cs[R71-74]

+        /// Zero is a real answer: it means no analog input is enabled, so there is no capacity to
+        /// stream. See <see cref="SampleRateCap"/> for where the figure comes from, how fresh it
+        /// is, and the one case that reports the board ceiling instead of zero for an empty
+        /// configuration — a device that has published no capability document at all.
Relevance

●●● Strong

Team often accepts fixing XML/remarks mismatches to match actual behavior and avoid misleading
callers.

PR-#321
PR-#98

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The interface remark limits the board-ceiling fallback to ‘no capability document’. The
implementation uses metadata.CapabilityDocument?.Streaming and only computes a model cap when a
RateModel exists; otherwise it passes nulls into Compute(...) and returns the hardware maximum. A
unit test confirms the board ceiling is returned even when a (minimal) capability document exists.

src/Daqifi.Core/Device/IStreamingDevice.cs[65-76]
src/Daqifi.Core/Device/Capabilities/SampleRateCap.cs[86-92]
src/Daqifi.Core/Device/Capabilities/SampleRateCap.cs[98-130]
src/Daqifi.Core.Tests/Device/Capabilities/SampleRateCapTests.cs[276-283]

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

### Issue description
`IStreamingDevice.MaximumStreamingFrequencyHz` remarks claim the board ceiling is returned for empty configurations only when there is *no* capability document. However, `SampleRateCap.ComputeForDevice(...)` also falls back to the board ceiling when a document exists but does not include usable rate information (no `Streaming`, no `CurrentMaximumRateHz`, no `RateModel`, or model can’t be evaluated).

### Issue Context
There is already a test confirming “document that states no rates” returns the board ceiling, so the docs should reflect that broader condition ("no usable cap/model information") rather than only "no capability document".

### Fix Focus Areas
- src/Daqifi.Core/Device/IStreamingDevice.cs[71-74]
- src/Daqifi.Core/Device/Capabilities/SampleRateCap.cs[86-92]

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


2. Digital-only treated as none ✗ Dismissed 🐞 Bug ≡ Correctness
Description
SampleRateCap.ComputeForDevice forces the cap to 0 whenever no enabled analog inputs are found,
which makes MaximumStreamingFrequencyHz report “no capacity” even when only digital channels are
enabled. This conflicts with Core’s digital-only stream decoding/parsing support and can mislead
consumers into treating digital-only streaming configurations as impossible.
Code

src/Daqifi.Core/Device/Capabilities/SampleRateCap.cs[R113-116]

+            if (totalCount == 0)
+            {
+                // Nothing to sample, so no capacity — the same answer the device gives for this
+                // case, measured on an NQ1 running firmware 3.7.2 with nothing enabled and again
Relevance

●● Moderate

Semantic disagreement: bench notes claim device returns 0 for digital-only; unsure team will change
this behavior.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The new logic hard-sets the model cap to 0 when no enabled analog inputs are present, and the new
test asserts the same outcome even when digital channels are enabled. Elsewhere in Core,
digital-only streaming frames are explicitly supported/decoded, suggesting that “digital-only” is
not necessarily “nothing to stream,” making the 0-cap semantics ambiguous/misleading.

src/Daqifi.Core/Device/Capabilities/SampleRateCap.cs[108-127]
src/Daqifi.Core.Tests/Device/Capabilities/SampleRateCapTests.cs[260-274]
src/Daqifi.Core/Device/Internal/StreamFrameDecoder.cs[160-166]
src/Daqifi.Core/Device/Internal/StreamFrameDecoder.cs[479-521]
src/Daqifi.Core.Tests/Device/DaqifiStreamingDeviceDecodeTests.cs[778-794]
src/Daqifi.Core.Tests/Device/SdCard/SdCardFileParserTests.cs[809-830]

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

### Issue description
`SampleRateCap.ComputeForDevice(...)` sets `modelCapHz = 0` when `totalCount == 0` (no enabled *analog inputs*), and the new tests assert that “only digital enabled” also yields a 0 cap. This effectively equates “no enabled analog inputs” with “nothing to stream”.

But Core contains explicit support for digital-only streaming frames (decoder logic + tests, SD-card parsing tests). If the public `MaximumStreamingFrequencyHz` is meant to describe overall streaming capacity (not just analog acquisition capacity), returning 0 for digital-only configs is misleading.

### Issue Context
Either:
- the cap is **analog-input-only** capacity (then docs/naming should say so explicitly, and “nothing to stream” wording should be corrected), or
- the cap is **overall stream** capacity (then digital-only configs should not be forced to 0).

### Fix Focus Areas
- src/Daqifi.Core/Device/Capabilities/SampleRateCap.cs[113-124]
- src/Daqifi.Core.Tests/Device/Capabilities/SampleRateCapTests.cs[247-275]
- src/Daqifi.Core/Device/IStreamingDevice.cs[71-74]

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


Qodo Logo

Comment thread src/Daqifi.Core/Device/Capabilities/SampleRateCap.cs
… enabled analog inputs

Qodo round 1: with the device stating no cap, the rate-model fallback answered
a healthy 18,333 Hz for an empty configuration, because the model's formula
keeps a finite per-tick overhead term at zero channels. Every "cap is 0, so
nothing is enabled" check downstream would read that as a live configuration.

The device's own answer for that case is 0 — measured on the bench NQ1 with
nothing enabled, and again with digital pins only — so the fallback now says 0
too. A device with no capability document at all is unchanged: it has said
nothing about how its channel set affects the rate, so it still reports the
board ceiling.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@tylerkron

Copy link
Copy Markdown
Contributor Author

/agentic_review

Comment thread src/Daqifi.Core/Device/Capabilities/SampleRateCap.cs Outdated
Comment thread src/Daqifi.Core/Device/IStreamingDevice.cs Outdated
@qodo-code-review

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 66eade3

…why digital-only reads zero

Qodo round 2. The board-ceiling fallback was documented as "no capability
document at all", but it also covers a document that carries neither a current
cap nor a rate model — the case an existing test already pins. Reworded on both
the interface member and ComputeForDevice.

The digital-only semantics were implicit in the code and are now stated: digital
pins are captured on the analog sample tick rather than driving one of their
own, which is why the model counts only analog inputs and why the device itself
answers 0 for a digital-only selection.

Documentation only; no behaviour change.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@tylerkron

Copy link
Copy Markdown
Contributor Author

/agentic_review

@tylerkron

Copy link
Copy Markdown
Contributor Author

Qodo-clean, CI green — ready for review.

Round 3 on head 0218b60: Bugs (0) / Rule violations (0) / Requirement gaps (0) / UX issues (0) / Cross-repo conflicts (0) / Skill insights (0), all three findings struck ✓ Resolved, 0 unresolved threads. 18 references to 0218b60 and none to an earlier head, so the review is on the current code. Settle re-check at +5 min: summary byte-identical, comment count unchanged, threads still 0. CI build SUCCESS.

Three rounds. Round 1 found one real bug — with the device stating no cap, the rate-model fallback answered 18,333 Hz for an empty configuration, because the model's per-tick overhead term stays finite at zero channels. Fixed in 66eade3, scoped to the fallback path (returning 0 for a device that has published nothing about its channel set would newly refuse rates the MCP server accepts today).

Round 2 found a real doc mismatch — the board-ceiling fallback was documented more narrowly than it behaves — fixed in 0218b60, and one finding I did not take: that a digital-only selection should not read 0. I measured that on the bench rather than argue it. The NQ1 answers current_max_rate_hz = 0 for digital-only just as it does for nothing enabled, and 7746 as soon as one analog channel joins, so 0 is the device's own answer and was already what this property returned on the authoritative path before this PR. That round also pulled against round 1, which had asked for exactly the zero it objected to. Reasoning is in the thread; the semantics are now stated in the docs instead of being implicit.

Re-verified after the round-1 production fix: full suite green on net9.0 (3,004 Core + 86 MCP) and net10.0 (3,004), 0 warnings in Debug and Release, and the bench table in the description re-run end to end on /dev/cu.usbmodem1101 — Core's cap matched the device's own figure in all six selections, including the two that now read 0. Round 2 was documentation only.

Not merging — this is for your review.

@qodo-code-review

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 0218b60

@qodo-code-review

Copy link
Copy Markdown

Qodo-clean, CI green — ready for review.

Round 3 on head 0218b60: Bugs (0) / Rule violations (0) / Requirement gaps (0) / UX issues (0) / Cross-repo conflicts (0) / Skill insights (0), all three findings struck ✓ Resolved, 0 unresolved threads. 18 references to 0218b60 and none to an earlier head, so the review is on the current code. Settle re-check at +5 min: summary byte-identical, comment count unchanged, threads still 0. CI build SUCCESS.

Three rounds. Round 1 found one real bug — with the device stating no cap, the rate-model fallback answered 18,333 Hz for an empty configuration, because the model's per-tick overhead term stays finite at zero channels. Fixed in 66eade3, scoped to the fallback path (returning 0 for a device that has published nothing about its channel set would newly refuse rates the MCP server accepts today).

Round 2 found a real doc mismatch — the board-ceiling fallback was documented more narrowly than it behaves — fixed in 0218b60, and one finding I did not take: that a digital-only selection should not read 0. I measured that on the bench rather than argue it. The NQ1 answers current_max_rate_hz = 0 for digital-only just as it does for nothing enabled, and 7746 as soon as one analog channel joins, so 0 is the device's own answer and was already what this property returned on the authoritative path before this PR. That round also pulled against round 1, which had asked for exactly the zero it objected to. Reasoning is in the thread; the semantics are now stated in the docs instead of being implicit.

Re-verified after the round-1 production fix: full suite green on net9.0 (3,004 Core + 86 MCP) and net10.0 (3,004), 0 warnings in Debug and Release, and the bench table in the description re-run end to end on /dev/cu.usbmodem1101 — Core's cap matched the device's own figure in all six selections, including the two that now read 0. Round 2 was documentation only.

Not merging — this is for your review.

Re-check complete: I see no remaining actionable issues on head 0218b60. The review is clean with 0 active findings; finding 1 and finding 3 are implemented, and finding 2 is dismissed. The reported green CI and unchanged review state are consistent with the current PR status.

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.

chore(api): the per-configuration sample-rate cap lives only in Daqifi.Mcp — move it into Core

1 participant