Reference
referenceStores holds with timestamps and filters out expired ones whenever a balance is read.
06 · Card holds
Compute available balance as posted funds minus active card holds, with capture, void and expiry.
referenceStores holds with timestamps and filters out expired ones whenever a balance is read.
alt_validRetires expired records at transaction entry and dispatches mutations centrally; reference filters active holds by timestamp.
wrong_available_ignores_holdsAuthorizes against posted funds, ignoring existing holds.
wrong_expired_holds_countCounts expired active records against availability.
wrong_partial_capture_keeps_remainderLeaves the remainder held after a partial capture.
closed_before_amount_validationExpect: rejectObserved: rejectedChecks closed status before capture amount validity, violating the explicit amount-before-closed priority.
bool_amount_acceptedExpect: rejectObserved: rejectedTreats bool as int, violating positive integer amounts never bool.
unknown_auth_is_409Expect: rejectObserved: rejectedReports unknown authorizations as closed, violating the 404 not_found rule.
alt_iso_textExpect: acceptObserved: acceptedStores ISO timestamp text and compares datetimes through the declared clock interface.
exceeds_before_closedExpect: rejectObserved: rejectedChecks overcapture before closed status, violating PROBLEM.md lines 21-24 requiring closed/expired status before overcapture (original review label: correct; F-407 false_fail clarification).
Each row groups a test’s parameterized cases; any failing case marks the row as Fail. * marks a wrong solution’s declared must_fail test.
| Test | Starter | Reference | Alternative | available ignores holds | expired holds count | partial capture keeps remainder |
|---|---|---|---|---|---|---|
| Existing holds reduce available | Fail | Pass | Pass | Fail * | Pass | Pass |
| Partial capture releases remainder | Fail | Pass | Pass | Pass | Pass | Fail * |
| Expiry exactly seven days | Fail | Pass | Pass | Fail | Fail * | Pass |
| Void overcapture and persistence | Fail | Pass | Pass | Fail | Pass | Pass |
| Overdraft boundary | Fail | Pass | Pass | Fail | Pass | Pass |
| Capture amount validation before closed status | Fail | Pass | Pass | Pass | Pass | Fail |
| Authorization validation and unknown ids | Fail | Pass | Pass | Pass | Pass | Pass |
| Naive clock rejected | Pass | Pass | Pass | Pass | Pass | Pass |
| Random operation model | Fail | Pass | Pass | Fail | Pass | Fail |
| Test | Starter | Reference | Alternative | available ignores holds | expired holds count | partial capture keeps remainder |
|---|---|---|---|---|---|---|
| Full capture | Fail | Pass | Pass | Pass | Pass | Pass |
| Ticket worked example | Fail | Pass | Pass | Fail | Pass | Fail |
| Invalid amount rejected | Pass | Pass | Pass | Pass | Pass | Pass |
holds.main.create_app(database_path,clock)->FastAPI uses SQLite. The declared holds.clock.Clock Protocol has now()->datetime, always aware (otherwise ValueError). The factory seeds two synthetic accounts on first use, without resetting existing state: acct_standard: posted10000, overdraft0; acct_flex: posted10000, overdraft5000. All money is integer CAD minor units (2 dp). All amounts must be positive ints, never bool.
{posted_minor,available_minor}. available = posted minus sum of active, unexpired holds.{amount_minor} ->201 {auth_id} iff amount <= available + that account's overdraft limit; otherwise402 insufficient_funds with no effect.{amount_minor}: amount <= held amount; overcapture ->422 capture_exceeds_hold with no effect. Successful capture ->200 {auth_id,status:"captured"}, deduct amount from posted balance, close the hold and release ALL unused remainder.{auth_id,status:"voided"}, close hold, no posted change.authorization_closed, no effect.invalid_amount; unknown account/auth ->404 not_found. Errors are {"error":{"code":"...","message":"..."}}; message text is free. For capture, validate body/amount first, then existence, then closed/expired status, then overcapture. Thus a positive overcapture on a closed/expired hold returns 409 authorization_closed; an invalid amount on that hold returns 422 invalid_amount. Void has no JSON body.Worked example (acct_standard, CAD minor units): authorize 3000 -> 201 and the balance is {posted_minor: 10000, available_minor: 7000}. Capture 1200 -> 200 and the balance is {posted_minor: 8800, available_minor: 8800}: the 1200 is deducted from posted and the unused 1800 of the hold is released. A further authorization of 8801 is declined with 402 insufficient_funds; 8800 is approved.
Tests use the HTTP interface and the injected clock only. No real accounts, system clock delays or network payments. Run python -m pytest tests -q from starter.