Skip to content

Add canonical WooCommerce domain abilities#64606

Merged
nerrad merged 55 commits into
trunkfrom
add/rsm-1335-domain-abilities
May 11, 2026
Merged

Add canonical WooCommerce domain abilities#64606
nerrad merged 55 commits into
trunkfrom
add/rsm-1335-domain-abilities

Conversation

@nerrad

@nerrad nerrad commented May 5, 2026

Copy link
Copy Markdown
Contributor

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-adapter wiring that surfaces these abilities (along with other projection-side concerns).

The PR adds:

  • Product abilities: woocommerce/products-query, woocommerce/product-create, woocommerce/product-update, woocommerce/product-delete.
  • Order abilities: woocommerce/orders-query, woocommerce/order-update-status, woocommerce/order-add-note.
  • Strict input/output JSON schemas with WooCommerce-aware enums (order/product status, agent-facing product type aliases, stock status, currency context, ISO 8601 dates with site-tz and _gmt variants).
  • Targeted property descriptions where schema type/enum/default values do not fully explain behavior, such as agent-facing product type aliases, money strings, conditional product fields, soft delete behavior, order date filters, and optional line item output.
  • Product query type filtering that uses the same agent-facing aliases as product creation/update (physical, digital, affiliate, grouped), mapping them to the relevant WooCommerce product type and product fields.
  • An explicit ability registry on AbilitiesLoader plus the woocommerce_ability_definition_classes filter for extensions.
  • Reserved 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 the woocommerce-abilities source, and registers the canonical definition.
  • Tests covering registration metadata, input validation, permission negative paths, schema contracts, and execution behavior for every canonical ability.

The write abilities are intentionally split by state transition (product-create, product-update, product-delete) instead of using an action enum 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 from class-woocommerce.php. Each definition declares meta.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 own permission_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:

  • Tightening input schemas with explicit enums and additionalProperties: false so unknown values are rejected by rest_validate_value_from_schema before execute() runs.
  • Keeping query abilities aligned with WooCommerce admin/operator semantics: product and order collection queries do not force a publish/status default, and access is controlled by the ability permission callbacks.
  • Re-checking the publish_products capability in product-update when transitioning a non-published product to publish, since the edit cap chain alone does not require it.
  • Returning WP_Error responses for failed product saves, failed order status updates, failed order note creation, and failed product deletes.
  • Attributing order notes added through order-add-note to the acting user (third arg to WC_Order::add_order_note).
  • Defaulting product-delete to a soft delete/trash operation, with permanent deletion requiring force: 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 behind MCPAdapterProvider::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 through woocommerce_ability_definition_classes should 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 the woocommerce-abilities log 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:

  1. Check out this branch.

  2. From plugins/woocommerce, run the unit tests:

    pnpm test:php:env -- --filter AbilitiesLoaderTest

    Confirm 97 tests / 368 assertions pass.

  3. 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.

  4. Confirm permission/input negative paths:

    • Anonymous users and subscribers without shop caps are rejected on every canonical ability.
    • order-update-status rejects status slugs not in wc_get_order_statuses().
    • product-create rejects unknown statuses and unknown input fields (additionalProperties: false).
    • orders-query rejects unknown orderby values.
    • product-update rejects a publish transition for a user with edit_others_products but no publish_products.
    • Negative product/order IDs are rejected by schema validation through minimum: 1.
  5. Confirm product type behavior:

    • product-create/product-update use product_type values physical, digital, affiliate, and grouped.
    • products-query uses type values physical, digital, affiliate, and grouped.
    • physical and digital both map to WooCommerce simple products, but query filtering also applies the matching virtual/downloadable product fields.
    • affiliate maps to external products and grouped maps to grouped products.
  6. Confirm loader coexistence behavior:

    • Extension ability definition classes can register non-woocommerce/ names through woocommerce_ability_definition_classes, including abilities in the WooCommerce category.
    • Extension ability definition classes using the reserved woocommerce/ ability-name prefix are skipped.
    • If a canonical WooCommerce ability name was already registered before AbilitiesLoader runs, WooCommerce unregisters that registration, writes a warning to the woocommerce-abilities log 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.

  1. From plugins/woocommerce, start the local environment:

    pnpm env:start
  2. 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%/'
  3. Authenticate as a local admin user and send an MCP initialize request to:

    http://localhost:8888/wp-json/mcp/mcp-adapter-default-server
    
  4. Call tools/list and confirm the bundled adapter exposes its meta-tools, including:

    mcp-adapter-discover-abilities
    mcp-adapter-get-ability-info
    mcp-adapter-execute-ability
    
  5. Call mcp-adapter-discover-abilities and confirm it returns:

    woocommerce/products-query
    woocommerce/product-create
    woocommerce/product-update
    woocommerce/product-delete
    woocommerce/orders-query
    woocommerce/order-update-status
    woocommerce/order-add-note
    
  6. Call mcp-adapter-get-ability-info for woocommerce/product-create and confirm the response includes meta.mcp.public: true and the full property-level input schema (with the status enum and required name).

  7. Optionally call mcp-adapter-execute-ability for woocommerce/products-query with { "per_page": 1 } and confirm it returns product data with currency and currency_symbol fields and both date_created and date_created_gmt variants.

