package reconcile import "strings" // The normalization layer keeps Proxmox round-trip quirks from reading as drift. // Reconcile compares NORMALIZED desired-vs-actual so a value the agent wrote and then // read back equal does not look like a change. `description`'s trailing newline (the // first proven case, slice-4 pre-check) is the seed; the structure is a per-field // registry so other quirks (omitted-default fields, boolean coercions, list ordering) // slot in as they are discovered — each is a Normalizer mapped to a field name. // Normalizer maps a raw field value to its canonical comparison form. It must be // idempotent: Normalizer(Normalizer(x)) == Normalizer(x). type Normalizer func(string) string // FieldNormalizers maps a Proxmox config field name to its Normalizer. A field with // no entry compares verbatim (identity). type FieldNormalizers map[string]Normalizer // DefaultNormalizers is the production set. Today only `description` needs one (PVE // appends a trailing newline on read). New quirks are added here as they are found. func DefaultNormalizers() FieldNormalizers { return FieldNormalizers{ "description": NormDescription, } } // Norm returns the canonical comparison form of value for field, applying the // field's Normalizer if one is registered, else the value unchanged. func (n FieldNormalizers) Norm(field, value string) string { if f, ok := n[field]; ok && f != nil { return f(value) } return value } // Equal reports whether two raw values for field are equal AFTER normalization. func (n FieldNormalizers) Equal(field, a, b string) bool { return n.Norm(field, a) == n.Norm(field, b) } // NormDescription strips the trailing newline(s) PVE appends to the LXC `description` // field on read, so a written value round-trips equal. (Proven slice-4 pre-check: // PVE stores `description` with a trailing "\n"; a verbatim compare always mismatches.) // Exported so the --selftest=task description round-trip uses the SAME helper the // reconciler does — one source of truth for the quirk. func NormDescription(s string) string { return strings.TrimRight(s, "\n") }