[wp-trac] [WordPress Trac] #64989: Abilities API: Add execution lifecycle filters to WP_Ability methods

WordPress Trac noreply at wordpress.org
Mon Mar 30 07:21:32 UTC 2026


#64989: Abilities API: Add execution lifecycle filters to WP_Ability methods
-------------------------+-----------------------
 Reporter:  gziolo       |      Owner:  (none)
     Type:  enhancement  |     Status:  new
 Priority:  normal       |  Milestone:  7.1
Component:  AI           |    Version:  6.9
 Severity:  normal       |   Keywords:  abilities
  Focuses:               |
-------------------------+-----------------------
 The Abilities API (#64098, WordPress 6.9) provides two registration-phase
 filters (`wp_register_ability_args`, `wp_register_ability_category_args`)
 and two execution-phase actions (`wp_before_execute_ability`,
 `wp_after_execute_ability`). The execution actions are observation-only —
 no hook exists that lets plugins transform input, modify output, override
 permission decisions, or short-circuit execution.

 This ticket proposes four new filters placed inside `WP_Ability` public
 methods and the `execute()` pipeline.

 == Background ==

 === Prior exploration ===

 [https://github.com/WordPress/abilities-api/pull/37 abilities-api#37]
 proposed four filters (`ability_input_schema`, `ability_output_schema`,
 `ability_permission_result`, `ability_execute_result`) placed inside read-
 only getters on `WP_Ability`. It was closed as "not planned" because
 getter-level filters fire many times during serialization (REST API schema
 responses, MCP tool listing, any introspection), introducing performance
 and behavioral unpredictability. Schema modification at read time also
 conflates registration-time concerns with execution-time concerns —
 registration-time customization is already handled by
 `wp_register_ability_args`.

 This proposal targets '''operational methods''' instead —
 `normalize_input()`, `check_permissions()` — which fire at decision
 points, typically once per request. This follows the same approach as the
 validation filters proposed in #64311.

 === Demonstrated need from plugins ===

 Two consumers of the Abilities API have independently built parallel hook
 systems because core lacks execution-time filters:

 '''MCP Adapter''' ([https://github.com/WordPress/mcp-adapter WordPress
 /mcp-adapter]) implements `mcp_adapter_`-prefixed hooks for validation
 toggling, caching, and post-execution audit. The team has decided to
 maintain their own lower-level filters on top of whatever core provides
 ([https://github.com/WordPress/mcp-adapter/pull/151 mcp-adapter#151]), but
 richer core hooks would reduce the scope of what the adapter needs to re-
 implement.

 '''AI Plugin''' ([https://github.com/WordPress/ai WordPress/ai]) needs
 input transformation (prompt enrichment), output transformation (response
 formatting, safety filtering), and failure handling.
 [https://github.com/WordPress/ai/pull/304 ai#304] proposes
 `wpai_`-prefixed hooks for these concerns. Whether the AI plugin should
 add its own execution-lifecycle filters or rely on core is an open
 question this ticket aims to resolve.

 Both plugins validate a three-tier extensibility pattern ('''core →
 protocol adapter → domain plugin''') where richer core hooks reduce
 fragmentation.

 == Proposal ==

 Four new filters. Two live inside public `WP_Ability` methods
 (`normalize_input()`, `check_permissions()`), ensuring consistent behavior
 across all call sites — `execute()`, REST API, WP-CLI. Two live on
 `execute()` itself because they govern orchestration flow. This mirrors
 how `rest_pre_dispatch` short-circuits the REST API dispatch pipeline.

 === Proposed execution lifecycle ===

 {{{
  ┌─ wp_pre_execute_ability (filter) ──── can short-circuit
  │
  ├─ validate_input()
  │
  ├─ normalize_input()
  │   └─ wp_ability_normalize_input (filter) ── inside method
  │
  ├─ check_permissions()
  │   ├─ permission_callback()
  │   └─ wp_ability_permission_result (filter) ── inside method
  │
  ├─ wp_before_execute_ability (action) ── existing
  │
  ├─ do_execute()
  │
  ├─ wp_ability_execute_result (filter) ── transform output
  │
  ├─ validate_output()
  │
  ├─ wp_after_execute_ability (action) ── existing
  │
  └─ return result
 }}}

 === 1. `wp_pre_execute_ability` — Short-circuit ===

 {{{#!php
 $pre = apply_filters( 'wp_pre_execute_ability', null, $ability_name,
 $input, $ability );
 if ( null !== $pre ) {
     return $pre;
 }
 }}}

 Fires in `execute()` before input validation. Returns non-null to bypass
 the entire pipeline. Modeled on `rest_pre_dispatch`.

 '''Use cases:''' Cached responses, rate limiting, maintenance mode, test
 mocking.

 === 2. `wp_ability_normalize_input` — Input transformation ===

 {{{#!php
 // Inside WP_Ability::normalize_input(), after built-in normalization:
 $input = apply_filters( 'wp_ability_normalize_input', $input,
 $ability_name, $ability );
 }}}

 Fires inside `normalize_input()` after the method's built-in logic. The
 filter receives schema-validated, normalized data. Returning a `WP_Error`
 halts execution.

 '''Use cases:''' AI prompt enrichment, parameter defaulting beyond what
 JSON Schema handles, injecting caller metadata.

 === 3. `wp_ability_permission_result` — Permission override ===

 {{{#!php
 // Inside WP_Ability::check_permissions(), after permission_callback
 returns:
 $permission = apply_filters( 'wp_ability_permission_result', $permission,
 $ability_name, $input, $ability );
 }}}

 Fires inside `check_permissions()` after `permission_callback` returns.
 `check_permissions()` is called from `execute()`, from the REST API
 `permissions_check` callback, and from WP-CLI's `wp ability can-run` —
 placing the filter inside the method ensures overrides apply consistently
 regardless of call site.

 '''Use cases:''' MCP Adapter transport-level permission layering, multi-
 factor authorization, temporary permission elevation for batch operations.

 === 4. `wp_ability_execute_result` — Output transformation ===

 {{{#!php
 $result = apply_filters( 'wp_ability_execute_result', $result,
 $ability_name, $input, $ability );
 }}}

 Fires in `execute()` after `do_execute()` returns, before output
 validation. Filters must return data that conforms to `output_schema` —
 output validation remains the final integrity gate.

 '''Use cases:''' AI response formatting, stripping internal metadata,
 content safety filtering, response enrichment.

 == Design principles ==

  * '''Filters inside operational methods, not getters.'''
 `normalize_input()` and `check_permissions()` fire at decision points,
 typically once per request — unlike the getters targeted in
 [https://github.com/WordPress/abilities-api/pull/37 abilities-api#37].
 Placing filters here ensures consistent behavior across all call sites.
  * '''Validation remains authoritative.''' Input transformation fires
 after input validation; output transformation fires before output
 validation. Filters cannot bypass schema validation.
  * '''Short-circuit stays on `execute()`.''' `wp_pre_execute_ability`
 governs orchestration flow, not individual method behavior.
  * '''Naming follows established patterns.''' `wp_` prefix per 6.9
 convention. `wp_ability_normalize_input` and
 `wp_ability_permission_result` name the method and what they filter.
 `wp_pre_execute_ability` mirrors `rest_pre_dispatch`.

 == Related ==

  * #64098 — Introduce Abilities API
  * [https://github.com/WordPress/abilities-api/pull/37 abilities-api#37] —
 Prior filter proposal (closed, lessons incorporated)
  * #64311 — Validation hooks proposal (same pattern)
  * [https://github.com/WordPress/ai/pull/304 ai#304] — AI plugin hook
 audit
  * [https://github.com/WordPress/mcp-adapter/pull/151 mcp-adapter#151] —
 MCP Adapter filters

-- 
Ticket URL: <https://core.trac.wordpress.org/ticket/64989>
WordPress Trac <https://core.trac.wordpress.org/>
WordPress publishing platform


More information about the wp-trac mailing list