E1 — Plugin & Theme Manager E2E Smoke (ADR-0014)¶
- Date: 2026-05-31
- Target: LXC 281 (
192.168.0.211), live FastAPI on127.0.0.1:8080, Next.js web onhttps://wp-pulse-app.monxas.casa - Site:
hotelaldamagolf-com(21 plugins, 8 themes — matrix expected 9, filesystem and API agree on 8) - Server build:
wp-pulse-server.servicerunning since 2026-05-31 16:43 UTC - Auth header:
X-Admin-Token: <WPP_ADMIN_TOKEN>(NOTAuthorization: Bearer …— matrix-doc nit)
Verdict matrix¶
| # | Test | Verdict | Notes |
|---|---|---|---|
| A1 | GET /sites/{slug}/plugins |
PASS | 200, 21 plugins, schema includes slug/name/version/active/has_update/vulnerabilities plus path/latest_version/tested_up_to_wp/tested_up_to_php/author/plugin_uri/last_updated_at/premium_detected. Summary: 21 active, 6 updates_available, 0 vulnerable. |
| A2 | UI /sites/{slug}/plugins |
PASS | 200, Next.js SSR HTML includes plugin names (air_addons, Yoast, WooCommerce) and 7 hotelaldamagolf references. Hydration completes client-side. |
| A3 | GET /wp-org/plugins/search?q=yoast |
PASS | 200, {query, results, page, pages}. First result slug=wordpress-seo, name “Yoast SEO …”. |
| A4 | GET /wp-org/plugins/{slug}/info |
PARTIAL PASS | …/yoast/info returns 404 — the matrix expects the plugin slug (wordpress-seo). With correct slug: 200 with sections.changelog and tested_up_to (NOT tested_up_to_wp as written in matrix — minor field-name discrepancy in the test plan). |
| A5 | Vulnerability detection | SKIP | No vulnerabilities found on any of the 21 plugins. plugin_vulnerabilities table exists but row count = 0 → Patchstack sync has not populated yet. Documented. |
| A6 | Self-deactivation guard for wp-pulse-agent |
FAIL — HIGH SEVERITY | Expected 400/409; got 200 OK with ok:true. The agent plugin was deactivated on the worktree, then I reactivated it manually. See bug #1. |
| A7 | Bulk action on non-existent plugin | PASS | 200 with ok_count:0, failed_count:1 and per-row ok:false, exit 1, wp-cli warning surfaced. |
| A8 | ZIP install endpoint | PASS (with field-name bug) | First call with form field zip_file → 422 missing file. The matrix doc says zip_file, but the API expects the field name file. With file= the 100-byte garbage ZIP was accepted and gracefully rejected by wp-cli with PCLZIP_ERR_BAD_FORMAT. See bug #2 (doc/UI). |
| A9 | WP.org install (sandbox:false) |
FAIL — MEDIUM SEVERITY | 200 returned but ok:false, status code is 200 (matrix expected 202). Root cause: wp-cli inside hotelaldamagolf-com-wpcli-run-* container hits “Failed to create directory /etc/X11/fs/.wp-cli/cache/: Permission denied” AND “Unable to create directory /var/www/html/wp-content/upgrade/hello-dolly.1.7.2”. wp-content/upgrade/ not writable for the wpcli container UID. hello-dolly did NOT land in the worktree. See bug #3. |
| A10 | WP.org install (sandbox:true) |
FAIL — MEDIUM SEVERITY | 200 with sandbox_preview_id + sandbox_hostname returned (matrix expected 202). After ~50 s polling /plugins/hello-dolly/sandbox, status flipped from provisioning → running BUT with error: "sandbox up; install failed: …Could not create directory /var/www/html/wp-content/upgrade…" — same wp-cli permission failure. Ephemeral hostname wpp-pl-sandbox-…preview.monxas.casa did NOT resolve (curl HTTP 000) — Caddy snippet for sandbox hosts has not been pushed; site snippet caddy-snippet.caddy contains zero wpp-pl-sandbox matches. See bug #4. Cleanup: deleted via DELETE /api/v1/version-previews/{id} → 204 OK. |
| B1 | GET /sites/{slug}/themes |
PARTIAL PASS | 200, {themes, active_slug}. 8 themes in API response and on filesystem; matrix expected 9. active_slug=melinda, melinda has active:true. Schema otherwise complete. Minor matrix-doc inaccuracy or one theme was uninstalled since the matrix was written. |
| B2 | UI /sites/{slug}/themes |
PASS | 200, SSR HTML contains Melinda, Themes, twentytwenty. |
| B3 | GET /wp-org/themes/search?q=astra |
PASS | 200, 24 results, first result is Astra by Brainstorm Force with full card payload. |
| B4 | POST /sites/{slug}/themes/{theme_slug}/preview-switch |
PASS — endpoint shape verified, NOT invoked | OpenAPI registers POST only on /api/v1/sites/{slug}/themes/{theme_slug}/preview-switch. Source (api/routers/themes.py:592) confirms it runs wp option get stylesheet then wp theme activate {theme_slug} against the local stack only and captures before/after screenshots via capture_pair. Call shape: POST /api/v1/sites/{slug}/themes/{theme_slug}/preview-switch with empty body, X-Admin-Token header. Response model PreviewSwitchResponse. |
Totals: 8 PASS · 3 FAIL · 1 SKIP · 1 PARTIAL-A4 · 1 PARTIAL-B1 — out of 14.
Bugs discovered¶
Bug #1 — wp-pulse-agent self-deactivation NOT blocked [HIGH]¶
curl -X POST -H "X-Admin-Token: …" -H "Content-Type: application/json" \
-d '{"action":"deactivate"}' \
http://127.0.0.1:8080/api/v1/sites/hotelaldamagolf-com/plugins/wp-pulse-agent/wp-pulse-agent.php/action
# → 200 OK, ok:true, stdout "Plugin 'wp-pulse-agent' deactivated."
Server accepts the deactivation against the worktree without any guard. If this action were ever to reach prod through a normal push flow, the agent would lose its uplink. Matrix correctly says “expect 400 or 409”. The router needs a deny-list on slugs {wp-pulse-agent} (and likely the WP-Pulse updater MU-plugin too) in the inline-action handler.
File: api/routers/plugins.py — the inline-action POST /{slug}/plugins/{plugin_path:path}/action.
Bug #2 — ZIP-install multipart field name [LOW · doc]¶
API requires multipart field name file. The test matrix (and possibly the UI client?) uses zip_file. With zip_file the response is 422 Unprocessable Entity: missing field "file". Either rename the matrix/UI to file, or accept both aliases in the FastAPI signature.
Bug #3 — Plugin install fails (wp-cli cannot write to wp-content/upgrade/) [MEDIUM]¶
Warning: Failed to create directory '/etc/X11/fs/.wp-cli/cache/': mkdir(): Permission denied.
Warning: No ha sido posible crear el directorio "/var/www/html/wp-content/upgrade/hello-dolly.1.7.2"
Warning: The 'hello-dolly' plugin could not be found.
Error: No plugins installed.
Both A9 (preview worktree) and A10 (sandbox preview) hit the same failure. Two issues stacked:
/etc/X11/fs/.wp-cli/cache/—wpis invoked with$HOMEunset or pointed at an unwritable path so it falls back to the X11 fontserver dir. The wp-cli container needsHOME=/tmp(or a tmpfs volume)./var/www/html/wp-content/upgrade/— directory does not exist in the cloned worktree and the wpcli container user lacks permission to create it. Plugin install therefore cannot complete on any preview, blocking the entire WP.org install flow.
Root cause is environmental on LXC 281 (uid/gid + missing upgrade/ dir on the cloned worktree). Affects ALL WP.org installs site-wide, not just hello-dolly.
Bug #4 — Sandbox ephemeral hostname is not routed by Caddy [MEDIUM]¶
POST /plugins/install/wp-org {sandbox:true} allocates wpp-pl-sandbox-hotelaldamagolf-com-62792511.preview.monxas.casa and reports status=running, but:
curl https://wpp-pl-sandbox-hotelaldamagolf-com-62792511.preview.monxas.casa/→ connection fails, no DNS / no Caddy match (HTTP 000).caddy-snippet.caddyat/srv/wp-pulse/sites/hotelaldamagolf-com/contains zerowpp-pl-sandboxreferences.
The sandbox provisioner is not registering the ephemeral host with Caddy (or the snippet isn’t being rendered/pushed). Sandboxes are therefore unreachable end-to-end; the modal’s “open preview” affordance will 404. Matrix says “document if Caddy snippet hasn't been pushed” — documented.
Bug #5 — Status codes don’t match matrix expectations [LOW]¶
/plugins/install/wp-org returns 200 on both success and async dispatch, not 202. Either change to 202 for sandbox-dispatch path or update the matrix.
State at end of run¶
wp-pulse-agentis active (was deactivated then reactivated during A6).POST /discardwas issued — worktree reset to HEAD,quarantine_columnreconciled to prod (smoke-e6/x.phpre-applied to match prod, which was already there).- Residual quarantine drift
smoke-rev-c5/c5.phpis pre-existing from a prior unrelated smoke run (not introduced by this E2E). - Sandbox preview
93f03d5d-…was DELETEd (204). - Final
pending-changes:files:0, quarantine:1 (pre-existing), db:1 (pre-existing .htaccess rule).
Surprises top-3¶
wp-pulse-agentdeactivation is silently honored — no allow-list / deny-list guard on the inline action endpoint, contradicting the safety story for the agent uplink.- WP.org install is broken end-to-end on this LXC because
wp-content/upgrade/is not writable from the wp-cli container — affects both worktree and sandbox paths. - Sandbox previews provision DNS/hostnames that are not registered with Caddy — the modal’s “Open preview” button has no chance of working until the per-sandbox snippet is rendered & reloaded.