Conversation
Implements the spec in AnalogJ#1022: a `devices` block in scrutiny.yaml lets a user set warn/fail thresholds for a single attribute on a single device, for when Scrutiny's observed failure rates do not match their hardware. devices: - scrutiny_uuid: 106089ed-2273-54e0-b498-ec4bdfc8ca6c attribute_overrides: - protocol: ATA attribute_id: "199" warn_threshold: 100 fail_threshold: 500 Three semantics worth reviewing explicitly, each pinned by a named test: - An override *replaces* the observed-threshold analysis for that attribute rather than layering on top of it. Without this an attribute can never reach "passed": a critical attribute that matches no bucket falls through to a warning. - A threshold is crossed when the value is strictly worse than it, where "worse" follows the attribute's Ideal. Assuming "greater than" would compare backwards for ideal-high attributes such as NVMe available_spare. - Overrides never mask the drive's own SMART verdict. They tune Scrutiny's heuristics; a drive reporting itself as failing stays failed. Thresholds are *int64 so that an explicit 0 is distinguishable from unset, and misconfigured overrides fail validation at startup rather than being silently dropped -- a silently ignored override looks identical to a working one. New status flags (AttributeStatus{Passed,Warning,Failed}Override and DeviceStatus{Passed,Failed}Override) record that an override decided a verdict, so the frontend can explain an otherwise surprising pass or fail. DeviceStatusPassedOverride is cleared when the device is failing for any other reason. AI disclosure: written with Claude Code (Claude Opus 5). The tests were authored first and confirmed failing for the right reasons before any implementation; every test here passes, and the full backend suite (including the InfluxDB-backed webapp/backend/pkg/web integration tests) is green. Refs AnalogJ#1022 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Brings coverage of the lines this branch changes to 100% (231/231). Overall backend coverage goes from 49.5% on master to 57.4%. Measuring found a real gap rather than confirming a good number: the first run came in at 87.4%, with the SCSI override path at 42.9% -- i.e. untested. Closing the rest meant covering three things worth having tests for regardless: - The `return err` paths when a `devices` block cannot be decoded, asserted both in attributeOverridesFor and in SaveSmartAttributes. The failure mode this guards against is a user believing a device is tuned while it is silently graded by stock analysis. - attributeOverridesFor's uuid lookup, including case-insensitive matching, driven through a real config parsed from YAML rather than a stub. - m20201107210306_FromPreInfluxDBSmartResultsCreatePostInfluxDBSmartResults, the pre-InfluxDB migration helper this branch touches, now exercised for ATA, NVMe and SCSI against an in-memory sqlite copy of the old schema. That helper had no test coverage upstream. One SCSI case documents existing behaviour rather than endorsing it: the non-override path looks SCSI attributes up in thresholds.NmveMetadata instead of ScsiMetadata, so a SCSI-only attribute id matches nothing and its recommended threshold is never checked. Left as-is to keep this change focused; the override path uses ScsiMetadata. AI disclosure: written with Claude Code (Claude Opus 5), reviewed and tested by me. Refs AnalogJ#1022 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Looking forward for this change! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implements the spec in #1022: a
devicesblock inscrutiny.yamlthat lets a user set warn/failthresholds for a single attribute on a single device, for when Scrutiny's observed failure rates
don't match their hardware.
Opening as a draft: three semantic decisions are baked in that I'd like your call on before I
finish the UI, since changing them afterwards means redoing that work. Each is isolated behind a
named test, so flipping any of them is small.
Three decisions worth reviewing
1. An override replaces the observed-threshold analysis rather than layering on top of it.
Without this an attribute can never actually reach "passed": a
Criticalattribute that matches nobucket falls through to
Could not determine Observed Failure Rate. Concretely — a healthy ATA 188of exactly
0warns today, because 188's first bucket isLow: 0, High: 100and the range test isexclusive on
Low, so0matches nothing. (That looks like an off-by-one worth fixing separately;happy to open an issue.)
2. A threshold is crossed when the value is strictly worse than it, where "worse" follows the
attribute's
Ideal. Assuming "greater than" would compare backwards for ideal-high attributes —NVMe
available_sparebeing the obvious one, where a lower value is worse.3. Overrides never mask the drive's own SMART verdict. An override tunes Scrutiny's heuristics;
a drive reporting
WhenFailed = FAILING_NOWstays failed.Also worth a look: thresholds are
*int64so an explicit0is distinguishable from "not set", anda malformed
devicesblock fails validation at startup rather than being dropped silently — asilently ignored override is indistinguishable from a working one.
New status flags (
AttributeStatus{Passed,Warning,Failed}Override,DeviceStatus{Passed,Failed}Override) record that an override decided a verdict so the frontend canexplain an otherwise surprising pass or fail.
DeviceStatusPassedOverrideis cleared when thedevice is failing for any other reason — a device isn't "passing because of an override" just
because an override cleared one attribute while another still fails.
Still to do before this is ready
docs/updateTesting
Tests were written before the implementation, in a deliberately staged sequence so the failures
were meaningful: tests first → compile failure; then types and signatures with no behaviour → tests
compiled and failed on assertions (
expected: 0x8, actual: 0x4); then implementation → green.That middle step matters because the no-override baselines passed at that point, independently
confirming the existing analysis before any new logic existed.
66 test functions and 61 sub-tests across the backend, 0 failures (master has 43 test
functions; this branch adds 23). The full backend suite passes, including the InfluxDB-backed
webapp/backend/pkg/webintegration tests.100% of the lines this branch changes are covered (231/231). Overall backend coverage goes from
49.5% on master to 57.4%; no package decreased. Getting there added coverage to
m20201107210306_FromPreInfluxDBSmartResultsCreatePostInfluxDBSmartResults— the pre-InfluxDBmigration helper this change touches — which had none upstream; it's now exercised for all three
protocols against an in-memory sqlite copy of the old schema.
Verified on real hardware
Test subject: a Seagate ST16000NM002C whose UltraDMA CRC Error Count (199) reads 118 after two
discrete SATA link events. Scrutiny grades that into its 70–130 bucket at 22.3% AFR — over the 20%
non-critical line — so the drive reports
FAILEDindefinitely, and 199 is a cumulative lifetimecounter that cannot be reset. Its media-health attributes (5, 197, 198) are all
0and it passesits own SMART self-assessment.
Only the thresholds were changed between runs; each was a real collection against the physical drive.
4failed-scrutiny2failed-scrutinywarn 130 / fail 5008passed-override8passed-overridewarn 100 / fail 50018warning + warning-override0passedwarn 50 / fail 10036failed + failed-override6failed-scrutiny + failed-overrideA warning does not fail the device (row 3), and an override can cause a failure rather than only
clearing one (row 4) — it's a threshold mechanism, not a mute switch. A malformed
devicesblockrefused to start, with a message naming the offending entry.
Not covered
device-status.pipe, but not yet run. The hardware verification swapped only the Go binary intoan existing container, so the frontend in play was stock.
ideal-high/ideal-low direction handling, but no live-drive verification — I don't have an NVMe or
SAS device with a suitable failure to override.
AI disclosure
Written with Claude Code (Claude Opus 5), reviewed and tested by me. Per
AI_POLICY.md: thisreferences accepted issue #1022, the tests were authored first and confirmed failing for the right
reasons before any implementation existed, and every result above was produced by running the code
— including the live-drive verification on my own hardware.