Nobody has started this yet — be first.
Business impact
"This customer's charge is permanently stuck in a Conflict/Pending state and support needs it cleared now, not in 24 hours when the TTL expires" is a routine operator request, not an edge case -- a client bug that double-submits a mangled payload under a key it then reuses correctly, or a key that got orphaned by a crashed request that never called Complete/Fail, both leave a key in a state no amount of correct client behavior can recover from before its TTL. Today the only remediation is "wait out the TTL" (up to 24h by default) or "restart the process" (memory backend only, and it clears every key, not just the one that's stuck). An operator-facing way to forget a single key turns a multi-hour-wait escalation into an immediate fix.
Problem
A Deleter contract has been added as scaffolding in pkg/idempotency/store.go (Delete(ctx, key) error), but nothing implements it yet.
Current behavior
A key stuck in a bad state cannot be cleared before its TTL naturally expires it -- there is no Delete method on MemoryStore or RedisStore to call.
Expected behavior
Both MemoryStore and RedisStore implement Deleter: Delete(ctx, key) removes the key's record entirely, such that the very next Begin call for that key -- with any fingerprint -- returns Proceed, exactly as if the key had never been seen.
Steps to reproduce
A key gets stuck in a bad state (e.g. Pending because a request crashed before calling Complete/Fail, or Conflict from a client bug). Call store.Delete(ctx, key) on MemoryStore or RedisStore. Observe: MemoryStore and RedisStore do not implement Deleter yet -- there is no method to call, so the only remediation is waiting out the TTL or restarting the process.
Why this matters
Every store with a claim-then-expire lifecycle eventually needs an operator escape hatch for the "this specific record is stuck and the normal recovery path is too slow" case. Implementing it as a separate, optional Deleter interface (rather than adding a required method to Store) matches how this package already treats MemoryStore.Close as optional -- backends that can't reasonably support an operation shouldn't be forced to fake one.
Suggested approach
MemoryStore: the state lives in a map[string]*memEntry guarded by mu. Delete needs the same mutex discipline every other method already uses -- look at how finish locks, mutates, and unlocks. RedisStore: the record is a single Redis hash key (built via redisKey). Look at what go-redis's Cmdable interface already gives you for removing a key outright (you don't need a new Lua script for this -- unlike Begin, there's no compound check-then-act decision being made, just an unconditional removal).
Acceptance criteria
Verification
go test ./test/ -run TestTask06 -v
Hints (0/2)
Try it without hints first — the reading is the exercise.
Working on this ticket
Work on a branch named for the ticket — that's what you'll submit.
Branch off your fork
Sign in to create it automatically, or do it yourself:
$git checkout -b feat/delete-keyFix it and commit
Meet every acceptance criterion, and add a test that would have caught this.
Push the branch
$git push -u origin feat/delete-keySubmit it below
Paste your fork URL and the branch name, with a short write-up of the root cause.
Questions
Ask about anything unclear in the ticket — the maintainer and anyone who has solved it can answer. Please don't post full solutions.
Sign in to ask a question or reply.
Sign inhttp://localhost:8080POST /v1/chargesTo run against Redis (the multi-replica-safe backend) with zero local setup:
docker compose up --build # app + redis, hot-reloading on save
Work the tickets in tasks/ (ordered easy → hard); each names one Go test in test/:
make test-race # go test -race -count=1 ./...
go test ./test/ -run TestTask01 -v # a single ticket
Level 2
Implement a feature
Extend the system within its own patterns.