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:
2026-06-08 23:21:55 +02:00
parent 605ce25f58
commit 05c450147c
16 changed files with 1904 additions and 78 deletions
+24
View File
@@ -0,0 +1,24 @@
// Package reconcile is the agent-side control core (slice 4): the engine that
// converges actual Proxmox state toward a desired state, the per-guest serializer
// that all mutation sources funnel through (doc 03 §10), the field-normalization
// layer that keeps Proxmox round-trip quirks from reading as drift, and the durable
// operation journal + idempotency store.
//
// Phase A (this file set) is structural and runs LIVE but UNFED: at slice 4 there is
// no desired-state provider (hub serving is slice 10, provisioning is slice 7), so
// the live engine computes an empty action set and performs ZERO mutations. The
// engine, serializer, normalizer, journal, and planner are all exercised against
// synthetic fixtures. The first live convergence arrives when slice 10 serves desired
// state into the DesiredProvider seam.
//
// Phase B (added later) layers the benign/destructive classifier and the
// reversibility gate (doc 03 §4) plus the signed-op consuming layer over
// internal/authz (doc 04) in front of the queue's executor — so every mutation passes
// the gate. Phase A deliberately wires only the benign-on-existing-guest action set:
// Start / Stop / SetConfig.
//
// Action set scope (slice 4): Start, Stop, SetConfig on EXISTING guests only.
// Provisioning (restore-to-new-guest) and destroy/overwrite are out of scope here —
// the destructive set is classified and gated in Phase B but not wired to live
// execution, because nothing serves destructive deltas until slice 10.
package reconcile