# Status Validation Pattern

> **Scope:** universal
> **Layer:** 0 (always load)
> **Keywords:** validation, fluent, dto, modelstate, fluentvalidation
> **Load When:** always

**Verified against:** .NET 10 (pure state-validation pattern — no external package). Last-verified: 2026-05-20.

---

**MORPH-SPEC Standard** — Validate what is INVALID, not what is valid.

## Problem

Validations assuming a single flow break when new flows are added.

```
Original: Created → PendingPayment → Processing → Completed → EmailSent
Free:     Created → Processing → Completed → EmailSent  (skips payment)
```

```csharp
// ❌ Assumes single flow — breaks free flow
if (order.Status != OrderStatus.PendingPayment)
    throw new InvalidOperationException("Must be pending payment");

// ✅ Validates invalid states — works for all flows
if (order.Status >= OrderStatus.Completed || order.Status == OrderStatus.Failed)
    throw new InvalidOperationException("Order already completed or failed");
```

## Enum Design

```csharp
public enum OrderStatus
{
    Created = 0, PendingPayment = 1, Processing = 2, Completed = 3, EmailSent = 4,
    // Error states (separated range)
    Failed = 100, Cancelled = 101, Refunded = 102
}
```

## Validation Patterns

| Pattern | When | Example |
|---------|------|---------|
| Final state check | Block after completion | `if (status >= OrderStatus.Completed)` |
| Error state list | Block specific errors | `if (new[] { Failed, Cancelled, Refunded }.Contains(status))` |
| Range comparison | Block finalized + errors | `if (status >= Completed \|\| status >= Failed)` |

## Anti-Patterns

| Anti-Pattern | Problem | Fix |
|-------------|---------|-----|
| `if (status != Expected)` | Assumes single flow | Validate invalid states instead |
| Exhaustive switch on valid states | Must modify for each new status | Validate only invalid states |
| Business logic in state validator | Mixed concerns | Separate state validation from business rules |

## Complete Example

```csharp
public async Task<Result> ProcessOrderAsync(Guid orderId, CancellationToken ct)
{
    var order = await _db.Orders.FindAsync([orderId], ct);
    if (order == null) return Result.Failure("Order not found");

    // State validation (invalid states)
    if (order.Status >= OrderStatus.Completed) return Result.Failure("Already completed");
    if (order.Status == OrderStatus.Failed) return Result.Failure("Cannot process failed order");
    if (order.Status == OrderStatus.Cancelled) return Result.Failure("Cannot process cancelled order");

    // Business validation (separate)
    if (order.RequiresPayment && !order.IsPaid) return Result.Failure("Payment required");

    order.MarkAsProcessing();
    await _db.SaveChangesAsync(ct);
    return Result.Success();
}
```

## Checklist

- [ ] Validates INVALID states (not valid)?
- [ ] Uses ordered enum comparison where possible?
- [ ] Error states treated separately (100+ range)?
- [ ] All flows documented in decisions.md?
- [ ] Tested with all possible flows?

---

*MORPH-SPEC by Polymorphism Tech*
