Don't let the body-location probe's detail line contradict its own verdict (#690) - #918
Conversation
…rdict (#690) The bare-stub report said two things at once: Verdict: opcode 84 answered with a bare stub — ambiguous ... No payload beyond the command byte (bare stub) — no body-location data on this firmware The verdict is right and the detail line settles what the verdict just called unsettled. A reply carrying no payload is one reply: it does not distinguish a firmware without body-location data from one that needs the strap worn, or in a determinate position, or asked with an argument this probe does not send. This file's own enum has UNKNOWN(0) and NOT_CONCLUSIVE(128), so the firmware plainly models "cannot tell right now" — which is exactly the reading the detail line ruled out. Same class as #913 and #914: a probe asserting a conclusion the run's inputs could not support, in the sentence a reader quotes. Found while sweeping the probes after #913 rather than reported. Both platforms carried the identical string — this is a shared defect, not a divergence — so both are reworded to say what was observed and what it does not establish, and both tests now assert the over-claim is ABSENT rather than only that "bare stub" appears. The old assertions passed under either wording, which is why the contradiction survived. The golden parity lock is unaffected: it pins a payload-bearing frame, so the bare-stub branch was never covered by it.
|
Re-reviewed before merge, specifically the two claims a reader would poke at. The new wording survives the one path where the verdict is not "ambiguous". A 5/MG reply with The sweep's one near-miss, checked and left alone. Also verified mechanically: the replacement string is byte-identical Swift↔Kotlin, and the Kotlin golden lock (like the Swift one) pins an ACCEPTED payload-bearing frame, so the bare-stub branch sits outside both locks — the strengthened bare-stub tests are the only coverage of this line, which is why they now assert absence of the over-claim rather than presence of a substring both wordings contain. |
WhoopBleClient logs every non-SUCCESS command response, which is how the MG haptics rejection (#48) would surface in-app. But every WHOOP 4.0 GET_BATTERY_LEVEL reply on record carries a zeroed [seq][result] prefix, so a battery read that returned a perfectly good percentage logs as: Command response: GET_BATTERY_LEVEL(26) → FAILURE(0) next to a battery gauge showing 42%. A log line asserting a failure that did not happen is exactly the artefact #900 exists to warn about — that issue began when a FAILURE(0) was read as evidence about firmware, and this line manufactures the same misreading on every battery read a 4.0 owner makes. The line still prints. Suppressing it would hide the anomaly, and the anomaly is the thing #900 wants a capture of. It is annotated instead, when the same frame also decoded a value: Command response: GET_BATTERY_LEVEL(26) → FAILURE(0) (the reply still carried a value: battery 42.5% — see #900, the 4.0 result byte is not established) A genuine failure that carried no value — the extended-battery case from #791, the UNSUPPORTED rejections — is untouched, because the annotation is gated on a decoded value being present. This does not decide what the 4.0 result byte means; #900 still wants one real capture of known provenance. It stops the log asserting an answer in the meantime, the same discipline as #913, #914 and #918. Android-only: the Swift side has no command-response result consumer, so there is no parity twin to change. No test — the string is inside a class that needs the Android framework, and the annotation is a log line rather than behaviour; the rendered output was checked against both real 4.0 battery fixtures instead.
WhoopBleClient logs every non-SUCCESS command response, which is how the MG haptics rejection (#48) would surface in-app. But every WHOOP 4.0 GET_BATTERY_LEVEL reply on record carries a zeroed [seq][result] prefix, so a battery read that returned a perfectly good percentage logs as: Command response: GET_BATTERY_LEVEL(26) → FAILURE(0) next to a battery gauge showing 42%. A log line asserting a failure that did not happen is exactly the artefact #900 exists to warn about — that issue began when a FAILURE(0) was read as evidence about firmware, and this line manufactures the same misreading on every battery read a 4.0 owner makes. The line still prints. Suppressing it would hide the anomaly, and the anomaly is the thing #900 wants a capture of. It is annotated instead, when the same frame also decoded a value: Command response: GET_BATTERY_LEVEL(26) → FAILURE(0) (the reply still carried a value: battery 42.5% — see #900, the 4.0 result byte is not established) A genuine failure that carried no value — the extended-battery case from #791, the UNSUPPORTED rejections — is untouched, because the annotation is gated on a decoded value being present. This does not decide what the 4.0 result byte means; #900 still wants one real capture of known provenance. It stops the log asserting an answer in the meantime, the same discipline as #913, #914 and #918. Android-only: the Swift side has no command-response result consumer, so there is no parity twin to change. No test — the string is inside a class that needs the Android framework, and the annotation is a log line rather than behaviour; the rendered output was checked against both real 4.0 battery fixtures instead.
Follow-up from the #913 review — found by sweeping the other probes for the same defect class, not reported.
The report contradicts itself
A bare-stub run prints both of these:
The verdict is right. The detail line settles what the verdict just called unsettled — and it is the line that reads like a finding.
One reply carrying no payload does not distinguish a firmware without body-location data from one that needs the strap worn, in a determinate position, or asked with an argument this probe does not send. The file's own enum has
UNKNOWN(0)andNOT_CONCLUSIVE(128), so the firmware explicitly models "cannot tell right now" — precisely the reading the detail line ruled out.Same class as #913 and #914: a probe asserting a conclusion the run's inputs could not support, in the sentence someone pastes into an issue.
Both platforms, and it is a shared defect
I checked whether this was a divergence and it is not — Swift and Kotlin carry the byte-identical string, so both are reworded:
The tests could not have caught it
Both existing bare-stub tests asserted only
text.contains("bare stub"), which passes under either wording. They now assert the over-claim is absent and that the verdict and the hedge are both present. That is the difference between a test that pins behaviour and one that happens to pass.The golden parity lock is unaffected — it pins a payload-bearing frame, so the bare-stub branch was never inside it. I verified that rather than assuming.