v0.4.0-rc1: slice 4 Phase A — reconcile engine (structural, runs live unfed)
New internal/reconcile package: the agent-side control core's structural half. - Per-guest serializer Queue (doc 03 §10): the single choke point all mutation sources funnel through; same-vmid serial in submit order, different vmids parallel (cond-var FIFO lanes). - Desired-state model + DesiredProvider seam; EmptyProvider is the only live source at slice 4 (no hub serving until slice 10) so the live engine computes an empty action set and performs zero mutations. - Normalization layer (FieldNormalizers): normalized desired-vs-actual so Proxmox round-trip quirks don't read as drift. normDesc promoted out of main.go to reconcile.NormDescription; selftest uses the shared helper. - Plan (pure diff): minimal benign action set (Start/Stop/SetConfig) for guests in both desired and actual; provision/destroy out of scope here. - Engine: dispatches onto the shared queue; honors the dual-mode SetConfig contract (UPID -> WaitTask; empty UPID -> synchronous success). - Durable op journal + idempotency store (mirrors authz.FileNonceStore): in-flight task ids for crash detection + AlreadyApplied dedupe across restart. - Wired into runDaemon alongside the hub loop, sharing the queue; runs cleanly with no desired state and no signers. Full module race-clean and vet-clean on the Linux build server. CHECKPOINT: Phase A only. Awaiting validation before Phase B (the reversibility gate + signed-op consuming layer, landing v0.4.0). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,47 @@
|
||||
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") }
|
||||
Reference in New Issue
Block a user