Nobody has started this yet — be first.
Business impact
A false idempotency conflict (409 idempotency_key_reused) for a request that is, semantically, identical to the original is a hard failure a client cannot self-heal from by retrying -- retrying with the same key just gets the same 409 again. If the set of headers folded into the fingerprint ever arrives in a different order across two calls that are otherwise the same request (a very real possibility once headers are collected from a map anywhere upstream -- Go deliberately randomizes map iteration order), a legitimate retry gets permanently rejected instead of replayed, and the caller has no recovery path except picking a brand-new Idempotency-Key, defeating the entire point of supplying one.
Problem
Fingerprint's doc comment states: "Header values are sorted by key name so header map iteration order never affects the result." The function no longer actually sorts the header keys before hashing them.
Current behavior
Two calls to Fingerprint with the exact same method, path, body, and header values, but the headerKeys slice listed in a different order, produce two different fingerprint hashes.
Expected behavior
The fingerprint must depend only on the set of header key/value pairs selected, never on the order they were listed in.
Steps to reproduce
h := http.Header{"X-Tenant": {"acme"}, "X-Region": {"us-east"}}
a := idempotency.Fingerprint("POST", "/v1/charges", []byte({}), h, []string{"X-Tenant", "X-Region"})
b := idempotency.Fingerprint("POST", "/v1/charges", []byte({}), h, []string{"X-Region", "X-Tenant"})
fmt.Println(a == b) // got: false, want: true
Why this matters
This is a "same input, different output" determinism bug in a function whose entire job is producing a stable identity for a request. Any caller composing the headerKeys slice dynamically (e.g. from a config map, or a per-tenant list of "headers that matter for this endpoint") is exposed the moment that composition doesn't happen to produce the same order every time.
Suggested approach
The doc comment already describes the intended behavior ("sorted by key name"). Compare it against what the function's key-processing loop actually does before hashing each header -- something that used to make that guarantee true is missing.
Acceptance criteria
Verification
go test ./test/ -run TestTask04 -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 fix/fingerprint-header-orderFix it and commit
Meet every acceptance criterion, and add a test that would have caught this.
Push the branch
$git push -u origin fix/fingerprint-header-orderSubmit 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 1
Fix a bug
Read existing behaviour, correct it.