Round 2 Loop A fresh money/security/dup-mod review. 23 findings fixed:
MONEY:
- CRITICAL: B1 duplicate auto-refund gains an attempt cap (b1_attempts col, cap 3) —
a rejected auto-refund no longer re-replays the expired key every sweep run
(which minted a stacking unauthorized charge each time); FAILED-webhook
demotion respects the cap; never re-replay a key whose B1 refund failed
- HIGH: A6 deposit_covered_by_discount skip path now APPLIES the eligible
campaign discount rows immediately (capped) instead of skipping with no
discount recorded — no more promised-discount-not-recorded overcharge
- MEDIUM: 2FA code burned by the SAVE gate is re-issued on failed
new-card+save_card charges (re-issue guard now covers req.SaveCard)
- LOW: GetBookingPaymentSummary excludes tip rows from paidAmount (remaining
now matches the authoritative tip-excluded balance)
SECURITY:
- MEDIUM: unacknowledged CRITICAL admin-notification flood capped (global cap
on critical_payment_log + refresh_token_reuse rows)
- MEDIUM: 2FA reissue no longer bypasses the mint cooldown (Check no longer
clears LastMintAt on gate-verify; cleared on terminal charge success)
- MEDIUM: twofa.StateFor map-saturation returns a shared permanently-locked
state instead of a fresh 5-guess budget per request
- MEDIUM: ProgressiveRateLimit rejects 429 past maxProgressiveSleepDelayMs
instead of sleeping unboundedly; login bcrypt concurrency semaphore added
- LOW: loginInProgress 409->429; webhook key-set/URL-unset startup check;
email-verification per-user attempt counter
DUP/MOD:
- formatCurrency single source (frontend format.ts, 7 files consolidated);
SquareRefundStatusToLocal single source (errors.go, all sites); admin
audit-log helper dedup; SCA retry model unified (proactive on all 6
surfaces); buyDailyTotal/daily-cap mirror via backend; lock TTL from
backend; generateUUID at all card-form sites; magic numbers named
(defaultPostgresHost, epsilon, fee constants); admin CASH + gift-card
terminal charges now audited; DAV_SKIP_INIT documented in manuals
Verified: 26/26 dev + 24/24 prod (GO_TESTING=1, the CI condition), both vet
tags, frontend tests+build, env-docs 42/42.
Quick wins from the Loop B close-out:
- backend/internal/dav/service_prod.go: package-level init() connected to
postgres and panicked when the DB was unreachable — breaking bare-shell
'go test -tags test,!dev' and any CI test-prod run without a live service.
init() now skips connecting under GO_TESTING (the pipeline sets it) or
DAV_SKIP_INIT, and defaults POSTGRES_HOST to 127.0.0.1 (the pipeline
postgres-service address, matching testutils/testdb) so real prod builds
still connect. The test suite wires its own pool in TestMain.
- README.md:21: corrected stale 'Gift card SPV/MPV VAT treatment configurable'
to the SPV-only posture (a stored MPV is overridden to SPV at read time).
Verified: 26/26 dev packages, both vet tags clean.
- Add TestMain to set test env vars and testdb.TruncateTables for test
isolation
- Add chi routing context to test helpers for path parameter extraction
- Fix SQL error handling to use errors.Is() instead of ==
- Add validators package with ID validation
- Fix admin test middleware chain (RequireAdmin wrapper)
- Update test user inserts to include phone and date_of_birth fields
- Update service delete test to check soft-delete (is_active=false)
- Update holiday hours test to use new schema (weekday, is_open)
- Add phone number validation tests for UK mobile numbers