Pin a log-derived finding to what it is, not to what it said

The evidence of a syslog or container finding carries a line count and the timestamp of the
first matching line, so it differs on every pass even when nothing about the fault has changed.
Both the acknowledgement and the announcement were pinned to it, which meant acking one expired
fifteen minutes later and the notification repeated every sweep, forever — worst exactly where
it matters least, on a fault that has been there since the machine was built. Those kinds now
pin to their identity. A fault that genuinely worsens has a different class and so is a
different finding, and still announces on its own.
This commit is contained in:
Gmer4Lfe
2026-08-09 22:25:51 -04:00
parent 426ca2e5c7
commit 47861b0dc3
+18
View File
@@ -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' => '',