Abilities API: add execution lifecycle filters to WP_Ability methods#11731
Abilities API: add execution lifecycle filters to WP_Ability methods#11731gziolo wants to merge 6 commits intoWordPress:trunkfrom
Conversation
|
The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the Core Committers: Use this line as a base for the props when committing in SVN: To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook. |
Test using WordPress PlaygroundThe changes in this pull request can previewed and tested using a WordPress Playground instance. WordPress Playground is an experimental project that creates a full WordPress instance entirely within the browser. Some things to be aware of
For more details about these limitations and more, check out the Limitations page in the WordPress Playground documentation. |
a8dc90a to
f59694c
Compare
Introduces a new filter inside `WP_Ability::normalize_input()` that fires after the method's built-in default-value handling, allowing plugins to transform input — for example, prompt enrichment or parameter defaulting beyond what JSON Schema handles. Returning a `WP_Error` halts execution. `WP_Ability::execute()` now short-circuits when `normalize_input()` returns a `WP_Error`, so a halt from the filter propagates as the execute result without reaching `validate_input()`, `check_permissions()`, or the `wp_before_execute_ability` action. Props gziolo. Fixes #64989.
Introduces a new filter inside `WP_Ability::check_permissions()` that fires after the registered `permission_callback` returns. Plugins can use it to enforce additional authorization rules — transport-level permission layering for protocol adapters, multi-factor gates, or temporary elevation for batch operations. Because the filter lives inside the method, overrides apply consistently across `execute()`, REST API permission checks, and WP-CLI call sites. Filters can return `true`, `false`, or a `WP_Error`. Props gziolo. Fixes #64989.
Introduces a short-circuit filter at the top of `WP_Ability::execute()`, modeled on `rest_pre_dispatch`. Returning a non-null value bypasses the entire pipeline — `normalize_input()`, `validate_input()`, `check_permissions()`, the registered `execute_callback`, output validation, and the `wp_before_execute_ability` / `wp_after_execute_ability` actions are all skipped, and the filter's return value is returned to the caller as-is. Useful for cached responses, rate limiting, maintenance mode, and test mocking. Callers that short-circuit are responsible for input integrity since input validation does not run. Props gziolo. Fixes #64989.
Introduces a new filter inside `WP_Ability::do_execute()` that fires after the registered `execute_callback` returns. Plugins can use it to transform the result — response formatting, stripping internal metadata, content safety filtering, response enrichment — or to recover from an execution failure by returning a successful value in place of a `WP_Error`. Because `do_execute()` is invoked from `WP_Ability::execute()` between `check_permissions()` and `validate_output()`, the transformed value is still validated against `output_schema` before the `wp_after_execute_ability` action fires. Placing the filter inside `do_execute()` keeps each "result" filter consistent with `wp_ability_normalize_input` and `wp_ability_permission_result`, which sit inside the methods whose return values they filter. Props gziolo. Fixes #64989.
f59694c to
9b2aa9a
Compare
Use a unique stdClass sentinel for wp_pre_execute_ability, mirroring the Customizer symbol pattern, so returning null can intentionally short-circuit execution.
|
This is super useful for any substrate that mediates ability execution on behalf of agents. Sharing four concrete use cases from that perspective so the filter shapes can be validated against them:
The "schema validation remains the final integrity gate" framing matches what a substrate wants downstream: filters can transform but can't bypass the contract. One small thing I'd double-check: when the agent runtime hooks cc / FYI: this would be consumed by |
Summary
Adds four new filters to
WP_Abilityto give plugins hook points across the executionlifecycle. Today the only execution-phase hooks are observation-only actions
(
wp_before_execute_ability,wp_after_execute_ability); plugins that need totransform input, modify output, override permission decisions, or short-circuit
execution have no place to do that in core, and have built parallel hook systems
on top.
This PR closes that gap by introducing four filters that live inside their
owning
WP_Abilitymethods (so they apply consistently acrossexecute()anddirect callers, including REST permission checks), plus one orchestration-level
filter at the top of
execute()for short-circuiting.Filters added
wp_pre_execute_abilityexecute()stdClasssentinel as the default sonullis a valid short-circuit result.wp_ability_normalize_inputnormalize_input()WP_Errorhalts execution.wp_ability_permission_resultcheck_permissions()permission_callbackresult. Applies consistently acrossexecute()and directcheck_permissions()callers.wp_ability_execute_resultdo_execute()WP_Error.Pipeline ordering inside
execute()Schema validation remains the final integrity gate:
wp_ability_normalize_inputfires before
validate_input(), andwp_ability_execute_resultfires beforevalidate_output(). Filters cannot bypass schema validation except byshort-circuiting via
wp_pre_execute_ability, where the caller takesresponsibility for the returned value's shape.
REST behavior
WP_REST_Abilities_V1_Run_Controller::check_ability_permissions()now propagatesWP_Errorresults fromnormalize_input()directly instead of feeding them intovalidate_input(). When the filter does not set its own status, the controllerdefaults to 400; filter-set statuses (e.g., 422, 429) are preserved.
Commits
wp_ability_normalize_inputfilterwp_ability_permission_resultfilterwp_pre_execute_abilityfilterwp_ability_execute_resultfilterstdClasssentinel forwp_pre_execute_abilitysonullis a valid short-circuit resultTest plan
npm run env:composer -- format— cleannpm run env:composer -- lint— cleannpm run env:composer -- compat— cleannpm run typecheck:php— 0 errorsNew tests cover:
WP_Errorrecovery, result transform), with filter args verified via args-as-guards on the transformed value rather than side-effect captures.wp_pre_execute_ability: pipeline configured to fail (permission denial) so the short-circuit value coming through cleanly proves the bypass.nullshort-circuit preserved by the sentinel pattern.wp_ability_execute_resultruns before output validation and beforewp_after_execute_ability.WP_Errorpropagation fromwp_ability_normalize_inputhalts execution.wp_ability_permission_resultfiring whencheck_permissions()is called directly.Trac ticket
https://core.trac.wordpress.org/ticket/64989
Use of AI Tools
AI assistance: Yes
Tool(s): Claude Code, Codex
Model(s): Claude Opus 4.7 (1M context)
Used for: Planning, implementation, unit tests, code review, commit messages, and PR description.