fix(sdlc): make phase transitions idempotent
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed

Allow transitioning to the current phase (no-op success) instead of
rejecting it as a "backward" transition. This fixes issues where
external systems retry transition commands.

Before: draft -> draft returned error
After: draft -> draft returns nil (already there)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
jordan 2026-02-08 14:21:05 -07:00
parent 00f55f7f6f
commit a419c53592
2 changed files with 10 additions and 4 deletions

View File

@ -140,7 +140,13 @@ func (f *Feature) CanTransitionTo(target FeaturePhase, cfg *Config) error {
currentIdx := PhaseIndex(f.Phase)
targetIdx := PhaseIndex(target)
if targetIdx <= currentIdx {
// Idempotent: if already in target phase, return success
if targetIdx == currentIdx {
return nil
}
// Reject backward transitions
if targetIdx < currentIdx {
return fmt.Errorf("%w: cannot move from %s to %s (backward)", ErrInvalidTransition, f.Phase, target)
}

View File

@ -128,9 +128,9 @@ func TestCanTransitionTo(t *testing.T) {
t.Error("CanTransitionTo(planned) = nil, want error (skip)")
}
// Invalid: draft -> draft (backward)
if err := f.CanTransitionTo(PhaseDraft, cfg); err == nil {
t.Error("CanTransitionTo(draft) = nil, want error (backward)")
// Idempotent: draft -> draft (allowed, returns nil)
if err := f.CanTransitionTo(PhaseDraft, cfg); err != nil {
t.Errorf("CanTransitionTo(draft) = %v, want nil (idempotent)", err)
}
// Invalid phase