v0.297.0: burn-down round 2 — 24 small rows (R-591 R-568 R-567 R-363 R-547 R-10 R-552 R-251 R-104 R-619 R-362 R-675 R-256 R-257 R-240 R-365 R-425 R-565 R-564 R-603 R-454 R-208 R-457-swept) + the banner countdown and deepCopyStack twins; MinAgent 0.131.0
gates / gates (push) Failing after 50s

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:
2026-10-05 20:12:56 +02:00
parent 114ff2761a
commit 1453cfc69b
68 changed files with 3953 additions and 116 deletions
+31
View File
@@ -391,6 +391,23 @@ func (r *Router) getStack(w http.ResponseWriter, req *http.Request, name string)
writeJSON(w, http.StatusOK, apiResponse{OK: true, Data: stack})
}
// markPasswordFieldsRequired (R-619) serves the deploy refusal's real rule on the wire: a
// `type: password` field is MANDATORY however the template's `required` reads, because the deploy
// never generates a password the household has not seen (stacks/deploy.go, the "password" case).
// Served `required:false`, a caller that trusts the contract was refused 400. Derived here, not stored,
// so templates need no edit. meta is the handler's own fresh LoadMetadata copy, so changing it touches
// no shared state. Pinned by TestR619_PasswordFieldIsServedAsRequired.
func markPasswordFieldsRequired(meta *stacks.Metadata) {
if meta == nil {
return
}
for i := range meta.DeployFields {
if meta.DeployFields[i].Type == "password" {
meta.DeployFields[i].Required = true
}
}
}
func (r *Router) getDeployFields(w http.ResponseWriter, req *http.Request, name string) {
meta, appCfg, err := r.stackMgr.GetDeployFields(name)
if err != nil {
@@ -398,6 +415,7 @@ func (r *Router) getDeployFields(w http.ResponseWriter, req *http.Request, name
return
}
markPasswordFieldsRequired(meta)
data := map[string]interface{}{
"metadata": meta,
"app_config": appCfg,
@@ -929,6 +947,18 @@ func (r *Router) dropLeftoverHold(name, why string) {
}
}
// clearInterruptedRestoreNotice (R-552) drops the removed app's „Megszakadt visszaállítás" notice. The
// notice tells the household to run the restore again; once the app is removed that advice has no
// subject, and nothing else would ever clear it. Pinned by TestR552_RemoveClearsTheInterruptedRestoreNotice.
func (r *Router) clearInterruptedRestoreNotice(name string) {
if r.backupMgr == nil {
return
}
if r.backupMgr.ClearInterruptedRestore(name) {
r.logger.Printf("[INFO] [api] remove %s: its interrupted-restore notice is cleared with it (R-552)", name)
}
}
// removeVerificationCopy (R-706, v0.279.0) deletes the app's off-site VERIFICATION copy
// (`backups/offsite-restore/<app>`, left by a full off-site restore for the household to inspect) when the
// app is removed "with its backups". Measured 2026-09-28 on demo-hp: ~1 GB stayed after such a removal, and
@@ -1057,6 +1087,7 @@ func (r *Router) removeStack(w http.ResponseWriter, req *http.Request, name stri
r.logger.Printf("[INFO] [api] remove %s: its update hold is cleared with it (R-491)", name)
}
}
r.clearInterruptedRestoreNotice(name)
writeJSON(w, http.StatusOK, apiResponse{OK: true, Data: resp, Message: "Stack " + name + " removed"})