Testing that has already taken place:

Validated locally on the current branch:

  • pnpm --filter=@woocommerce/plugin-woocommerce test:php:env -- --filter AbilitiesLoaderTest passed: 97 tests, 368 assertions.
  • composer exec phpcs -- -s --exclude=Suin.Classes.PSR4 src/Internal/Abilities/AbilitiesLoader.php tests/php/src/Internal/Abilities/AbilitiesLoaderTest.php passed.
  • pnpm --filter=@woocommerce/plugin-woocommerce exec php vendor/bin/phpstan analyse --configuration=phpstan.neon --memory-limit=4G --debug src/Internal/Abilities/AbilitiesLoader.php passed.
  • php -l passed for the touched PHP source and test files.
  • git diff --check passed.

Surface verification completed for this branch:

  • The canonical abilities are discoverable and executable through the bundled WordPress MCP adapter default server at /wp-json/mcp/mcp-adapter-default-server when the WooCommerce MCP integration feature flag is enabled.
  • The canonical abilities are also reachable through /wp-abilities/v1/abilities/{name}/run when the MCP flag is disabled, and permission/input enum validation applies on that path.

Milestone

Note: Check the box above to have the milestone automatically assigned when merged.
Alternatively (e.g. for point releases), manually assign the appropriate 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

  • Patch
  • Minor
  • Major

Type

  • Fix - Fixes an existing bug
  • Add - Adds functionality
  • Update - Update existing functionality
  • Dev - Development related task
  • Tweak - A minor adjustment to the codebase
  • Performance - Address performance issues
  • Enhancement - Improvement to existing functionality

Message

Add canonical WooCommerce domain abilities for product and order management.

Changelog Entry Comment

Comment

@github-actions github-actions Bot added the plugin: woocommerce Issues related to the WooCommerce Core plugin. label May 5, 2026
@nerrad nerrad force-pushed the add/rsm-1335-domain-abilities branch from f4846b3 to 5305ae8 Compare May 6, 2026 14:38
@nerrad nerrad closed this May 6, 2026
@nerrad nerrad reopened this May 6, 2026
@nerrad nerrad force-pushed the add/rsm-1335-domain-abilities branch from 2111904 to 798f30e Compare May 6, 2026 19:54
@nerrad nerrad marked this pull request as ready for review May 6, 2026 20:29
@nerrad nerrad self-assigned this May 6, 2026
@nerrad nerrad added the Enhancement The issue is a request for an enhancement. label May 6, 2026
@github-actions

github-actions Bot commented May 6, 2026

Copy link
Copy Markdown
Contributor

Test using WordPress Playground

The changes in this pull request can be previewed and tested using a WordPress Playground instance.
WordPress Playground is an experimental project that creates a full WordPress instance entirely within the browser.

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.

@coderabbitai

coderabbitai Bot commented May 6, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It 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 reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This 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.

Changes

Domain Abilities for Products and Orders

Layer / File(s) Summary
Ability Definition Interface
plugins/woocommerce/src/Abilities/AbilityDefinition.php
New public interface contract: get_name() and get_registration_args() specify ability identity and registration payload.
Ability Registration Infrastructure
plugins/woocommerce/src/Internal/Abilities/AbilitiesLoader.php, plugins/woocommerce/src/Internal/Abilities/AbilitiesRegistry.php, plugins/woocommerce/src/Internal/Abilities/AbilitiesCategories.php
AbilitiesLoader centralizes WooCommerce ability initialization and registration hooks, applies woocommerce_ability_definition_classes filter, calls AbilitiesRestBridge::init(), and AbilitiesRegistry now delegates init to the loader; AbilitiesCategories::register_categories() conditionally registers categories.
Base Domain Classes & Shared Helpers
plugins/woocommerce/src/Internal/Abilities/Domain/DomainAbility.php, .../Traits/OrderAbilityTrait.php, .../Traits/ProductAbilityTrait.php
DomainAbility provides JSON-schema helpers and ID extraction; OrderAbilityTrait supplies order schema/permission/formatting helpers (~230 LOC); ProductAbilityTrait supplies product schemas, type mapping, validation, persistence, and response formatting (~625 LOC).
Product Abilities
plugins/woocommerce/src/Internal/Abilities/Domain/ProductsQuery.php, ProductCreate.php, ProductUpdate.php, ProductDelete.php
Adds products-query, product-create, product-update, and product-delete abilities with strict input/output schemas, type-specific handling, validation, permission callbacks, save/delete persistence, and formatted responses.
Order Abilities
plugins/woocommerce/src/Internal/Abilities/Domain/OrdersQuery.php, OrderUpdateStatus.php, OrderAddNote.php
Adds orders-query, order-update-status, and order-add-note abilities with filtering/pagination, status normalization, note sanitization, permission checks, and formatted order outputs.
Tests & Fixtures
plugins/woocommerce/tests/php/src/Internal/Abilities/AbilitiesLoaderTest.php, TestExtensionAbilityDefinition.php, plugins/woocommerce/changelog/rsm-1335-domain-abilities
Comprehensive test suite and test fixture exercising registration, schemas, permissions, validation, execution paths, and edge cases; changelog entry added.

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The PR title 'Add canonical WooCommerce domain abilities' directly and clearly summarizes the main change: introducing a set of canonical, domain-backed WooCommerce abilities for products and orders.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description check ✅ Passed The PR description clearly relates to the changeset, detailing new WooCommerce domain abilities for products and orders with input/output schemas, permission controls, and execution behavior.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch add/rsm-1335-domain-abilities

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.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🧹 Nitpick comments (2)
plugins/woocommerce/tests/php/src/Internal/Abilities/AbilitiesLoaderTest.php (1)

