<< All versions

Skill v1.0.0

currentTrusted Publisher100/100
microsoft/accordant/state
──Details
PublishedOctober 1, 2026 at 05:01 AM
Content Hashsha256:623bcbc707064d6d...
Git SHA
──Files
Files (1 file, 5.3 KB)
SKILL.md5.3 KBactive
SKILL.md · 194 lines · 5.3 KB

version: "1.0.0" 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

TypeNotes
Primitivesint, string, decimal, bool, DateTime, Guid, etc.
CollectionsDictionary<K,V>, List<T>, HashSet<T>
Nested [State]Other classes marked with [State]
EnumsAny 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
All versions