- Add pessimistic locking (SELECT FOR UPDATE) on card write operations to prevent race conditions in stamp_service and points_service - Replace 16 console.log/error/warn calls with LogConfig.createLogger() in 3 storefront JS files (dashboard, history, enroll) - Delete all stale lu.json locale files across 8 modules (lb is the correct ISO 639-1 code for Luxembourgish) - Update architecture rules and docs to reference lb.json not lu.json - Add production-readiness.md report for loyalty module Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
5.7 KiB
Loyalty Module — Production Readiness Report
Date: 2026-03-13 Tests: 213 passing | Languages: en, fr, de, lb (complete)
Overall Assessment: READY WITH RESERVATIONS
The module has strong fundamentals — clean architecture, comprehensive tests, proper exception handling, full i18n (4 languages), wallet integration, and anti-fraud controls. All critical issues have been resolved.
CRITICAL — Must Fix Before Launch
1. Race Conditions on Card Operations — RESOLVED
Added get_card_for_update() method to card_service.py using SELECT ... FOR UPDATE. All 7 write methods in stamp_service.py (3) and points_service.py (4) now re-fetch the card with a row-level lock before modifying fields.
2. Stale Locale File lu.json — RESOLVED
lu.jsonAll lu.json files deleted across the codebase. lb is the canonical Luxembourgish code (ISO 639-1). Architecture rules and docs updated.
3. Console.log Statements in Storefront JS — RESOLVED
All 16 console.log/error/warn calls replaced with LogConfig.createLogger() in 3 storefront JS files (dashboard, history, enroll).
HIGH — Should Fix Before Launch
4. Rate Limiting on Public Endpoints
Status: Rate limiter middleware exists (middleware/rate_limiter.py) but is NOT applied to loyalty endpoints.
Vulnerable endpoints:
POST /loyalty/enroll— public, no auth requiredPOST /pins/{pin_id}/verify— PIN lockout helps but doesn't prevent request flooding
Fix: Apply @rate_limit() decorator to public loyalty routes.
Effort: 1 hour
5. T&C Strategy Unresolved
Location: storefront/enroll.html line 140 — TODO comment noting current approach (small text field on program model) won't scale for full legal T&C.
Options:
- (A) Leverage CMS module to host T&C pages
- (B) Create dedicated T&C page within loyalty module
- (C) Accept current approach for MVP, plan post-launch
Effort: Depends on option chosen
6. Accessibility (ARIA Labels)
Status: Zero aria-label attributes across all loyalty templates. Icon-only buttons, modals, and form inputs lack screen reader support.
Priority areas:
- PIN entry modal (terminal) — security-critical, needs aria-modal
- Icon-only action buttons (view, edit, delete in tables)
- Modal dialogs (delete confirmation, terms, barcode)
Effort: 2-3 hours
MEDIUM — Address Post-Launch
7. Point Expiration Task Performance
point_expiration.py processes cards one-by-one. With large card volumes, this could cause long-running DB locks.
Fix: Batch processing with LIMIT and chunked commits.
8. Wallet Sync Retry Logic
Wallet sync task doesn't retry failed individual cards. A transient API error skips the card until next hourly run.
Fix: Add retry with exponential backoff per card.
9. Audit Logging Enhancement
Current: LoyaltyTransaction captures operations but not all operations create transaction records (e.g., program config changes, PIN management).
Fix: Add structured audit logging for admin/management operations.
10. Test Coverage Metrics
Tests pass (213) but no coverage measurement. Unknown blind spots.
Fix: Add pytest-cov and target >80% line coverage.
LOW / Nice-to-Have
11. Field-Level Validation Errors
Current: Generic toast notifications for form errors. No field-specific error highlighting.
12. Session Storage in Enroll-Success
enroll-success.html stores wallet URLs in sessionStorage. Fragile if user navigates away before page loads.
13. Tier System (Future)
Model has tier_config field (Bronze/Silver/Gold) but no business logic implementation. Properly marked as future.
What's Already Solid
| Area | Status | Details |
|---|---|---|
| Architecture | Excellent | Clean service layer, proper separation of concerns |
| Exception Handling | Excellent | 30+ custom exceptions, no bare except clauses |
| Authentication | Strong | All routes protected, role-based access |
| PIN Security | Strong | bcrypt hashing, lockout mechanism, timing-safe comparison |
| Wallet Security | Strong | JWT/PKCS#7 signing, env-based secrets, token validation |
| SQL Injection | Safe | SQLAlchemy ORM throughout, no raw SQL |
| Input Validation | Good | Pydantic schemas on all endpoints |
| Database Design | Excellent | Proper indexes, FKs, cascades, composite indexes |
| Tests | Good | 213 tests, unit + integration coverage |
| i18n | Complete | 4 languages (en/fr/de/lb), ~300 keys each |
| Dark Mode | Excellent | Consistent dark: variants on all elements |
| Responsive Design | Excellent | Mobile-first with proper breakpoints |
| Empty States | Excellent | All list views have proper empty states |
| Loading States | Excellent | Spinners and loading indicators everywhere |
| Confirmation Dialogs | Good | All destructive actions have modals |
| Pagination | Excellent | Full pagination on all list views |
| Documentation | Good | 5 docs (business logic, data model, user journeys, UI design, analysis) |
Pre-Launch Checklist
Fix race conditions with— DONEwith_for_update()on card operationsResolve— DONElu.jsonvslb.jsonlocale situationReplace console.log with LogConfig in 3 storefront JS files— DONE- Apply rate limiting to public enrollment endpoint
- Decide on T&C strategy (or accept current for MVP)
- Add basic ARIA labels to modals and icon-only buttons
- Manual QA: Walk through all 8 user journeys in
docs/user-journeys.md - Verify Google Wallet configuration in production environment
- Configure Apple Wallet (if needed for launch) or gracefully disable
- Review
LOYALTY_*environment variables in production config