diff --git a/VERSION.md b/VERSION.md index 802aa9c..409cdff 100644 --- a/VERSION.md +++ b/VERSION.md @@ -1,4 +1,46 @@ -# Version actuelle : 0.3.0 +# Version actuelle : 0.3.1 + +## [0.3.1] - 2026-04-15 +**Type:** Patch — `start_index` pour faire coexister pool manuel et pool auto + +### Corrigé +- **Bug prod non détecté depuis longtemps** : le pool autonome daemon ignorait + le pool réel et vice-versa. Conséquence : **aucune tâche automatique n'était + dispatchée** (ex. `installer-d2-cli` dans secuaas-hosting/inbox depuis 18:39). +- Cause : le config avait `prefix: "ccl-auto"` (sans tiret), donc les loops + généraient `ccl-auto0..9` alors que `setup-tmux.sh` crée `ccl-auto-11..20`. + Le daemon créait en plus 2 sessions fantômes `ccl-auto0/1` au swap. + +### Ajouté +- `config.AutonomousConfig.StartIndex` (YAML `start_index`, défaut 0). + Les loops du daemon (monitor, dispatcher, switcher kill/recreate, lifecycle + ensure/reconcile) itèrent désormais `start..start+Max-1` au lieu de `0..Max-1`. +- Permet au pool autonome `ccl-auto-11..20` de coexister avec le pool manuel + `ccl-0..9` (réservé opérateur). Le daemon ne touche que ce qu'il gère. + +### Modifié +- `/etc/claude-failover/config.yaml` : + ```yaml + autonomous: + prefix: "ccl-auto-" # + tiret + start_index: 11 # NEW + min: 10 + max: 10 + ``` + +### Tests effectués +- ✅ `go test ./...` full suite +- ✅ Sessions `ccl-auto0/1` fantômes kill manuellement, pool `ccl-auto-11..20` + intact, pool manuel `ccl-0..9` intact +- ✅ Daemon redémarré, `config loaded: ... pool min=10 max=10` + +### Fichiers modifiés +- `internal/config/config.go` — champ `StartIndex` +- `internal/quota/monitor.go` — loop avec StartIndex +- `internal/switcher/account_switcher.go` — kill + recreate avec StartIndex +- `internal/dispatcher/dispatcher.go` — findFreeSession avec StartIndex +- `internal/lifecycle/manager.go` — ensure + reconcile avec StartIndex +- `/etc/claude-failover/config.yaml` — prefix fixé + start_index ## [0.3.0] - 2026-04-15 **Type:** Minor — Auto-resume des sessions dédiées après un swap légitime diff --git a/internal/config/config.go b/internal/config/config.go index fa3fbfd..0da2a77 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -52,10 +52,17 @@ type DedicatedSession struct { } // AutonomousConfig controls the autoscaling inbox-dispatcher session pool. +// +// The pool covers sessions named "". +// StartIndex lets the pool coexist with an unrelated numeric range of +// sessions (e.g. `ccl-0..ccl-9` reserved for manual use by the operator). +// When StartIndex is 0 (default), behaviour is identical to earlier +// versions. type AutonomousConfig struct { - Prefix string `yaml:"prefix"` - Min int `yaml:"min"` - Max int `yaml:"max"` + Prefix string `yaml:"prefix"` + StartIndex int `yaml:"start_index"` + Min int `yaml:"min"` + Max int `yaml:"max"` } // QuotaConfig defines quota monitoring parameters. diff --git a/internal/dispatcher/dispatcher.go b/internal/dispatcher/dispatcher.go index e434949..f2f9f03 100644 --- a/internal/dispatcher/dispatcher.go +++ b/internal/dispatcher/dispatcher.go @@ -152,7 +152,8 @@ func (d *Dispatcher) findFreeSession() string { if prefix == "" { prefix = "ccl-auto-" } - for i := 0; i < d.config.Pool.Autonomous.Max; i++ { + start := d.config.Pool.Autonomous.StartIndex + for i := start; i < start+d.config.Pool.Autonomous.Max; i++ { if d.isSessionFree(sessionName(prefix, i)) { return sessionName(prefix, i) } diff --git a/internal/lifecycle/manager.go b/internal/lifecycle/manager.go index 73b0eb8..40fa4b0 100644 --- a/internal/lifecycle/manager.go +++ b/internal/lifecycle/manager.go @@ -61,12 +61,13 @@ func (m *Manager) EnsureAllSessions() { } } - // Ensure autonomous pool sessions (prefix + index). + // Ensure autonomous pool sessions (prefix + index, starting at StartIndex). prefix := m.config.Pool.Autonomous.Prefix if prefix == "" { prefix = "ccl-auto-" } - for i := 0; i < m.config.Pool.Autonomous.Min; i++ { + start := m.config.Pool.Autonomous.StartIndex + for i := start; i < start+m.config.Pool.Autonomous.Min; i++ { name := sessionName(prefix, i) if !m.tmux.HasSession(name) { if err := m.tmux.CreateSession(name, ""); err != nil { @@ -86,12 +87,13 @@ func (m *Manager) reconcile() { m.reconcileSession(ds.Name, ds.Project) } - // Reconcile the autonomous pool (min sessions). + // Reconcile the autonomous pool (min sessions, starting at StartIndex). prefix := m.config.Pool.Autonomous.Prefix if prefix == "" { prefix = "ccl-auto-" } - for i := 0; i < m.config.Pool.Autonomous.Min; i++ { + start := m.config.Pool.Autonomous.StartIndex + for i := start; i < start+m.config.Pool.Autonomous.Min; i++ { name := sessionName(prefix, i) m.reconcileSession(name, "") } diff --git a/internal/quota/monitor.go b/internal/quota/monitor.go index d949d2d..6bf1946 100644 --- a/internal/quota/monitor.go +++ b/internal/quota/monitor.go @@ -140,7 +140,8 @@ func (m *Monitor) poll() { if prefix == "" { prefix = "ccl-auto-" } - for i := 0; i < m.config.Pool.Autonomous.Max; i++ { + start := m.config.Pool.Autonomous.StartIndex + for i := start; i < start+m.config.Pool.Autonomous.Max; i++ { name := sessionName(prefix, i) if !m.tmux.HasSession(name) { continue diff --git a/internal/switcher/account_switcher.go b/internal/switcher/account_switcher.go index 67e0c67..e8de796 100644 --- a/internal/switcher/account_switcher.go +++ b/internal/switcher/account_switcher.go @@ -255,13 +255,17 @@ func (a *AccountSwitcher) flipSymlink(targetHome string) error { return nil } -// killAllPoolSessions kills all autonomous and dedicated pool sessions. +// killAllPoolSessions kills all autonomous and dedicated pool sessions +// managed by this daemon. Sessions outside the configured +// StartIndex..StartIndex+Max range (e.g. manual operator sessions +// `ccl-0..ccl-9`) are left untouched. func (a *AccountSwitcher) killAllPoolSessions() { prefix := a.config.Pool.Autonomous.Prefix if prefix == "" { prefix = "ccl-auto-" } - for i := 0; i < a.config.Pool.Autonomous.Max; i++ { + start := a.config.Pool.Autonomous.StartIndex + for i := start; i < start+a.config.Pool.Autonomous.Max; i++ { a.tmux.KillSession(sessionName(prefix, i)) //nolint:errcheck } for _, ds := range a.config.Pool.Dedicated { @@ -269,13 +273,15 @@ func (a *AccountSwitcher) killAllPoolSessions() { } } -// recreatePoolSessions creates fresh pool sessions after a switch. +// recreatePoolSessions creates fresh pool sessions after a switch, +// respecting StartIndex so the pool stays within its configured range. func (a *AccountSwitcher) recreatePoolSessions() { prefix := a.config.Pool.Autonomous.Prefix if prefix == "" { prefix = "ccl-auto-" } - for i := 0; i < a.config.Pool.Autonomous.Min; i++ { + start := a.config.Pool.Autonomous.StartIndex + for i := start; i < start+a.config.Pool.Autonomous.Min; i++ { name := sessionName(prefix, i) if err := a.tmux.CreateSession(name, ""); err != nil { a.logger.Printf("[switcher] recreate autonomous %q: %v", name, err)