v0.280.0: the setup gate (decision 46); R-710 'I changed it' + absent-record window; R-709 password fields off the page; password:N:special generator
gates / gates (push) Successful in 25s

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-09-29 08:51:46 +02:00
parent 2548b4c924
commit 7fa8768cfd
37 changed files with 4015 additions and 107 deletions
+31 -3
View File
@@ -2,6 +2,7 @@ package web
import (
"strings"
"time"
"gitea.dooplex.hu/admin/felhom-controller/internal/stacks"
)
@@ -14,21 +15,48 @@ import (
// after_install) or the command failed, the page and the install dialog say plainly what the default is and
// to change it at once. Pinned by internal/web/known_login_test.go.
// afterInstallWindow is how long after an install an ABSENT after_install record still means "not run yet":
// the deploy-done hook waits up to 10 minutes for the app, then tries 6 × 20 s (after_install.go).
const afterInstallWindow = 30 * time.Minute
// knownLoginNow is the page's clock (a test seam).
var knownLoginNow = time.Now
// defaultLoginInEffect: the template has a default login and nothing has replaced it on this install.
// installed=false is the install dialog, before the install: a declared after_install WILL replace it.
func defaultLoginInEffect(meta *stacks.Metadata, cfg *stacks.AppConfig, installed bool) bool {
if meta == nil || strings.TrimSpace(meta.AppInfo.DefaultCreds) == "" {
return false
}
// R-710: the household said it changed the login by hand (the app page's "I changed it").
if installed && cfg != nil && cfg.DefaultLogin != nil {
return false
}
if meta.AfterInstall == nil {
return true
}
if !installed {
return false
}
// Installed with an after_install: in effect only when the command FAILED (absent = not run yet — the
// deploy-done hook runs it within minutes; the page does not warn about a default it is replacing).
return cfg != nil && cfg.AfterInstall != nil && !cfg.AfterInstall.OK
if cfg == nil {
return true // the app's record is unreadable: say what the template's default is
}
if cfg.AfterInstall != nil {
return !cfg.AfterInstall.OK
}
// R-710 (measured on demo-hp 2026-09-29): an ABSENT record means "not run yet" only inside the command's
// window after the install. An app installed before its template gained an after_install never runs it —
// read as "not run yet" for ever, its live default login went unannounced.
at, err := time.Parse(time.RFC3339, cfg.DeployedAt)
return err != nil || knownLoginNow().Sub(at) > afterInstallWindow
}
// defaultLoginReplaced: installed, and something replaced the default (after_install, or the household).
func defaultLoginReplaced(meta *stacks.Metadata, cfg *stacks.AppConfig, installed bool) bool {
if meta == nil || !installed || defaultLoginInEffect(meta, cfg, installed) {
return false
}
return meta.AfterInstall != nil || (cfg != nil && cfg.DefaultLogin != nil)
}
// knownLoginLine is the sentence, in lang, or "" when no default login is in effect.