skills/microservice/query/sequence-checking/SKILL.md
Use when adding inline sequence validation for idempotent event processing.
npx skillsauth add faysilalshareef/dotnet-ai-kit sequence-checkingInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
int Sequence starting at 1, incrementing by 1 per aggregateint Sequence { get; private set; }if (entity.Sequence != @event.Sequence - 1)true tells the listener to CompleteMessage (processed or already done)false tells the listener to AbandonMessage (retry later)Sequence to @event.SequenceThe sequence check is a single if statement that handles both duplicates and gaps:
if (entity.Sequence != @event.Sequence - 1)
return entity.Sequence >= @event.Sequence;
This single expression covers TWO cases:
true (idempotent)false (retry)public async Task<bool> Handle(
Event<OrderUpdatedData> @event,
CancellationToken cancellationToken)
{
var order = await _unitOfWork.Orders.FindAsync(@event.AggregateId);
// Entity not found — creation event hasn't been processed yet
if (order is null)
return false;
// THE CORE GUARD: check sequence is exactly next expected
if (order.Sequence != @event.Sequence - 1)
return order.Sequence >= @event.Sequence;
// entity.Sequence >= event.Sequence -> true (already processed, complete msg)
// entity.Sequence < event.Sequence - 1 -> false (gap, abandon msg)
// Process: call entity behavior method (which sets Sequence internally)
order.UpdateDetails(@event.Data, @event.Sequence);
await _unitOfWork.SaveChangesAsync(cancellationToken);
return true;
}
Creation handlers do NOT use the sequence guard. They check for existence instead:
public async Task<bool> Handle(
Event<OrderCreatedData> @event,
CancellationToken cancellationToken)
{
var order = await _unitOfWork.Orders.FindAsync(@event.AggregateId);
// Already exists — idempotent, return true
if (order is not null)
return true;
order = new Order(@event); // Constructor sets Sequence = @event.Sequence
await _unitOfWork.Orders.AddAsync(order);
await _unitOfWork.SaveChangesAsync(cancellationToken);
return true;
}
The entity behavior method is where Sequence gets updated:
// In the entity class
public void UpdateDetails(OrderUpdatedData data, int sequence)
{
CustomerName = data.CustomerName;
Email = data.Email;
Total = data.Total;
Sequence = sequence; // <-- Sequence updated to @event.Sequence
}
Some entities use IncrementSequence() after calling the behavior method:
if (product.Sequence == @event.Sequence - 1)
{
product.Update(@event);
product.IncrementSequence(); // Sequence++
await _unitOfWork.SaveChangesAsync(cancellationToken);
}
return product.Sequence >= @event.Sequence;
Condition | Return | Message Action | Why
---------------------------------------|--------|----------------|--------------------
entity is null (update event) | false | Abandon | Creation not yet processed
entity.Sequence == @event.Sequence - 1 | true | Complete | Next in order, process it
entity.Sequence >= @event.Sequence | true | Complete | Already processed (idempotent)
entity.Sequence < @event.Sequence - 1 | false | Abandon | Gap — missing intermediate events
entity is not null (creation event) | true | Complete | Already created (idempotent)
Returning true for already-processed events is critical because:
CompleteMessageAsync when handler returns truefalse, the message would be abandoned and retried forevertrue = "I have handled this (or it was already handled), move on"Returning false for sequence gaps causes:
AbandonMessageAsync| Anti-Pattern | Correct Approach |
|---|---|
| SequenceChecker helper class | Inline the check in each handler |
| if (@event.Sequence <= entity.Sequence) return true | if (entity.Sequence != @event.Sequence - 1) return entity.Sequence >= @event.Sequence |
| Separate if statements for duplicate and gap | Single combined expression |
| Throwing on sequence mismatch | Return bool — let listener decide action |
| Not updating Sequence after processing | Behavior method must set Sequence = sequence |
# Find the core sequence guard pattern
grep -r "Sequence != @event.Sequence - 1" --include="*.cs" Application/
# Find Sequence >= comparison for idempotency
grep -r "Sequence >= @event.Sequence" --include="*.cs" Application/
# Find entity Sequence property
grep -r "public int Sequence" --include="*.cs" Domain/Entities/
if (entity.Sequence != @event.Sequence - 1) return entity.Sequence >= @event.Sequence;Sequence to the new valuetrue is returned (not reprocessed)false is returned (abandoned for retry)data-ai
Use when about to claim work is complete, fixed, passing, or ready — before committing, creating PRs, or moving to the next task. Requires running verification commands and confirming output before making any success claims.
development
Use when encountering any bug, test failure, build error, or unexpected behavior — before proposing fixes or making changes.
development
Use when checkpointing, wrapping up, or handing off an AI-assisted development session.
development
Use when following the Specification-Driven Development lifecycle from plan through ship.