controller v0.242.0: a removed app is listed with its kept backup; five small ones (R-487 R-491 R-490 R-489 R-476 R-456)
gates / gates (push) Successful in 14s

R-487: the local backup lists are keyed on the drives, not on what is
deployed — a removed app whose unit was kept is listed with the restore
that reinstalls it, the picker answers for it, and the restore opens the
unit where it sits. R-491: a removal clears the app's update hold.
R-490: /api/system/info reaches the API router and reads the default
storage path. R-489: volumes_removed is the real before/after difference,
[] when none. R-476: a Tier-2 copy is dated by its data, not its manifest.
R-456: the boot-orphan rule is pinned. Every fix red-proofed.
This commit is contained in:
2026-09-13 22:50:18 +02:00
parent c1f62ddae8
commit d698ce343b
22 changed files with 838 additions and 13 deletions
+4
View File
@@ -1747,6 +1747,10 @@ func main() {
mux.Handle("/api/sharing/", webServer.RequireAuth(webServer.CsrfProtect(http.HandlerFunc(webServer.ServeSharingAPI))))
// Guest RAM resize (v0.143.0, R-24): read current allocation/bounds + apply a bounded resize.
mux.Handle("/api/system/", webServer.RequireAuth(webServer.CsrfProtect(http.HandlerFunc(webServer.ServeSystemAPI))))
// R-490 (v0.242.0): the web layer's /api/system/ prefix knows only the memory routes and answered
// 404 to /api/system/info, so the monitoring page's memory-distribution card never rendered. An
// exact pattern wins over the prefix in ServeMux. Pinned by TestR490_SystemInfoIsMountedAheadOfTheWebPrefix.
mux.Handle("/api/system/info", webServer.RequireAuth(webServer.CsrfProtect(http.HandlerFunc(apiRouter.ServeHTTP))))
// Standalone full-server (guest) restart — the "Kiszolgáló újraindítása" maintenance affordance,
// a sibling to the controller-only /api/selfrestart. Reuses the agent GuestReboot primitive.
mux.Handle("/api/server/reboot", webServer.RequireAuth(webServer.CsrfProtect(http.HandlerFunc(webServer.HandleServerReboot))))
@@ -73,3 +73,52 @@ func TestR475_AdapterReadsEveryTier(t *testing.T) {
t.Errorf("the hold must carry UpdateCopyHolds through HoldAfterFailedUpdateHolding (R-479); selectors:%s", h)
}
}
// R-490 — /api/system/info reaches the API router; the web layer's /api/system/ prefix used to eat it.
//
// COMPANION RED-PROOF (REPORT.md): delete the exact-path mount — this fails.
func TestR490_SystemInfoIsMountedAheadOfTheWebPrefix(t *testing.T) {
src, err := parser.ParseFile(token.NewFileSet(), "main.go", nil, 0)
if err != nil {
t.Fatal(err)
}
var exact, prefix bool
ast.Inspect(src, func(n ast.Node) bool {
call, ok := n.(*ast.CallExpr)
if !ok || len(call.Args) < 2 {
return true
}
sel, ok := call.Fun.(*ast.SelectorExpr)
if !ok || sel.Sel.Name != "Handle" {
return true
}
lit, ok := call.Args[0].(*ast.BasicLit)
if !ok {
return true
}
switch lit.Value {
case `"/api/system/info"`:
exact = strings.Contains(fmtNode(call.Args[1]), "apiRouter")
case `"/api/system/"`:
prefix = true
}
return true
})
if !prefix {
t.Fatal("the /api/system/ prefix mount is gone — this test's premise moved")
}
if !exact {
t.Error("/api/system/info must be mounted on the API router ahead of the web layer's /api/system/ prefix (R-490)")
}
}
func fmtNode(n ast.Node) string {
var names []string
ast.Inspect(n, func(x ast.Node) bool {
if id, ok := x.(*ast.Ident); ok {
names = append(names, id.Name)
}
return true
})
return strings.Join(names, " ")
}