R-812 option A (hub): the Proxmox package set — candidate, operator approval, System page
Layer pve: never auto-approved; the candidate is the Proxmox userspace set every ring-0 box reports (kernel / boot / firmware names left out); the operator's "Approve Proxmox set" button appears only after 2 healthy night pve steps on every ring-0 box; an approval nudges no box (ring 1 by a signed os_pve_step). 11 §5.10 written (BUILT, unreleased, not yet proven live); §8 step 6 split (userspace §5.10, kernel R-836). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
@@ -70,6 +70,15 @@ func (s *Server) handleOSAdmin(w http.ResponseWriter, r *http.Request, path stri
|
||||
}
|
||||
id, err := view.ApproveDocker()
|
||||
reply(map[string]string{"release_id": id}, err)
|
||||
case r.Method == http.MethodPost && path == "/os/approve-pve":
|
||||
// R-812 option A (`09` §3 decision 163): the operator approves the Proxmox package set ring 0 ran.
|
||||
view, ok := s.osUpdates.(OSSystemView)
|
||||
if !ok {
|
||||
reply(nil, fmt.Errorf("proxmox approval not available"))
|
||||
return
|
||||
}
|
||||
id, err := view.ApprovePVE()
|
||||
reply(map[string]string{"release_id": id}, err)
|
||||
default:
|
||||
http.Error(w, "not found", http.StatusNotFound)
|
||||
}
|
||||
|
||||
@@ -59,6 +59,7 @@ var r135PostRoutes = []string{
|
||||
"/os/enabled/h1",
|
||||
"/os/approve-now",
|
||||
"/os/approve-docker",
|
||||
"/os/approve-pve",
|
||||
// Not a route: the gate must refuse BEFORE routing, so even an unknown path is 403, never 404.
|
||||
"/no-such-route",
|
||||
}
|
||||
|
||||
@@ -57,6 +57,7 @@ type OSSystemView interface {
|
||||
BundleThreshold() time.Duration
|
||||
AgentThreshold() time.Duration
|
||||
ApproveDocker() (string, error)
|
||||
ApprovePVE() (string, error) // R-812 option A: the Proxmox package set
|
||||
}
|
||||
|
||||
// agentCell is the "Agent" cell (R-530, hub v0.135.0): the box's agent against the vouched one, how far behind and
|
||||
|
||||
@@ -185,3 +185,31 @@ func TestHostsPage_ProxmoxKernelColumn(t *testing.T) {
|
||||
t.Fatalf("hosts column missing:\n%s", b[:min(len(b), 400)])
|
||||
}
|
||||
}
|
||||
|
||||
// R-812 option A: the "Approve Proxmox set" button appears ONLY when the rule allows it (one render test per branch).
|
||||
//
|
||||
// COMPANION RED-PROOF (observed): drop the `(eq .Waiting "")` from the pve button's condition → "button shown after ONE night".
|
||||
func TestSystemPage_PVEButtonOnlyWhenReady(t *testing.T) {
|
||||
s, st, svc := systemServer(t)
|
||||
if strings.Contains(getSystem(t, s), `action="/os/approve-pve"`) {
|
||||
t.Fatal("button shown with no Proxmox candidate")
|
||||
}
|
||||
_ = st.SetOSRing("full-1", 0)
|
||||
clock := time.Date(2026, 10, 7, 3, 0, 0, 0, time.UTC)
|
||||
svc.Now = func() time.Time { return clock }
|
||||
night := func() {
|
||||
r := osupdates.Report{RunID: time.Now().String(), Layer: "pve", Trigger: "night", Mode: "apply", Outcome: "nothing",
|
||||
Healthy: true, Installed: []osupdates.Package{{Name: "pve-manager", Version: "9.2.21", Origin: "Proxmox"}}}
|
||||
if err := svc.Ingest("full-1", r); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
night()
|
||||
if strings.Contains(getSystem(t, s), `action="/os/approve-pve"`) {
|
||||
t.Fatal("button shown after ONE night")
|
||||
}
|
||||
night()
|
||||
if !strings.Contains(getSystem(t, s), `action="/os/approve-pve"`) {
|
||||
t.Fatal("button missing after two healthy nights")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,6 +67,11 @@
|
||||
<input type="hidden" name="_csrf" value="{{$.CSRFToken}}"><input type="hidden" name="return" value="/system">
|
||||
<button type="submit" class="btn btn-sm" data-confirm="Approve this Docker engine set? Ring-1 boxes take it only through a signed operator job.">Approve Docker set</button>
|
||||
</form>{{end}}
|
||||
{{if and (eq .Layer "pve") .Fingerprint (not .Approved) (eq .Waiting "")}}
|
||||
<form method="POST" action="/os/approve-pve" style="margin-top: 0.3rem;">
|
||||
<input type="hidden" name="_csrf" value="{{$.CSRFToken}}"><input type="hidden" name="return" value="/system">
|
||||
<button type="submit" class="btn btn-sm" data-confirm="Approve this Proxmox package set (no kernel)? Ring-1 boxes take it only through a signed operator job.">Approve Proxmox set</button>
|
||||
</form>{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user