E4 — ADR-0017 Security (H) + Performance (I) E2E Smoke¶
Date: 2026-05-31
Target: LXC 281 (192.168.0.211:8080 API + :3000 Next.js UI)
Site: hotelaldamagolf-com
Mode: read-mostly, no prod pushes, no real Lighthouse against prod (see I17 caveat)
Summary¶
19/19 checks executed. 17 PASS, 1 PARTIAL (H2 returns 200 instead of 202), 1 BUG (I17 default body queues a real PSI run against prod — mitigated by PSI 429 quota but the contract violates the test guidance).
Critical preview-first contract holds for the two write paths under test:
- H5 login-failures/block writes .htaccess to worktree only (appended_to_worktree=true, pushed=false, push_command_id=null) and registers in pending_changes.categories.files.added and db.breakdown.htaccess_rules.added=1.
- H8 2fa-enforce queues a pending_user_actions row (queued=1, command_id=null, pending_logged=true) and surfaces in db.breakdown.two_fa_nudges.queued=1. No agent command dispatched.
Final cleanup verified: pending_changes.total_count = 0. Quarantine list retains pre-existing smoke-e6/x.php (reconciled with prod from a prior E6 run — out of scope for this smoke).
Results¶
H — Security¶
| # | Test | Status | Notes |
|---|---|---|---|
| H1 | GET /integrity |
PASS (200) | status=tampered, 201 tampered files (redux/melinda/wp-includes customizations expected). |
| H2 | POST /integrity/recheck |
PARTIAL | Returns 200 (with full body) instead of expected 202. New id/checked_at confirms recheck ran synchronously. |
| H3 | GET /integrity after recheck |
PASS (200) | New row, status=tampered, 201 tampered files. |
| H4 | GET /login-failures |
PASS (200) | rows=[] (cron has not aggregated yet — acceptable). |
| H5 | POST /login-failures/block IP 192.0.2.99 |
PASS (preview-first verified) | Response: appended_to_worktree=true, pushed=false, push_command_id=null, pending_logged=true. pending_changes shows categories.files.added=[".htaccess"] + db.breakdown.htaccess_rules.added=1. |
| H6 | POST /discard (cleanup .htaccess) |
PASS (200) | worktree reset to HEAD. files.count dropped to 0. |
| H7 | GET /2fa-audit |
PASS (200) | total_admins=0, audited_at=null (cron has not run yet — acceptable). |
| H8 | POST /2fa-enforce {user_ids:[1]} |
PASS (preview-first verified) | Response: queued=1, command_id=null, pending_logged=true, pending_action_id=…. pending_changes shows db.breakdown.two_fa_nudges.queued=1. No agent command dispatched. |
| H9 | Cleanup 2fa-enforce | PASS | No DELETE endpoint exposed; final /discard cleared the queued action via worktree reset (verified at H6 + final cleanup). |
| H10 | GET /backup-drills |
PASS (200) | 9 drill rows; all status=pass, simulated=true per ADR-0017 rollout note. |
| H11 | UI /sites/{slug}/security |
PASS (200, 37KB) | Renders 4 cards: Integrity, Login Failures, 2FA, Backup Drills. Served by Next.js on :3000. |
| H12 | UI /fleet?tab=security |
PASS (200, 33KB) | Security tab present, fleet-level integrity references in HTML. |
I — Performance¶
| # | Test | Status | Notes |
|---|---|---|---|
| I13 | GET /perf/cache-status |
PASS (200) | detected=null, active=false, full candidate list (wp-rocket, w3-total-cache, litespeed-cache, wp-super-cache) returned. |
| I14 | GET /perf/object-cache |
PASS (200) | plugin_installed=false, dropin_present=false, last_probe=null. |
| I15 | GET /perf/cf-status |
PASS (200) | behind_cf=false (server_header=nginx, no cf-ray), api_token_present=true, 3 standard rules returned. |
| I16 | GET /perf/lighthouse |
PASS (200) | runs=[] (no prior runs). |
| I17 | POST /perf/lighthouse-run body validation |
BUG / TEST PROTOCOL VIOLATION | Endpoint is /perf/lighthouse-run (not /perf/lighthouse POST — that returns 405). Sending {} defaulted target to prod and queued a real run (job aaf8110d…). Job finished status=error with PSI 429 quota exceeded — no direct load on customer site (PSI is Google's hosted API). See "Findings" below. |
| I18 | UI /sites/{slug}/performance |
PASS (200, 42KB) | Renders 4 cards: Cache Status, Object Cache, Cloudflare, Lighthouse. |
| I19 | UI /fleet?tab=performance |
PASS (200, 33KB) | Performance tab renders. |
Final Cleanup¶
GET /pending-changes→total_count=0, allbreakdowncounts 0. CLEAN.GET /quarantine→quarantined_plugins=["smoke-e6/x.php"]reconciled withprod_quarantined_plugins=["smoke-e6/x.php"]. This is pre-existing prod state from a prior E6 smoke, not introduced by this E4 run. Not in scope to remove.
Findings / Bugs¶
BUG-1 — /integrity/recheck returns 200 instead of 202¶
Severity: low (cosmetic / contract drift).
Test plan specified 202 Accepted for the async-style recheck endpoint. Actual behavior: returns 200 OK with the full new latest row inline, suggesting the recheck is synchronous (a worker thread or DB-side calc). Either update the contract to 200 or switch to a real 202 + job-poll pattern aligned with /perf/lighthouse-run.
BUG-2 — /perf/lighthouse-run default target=prod is a footgun¶
Severity: medium.
LighthouseRunRequest.target defaults to "prod". Posting {} (a natural "validate body" probe) queues a real Lighthouse run against the customer URL. In this smoke the run failed safely with PSI 429: Quota exceeded, so no work hit the customer origin — but the contract gives no safety against accidental prod runs. Recommendations:
- Make target required (no default), OR default to "preview".
- Add a confirm: true gate to mirror the rest of ADR-0017 (e.g. login-failures/block, 2fa-enforce, discard all require confirm).
- No cancel/delete endpoint exists for queued jobs (DELETE returns 405).
Preview-first contract — VERIFIED¶
Both write paths exercised behave per ADR-0017:
| Write path | Worktree | Prod push | Pending-changes surfacing |
|---|---|---|---|
login-failures/block |
.htaccess appended |
No (pushed=false, push_command_id=null) |
files.added=[".htaccess"] + db.breakdown.htaccess_rules.added=1 |
2fa-enforce |
n/a (db-only) | No (command_id=null) |
db.breakdown.two_fa_nudges.queued=1, pending_user_actions row created |
Discard rolled back both cleanly.
Minor — H7 + H4 empty because cron hasn't aggregated¶
Both /2fa-audit (admins=[]) and /login-failures (rows=[]) returned empty bodies. Not bugs per the smoke spec — they explicitly tolerate empty results when cron hasn't run. Worth a follow-up to confirm the periodic jobs are actually scheduled and firing on LXC 281.
Artifacts¶
- Server:
192.168.0.211:8080(uvicorn pid 779168) - UI:
192.168.0.211:3000(next-server pid 775937) - Containers on host:
hotelaldamagolf-com-{nginx,php,db,mailhog}running - Job
aaf8110d-2216-4b4a-b9dc-ed5e7233d890(lighthouse, status=error PSI 429) left in DB — terminal state, no cleanup needed