diff --git a/Plugin/unraid/include/ai_repair.php b/Plugin/unraid/include/ai_repair.php index 2d4cd32..15e502b 100644 --- a/Plugin/unraid/include/ai_repair.php +++ b/Plugin/unraid/include/ai_repair.php @@ -322,6 +322,10 @@ function vv_ai_finding_write(array $f): array { // What the key read when the operator acknowledged it. Null unless acked; the ack // expires the moment the live value stops matching this. 'ack_value' => null, + // A stable identity for the kinds whose evidence changes on every pass. Empty for the + // rest, which pin to a conf value or to the evidence itself. Stored rather than derived + // so an ack given in the browser pins to exactly what the sweep will compare against. + 'pin' => mb_substr((string)($f['pin'] ?? ''), 0, 120), // What this finding looked like when it was last announced. Carried across sightings // below — a stamp that reset every fifteen minutes would be a notification every fifteen // minutes, which is how an operator learns to ignore the channel. @@ -418,7 +422,17 @@ function vv_ai_finding_dismiss(string $id, string $note = ''): bool { // label. Those pin to the shape of the fault instead: "indexers unavailable: NzbNoob" and // "indexers unavailable: NzbNoob, Miatrix" are one finding getting worse, and an ack given for // the first has not been given for the second. +// A finding may supply its own pin, and the log-derived kinds must. Their evidence carries a line +// count and the timestamp of the first matching line, so it is different on every single pass — +// pinning to it would expire an acknowledgement within fifteen minutes and re-announce a fault +// the operator had just said they knew about, forever. A PCIe controller that has thrown +// correctable errors since the machine was built is exactly that case. +// +// The pin those kinds supply is their identity: the fault class and the thing it is about. A +// fault that gets genuinely worse — correctable becoming uncorrectable — has a different class, +// so it is a different finding and announces on its own rather than hiding behind this one's ack. function vv_ai_finding_ack_pin(array $f): string { + if (($f['pin'] ?? '') !== '') return (string)$f['pin']; $key = (string)($f['conf_key'] ?? ''); if ($key !== '') return (string)(vv_conf_vars()[$key] ?? ''); return 'ev:' . substr(sha1((string)($f['evidence'] ?? '')), 0, 16); @@ -657,6 +671,9 @@ function vv_ai_syslog_findings(int $since, ?array $lines = null): array { $found[] = [ 'kind' => 'system_fault', 'subject' => $a['subject'], + // Identity, not contents — the evidence below counts lines and quotes a timestamp, + // so it differs every pass and would expire an acknowledgement immediately. + 'pin' => 'sys:' . $a['what'] . '|' . $a['subject'], // What identifies it: the class of fault, not the message. The wording of a kernel // line changes between releases and the fault does not. 'ref' => $a['what'], @@ -774,6 +791,7 @@ function vv_ai_container_findings(int $since, ?array $logsByContainer = null): a $found[] = [ 'kind' => 'container_fault', 'subject' => $a['name'], + 'pin' => 'ctr:' . $a['what'] . '|' . $a['name'], 'ref' => $a['what'], 'conf_key' => '', 'conf_file' => '',