Skip to content

Abilities API: add execution lifecycle filters to WP_Ability methods#11731

Open
gziolo wants to merge 4 commits intoWordPress:trunkfrom
gziolo:feature/abilities-execution-lifecycle-filters
Open

Abilities API: add execution lifecycle filters to WP_Ability methods#11731
gziolo wants to merge 4 commits intoWordPress:trunkfrom
gziolo:feature/abilities-execution-lifecycle-filters

Conversation

@gziolo
Copy link
Copy Markdown
Member

@gziolo gziolo commented May 6, 2026

Summary

Adds four new filters to WP_Ability to give plugins hook points across the execution
lifecycle. Today the only execution-phase hooks are observation-only actions
(wp_before_execute_ability, wp_after_execute_ability); plugins that need to
transform 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, three living inside their
owning WP_Ability methods (so they apply consistently across execute(), REST
permission checks, and WP-CLI), and one on execute() itself for orchestration
control.

Filters added

Filter Where Purpose
wp_pre_execute_ability top of execute() Short-circuit. Returning non-null bypasses the entire pipeline (modeled on rest_pre_dispatch).
wp_ability_normalize_input inside normalize_input() Transform input — prompt enrichment, parameter defaulting beyond JSON Schema, caller metadata injection. Returning WP_Error halts execution.
wp_ability_permission_result inside check_permissions() Override the registered permission_callback result. Applies consistently across all call sites.
wp_ability_execute_result inside execute(), after do_execute() Transform the result before output validation. Can also recover from do_execute() failures by returning a successful value in place of WP_Error.

Pipeline ordering inside execute()

wp_pre_execute_ability  (filter, can short-circuit)
  → normalize_input()   → wp_ability_normalize_input  (filter)
  → validate_input()
  → check_permissions() → wp_ability_permission_result (filter)
  → wp_before_execute_ability  (existing action)
  → do_execute()
  → wp_ability_execute_result  (filter, runs before output validation)
  → validate_output()
  → wp_after_execute_ability   (existing action)
  → return

Schema validation remains the final integrity gate: wp_ability_normalize_input
fires before validate_input(), and wp_ability_execute_result fires before
validate_output(). Filters cannot bypass schema validation except by
short-circuiting via wp_pre_execute_ability, where the caller takes
responsibility for the returned value's shape.

Commits

This PR is split into four atomic commits, one per filter, in lifecycle order.
Reviewers can read commit-by-commit or as a single diff.

  1. wp_ability_normalize_input
  2. wp_ability_permission_result
  3. wp_pre_execute_ability
  4. wp_ability_execute_result

Test plan

  • npm run env:composer -- format — clean
  • npm run env:composer -- lint — clean
  • npm run env:composer -- compat — clean
  • npm run typecheck:php — 0 errors
  • npm run test:php -- --group=abilities-api — 263/263 passing
  • Full PHPUnit suite — only unrelated pre-existing failure (Tests_Theme_ThemeDir::test_broken_themes, fails on trunk too)

20 new tests cover:

  • Each filter receives the documented parameters.
  • Transformation use cases (input rewrite, permission override, result repair).
  • Short-circuit semantics for wp_pre_execute_ability (asserts no downstream filter, action, callback fires).
  • Ordering: wp_ability_execute_result runs before output validation and before wp_after_execute_ability.
  • Failure handling: wp_ability_execute_result receiving WP_Error from do_execute().
  • wp_ability_permission_result firing when check_permissions() is called directly (REST/WP-CLI path).
  • WP_Error propagation from wp_ability_normalize_input halts the rest of the pipeline.

Trac ticket

https://core.trac.wordpress.org/ticket/64989

Use of AI Tools

AI assistance: Yes
Tool(s): Claude Code
Model(s): Claude Opus 4.7 (1M context)
Used for: Planning, implementation, unit tests, commit messages, and PR description.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 6, 2026

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 props-bot label.

Core Committers: Use this line as a base for the props when committing in SVN:

Props gziolo.

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 6, 2026

Test using WordPress Playground

The 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

  • All changes will be lost when closing a tab with a Playground instance.
  • All changes will be lost when refreshing the page.
  • A fresh instance is created each time the link below is clicked.
  • Every time this pull request is updated, a new ZIP file containing all changes is created. If changes are not reflected in the Playground instance,
    it's possible that the most recent build failed, or has not completed. Check the list of workflow runs to be sure.

For more details about these limitations and more, check out the Limitations page in the WordPress Playground documentation.

Test this pull request with WordPress Playground.

@gziolo gziolo force-pushed the feature/abilities-execution-lifecycle-filters branch 2 times, most recently from a8dc90a to f59694c Compare May 6, 2026 10:56
gziolo added 4 commits May 6, 2026 13:08
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.
@gziolo gziolo force-pushed the feature/abilities-execution-lifecycle-filters branch from f59694c to 9b2aa9a Compare May 6, 2026 11:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant