R-330: the controller decodes SMART 187/188/199 from the agent (carried only, no verdict change)
gates / gates (push) Successful in 56s
gates / gates (push) Successful in 56s
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
- **R-542:** `GET /api/disks/candidates` no longer offers a drive that backs a REGISTERED storage path — not under `initialize` (the format wizard renders that list as-is, so a household's in-use data drive was offered for formatting) and not again under `attach`. The join is the guest's own mount table (a registered path's mount source is the host device). Fail-safe: an unreadable mount table empties `initialize`; `attach` passes through. Tests `TestR542_*` (two red-proofs observed).
|
- **R-542:** `GET /api/disks/candidates` no longer offers a drive that backs a REGISTERED storage path — not under `initialize` (the format wizard renders that list as-is, so a household's in-use data drive was offered for formatting) and not again under `attach`. The join is the guest's own mount table (a registered path's mount source is the host device). Fail-safe: an unreadable mount table empties `initialize`; `attach` passes through. Tests `TestR542_*` (two red-proofs observed).
|
||||||
- **R-516 (the Go-literal leftover):** 22 messages that lived as Go string literals — the escrow handler (6: no password set, too many attempts, staging failed, a code already being made, a code not shown, no active job), the share-page password (2), the export upload size mismatch, the network-storage uid refusal and in-use removal, and the network-storage attach failures (10 categories + the Windows-name tip) — are bundle keys now (`api.escrow.*`, `api.share.*`, `api.export.*`, `api.netstorage.*`), in the te-form in Hungarian (14 of them were in the formal „ön" form: „próbálja", „állítson be", „várja meg", „indítsa", „töltse", „ellenőrizze", „használja", „kapcsolja be", „állítsa", „jelezze", „távolítsa el", „adjon meg"), with English. A failed attach job is rendered in the READER's language at the status read (it ran detached; it keeps its category, server and uid). The NAS app-namespace refusal says „Válassz" (stays a settings constant). Left on purpose: the setup wizard and `recovery-info.txt` (R-554), and the SMART and fill-watch texts — they are the hub event's message, mailed as is (wire text). Tests `TestR516_GoLiteralFormalFormsAreGone` (keys in both languages + a source scan with a positive control), `TestR516_NetAddFailureFollowsTheReader`; red-proofs `felhom.eu/documentation/audits/night-burndown-2026-10-06/ctrl/R-516-red*.txt`.
|
- **R-516 (the Go-literal leftover):** 22 messages that lived as Go string literals — the escrow handler (6: no password set, too many attempts, staging failed, a code already being made, a code not shown, no active job), the share-page password (2), the export upload size mismatch, the network-storage uid refusal and in-use removal, and the network-storage attach failures (10 categories + the Windows-name tip) — are bundle keys now (`api.escrow.*`, `api.share.*`, `api.export.*`, `api.netstorage.*`), in the te-form in Hungarian (14 of them were in the formal „ön" form: „próbálja", „állítson be", „várja meg", „indítsa", „töltse", „ellenőrizze", „használja", „kapcsolja be", „állítsa", „jelezze", „távolítsa el", „adjon meg"), with English. A failed attach job is rendered in the READER's language at the status read (it ran detached; it keeps its category, server and uid). The NAS app-namespace refusal says „Válassz" (stays a settings constant). Left on purpose: the setup wizard and `recovery-info.txt` (R-554), and the SMART and fill-watch texts — they are the hub event's message, mailed as is (wire text). Tests `TestR516_GoLiteralFormalFormsAreGone` (keys in both languages + a source scan with a positive control), `TestR516_NetAddFailureFollowsTheReader`; red-proofs `felhom.eu/documentation/audits/night-burndown-2026-10-06/ctrl/R-516-red*.txt`.
|
||||||
|
- **R-330 (disk health Phase 2, the wire only):** `agentapi.SmartSummary` decodes the three SATA counters agent v0.150.0+ sends — `reported_uncorrect` (187), `command_timeout` (188), `udma_crc_errors` (199); nil = unknown (an older agent or a device that does not report them), a measured 0 stays 0. **Carried only:** no verdict, banner or mail reads them yet (that would change what a household is told — the row's next slice). MinAgent unchanged: an older agent simply omits them. Tests `TestR330_DecodesTheThreeCounters` (red-proved), `TestR330_CountersChangeNoVerdictYet`.
|
||||||
|
|
||||||
## v0.301.0 — one short stop per backup tier; restores replay before the app starts; the family window reopens a command-closed sign-up (R-518, R-638, R-717; `09` §3 154, 156) (2026-10-06)
|
## v0.301.0 — one short stop per backup tier; restores replay before the app starts; the family window reopens a command-closed sign-up (R-518, R-638, R-717; `09` §3 154, 156) (2026-10-06)
|
||||||
|
|
||||||
|
|||||||
@@ -1065,6 +1065,13 @@ type SmartSummary struct {
|
|||||||
ReallocatedSectors *int `json:"reallocated_sectors"`
|
ReallocatedSectors *int `json:"reallocated_sectors"`
|
||||||
PendingSectors *int `json:"pending_sectors"`
|
PendingSectors *int `json:"pending_sectors"`
|
||||||
OfflineUncorrectable *int `json:"offline_uncorrectable"`
|
OfflineUncorrectable *int `json:"offline_uncorrectable"`
|
||||||
|
// R-330 (agent v0.150.0+): three more SATA raw counters — 187 Reported_Uncorrect, 188
|
||||||
|
// Command_Timeout (vendor-packed on some drives: carried as reported), 199 UDMA_CRC_Error_Count.
|
||||||
|
// nil = unknown (an older agent, an NVMe/USB device, or not reported), never zero. CARRIED ONLY:
|
||||||
|
// no verdict, banner or mail reads them yet — that would change what a household is told.
|
||||||
|
ReportedUncorrect *int64 `json:"reported_uncorrect,omitempty"`
|
||||||
|
CommandTimeout *int64 `json:"command_timeout,omitempty"`
|
||||||
|
UDMACRCErrors *int64 `json:"udma_crc_errors,omitempty"`
|
||||||
// NVMe attributes.
|
// NVMe attributes.
|
||||||
CriticalWarning *int `json:"critical_warning"`
|
CriticalWarning *int `json:"critical_warning"`
|
||||||
MediaErrors *int `json:"media_errors"`
|
MediaErrors *int `json:"media_errors"`
|
||||||
|
|||||||
@@ -0,0 +1,61 @@
|
|||||||
|
package agentapi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// R-330 (disk health Phase 2, the wire only). The agent (v0.150.0+) sends three more SATA raw
|
||||||
|
// counters; the controller must DECODE them (a field the struct lacks is silently dropped by
|
||||||
|
// encoding/json) and must not yet ACT on them.
|
||||||
|
|
||||||
|
// The 2026-08-14 failing drive as the agent now reports it (values from
|
||||||
|
// felhom.eu/documentation/audits/fixtures/smart-ST3000VX010-failing-2026-08-14.json).
|
||||||
|
const r330AgentJSON = `{"health":"PASSED","temperature_c":40,"power_on_hours":null,
|
||||||
|
"reallocated_sectors":0,"pending_sectors":352,"offline_uncorrectable":352,
|
||||||
|
"reported_uncorrect":1001,"command_timeout":0,"udma_crc_errors":0,
|
||||||
|
"critical_warning":null,"media_errors":null,"percentage_used":null}`
|
||||||
|
|
||||||
|
// COMPANION RED-PROOF (observed): rename the controller tag `reported_uncorrect` → `reported_uncorrectable`
|
||||||
|
// → this fails with "187 must decode from the agent's JSON (raw 1001); got <nil>". Restored.
|
||||||
|
func TestR330_DecodesTheThreeCounters(t *testing.T) {
|
||||||
|
var s SmartSummary
|
||||||
|
if err := json.Unmarshal([]byte(r330AgentJSON), &s); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if s.ReportedUncorrect == nil || *s.ReportedUncorrect != 1001 {
|
||||||
|
t.Fatalf("187 must decode from the agent's JSON (raw 1001); got %v", s.ReportedUncorrect)
|
||||||
|
}
|
||||||
|
// A measured ZERO stays a zero (not nil): 188 and 199 were reported as 0 by this drive.
|
||||||
|
if s.CommandTimeout == nil || *s.CommandTimeout != 0 || s.UDMACRCErrors == nil || *s.UDMACRCErrors != 0 {
|
||||||
|
t.Fatalf("188/199 reported as 0 must decode as a measured 0; got %v %v", s.CommandTimeout, s.UDMACRCErrors)
|
||||||
|
}
|
||||||
|
|
||||||
|
// An older agent omits the keys → nil (unknown), never 0.
|
||||||
|
var old SmartSummary
|
||||||
|
if err := json.Unmarshal([]byte(`{"health":"PASSED","pending_sectors":0}`), &old); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if old.ReportedUncorrect != nil || old.CommandTimeout != nil || old.UDMACRCErrors != nil {
|
||||||
|
t.Fatalf("an older agent's report must leave the counters nil (unknown); got %v %v %v",
|
||||||
|
old.ReportedUncorrect, old.CommandTimeout, old.UDMACRCErrors)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CARRIED ONLY: tonight the counters change no verdict, label or degraded-attribute list — reading
|
||||||
|
// them is a change to what a household is told and is not taken here (R-330's next slice).
|
||||||
|
func TestR330_CountersChangeNoVerdictYet(t *testing.T) {
|
||||||
|
with := realDrive()
|
||||||
|
big := int64(5000)
|
||||||
|
with.ReportedUncorrect, with.CommandTimeout, with.UDMACRCErrors = &big, &big, &big
|
||||||
|
without := realDrive()
|
||||||
|
for _, prior := range []DiskPrior{{}, {SawUncorrectable: true}} {
|
||||||
|
if a, b := DiskVerdictFor(with, prior), DiskVerdictFor(without, prior); a != b {
|
||||||
|
t.Fatalf("prior %+v: the R-330 counters changed the verdict (%v vs %v) — that is a household-facing change", prior, a, b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if a, b := DegradedAttributes(with), DegradedAttributes(without); !reflect.DeepEqual(a, b) {
|
||||||
|
t.Fatalf("the R-330 counters changed the degraded-attribute list: %v vs %v", a, b)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user