92-93: ⚡ Quick win

This setup masks loader regressions around category registration.

Because setUp() pre-registers woocommerce before register_domain_abilities(), the suite can stay green even if AbilitiesLoader stops calling AbilitiesCategories::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_order is duplicated identically in OrderAddNote

Both OrderUpdateStatus::can_edit_order() (lines 127-131 here) and OrderAddNote::can_edit_order() (lines 118-122 in OrderAddNote.php) contain byte-for-byte identical implementations. This is a clear candidate for extraction into OrderAbilityTrait.

♻️ 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 OrderUpdateStatus and OrderAddNote, replace the can_edit_order body 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

📥 Commits

Reviewing files that changed from the base of the PR and between ba84b34 and e37d739.

📒 Files selected for processing (17)
  • plugins/woocommerce/changelog/rsm-1335-domain-abilities
  • plugins/woocommerce/src/Internal/Abilities/AbilitiesCategories.php
  • plugins/woocommerce/src/Internal/Abilities/AbilitiesLoader.php
  • plugins/woocommerce/src/Internal/Abilities/AbilitiesRegistry.php
  • plugins/woocommerce/src/Internal/Abilities/AbilityDefinition.php
  • plugins/woocommerce/src/Internal/Abilities/Domain/DomainAbility.php
  • plugins/woocommerce/src/Internal/Abilities/Domain/OrderAddNote.php
  • plugins/woocommerce/src/Internal/Abilities/Domain/OrderUpdateStatus.php
  • plugins/woocommerce/src/Internal/Abilities/Domain/OrdersQuery.php
  • plugins/woocommerce/src/Internal/Abilities/Domain/ProductCreate.php
  • plugins/woocommerce/src/Internal/Abilities/Domain/ProductDelete.php
  • plugins/woocommerce/src/Internal/Abilities/Domain/ProductUpdate.php
  • plugins/woocommerce/src/Internal/Abilities/Domain/ProductsQuery.php
  • plugins/woocommerce/src/Internal/Abilities/Domain/Traits/OrderAbilityTrait.php
  • plugins/woocommerce/src/Internal/Abilities/Domain/Traits/ProductAbilityTrait.php
  • plugins/woocommerce/tests/php/src/Internal/Abilities/AbilitiesLoaderTest.php
  • plugins/woocommerce/tests/php/src/Internal/Abilities/TestExtensionAbilityDefinition.php

Comment thread plugins/woocommerce/src/Abilities/AbilityDefinition.php
Comment thread plugins/woocommerce/src/Internal/Abilities/Domain/ProductCreate.php
Comment thread plugins/woocommerce/src/Internal/Abilities/Domain/ProductUpdate.php
Comment thread plugins/woocommerce/src/Internal/Abilities/Domain/Traits/OrderAbilityTrait.php Outdated
Comment thread plugins/woocommerce/src/Internal/Abilities/Domain/Traits/ProductAbilityTrait.php Outdated
Comment thread plugins/woocommerce/src/Internal/Abilities/Domain/ProductCreate.php Outdated
Comment thread plugins/woocommerce/src/Internal/Abilities/Domain/Traits/ProductAbilityTrait.php Outdated
Comment thread plugins/woocommerce/src/Internal/Abilities/Domain/ProductUpdate.php Outdated

