[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:42:22 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:2>
WordPress Trac <https://core.trac.wordpress.org/>
WordPress publishing platform
More information about the wp-trac
mailing list