Skip to content

docs: correct the idempotency description to match shipped behavior - #184

Open
ivanball wants to merge 1 commit into
mainfrom
docs/idempotency-claude-md-drift
Open

docs: correct the idempotency description to match shipped behavior#184
ivanball wants to merge 1 commit into
mainfrom
docs/idempotency-claude-md-drift

Conversation

@ivanball

@ivanball ivanball commented Aug 2, 2026

Copy link
Copy Markdown
Owner

The CLAUDE.md "Other Framework Pieces" bullet described the idempotency concurrency guard as "per-key SemaphoreSlim double-check locking". That was wrong twice over: the semaphore is striped, not per-key, and since the IDistributedLock work it is only the fallback for a host that registers no lock.

Found while re-verifying the Medium article series against source. ADR-017 already records the decision correctly, so only this summary had drifted. Documentation only, no code change.

Corrected against source read this run

Claim Evidence
AddCaching registers IDistributedLock unconditionally, resolving to RedisDistributedLock (SET-NX-PX + Lua compare-and-delete) when an IConnectionMultiplexer is present, else a warn-once InProcessDistributedLock Source/Core/MMCA.Common.Infrastructure/DependencyInjection.cs:181-193
IdempotencyFilter resolves that lock; LockTimeToLive 30s, LockWait 5s IdempotencyFilter.cs:109, :80, :87
The striped path is the documented no-lock fallback IdempotencyFilter.cs:73, remarks at :31-34 and :67
A duplicate that cannot take the lock gets 409 Conflict (previously undocumented here) IdempotencyFilter.cs:152, :203-208
The cache key is not the bare client key: subject, method, route and client key joined and SHA-256 hashed IdempotencyFilter.cs:317-331
Cached results are 2xx ObjectResult or body-less 2xx StatusCodeResult, a NoContent() 204 replaying as a bare status code IdempotencyFilter.cs:281-306, :232

This is also the change that earned scorecard §10 Cross-Cutting Concerns Implementation 8 to 9 in the 2026-08-01 re-score.

🤖 Generated with Claude Code

The "Other Framework Pieces" bullet still described the concurrency guard as
"per-key SemaphoreSlim double-check locking", which was wrong twice over: the
semaphore is striped rather than per-key, and since the IDistributedLock work
it is only the fallback for a host that registers no lock.

Corrected against source read this run:
- AddCaching registers IDistributedLock unconditionally, resolving to the
  SET-NX-PX plus Lua compare-and-delete RedisDistributedLock when an
  IConnectionMultiplexer is present and a warn-once InProcessDistributedLock
  otherwise (Infrastructure/DependencyInjection.cs:181-193).
- IdempotencyFilter resolves that lock (:109) with LockTimeToLive 30s (:80)
  and LockWait 5s (:87); the KeyedSemaphoreStripe path (:73) is documented in
  its own remarks (:31-34, :67) as the no-IDistributedLock fallback.
- A duplicate that cannot take the lock gets 409 Conflict (:152, :203-208),
  which the bullet never mentioned.
- The cache key is not the bare client key: BuildCacheKey (:317-331) joins
  subject, method, route and client key and hashes with SHA-256.
- Cached results are 2xx ObjectResult or body-less 2xx StatusCodeResult, a
  NoContent() 204 replaying as a bare status code (:281-306, :232).

Found while re-verifying the Medium series against source; ADR-017 already
records the decision, so only this summary had drifted.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GtKVCeZgqUfArdBFGrZo7k
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant