[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