Guard the agent runtime against admin-UI deactivation on managed installs - #329
Merged
Conversation
…d installs Closes #328. Supersedes #323. On a managed install the site owner has wp-admin access and no reason to understand the plugins list. Deactivating Data Machine there costs them their assistant's memory and tools, with no obvious way back. #323 proposed moving the runtime into mu-plugins. That would also prevent deactivation, but WordPress fatal-error recovery PAUSES a plugin that fatals and cannot do that for an mu-plugin — so a Data Machine fatal would go from "WP pauses it, site stays up, agent gone" to a white screen. On a site taking card payments that trades a broken assistant for a broken shop, and it moves a failure out of the range the agent can recover from into one only the operator can. It would also fork the install layout on posture across install_plugin, update_plugin_to_latest_tag, and recovery, plus a loader stub. This does the same job from the mu-plugin layer wp-coding-agents already installs: the Deactivate and Delete links are replaced with a short explanation, and the request is refused server-side because hiding links is presentation only — the request can still arrive by URL, bulk action, or any caller of deactivate_plugins(). WP-CLI is deliberately not guarded. `wp plugin deactivate data-machine` is the operator's recovery path and was used during the h44lacrosse.com rollout. The accident happens in wp-admin; the recovery happens on the CLI. data-machine is guarded unconditionally because wp-coding-agents installs it on every agent install — a property of the product, not of one site. Companions are DISCOVERED from what is actually active rather than assumed, which is the #320 rule. The guard is written only under managed posture and removed on a switch back, so an engineering install cannot keep a guard nothing maintains. tests/runtime-guard.sh dispatches through the captured hook registry rather than calling the functions directly, so the add_action/add_filter wiring is part of what is tested. Verified non-vacuous against four regressions: removing the deactivate hook, the delete hook, the action-links filter, or the WP-CLI escape each fail the suite. An earlier version of the test called the functions directly and did not catch the first three.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #328. Supersedes #323.
On a managed install the site owner has wp-admin access and no reason to understand the plugins list. Deactivating Data Machine there costs them their assistant's memory and tools, with no obvious way back.
Why not the mu-plugin move
#323 proposed relocating the runtime into
wp-content/mu-plugins/. It would work, and it is the wrong instrument.WordPress fatal-error recovery pauses a plugin that fatals. It cannot do that for an mu-plugin.
Making the runtime unkillable also makes it unpausable. On a site taking card payments that trades "the assistant stopped working" for "the shop stopped taking money", and it moves a failure out of the range the agent can recover from into one only the operator can — the inverse of what the managed posture is for.
It would also fork the install layout on posture across
install_plugin,update_plugin_to_latest_tag, and the recovery procedure, plus a loader stub since mu-plugins do not autoload subdirectories. #318, #320, and #322 were each posture leaking somewhere that should have stayed uniform.Worth recording: the move only ever bought deactivation protection. Editing is already denied for both
wp-content/plugins/**andwp-content/mu-plugins/**under managed (#322).What this does
A guard in the mu-plugin layer wp-coding-agents already installs:
deactivate_plugins()Both
deactivate_pluginanddelete_pluginfire immediately before the mutation in core, sowp_dieprevents it.WP-CLI stays open, deliberately
wp plugin deactivate data-machineis the operator's recovery path — it was used on h44lacrosse.com during the managed-posture rollout. The guard targets wp-admin, where the accident happens, not the CLI, where recovery happens.WP_CLIand cron are exempted explicitly.Which plugins
data-machineunconditionally — wp-coding-agents installs it on every agent install, so that is a property of the product, not of one site. Companions are discovered from what is actually active rather than assumed, per the #320 rule about not naming a stack the tool has not inspected.Written only under managed posture, and removed on a switch back so an engineering install cannot keep a guard nothing maintains.
Tests
tests/runtime-guard.shdispatches through the captured hook registry rather than calling the functions directly, so theadd_action/add_filterwiring is part of what is tested.That mattered: an earlier version called the functions directly and passed even with the hooks unregistered. Verified non-vacuous against four regressions —
deactivate_pluginhookdelete_pluginhookplugin_action_linksfilterAlso covered: posture scoping, cleanup on switch, generated PHP parses, companions discovered, unrelated plugins untouched.
Full suite: no new failures (same 7 pre-existing environment failures as
main).bash -nandphp -lclean.