fix(switcher+symlinks): rollback on ensure failure (Bug #1) + requiredShared contract test (Bug #10)
Bug #1 (CRITIQUE) — A3 flip+ensure inconsistency - Before: EnsureForAccount failure after flip was WARN-only, SetActiveAccount still fired → daemon declared target active while shared symlinks were absent/divergent → transcripts silently duplicated, resume broken. - After: ensure failure triggers rollback flip to previous account home; if rollback succeeds → explicit error, ActiveAccount stays on previous. If rollback ALSO fails → sticky partialSwap flag + ErrPartialSwap; all further swaps refused until operator intervention (daemon restart). - New public IsPartialSwap() for watchdog / health-check integration. Bug #10 (MOYENNE) — requiredShared contract never exercised - All existing tests override a.sharedSymlinks with tmpdir-scoped lists, so symlinks.RequiredShared itself was never tested. A rename or drop would pass every test but silently break prod failover. - TestRequiredSharedIsCoherent asserts (no filesystem): 3 entries with the exact required names, absolute targets, and a single shared parent directory (invariant EnsureForAccount depends on). Tests: - go test ./... PASS - go test -race ./... PASS (no data race) - 2 new switcher tests: TestFlipEnsureFailureTriggersRollback, TestFlipEnsureAndRollbackFailure - 1 new symlinks test: TestRequiredSharedIsCoherent - 1 obsolete test replaced: TestFlipEnsureSymlinksFailureDoesNotAbortSwap (encoded the old buggy best-effort behaviour)
This commit is contained in:
parent
8eaf0bbd35
commit
20063b1939
4 changed files with 356 additions and 24 deletions
62
VERSION.md
62
VERSION.md
|
|
@ -1,4 +1,64 @@
|
|||
# Version actuelle : 0.3.7
|
||||
# Version actuelle : 0.3.8
|
||||
|
||||
## [0.3.8] - 2026-04-16
|
||||
**Type:** Patch — Bug #1 (A3 flip+ensure inconsistency) + Bug #10 (requiredShared contract test)
|
||||
|
||||
### Corrigé — Bug #1 (CRITIQUE)
|
||||
- `AccountSwitcher.executeSwitch` ne continue plus silencieusement quand
|
||||
`symlinks.EnsureForAccount` échoue après le flip : il **roll-back** le lien
|
||||
`~/.claude` vers le home du compte précédent et **n'appelle pas**
|
||||
`SetActiveAccount`. Évite l'état incohérent où le daemon déclare le compte
|
||||
cible actif alors que ses shared symlinks sont divergents → transcripts
|
||||
dupliqués silencieusement, resume cassé.
|
||||
- Si le rollback réussit : swap annulé, état filesystem = état pré-swap,
|
||||
erreur explicite retournée par `executeSwitchE`.
|
||||
- Si ensure ET rollback échouent : `partialSwap` atomique sticky set,
|
||||
`ErrPartialSwap` retourné, tout futur swap est refusé tant que le
|
||||
daemon n'est pas redémarré par l'opérateur.
|
||||
- Nouvelle méthode publique `AccountSwitcher.IsPartialSwap() bool` pour
|
||||
que health-checks et watchdog exposent l'état dégradé.
|
||||
|
||||
### Ajouté — Tests Bug #1
|
||||
- `TestFlipEnsureFailureTriggersRollback` : plant un lien divergent sur
|
||||
le home cible → ensure échoue → rollback réussit → `ActiveAccount` reste
|
||||
compte1 → `~/.claude` pointe sur previousHome → `IsPartialSwap` = false.
|
||||
- `TestFlipEnsureAndRollbackFailure` : force les deux flips à échouer
|
||||
(homeDir = fichier régulier) → `ErrPartialSwap` retourné, flag sticky
|
||||
set, swap suivant refusé.
|
||||
|
||||
### Ajouté — Bug #10
|
||||
- `TestRequiredSharedIsCoherent` (`internal/symlinks/shared_test.go`) :
|
||||
valide le contrat de la constante package-level `RequiredShared` jamais
|
||||
exercée auparavant (tous les autres tests utilisent un override scoped
|
||||
en `t.TempDir()`). Vérifie sans toucher au filesystem :
|
||||
- exactement 3 entrées (`session-env`, `file-history`, `projects`)
|
||||
- targets absolus
|
||||
- `filepath.Dir(target)` identique pour les 3 entrées (invariant
|
||||
"3 liens sous un même shared root" sur lequel repose `EnsureForAccount`).
|
||||
|
||||
### Rationale
|
||||
- Continuer après un ensure échoué revient à valider que le compte cible
|
||||
est "sain" alors que les shared symlinks sont absents ou divergents.
|
||||
Conséquence en prod : premier `claude --resume` écrit dans
|
||||
`~/.claude/projects/` (privé) → transcripts dupliqués, undo
|
||||
désynchronisé, failover complètement cassé sans log d'alerte.
|
||||
- Le rollback garantit qu'un compte cible mal configuré ne peut PAS
|
||||
dégrader le state du daemon : on retourne à l'état pré-swap et on
|
||||
signale l'erreur à l'appelant.
|
||||
- `ErrPartialSwap` + `IsPartialSwap()` documente un état où l'intervention
|
||||
humaine est obligatoire — préférable à un retry automatique qui
|
||||
empirerait la divergence.
|
||||
|
||||
### Tests
|
||||
- ✅ `go test ./...` : tous les packages PASS
|
||||
- ✅ `go test -race ./...` : PASS, aucun data race
|
||||
- ✅ `go vet ./...` : clean
|
||||
- ✅ `go build ./...` : clean
|
||||
|
||||
### Fichiers modifiés
|
||||
- `internal/switcher/account_switcher.go` (+rollback + IsPartialSwap + ErrPartialSwap)
|
||||
- `internal/switcher/account_switcher_test.go` (2 nouveaux tests, 1 test obsolète remplacé)
|
||||
- `internal/symlinks/shared_test.go` (+TestRequiredSharedIsCoherent)
|
||||
|
||||
## [0.3.7] - 2026-04-16
|
||||
**Type:** Patch — Phase 1 / Chantier A3 : wire EnsureForAccount post-flip
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue