Problem
hashIp("unknown") is identical for every header-less client, so vote routes treat them as one voter (toggles collide) and they share one rate-limit bucket.
Where
lib/utils/hash.ts (x-real-ip ?? "unknown") + vote/rate-limit consumers.
Acceptance
Add a failing test first hashing two header-less requests, expecting isolated identities (or explicit rejection from voting). Then fix.
Problem
hashIp("unknown")is identical for every header-less client, so vote routes treat them as one voter (toggles collide) and they share one rate-limit bucket.Where
lib/utils/hash.ts(x-real-ip ?? "unknown") + vote/rate-limit consumers.Acceptance
Add a failing test first hashing two header-less requests, expecting isolated identities (or explicit rejection from voting). Then fix.