@rtio rtio left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread plugins/woocommerce/src/Internal/Abilities/Domain/ProductUpdate.php
Comment thread plugins/woocommerce/src/Internal/Abilities/AbilitiesLoader.php
Comment thread plugins/woocommerce/src/Internal/Abilities/Domain/OrdersQuery.php
Comment thread plugins/woocommerce/src/Internal/Abilities/Domain/ProductDelete.php Outdated
Comment thread plugins/woocommerce/src/Internal/Abilities/Domain/OrderAddNote.php
Comment thread plugins/woocommerce/src/Internal/Abilities/Domain/OrdersQuery.php Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 4e7b5ef and 5d9608f.

📒 Files selected for processing (8)
  • plugins/woocommerce/src/Internal/Abilities/Domain/OrderAddNote.php
  • plugins/woocommerce/src/Internal/Abilities/Domain/OrderUpdateStatus.php
  • plugins/woocommerce/src/Internal/Abilities/Domain/OrdersQuery.php
  • plugins/woocommerce/src/Internal/Abilities/Domain/ProductDelete.php
  • plugins/woocommerce/src/Internal/Abilities/Domain/ProductsQuery.php
  • plugins/woocommerce/src/Internal/Abilities/Domain/Traits/OrderAbilityTrait.php
  • plugins/woocommerce/src/Internal/Abilities/Domain/Traits/ProductAbilityTrait.php
  • plugins/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

Comment thread plugins/woocommerce/src/Internal/Abilities/Domain/OrderAddNote.php Outdated

@nerrad nerrad left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_method and request-shape massaging
  • Output schema validation (which catches the permalink: false and 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_status no-op is silently treated as success in OrderUpdateStatus. When requested status equals current, update_status returns true and the ability returns success. Annotation says idempotent: false — flip it or detect the no-op.
  • wc_format_decimal silently coerces non-numeric prices to '0'. Schema enforces type: 'string' only. regular_price: 'banana' saves the product at price 0. Add pattern: '^[0-9]*\\.?[0-9]+$' or numeric validation.
  • idempotent: true on queries with orderby: 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_pages returned. Most WC REST endpoints include both total and total_pages. Clients have to compute pagination from total / per_page. Surface-design note.
  • include_line_items=true with per_page=100 is N+1. Per-order item hydration without bulk priming. Worth a doc warning if not a priming fix.
  • Internal\Abilities\Domain\Traits\* and DomainAbility aren't reachable to extension authors who implement the new public AbilityDefinition contract. They have no shared trait to reuse for product/order formatting. Either expose helpers in the Abilities (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

Comment thread plugins/woocommerce/src/Internal/Abilities/Domain/OrderAddNote.php Outdated
Comment thread plugins/woocommerce/src/Internal/Abilities/Domain/ProductDelete.php Outdated
Comment thread plugins/woocommerce/src/Internal/Abilities/Domain/ProductDelete.php
Comment thread plugins/woocommerce/src/Internal/Abilities/Domain/ProductsQuery.php Outdated
Comment thread plugins/woocommerce/src/Internal/Abilities/Domain/Traits/ProductAbilityTrait.php Outdated
Comment thread plugins/woocommerce/src/Internal/Abilities/Domain/OrdersQuery.php Outdated
Comment thread plugins/woocommerce/src/Internal/Abilities/Domain/OrdersQuery.php Outdated
Comment thread plugins/woocommerce/src/Internal/Abilities/AbilitiesLoader.php Outdated
Comment thread plugins/woocommerce/src/Internal/Abilities/Domain/OrdersQuery.php
Comment thread plugins/woocommerce/src/Internal/Abilities/AbilitiesRegistry.php

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 5d18c08 and ce27fda.

📒 Files selected for processing (8)
  • plugins/woocommerce/src/Internal/Abilities/AbilitiesLoader.php
  • 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/ProductDelete.php
  • plugins/woocommerce/src/Internal/Abilities/Domain/ProductsQuery.php
  • plugins/woocommerce/src/Internal/Abilities/Domain/Traits/ProductAbilityTrait.php
  • plugins/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

Comment thread plugins/woocommerce/src/Internal/Abilities/Domain/Traits/ProductAbilityTrait.php Outdated
Comment thread plugins/woocommerce/src/Internal/Abilities/Domain/OrdersQuery.php
Comment thread plugins/woocommerce/src/Internal/Abilities/Domain/ProductCreate.php Outdated
@nerrad nerrad force-pushed the add/rsm-1335-domain-abilities branch from ab8539d to f96f0a2 Compare May 11, 2026 15:55
@nerrad nerrad merged commit 713728a into trunk May 11, 2026
40 checks passed
@nerrad nerrad deleted the add/rsm-1335-domain-abilities branch May 11, 2026 17:54
@github-actions github-actions Bot added this to the 10.9.0 milestone May 11, 2026
beaulebens pushed a commit that referenced this pull request May 15, 2026
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Enhancement The issue is a request for an enhancement. plugin: woocommerce Issues related to the WooCommerce Core plugin.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants