feat: capture staging-verification lessons from iWP Cache deployment

New skill wordpress-plugin-staging-verification: use the persistent
staging-1 jail (real-world plugin set left active) instead of a
disposable clean-room jail, full verification checklist, and the
cosmetic-proc_open-error-vs-real-fatal distinction.

Extended bastille-jail-provisioning: bastille0-loopback-missing gotcha
(pass the interface explicitly), IP-alias-can-silently-fail-after-
recreate gotcha, host-reverse-proxy-vs-jail-IP curl mixup, Valkey as
part of the stock stack (correct FreeBSD package name, ACL auth
requirement).

Extended wordpress-plugin-conventions: drop-in source files
(object-cache.php/advanced-cache.php logic) must be excluded from any
glob-based plugin autoloader, or they redeclare WP core's own
wp_cache_*() functions and fatal -- real bug found and fixed live during
iWP Cache's first staging activation attempt.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-02 21:39:39 +02:00
co-authored by Claude Sonnet 5
parent 6a9c364012
commit eeb08a0d77
4 changed files with 191 additions and 0 deletions
@@ -112,6 +112,36 @@ needs `str_contains`/`str_starts_with`/`str_ends_with` (PHP 8.0+) on an
environment that might run 7.4, guard with `function_exists()`
polyfills rather than assuming they exist.
## Drop-in source files (`advanced-cache.php`, `object-cache.php`) must be excluded from any normal plugin autoload/glob
If a plugin ships an `object-cache.php` or `advanced-cache.php` drop-in
(installed by copying a stub into `wp-content/` on activation), the PHP
file containing the *real logic* for that drop-in must **never** also be
loaded as a normal plugin include — not via an explicit `require`, and
not via a glob-based auto-loader that doesn't know to skip it. WordPress
core's own `wp-includes/cache.php` declares the same global `wp_cache_*()`
function names as the fallback used when no `object-cache.php` drop-in is
active; if the plugin's own copy of those functions loads a second time in
the same request (as a normal plugin file, in addition to — or instead
of — the standalone drop-in load), PHP fatals with "Cannot redeclare
function". Confirmed live during iWP Cache staging verification
(2026-08-02): a glob-based module auto-loader's exclusion list was written
before the object-cache class file existed, so it got swept in and
silently broke every activation. **If a plugin has any glob-based
autoload for its `includes/` directory, explicitly exclude every
drop-in-logic file by name** — don't rely on "it'll only load once" being
obviously true just because the code looks like a normal class file.
## `object-cache.php`/`advanced-cache.php` code runs standalone, before ABSPATH-based guards mean what they normally mean
The usual `if (!defined('ABSPATH')) exit;` guard doesn't prevent a
drop-in-logic file from loading twice in the same request, because by the
time it's `require`'d a second time (as a stray plugin include), ABSPATH
*is* already defined — WordPress has fully booted. A guard meant to stop
"direct access over HTTP" does nothing to stop "accidentally required
twice from two different code paths." Don't assume that guard is doing
more than it actually does.
## Don't build what WordPress core already gives you
Before writing custom code for: cron scheduling (`wp_schedule_event`),