phase 3 review: fix broken error classification, stub checks, wasted uuid, token idempotency, dead code

- actuator/ssh.go: custom errorsAs chain broken — all SSH errors classified as SSHErrorOther.
  Replaced with standard errors.As + errors.Is.
- scheduler/scheduler.go: all four check functions were stubs returning healthy.
  Implemented real HTTP GET, TCP dial, unix.Statfs disk, and TLS cert expiry checks.
- learning/learning.go: uuid.NewV7() called unconditionally before ON CONFLICT upsert.
  Now looks up existing pattern first, reuses entity_id.
- notifier/notifier.go: removed dead var_, fixed token regeneration every 15s.
  Now skips if token_hash already set.
- phase3.go: removed dead GetPattern+dummy args call in PatchPattern.
- classify.go: removed unused var_ guard.
This commit is contained in:
2026-07-07 15:27:31 +02:00
parent 095a3967c4
commit aa197190cd
6 changed files with 182 additions and 87 deletions

View File

@@ -7,6 +7,7 @@ package actuator
import (
"context"
"encoding/json"
"errors"
"fmt"
"log/slog"
"net"
@@ -78,21 +79,18 @@ func classifySSHError(err error) SSHErrorClass {
return SSHErrorOther
}
// Context deadline/cancel → timeout
if err == context.DeadlineExceeded {
if errors.Is(err, context.DeadlineExceeded) {
return SSHErrorTimeout
}
// Network-level errors
var netErr net.Error
if ok := errorsAs(err, &netErr); ok {
if errors.As(err, &netErr) {
if netErr.Timeout() {
return SSHErrorNetwork
}
return SSHErrorNetwork
}
// SSH auth errors
if strings.Contains(err.Error(), "unable to authenticate") ||
strings.Contains(err.Error(), "no supported methods remain") ||
strings.Contains(err.Error(), "ssh: handshake failed") ||
@@ -101,70 +99,14 @@ func classifySSHError(err error) SSHErrorClass {
return SSHErrorAuth
}
// Exit errors (non-zero remote exit)
var exitErr *ssh.ExitError
if ok := errorsAs(err, &exitErr); ok {
if errors.As(err, &exitErr) {
return SSHErrorRemote
}
return SSHErrorOther
}
// errorsAs is a small wrapper to work with Go 1.26's errors.As signature.
func errorsAs(err error, target interface{}) bool {
// Use the standard errors.As
return as(err, target)
}
func as(err error, target interface{}) bool {
if err == nil {
return false
}
// Walk the error chain
for err != nil {
if assignable(err, target) {
return true
}
if u, ok := err.(interface{ Unwrap() error }); ok {
err = u.Unwrap()
} else if u, ok := err.(interface{ Unwrap() []error }); ok {
// Multi-error: check first
for _, e := range u.Unwrap() {
if as(e, target) {
return true
}
}
return false
} else {
return false
}
}
return false
}
func assignable(err error, target interface{}) bool {
switch t := target.(type) {
case *error:
return false
case **net.OpError:
*t, _ = err.(*net.OpError)
return *t != nil
case **ssh.ExitError:
*t, _ = err.(*ssh.ExitError)
return *t != nil
default:
// Use the original errors.As for typed interfaces
return tryAssign(err, target)
}
}
func tryAssign(err error, target interface{}) bool {
// Standard reflection-free check: if target is *E where E is an interface
// and err implements E, it matches.
// For concrete pointer types, use type assertion.
return false
}
// ─── SSH execution ────────────────────────────────────────────────────────
// SSHConfig holds connection parameters for SSH sessions.