Files
admin added9d226 v0.81.0: NAS verify-before-commit — retry=0, journal classifier, detached verify job + auto-rollback
Agent half of the verify-before-commit task (SPIKE-nas-verify-2026-07-11, b57f6c1):
retry=0 in the production NFS options (Q4-vi); ClassifyNetVerifyFailure on the live
Q4 strings (nfs_export merges not-found/not-permitted); add = sync fast-fail (2s TCP
pre-probe, nothing installed) + detached in-memory verify job judging /proc/mounts
only, auto-rollback on failure; GET /netstorage/verify-status (phase none = the
controller's Scenario-F rollback signal); unprivileged journalctl (systemd-journal
group, NO new sudoers grants).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
2026-07-11 09:44:06 +02:00

62 lines
3.5 KiB
Go

package storage
import "strings"
// Network-mount verify failure classification — the categorized refusal behind verify-before-commit.
// The categories and their journal substrings are the LIVE-MEASURED error taxonomy of
// SPIKE-nas-verify-2026-07-11 §Q4 (felhom.eu/documentation/audits/): every mount failure exits
// rc=32, so classification MUST run on the journal's message strings, never on exit codes — an
// exit-code classifier cannot even split wrong-password from wrong-share-name.
// Verify failure category codes. The controller maps these to the customer-facing Hungarian
// messages; the codes themselves are a stable wire vocabulary — do not rename casually.
const (
NetVerifyUnreachable = "unreachable" // no host behind the endpoint (pre-probe or No route/refused)
NetVerifyNFSExport = "nfs_export" // NFS export missing OR not permitted — MERGED (Q4 ii≡iii)
NetVerifySMBAuth = "smb_auth" // SMB wrong username/password (mount error(13))
NetVerifySMBShare = "smb_share" // SMB share name not found (mount error(2))
NetVerifyTimeout = "timeout" // blocked until systemd's 90 s start cap (black-holed-but-routed)
NetVerifyMountFailed = "mount_failed" // no diagnostic matched / journal unavailable
)
// netVerifyRule is one first-match-wins row of the classification table. The substrings are
// VERBATIM from the spike's Q4 transcripts (mount.nfs4 / mount.cifs / systemd on PVE 8) — matching
// is on the stable fragment, tolerant of surrounding version drift.
type netVerifyRule struct {
substr string
code string
hint string
}
// netVerifyRules — ordered: protocol-specific diagnostics before generic ones. Note the NFS rule
// keys on the full "reason given by server:" fragment, so SMB's "mount error(2): No such file or
// directory" can never shadow it (and vice versa).
var netVerifyRules = []netVerifyRule{
{"No route to host", NetVerifyUnreachable, "no route to the server (retry=0 fast-fail)"},
{"Connection refused", NetVerifyUnreachable, "the server refused the connection"},
{"Connection timed out", NetVerifyUnreachable, "the connection timed out"},
{"reason given by server: No such file or directory", NetVerifyNFSExport,
"NFS export not found OR not permitted for this client — NFSv4 cannot distinguish the two (SPIKE Q4 ii≡iii)"},
{"mount error(13)", NetVerifySMBAuth, "SMB logon failure (STATUS_LOGON_FAILURE) — wrong username or password"},
{"mount error(2)", NetVerifySMBShare, "SMB share not found (BAD_NETWORK_NAME)"},
{"Mounting timed out. Terminating", NetVerifyTimeout,
"mount blocked until systemd's start timeout — server routed but not answering"},
}
// ClassifyNetVerifyFailure maps a mount unit's journal tail to a verify failure category + an
// operator-facing English hint (the Hungarian customer message is the controller's job). Pure,
// table-driven, first match wins. tcpReachable (the endpoint pre-probe result at classification
// time) only breaks the tie when NO substring matched: an empty/unmatched journal against a
// dead endpoint is still `unreachable`, not the generic `mount_failed`.
func ClassifyNetVerifyFailure(journalTail string, tcpReachable bool) (code, hint string) {
for _, r := range netVerifyRules {
if strings.Contains(journalTail, r.substr) {
return r.code, r.hint
}
}
if !tcpReachable {
return NetVerifyUnreachable, "no mount diagnostic in the journal and the endpoint is not reachable"
}
return NetVerifyMountFailed, "mount failed with no recognized diagnostic — see the raw journal detail"
}