v0.281.0: 'Done' asks the probe first; sign-up closed after the first admin (decision 47); R-713 code-bound values refused, ${NAME|base64}
gates / gates (push) Successful in 23s

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 09:57:58 +02:00
parent 3a12c7341a
commit 149467c795
28 changed files with 1928 additions and 15 deletions
+14
View File
@@ -65,6 +65,8 @@ type SetupGateRecord struct {
Hosts []string `yaml:"hosts,omitempty" json:"hosts,omitempty"`
OpenedAt string `yaml:"opened_at,omitempty" json:"opened_at,omitempty"`
OpenedBy string `yaml:"opened_by,omitempty" json:"opened_by,omitempty"`
// SignupOpenUntil (v0.281.0, decision 47): the household opened sign-up until this time (signup_block.go).
SignupOpenUntil string `yaml:"signup_open_until,omitempty" json:"signup_open_until,omitempty"`
}
// Closed reports whether the gate stands.
@@ -296,6 +298,14 @@ func (m *Manager) OpenSetupGate(name, by string) error {
}
dir := filepath.Dir(st.ComposePath)
now := m.now().UTC().Format(time.RFC3339)
// Decision 47: the sign-up block goes up BEFORE the gate comes down, so there is no moment where a stranger
// can sign up. Cannot write it → the gate stays closed (the loop or the next press tries again).
block := strings.TrimSpace(st.Meta.SignupBlock)
if block != "" {
if err := m.writeSignupBlock(name, st.AppConfig.SetupGate.Hosts, block); err != nil {
return fmt.Errorf("setup gate %s: the sign-up block could not be written, so the gate stays closed: %w", name, err)
}
}
opened := false
m.mutateAppConfig(name, dir, "setup_gate", func(cfg *AppConfig) bool {
if !cfg.SetupGate.Closed() {
@@ -306,6 +316,9 @@ func (m *Manager) OpenSetupGate(name, by string) error {
return true
})
if !opened {
if block != "" {
_ = m.removeSignupBlockFile(name) // still gated: the household may still need the sign-up address
}
return fmt.Errorf("setup gate %s: the record could not be written", name)
}
if err := m.removeSetupGateFile(name); err != nil {
@@ -435,6 +448,7 @@ func (m *Manager) SetupGateTick() {
}
}
}
m.reconcileSignupBlocks()
}
// RunSetupGateLoop runs SetupGateTick every interval until ctx ends.