accordant-state

$npx mdskill add microsoft/accordant/accordant-state

State is what an external observer needs to know to predict what an operation should return. Keep it minimal — only track what's necessary to define correct behavior.

SKILL.md

.github/skills/accordant-stateView on GitHub ↗
---
name: accordant-state
description: How to design state models in Accordant - use this skill when defining what your system tracks between operations
---

# Designing State in Accordant

State is what an external observer needs to know to predict what an operation should return. Keep it minimal — only track what's necessary to define correct behavior.

## The [State] Attribute

Use `[State]` on a partial class to get automatic cloning, equality, and hashing:

```csharp
[State]
public partial class BankState
{
    public Dictionary<string, decimal> Accounts { get; set; } = new();
}
```

The source generator handles:
- **Deep cloning** for immutable state transitions
- **Equality comparison** for state deduplication
- **Hashing** for state graph exploration

## State Design Principles

### Keep It Minimal

Ask: "Does any operation need this to determine its response?" If no, leave it out.

```csharp
// ❌ Too detailed - includes implementation concerns
[State]
public partial class BadState
{
    public Dictionary<string, AccountEntity> Accounts { get; set; }  // EF entities
    public DateTime LastModified { get; set; }  // Not needed for behavior
    public string ConnectionString { get; set; }  // Implementation detail
}

// ✅ Just what operations need
[State]
public partial class GoodState
{
    public Dictionary<string, decimal> Accounts { get; set; } = new();  // Just balances
}
```

### Nested State Classes

For complex domains, nest state classes:

```csharp
[State]
public partial class AppState
{
    public Dictionary<string, UserState> Users { get; set; } = new();
}

[State]
public partial class UserState
{
    public string Name { get; set; } = string.Empty;
    public Dictionary<string, TodoState> Todos { get; set; } = new();
}

[State]
public partial class TodoState
{
    public string Title { get; set; } = string.Empty;
    public bool Completed { get; set; } = false;
}
```

## State Transitions

Operations return expected outcomes that describe both the response and the next state.

### No State Change: `.SameState()`

For errors or read-only operations:

```csharp
// Error - doesn't modify state
if (!state.Accounts.ContainsKey(accountId))
    return Expect.That<ApiResult<decimal>>(r => r.IsNotFound).SameState();

// Read-only operation
return Expect.That<ApiResult<decimal>>(r => r.IsSuccess && r.Data == balance).SameState();
```

### State Changes: `.ThenState()`

The lambda receives a **clone** of the current state. Modify it directly:

```csharp
return Expect.That<ApiResult<decimal>>(r => r.IsSuccess && r.Data == 0)
       .ThenState<BankState>(nextState => nextState.Accounts[accountId] = 0);
```

**Convention**: Always name the parameter `nextState` for clarity.

### Response-Dependent State

When state depends on server-generated values (IDs, timestamps):

```csharp
return Expect.That<ApiResult<Order>>(r => r.IsSuccess && r.Data.OrderId != null)
       .ThenState<AppState>(
           (ApiResult<Order> response, AppState nextState) =>
               nextState.Orders[response.Data.OrderId] = new OrderState { /* ... */ },
           mock: () => new ApiResult<Order> { Data = new Order { OrderId = Guid.NewGuid().ToString() } }
       );
```

The `mock:` provides a plausible response for test generation (when no real system is running).

## Supported Property Types

| Type | Notes |
|------|-------|
| Primitives | `int`, `string`, `decimal`, `bool`, `DateTime`, `Guid`, etc. |
| Collections | `Dictionary<K,V>`, `List<T>`, `HashSet<T>` |
| Nested [State] | Other classes marked with `[State]` |
| Enums | Any enum type |

**Dictionary keys**: Supported types are `string`, `int`, `long`, and `Guid`. Keys are sorted during serialization to ensure consistent hashing.

## [SharedState] for Large Values

For large values where cloning would be expensive:

```csharp
[State]
public partial class ImageState
{
    public string Name { get; set; }
    
    [SharedState(nameof(ContentFingerprint))]
    public List<byte> Content { get; set; }
    
    // Fingerprint used for equality instead of serializing all bytes
    public string ContentFingerprint => Content == null 
        ? null 
        : Convert.ToHexString(SHA256.HashData(Content.ToArray()));
}
```

**Important**: Shared values are shallow-copied, so treat them as immutable. Never modify after setting.

## Common Mistakes

### Mutable State Access

```csharp
// ❌ Wrong - modifying original state
return Expect.That(r => r.IsSuccess)
       .ThenState<AppState>(nextState => {
           state.Users[id] = newUser;  // BAD: modifying 'state', not 'nextState'
           return nextState;
       });

// ✅ Correct - modify the clone
return Expect.That(r => r.IsSuccess)
       .ThenState<AppState>(nextState => nextState.Users[id] = newUser);
```

### Overly Complex State

```csharp
// ❌ Too complex - slows down state exploration
[State]
public partial class TooMuchState
{
    public List<AuditEntry> AuditLog { get; set; }  // Unbounded growth
    public Dictionary<string, List<HistoricalBalance>> BalanceHistory { get; set; }
}

// ✅ Focus on what determines behavior
[State]
public partial class JustRightState
{
    public Dictionary<string, decimal> Balances { get; set; }
}
```

## Next Steps

- **Operations**: Learn to write Apply methods that use state
- **Test Generation**: See how state complexity affects test count

More from microsoft/accordant

SkillDescription
accordant-async-operationsHow to model background work, step functions, and polling - use this skill when testing async workflows, job queues, or operations that complete in the background
accordant-concurrencyHow to test for race conditions using concurrent tests and linearizability - use this skill when testing concurrent operations or finding race condition bugs
accordant-cross-languageTesting systems not written in .NET - trace-based conformance testing, JSON test plan export, and executing from any language. Use when the user's system is in Python, Go, Java, Rust, C++, or any non-.NET language, or when they ask about trace validation or exporting test cases.
accordant-operationsHow to define operations with Apply and Execute methods - use this skill when creating spec operations or understanding the Apply/Execute pattern
accordant-overviewWhat Accordant is, when to use model-based testing, and core concepts - use this skill when starting with Accordant or explaining its purpose
accordant-patternsCommon patterns in Accordant - response-dependent state, request derivations, error handling, and HTTP integration
accordant-quickrefQuick reference and cheatsheet for Accordant API - use this skill for quick syntax lookup or code snippets
accordant-test-generationHow Accordant generates and runs tests - use this skill when configuring test generation, understanding state graphs, or running tests
accordant-troubleshootingCommon mistakes and debugging tips for Accordant specs - use this skill when tests fail unexpectedly or specs don't behave as expected