feat(loyalty): production readiness round 2 — 12 security, integrity & correctness fixes
Some checks failed
CI / ruff (push) Successful in 12s
CI / validate (push) Successful in 27s
CI / dependency-scanning (push) Successful in 31s
CI / pytest (push) Failing after 3h14m58s
CI / docs (push) Has been cancelled
CI / deploy (push) Has been cancelled

Security:
- Fix TOCTOU race conditions: move balance/limit checks after row lock in redeem_points, add_stamp, redeem_stamps
- Add PIN ownership verification to update/delete/unlock store routes
- Gate adjust_points endpoint to merchant_owner role only

Data integrity:
- Track total_points_voided in void_points
- Add order_reference idempotency guard in earn_points

Correctness:
- Fix LoyaltyProgramAlreadyExistsException to use merchant_id parameter
- Add StorefrontProgramResponse excluding wallet IDs from public API
- Add bounds (±100000) to PointsAdjustRequest.points_delta

Audit & config:
- Add CARD_REACTIVATED transaction type with audit record
- Improve admin audit logging with actor identity and old values
- Use merchant-specific PIN lockout settings with global fallback
- Guard MerchantLoyaltySettings creation with get_or_create pattern

Tests: 27 new tests (265 total) covering all 12 items — unit and integration.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-16 23:37:23 +01:00
parent b6047f5b7d
commit 7d652716bb
20 changed files with 955 additions and 28 deletions

View File

@@ -1074,6 +1074,48 @@ sudo systemctl status gitea-runner
Verify the runner shows as **Online** in Gitea: **Site Administration > Actions > Runners**.
### 15.1 Runner Configuration
Generate a config file to override defaults (notably the 3h job timeout which causes silent CI failures on a 4GB server):
```bash
cd ~/gitea-runner
./act_runner generate-config > config.yaml
sed -i 's/timeout: 3h/timeout: 1h/' config.yaml
sudo systemctl restart gitea-runner
```
Key settings in `config.yaml`:
| Setting | Default | Recommended | Why |
|---|---|---|---|
| `runner.timeout` | 3h | 1h | Prevents silent failures — tests take ~25min, so 1h is generous |
| `runner.shutdown_timeout` | 0s | 0s | OK as-is |
| `runner.fetch_timeout` | 5s | 5s | OK as-is |
!!! tip "CI also has per-job and per-test timeouts"
The `.gitea/workflows/ci.yml` sets `timeout-minutes: 45` on the pytest job and `--timeout=120` per individual test. These work together with the runner timeout to catch different failure modes.
### 15.2 Swap for CI Stability
The CI runner spins up Docker-in-Docker containers for each job. On a 4GB server running the full app stack, this can exhaust available RAM and silently kill the pytest process. Adding 1GB swap prevents this.
!!! note "No extra cost"
Swap uses existing SSD disk space, not additional Hetzner resources.
```bash
sudo fallocate -l 1G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
# Verify
free -h
```
Expected output should show `Swap: 1.0Gi` in the total column.
## Step 16: Continuous Deployment
Automate deployment on every successful push to master. The Gitea Actions runner and the app both run on the same server, so the deploy job SSHes from the CI Docker container to `172.17.0.1` (Docker bridge gateway — see note in 16.2).