domain: Write lockout state only through narrow atomic repo ops #86
No reviewers
Labels
No labels
kind
bug
kind
enhancement
wayfinder
grilling
wayfinder
map
wayfinder
prototype
wayfinder
research
wayfinder
task
workflow
needs-info
workflow
needs-triage
workflow
ready-for-agent
workflow
ready-for-human
workflow
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
rosa/vernier!86
Loading…
Reference in a new issue
No description provided.
Delete branch "domain/lockout-narrow-atomic-repo-ops"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Implements #56 per ADR 0011 and the Authentication vocabulary in CONTEXT.md.
What changed
The two lock columns (
failed_login_attempts,locked_until) are now written by exactly two repository operations and nothing else:record_failed_login(user_id, &LockoutPolicy)— one atomic act (a singleUPDATEin the Postgres adapter) that counts the failure and, on reaching the threshold, enters the Lockout and restarts the count in the same statement. Reports aLockoutOutcome:Counted { attempts }orLocked { until }.clear_lockout(user_id)— zeroes the count and lifts the Lockout; called unconditionally on every successful login. Idempotent.UpdateUserRequestloses the lock-column fields entirely, so no profile round-trip can clobber Lockout state — the rule is a compile-time fact, not discipline. The oldincrement_login_attemptsport op is removed, and its now-infallibleTryFrom<User>becameFrom<User>.New domain types alongside
User:LockoutPolicy(threshold + duration,Defaultof 10 attempts / 15 minutes, held by the service, no app-config surface) andLockoutOutcome.Behavior changes (deliberate, per ADR 0011)
UserLocked(until)instead of reading as one more wrong password.Tests
lockout_repository_conformance.rspins both adapters to the same semantics in the establishedboth_adapters!pattern: below-threshold counting, the crossing recording (expiry ≈ now + duration, count restarted), idempotent clearing, andupdate_userleaving both a mid-count and a locked user untouched.confirm_emailleaving a Lockout in place.The
cimise task passes; the sqlx offline cache is regenerated.Closes #56