[wp-trac] [WordPress Trac] #65248: Abilities API: add `wp_ability_invoked` action for pipeline-entry observers

WordPress Trac noreply at wordpress.org
Fri May 15 11:39:52 UTC 2026


#65248: Abilities API: add `wp_ability_invoked` action for pipeline-entry observers
---------------------------+---------------------
 Reporter:  gziolo         |       Owner:  (none)
     Type:  enhancement    |      Status:  new
 Priority:  normal         |   Milestone:  7.1
Component:  Abilities API  |     Version:  6.9
 Severity:  normal         |  Resolution:
 Keywords:                 |     Focuses:
---------------------------+---------------------
Description changed by gziolo:

Old description:

> The execution lifecycle filters proposed in #64989 give the Abilities API
> four
> seams: `wp_pre_execute_ability`, `wp_before_execute_ability`,
> `wp_after_execute_ability`,
> and `wp_execute_ability_result`. `wp_before_execute_ability` fires after
> input
> normalization, validation, and the permission check — its contract is
> "execution is
> imminent." That contract is correct: rate-limiter, cached-response, and
> dry-run observers
> all rely on it not firing for calls that exit early.
>
> That leaves a gap for audit and snapshot observers, which need the
> opposite signal: "the
> pipeline received this invocation, regardless of outcome." They want a
> hook that fires
> for every call — approval-pending, rate-limited, cached, dry-run, and
> normal — so the
> audit row exists no matter how the call exits, and fires before
> normalization so the
> input is captured raw.
>
> == Proposal ==
>
> Add a `wp_ability_invoked` action at the top of `WP_Ability::execute()`,
> before
> `wp_pre_execute_ability` runs:
>
> {{{
> #!php
> do_action( 'wp_ability_invoked', $this->name, $input, $this );
> }}}
>
> The argument shape mirrors `wp_before_execute_ability` so observers can
> attach the same
> callback signature to both hooks. The past-tense verb communicates
> "entered the pipeline"
> without implying the call will execute.
>
> == Lifecycle position ==
>
> {{{
> execute( $input ) {
>     do_action( 'wp_ability_invoked', ... );      // new — every call, raw
> input
>     apply_filters( 'wp_pre_execute_ability', ... ); // existing — short-
> circuit seam
>     // normalize_input → validate_input → check_permissions
>     do_action( 'wp_before_execute_ability', ... ); // existing —
> execution imminent
>     // execute_callback
>     do_action( 'wp_after_execute_ability', ... );
>     return apply_filters( 'wp_execute_ability_result', ... );
> }
> }}}
>
> == Motivation ==
>
> Surfaced in
> [https://github.com/WordPress/ai/discussions/477#discussioncomment-16900589
> WordPress/ai#477]. The AbilityGuard plugin currently writes its audit row
> inline inside
> its `wp_pre_execute_ability` handler because no earlier signal exists,
> which conflates
> "build the approval envelope" with "record that an invocation happened."
> Landing
> `wp_ability_invoked` lets that work move to a dedicated observer listener
> and keeps the
> pre-execute handler focused on its short-circuit responsibility.
>
> The same signal benefits any observability layer that needs invocation
> entry counts
> independent of outcome (telemetry, rate-limit accounting that includes
> rejected calls,
> etc.).

New description:

 The execution lifecycle filters proposed in #64989 give the Abilities API
 four seams: `wp_pre_execute_ability`, `wp_before_execute_ability`,
 `wp_after_execute_ability`, and `wp_execute_ability_result`.
 `wp_before_execute_ability` fires after input normalization, validation,
 and the permission check — its contract is "execution is imminent." That
 contract is correct: rate-limiter, cached-response, and dry-run observers
 all rely on it not firing for calls that exit early.

 That leaves a gap for audit and snapshot observers, which need the
 opposite signal: "the pipeline received this invocation, regardless of
 outcome." They want a hook that fires for every call — approval-pending,
 rate-limited, cached, dry-run, and normal — so the audit row exists no
 matter how the call exits, and fires before normalization so the input is
 captured raw.

 == Proposal ==

 Add a `wp_ability_invoked` action at the top of `WP_Ability::execute()`,
 before `wp_pre_execute_ability` runs:

 {{{
 #!php
 do_action( 'wp_ability_invoked', $this->name, $input, $this );
 }}}

 The argument shape mirrors `wp_before_execute_ability` so observers can
 attach the same callback signature to both hooks. The past-tense verb
 communicates "entered the pipeline" without implying the call will
 execute.

 == Lifecycle position ==

 {{{
 execute( $input ) {
     do_action( 'wp_ability_invoked', ... );      // new — every call, raw
 input
     apply_filters( 'wp_pre_execute_ability', ... ); // existing — short-
 circuit seam
     // normalize_input → validate_input → check_permissions
     do_action( 'wp_before_execute_ability', ... ); // existing — execution
 imminent
     // execute_callback
     do_action( 'wp_after_execute_ability', ... );
     return apply_filters( 'wp_execute_ability_result', ... );
 }
 }}}

 == Motivation ==

 Surfaced in
 [https://github.com/WordPress/ai/discussions/477#discussioncomment-16900589
 WordPress/ai#477]. The AbilityGuard plugin currently writes its audit row
 inline inside its `wp_pre_execute_ability` handler because no earlier
 signal exists, which conflates "build the approval envelope" with "record
 that an invocation happened." Landing `wp_ability_invoked` lets that work
 move to a dedicated observer listener and keeps the pre-execute handler
 focused on its short-circuit responsibility.

 The same signal benefits any observability layer that needs invocation
 entry counts independent of outcome (telemetry, rate-limit accounting that
 includes rejected calls, etc.).

--

-- 
Ticket URL: <https://core.trac.wordpress.org/ticket/65248#comment:1>
WordPress Trac <https://core.trac.wordpress.org/>
WordPress publishing platform


More information about the wp-trac mailing list