Add canonical WooCommerce domain abilities#64606
Conversation
f4846b3 to
5305ae8
Compare
2111904 to
798f30e
Compare
Test using WordPress PlaygroundThe changes in this pull request can be previewed and tested using a WordPress Playground instance. Test this pull request with WordPress Playground. Note that this URL is valid for 30 days from when this comment was last updated. You can update it by closing/reopening the PR or pushing a commit that changes plugin code. |
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughThis PR adds an AbilityDefinition contract, an AbilitiesLoader registration system, domain ability classes for products and orders (query/create/update/delete/update-status/add-note), shared product/order traits and a DomainAbility base, plus comprehensive tests and a changelog entry. ChangesDomain Abilities for Products and Orders
🎯 3 (Moderate) | ⏱️ ~25 minutes 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Tip 💬 Introducing Slack Agent: The best way for teams to turn conversations into code.Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.
Built for teams:
One agent for your entire SDLC. Right inside Slack. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 7
🧹 Nitpick comments (2)
plugins/woocommerce/tests/php/src/Internal/Abilities/AbilitiesLoaderTest.php (1)
92-93: ⚡ Quick winThis setup masks loader regressions around category registration.
Because
setUp()pre-registerswoocommercebeforeregister_domain_abilities(), the suite can stay green even ifAbilitiesLoaderstops callingAbilitiesCategories::register_categories(). For the loader-focused tests, I'd either let the loader create the category or add an explicit assertion that it did.Also applies to: 838-862
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@plugins/woocommerce/tests/php/src/Internal/Abilities/AbilitiesLoaderTest.php` around lines 92 - 93, The test setup currently pre-registers the woocommerce category via register_woocommerce_category() before calling register_domain_abilities(), which masks regressions in AbilitiesLoader (specifically missing AbilitiesCategories::register_categories()); update the tests so loader-focused cases do not pre-register the category — either remove or conditionally skip register_woocommerce_category() in setUp() for those tests, or add an explicit assertion after invoking AbilitiesLoader::register_domain_abilities() that AbilitiesCategories::register_categories() effectively created the 'woocommerce' category (use the same helper/assertion used elsewhere to check category registration) to ensure the loader is responsible for registration rather than the test setup.plugins/woocommerce/src/Internal/Abilities/Domain/OrderUpdateStatus.php (1)
127-131: ⚡ Quick win
can_edit_orderis duplicated identically inOrderAddNoteBoth
OrderUpdateStatus::can_edit_order()(lines 127-131 here) andOrderAddNote::can_edit_order()(lines 118-122 inOrderAddNote.php) contain byte-for-byte identical implementations. This is a clear candidate for extraction intoOrderAbilityTrait.♻️ Proposed refactor
In
plugins/woocommerce/src/Internal/Abilities/Domain/Traits/OrderAbilityTrait.php, add:/** * Check order edit access. * * `@param` mixed $input Ability input. * `@return` bool */ protected static function can_edit_order_ability( $input = array() ): bool { $order_id = self::get_id_from_input( $input ); return $order_id > 0 && wc_rest_check_post_permissions( 'shop_order', 'edit', $order_id ); }Then in both
OrderUpdateStatusandOrderAddNote, replace thecan_edit_orderbody with a delegation:public static function can_edit_order( $input = array() ): bool { - $order_id = self::get_id_from_input( $input ); - return $order_id > 0 && wc_rest_check_post_permissions( 'shop_order', 'edit', $order_id ); + return self::can_edit_order_ability( $input ); }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@plugins/woocommerce/src/Internal/Abilities/Domain/OrderUpdateStatus.php` around lines 127 - 131, OrderUpdateStatus::can_edit_order and OrderAddNote::can_edit_order are identical — extract the logic into a shared trait and delegate to it: create Plugins\WooCommerce\Internal\Abilities\Domain\Traits\OrderAbilityTrait with a protected static method can_edit_order_ability($input = array()) that calls self::get_id_from_input($input) and wc_rest_check_post_permissions('shop_order','edit',$order_id), then update OrderUpdateStatus::can_edit_order and OrderAddNote::can_edit_order to simply call and return that trait method (keeping method signatures) so the duplicate byte-for-byte code is removed.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@plugins/woocommerce/src/Internal/Abilities/AbilityDefinition.php`:
- Around line 12-34: The AbilityDefinition interface under Internal\Abilities
should not be the extension-facing contract; either relocate the interface out
of the Internal namespace (e.g., move Internal\Abilities\AbilityDefinition to a
non-Internal namespace like Abilities\AbilityDefinition) or stop exposing it in
the filter consumer and validate extension-supplied classes at runtime instead —
when handling the woocommerce_ability_definition_classes filter, do not require
the Internal\Abilities\AbilityDefinition type; instead accept class names and
assert they implement the required static methods get_name() and
get_registration_args() (use is_callable([ $class, 'get_name' ]) and
is_callable([ $class, 'get_registration_args' ]) or equivalent) and document the
public contract accordingly.
In `@plugins/woocommerce/src/Internal/Abilities/Domain/OrderUpdateStatus.php`:
- Around line 101-112: The code treats any false return from
WC_Abstract_Order::update_status() as an error, but update_status() returns
false when no transition is needed; change OrderUpdateStatus logic to first
compare $order->get_status() with the requested $status and short-circuit to
success if they match, otherwise call $order->update_status(...) and only return
a WP_Error when update_status() returns false and the prior status differed from
the requested one; keep the existing note sanitization and error payload but
only emit the 500 error for real update failures.
In `@plugins/woocommerce/src/Internal/Abilities/Domain/ProductCreate.php`:
- Around line 86-92: After calling $product->save() in ProductCreate, check its
return value (WC_Data::save) and handle a 0 return as a failure: if
$product->save() === 0, do not proceed to return formatted product; instead
log/throw an appropriate error or return a failure response (e.g., WP_Error or
exception) so callers don't receive a product with id: 0; update the block
around set_product_props_from_input, the $product->save() call, and the return
that uses format_product_for_response to enforce this guard.
In `@plugins/woocommerce/src/Internal/Abilities/Domain/ProductUpdate.php`:
- Around line 76-98: In ProductUpdate::execute, reject id-only or empty update
payloads before calling set_product_props_from_input and $product->save(): after
obtaining $product via get_product_from_input( $input ) and before calling
set_product_props_from_input, validate that $input contains at least one mutable
product field (e.g., title, price, status, description, regular_price,
sale_price, stock_quantity, etc.) beyond an 'id' key; if no mutable fields are
present return a \WP_Error (e.g., 'woocommerce_empty_update') with an
appropriate message and rest_authorization_required_code() status to avoid
unnecessary save() side-effects.
In
`@plugins/woocommerce/src/Internal/Abilities/Domain/Traits/OrderAbilityTrait.php`:
- Around line 134-143: In get_order_from_input, reject non-positive or zero IDs
before calling wc_get_order: validate that $input['id'] is present and that
absint($input['id']) yields a value >= 1 (or use intval and check > 0), and if
not return a \WP_Error with code 'woocommerce_order_id_required' (status 400);
only after this positive-ID check call wc_get_order and proceed as before to
avoid absint turning negative IDs into valid positive lookups.
In
`@plugins/woocommerce/src/Internal/Abilities/Domain/Traits/ProductAbilityTrait.php`:
- Around line 132-140: The loop in ProductAbilityTrait that applies $setters
currently runs all string inputs through wc_clean(), which strips markup from
description and short_description; update the logic in the foreach that handles
$field/$setter so that when $field equals 'description' or 'short_description'
you do NOT call wc_clean() (leave the string as-is or apply a markup-safe
sanitization if required), but continue to wc_clean() other plain-text fields;
ensure the is_callable($product, $setter) check and final
$product->{$setter}($value) invocation remain unchanged.
- Around line 88-97: The get_product_from_input() function currently normalizes
the incoming id with absint(), which turns negative IDs into positive ones and
can cause accidental updates/deletes; change the validation to explicitly reject
non-positive or non-numeric IDs before calling wc_get_product(): ensure you
check that $input['id'] is numeric/integer and greater than 0 (e.g.,
cast/validate with is_numeric/ctype_digit and intval > 0) and return a WP_Error
(same shape) for invalid IDs, then call wc_get_product with the validated
positive int rather than using absint().
---
Nitpick comments:
In `@plugins/woocommerce/src/Internal/Abilities/Domain/OrderUpdateStatus.php`:
- Around line 127-131: OrderUpdateStatus::can_edit_order and
OrderAddNote::can_edit_order are identical — extract the logic into a shared
trait and delegate to it: create
Plugins\WooCommerce\Internal\Abilities\Domain\Traits\OrderAbilityTrait with a
protected static method can_edit_order_ability($input = array()) that calls
self::get_id_from_input($input) and
wc_rest_check_post_permissions('shop_order','edit',$order_id), then update
OrderUpdateStatus::can_edit_order and OrderAddNote::can_edit_order to simply
call and return that trait method (keeping method signatures) so the duplicate
byte-for-byte code is removed.
In
`@plugins/woocommerce/tests/php/src/Internal/Abilities/AbilitiesLoaderTest.php`:
- Around line 92-93: The test setup currently pre-registers the woocommerce
category via register_woocommerce_category() before calling
register_domain_abilities(), which masks regressions in AbilitiesLoader
(specifically missing AbilitiesCategories::register_categories()); update the
tests so loader-focused cases do not pre-register the category — either remove
or conditionally skip register_woocommerce_category() in setUp() for those
tests, or add an explicit assertion after invoking
AbilitiesLoader::register_domain_abilities() that
AbilitiesCategories::register_categories() effectively created the 'woocommerce'
category (use the same helper/assertion used elsewhere to check category
registration) to ensure the loader is responsible for registration rather than
the test setup.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
Run ID: d0e958c9-f25a-4018-b40c-287202cf7374
📒 Files selected for processing (17)
plugins/woocommerce/changelog/rsm-1335-domain-abilitiesplugins/woocommerce/src/Internal/Abilities/AbilitiesCategories.phpplugins/woocommerce/src/Internal/Abilities/AbilitiesLoader.phpplugins/woocommerce/src/Internal/Abilities/AbilitiesRegistry.phpplugins/woocommerce/src/Internal/Abilities/AbilityDefinition.phpplugins/woocommerce/src/Internal/Abilities/Domain/DomainAbility.phpplugins/woocommerce/src/Internal/Abilities/Domain/OrderAddNote.phpplugins/woocommerce/src/Internal/Abilities/Domain/OrderUpdateStatus.phpplugins/woocommerce/src/Internal/Abilities/Domain/OrdersQuery.phpplugins/woocommerce/src/Internal/Abilities/Domain/ProductCreate.phpplugins/woocommerce/src/Internal/Abilities/Domain/ProductDelete.phpplugins/woocommerce/src/Internal/Abilities/Domain/ProductUpdate.phpplugins/woocommerce/src/Internal/Abilities/Domain/ProductsQuery.phpplugins/woocommerce/src/Internal/Abilities/Domain/Traits/OrderAbilityTrait.phpplugins/woocommerce/src/Internal/Abilities/Domain/Traits/ProductAbilityTrait.phpplugins/woocommerce/tests/php/src/Internal/Abilities/AbilitiesLoaderTest.phpplugins/woocommerce/tests/php/src/Internal/Abilities/TestExtensionAbilityDefinition.php
rtio
left a comment
There was a problem hiding this comment.
A handful of additional comments after a multi-agent pass. Three I'd block on (publish-cap bypass via future/private, filter wipeout, _doing_it_wrong from double category registration); the rest are smaller asymmetries and test gaps.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@plugins/woocommerce/src/Internal/Abilities/Domain/OrderAddNote.php`:
- Around line 82-88: The current empty() check on $input['note'] allows
whitespace-only strings to pass; instead sanitize and trim the note before
validating. In the OrderAddNote class/method, retrieve and sanitize the input
(e.g., $note = wp_kses_post($input['note']) or similar), then if trim($note) ===
'' return the WP_Error ('woocommerce_order_note_required', ...) to block
whitespace-only notes before calling add_order_note; adjust any downstream uses
to use the sanitized $note.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
Run ID: 0d623c2d-749f-4574-89bc-48cb1a60bb05
📒 Files selected for processing (8)
plugins/woocommerce/src/Internal/Abilities/Domain/OrderAddNote.phpplugins/woocommerce/src/Internal/Abilities/Domain/OrderUpdateStatus.phpplugins/woocommerce/src/Internal/Abilities/Domain/OrdersQuery.phpplugins/woocommerce/src/Internal/Abilities/Domain/ProductDelete.phpplugins/woocommerce/src/Internal/Abilities/Domain/ProductsQuery.phpplugins/woocommerce/src/Internal/Abilities/Domain/Traits/OrderAbilityTrait.phpplugins/woocommerce/src/Internal/Abilities/Domain/Traits/ProductAbilityTrait.phpplugins/woocommerce/tests/php/src/Internal/Abilities/AbilitiesLoaderTest.php
🚧 Files skipped from review as they are similar to previous changes (4)
- plugins/woocommerce/src/Internal/Abilities/Domain/OrderUpdateStatus.php
- plugins/woocommerce/src/Internal/Abilities/Domain/OrdersQuery.php
- plugins/woocommerce/src/Internal/Abilities/Domain/Traits/ProductAbilityTrait.php
- plugins/woocommerce/src/Internal/Abilities/Domain/Traits/OrderAbilityTrait.php
nerrad
left a comment
There was a problem hiding this comment.
Critical follow-up review pass after the prior rounds. Most of what I found are tactical defects rather than structural issues — the architecture is sound. Inline comments below; this summary covers what doesn't fit on a single line.
Cross-cutting issue: tests don't exercise the public surface
All ~67 tests call $ability->execute() directly. That bypasses:
- WP Abilities REST endpoint method enforcement (GET vs POST vs DELETE based on
readonly/destructive/idempotent) validate_request_methodand request-shape massaging- Output schema validation (which catches the
permalink: falseand variation-type mismatch issues flagged inline)
This is a [SHOULD-FIX] because direct execute() testing missed at least three real defects flagged below. Add at least one WP_REST_Request round-trip per ability against /wp-abilities/v1/abilities/{name}/run, and a test pair (HPOS on / HPOS off) for OrdersQuery filters.
Smaller items not worth a dedicated comment
update_statusno-op is silently treated as success inOrderUpdateStatus. When requested status equals current,update_statusreturnstrueand the ability returns success. Annotation saysidempotent: false— flip it or detect the no-op.wc_format_decimalsilently coerces non-numeric prices to'0'. Schema enforcestype: 'string'only.regular_price: 'banana'saves the product at price 0. Addpattern: '^[0-9]*\\.?[0-9]+$'or numeric validation.idempotent: trueon queries withorderby: date_modified. Strict idempotency = same input → same output; that's not true if "now" sneaks into orderby. Low risk; the WP Abilities run controller doesn't enforce strict idempotency yet.- No
total_pagesreturned. Most WC REST endpoints include bothtotalandtotal_pages. Clients have to compute pagination fromtotal / per_page. Surface-design note. include_line_items=truewithper_page=100is N+1. Per-order item hydration without bulk priming. Worth a doc warning if not a priming fix.Internal\Abilities\Domain\Traits\*andDomainAbilityaren't reachable to extension authors who implement the new publicAbilityDefinitioncontract. They have no shared trait to reuse for product/order formatting. Either expose helpers in theAbilities(non-Internal) namespace or document that extenders should implement their own.
Verdict
[BLOCK]s should be addressed before merge: B1 (whitespace note — fix was claimed but never landed), B2 (force=false hard-delete on EMPTY_TRASH_DAYS=0), B3 (variation acceptance asymmetry — ProductDelete accepts, ProductsQuery accepts but produces invalid output, ProductCreate/Update reject), B4 (permalink: false reachable through auto-draft).
[SHOULD-FIX]s can be follow-ups if you want to keep this PR moving — S1/S2 are real correctness bugs on legacy CPT order storage, but you can scope them to a "HPOS-vs-CPT parity for orders-query" follow-up issue.
*left by Biff (executive assistant agent) on behalf of Darren
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@plugins/woocommerce/src/Internal/Abilities/Domain/Traits/ProductAbilityTrait.php`:
- Around line 429-430: In ProductAbilityTrait where the 'grouped_products'
branch currently returns array_map('absint', $value), stop coercing malformed
IDs and instead validate each entry is a positive integer: iterate $value in the
'grouped_products' case, for each element use a strict check (e.g.
ctype_digit((string)$id) or filter_var($id, FILTER_VALIDATE_INT) && (int)$id >
0), and if any entry fails, reject the input (throw an exception or return a
validation error/false) so the mutation fails; only after all entries pass
return an array of ints (e.g. array_map('intval', $value)).
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
Run ID: 10c30c7c-5971-4127-8368-ff3ca487782c
📒 Files selected for processing (8)
plugins/woocommerce/src/Internal/Abilities/AbilitiesLoader.phpplugins/woocommerce/src/Internal/Abilities/AbilitiesRegistry.phpplugins/woocommerce/src/Internal/Abilities/Domain/OrderAddNote.phpplugins/woocommerce/src/Internal/Abilities/Domain/OrdersQuery.phpplugins/woocommerce/src/Internal/Abilities/Domain/ProductDelete.phpplugins/woocommerce/src/Internal/Abilities/Domain/ProductsQuery.phpplugins/woocommerce/src/Internal/Abilities/Domain/Traits/ProductAbilityTrait.phpplugins/woocommerce/tests/php/src/Internal/Abilities/AbilitiesLoaderTest.php
✅ Files skipped from review due to trivial changes (1)
- plugins/woocommerce/src/Internal/Abilities/AbilitiesLoader.php
🚧 Files skipped from review as they are similar to previous changes (4)
- plugins/woocommerce/src/Internal/Abilities/AbilitiesRegistry.php
- plugins/woocommerce/src/Internal/Abilities/Domain/OrderAddNote.php
- plugins/woocommerce/src/Internal/Abilities/Domain/OrdersQuery.php
- plugins/woocommerce/src/Internal/Abilities/Domain/ProductsQuery.php
ab8539d to
f96f0a2
Compare
Add canonical WooCommerce domain abilities for product and order management. Introduce domain-backed Abilities API definitions for product query/create/update/delete and order query/status update/note creation, with strict WooCommerce-aware schemas, permission checks, extension registration hooks, reserved WooCommerce namespace handling, and REST/MCP projection metadata. Add coverage for registration, validation, permissions, edge cases, and execution behavior.
Submission Review Guidelines:
Changes proposed in this Pull Request:
This adds a set of canonical WooCommerce domain abilities backed directly by WooCommerce product and order APIs. Abilities are the canonical capability contract for what a site/plugin can do: they are transport-neutral and intended to be consumed by many projections (MCP, the Abilities REST API, future Command Palette workflows, admin tooling, CLI, agent runtimes, and so on). This PR establishes the semantic, domain-backed layer for products and orders so those projections can compose against a stable contract instead of mirroring REST endpoint shapes.
The existing experimental REST-derived abilities (
AbilitiesRestBridge) remain as a compatibility projection. They inherit REST endpoint granularity and are not ideal as the canonical contract, but should not be silently retired.This PR is a companion to #64425, which provides the broader Abilities API integration and the bundled
@wordpress/mcp-adapterwiring that surfaces these abilities (along with other projection-side concerns).The PR adds:
woocommerce/products-query,woocommerce/product-create,woocommerce/product-update,woocommerce/product-delete.woocommerce/orders-query,woocommerce/order-update-status,woocommerce/order-add-note._gmtvariants).physical,digital,affiliate,grouped), mapping them to the relevant WooCommerce product type and product fields.AbilitiesLoaderplus thewoocommerce_ability_definition_classesfilter for extensions.woocommerce/ability-name protection: extension classes can use the WooCommerce ability category, but should use their own ability-name prefix. If a previously registered ability shadows a canonical WooCommerce ability name, WooCommerce unregisters it, logs a warning under thewoocommerce-abilitiessource, and registers the canonical definition.The write abilities are intentionally split by state transition (
product-create,product-update,product-delete) instead of using anactionenum bundle, so clients can discover and reason about each operation independently.Surface and gating
Canonical domain abilities are registered with the WordPress Abilities API on every request via
AbilitiesLoader, which is bootstrapped unconditionally fromclass-woocommerce.php. Each definition declaresmeta.show_in_rest = true, so the WP Abilities API exposes the standard REST run endpoint at/wp-abilities/v1/abilities/{name}/run. That endpoint is gated by each ability's ownpermission_callback.MCP exposure is projection-specific. The WooCommerce MCP integration feature flag controls whether the bundled WordPress MCP adapter exposes these tools; it does not gate underlying ability registration or the Abilities REST surface.
This PR addresses that wider surface by:
additionalProperties: falseso unknown values are rejected byrest_validate_value_from_schemabeforeexecute()runs.publish_productscapability inproduct-updatewhen transitioning a non-published product topublish, since theeditcap chain alone does not require it.WP_Errorresponses for failed product saves, failed order status updates, failed order note creation, and failed product deletes.order-add-noteto the acting user (third arg toWC_Order::add_order_note).product-deleteto a soft delete/trash operation, with permanent deletion requiringforce: true.Naming coexistence with the REST-derived abilities
The existing experimental REST-derived abilities (
AbilitiesRestBridge, e.g.woocommerce/products-list,woocommerce/products-create) and the canonical domain abilities introduced here (woocommerce/products-query,woocommerce/product-create, ...) are intentionally distinct identifiers. The REST-derived bridge remains gated behindMCPAdapterProvider::is_mcp_request()and is retained as a compatibility projection for the deprecated WooCommerce MCP compatibility path. The canonical surface introduced here is intended to be the primary capability contract that downstream projections (MCP, REST Abilities, Command Palette workflows, admin tooling, agents) compose against.The
woocommerce/ability-name prefix is reserved for WooCommerce core definitions. Extensions registering ability definition classes throughwoocommerce_ability_definition_classesshould use their own ability-name prefix, even when placing the ability in the WooCommerce category. If another callback has already registered a canonical WooCommerce ability name before the loader runs, WooCommerce unregisters that registration, writes a warning to thewoocommerce-abilitieslog source with the ability name and replacement class, and then registers the core definition.Screenshots or screen recordings:
Not applicable. This is a PHP/API change with no UI updates.
How to test the changes in this Pull Request:
Check out this branch.
From
plugins/woocommerce, run the unit tests:Confirm 97 tests / 368 assertions pass.
Confirm the abilities register with full metadata (categories, MCP flags, annotations) and that input/output schemas declare WooCommerce enums, agent-facing product type aliases, currency context, date context, and targeted property descriptions.
Confirm permission/input negative paths:
order-update-statusrejects status slugs not inwc_get_order_statuses().product-createrejects unknown statuses and unknown input fields (additionalProperties: false).orders-queryrejects unknownorderbyvalues.product-updaterejects a publish transition for a user withedit_others_productsbut nopublish_products.minimum: 1.Confirm product type behavior:
product-create/product-updateuseproduct_typevaluesphysical,digital,affiliate, andgrouped.products-queryusestypevaluesphysical,digital,affiliate, andgrouped.physicalanddigitalboth map to WooCommerce simple products, but query filtering also applies the matching virtual/downloadable product fields.affiliatemaps to external products andgroupedmaps to grouped products.Confirm loader coexistence behavior:
woocommerce/names throughwoocommerce_ability_definition_classes, including abilities in the WooCommerce category.woocommerce/ability-name prefix are skipped.AbilitiesLoaderruns, WooCommerce unregisters that registration, writes a warning to thewoocommerce-abilitieslog source, and registers the canonical definition.Verify MCP adapter exposure
The bundled WordPress MCP adapter endpoint is initialized only when the WooCommerce MCP integration feature flag is enabled. The abilities themselves register independently through the WordPress Abilities API; the adapter exposes them because they include
meta.mcp.public: true.From
plugins/woocommerce, start the local environment:Enable the MCP feature flag and non-plain permalinks:
pnpm wp-env run cli wp option update woocommerce_feature_mcp_integration_enabled yes pnpm wp-env run cli wp rewrite structure '/%postname%/'Authenticate as a local admin user and send an MCP
initializerequest to:Call
tools/listand confirm the bundled adapter exposes its meta-tools, including:Call
mcp-adapter-discover-abilitiesand confirm it returns:Call
mcp-adapter-get-ability-infoforwoocommerce/product-createand confirm the response includesmeta.mcp.public: trueand the full property-level input schema (with the status enum and requiredname).Optionally call
mcp-adapter-execute-abilityforwoocommerce/products-querywith{ "per_page": 1 }and confirm it returns product data withcurrencyandcurrency_symbolfields and bothdate_createdanddate_created_gmtvariants.Testing that has already taken place:
Validated locally on the current branch:
pnpm --filter=@woocommerce/plugin-woocommerce test:php:env -- --filter AbilitiesLoaderTestpassed: 97 tests, 368 assertions.composer exec phpcs -- -s --exclude=Suin.Classes.PSR4 src/Internal/Abilities/AbilitiesLoader.php tests/php/src/Internal/Abilities/AbilitiesLoaderTest.phppassed.pnpm --filter=@woocommerce/plugin-woocommerce exec php vendor/bin/phpstan analyse --configuration=phpstan.neon --memory-limit=4G --debug src/Internal/Abilities/AbilitiesLoader.phppassed.php -lpassed for the touched PHP source and test files.git diff --checkpassed.Surface verification completed for this branch:
/wp-json/mcp/mcp-adapter-default-serverwhen the WooCommerce MCP integration feature flag is enabled./wp-abilities/v1/abilities/{name}/runwhen the MCP flag is disabled, and permission/input enum validation applies on that path.Milestone
Changelog entry
A changelog entry has been added manually in
plugins/woocommerce/changelog/rsm-1335-domain-abilities.Automatically create a changelog entry from the details below.
This Pull Request does not require a changelog entry. (Comment required below)
Changelog Entry Details
Significance
Type
Message
Add canonical WooCommerce domain abilities for product and order management.
Changelog Entry Comment
Comment