Make WordPress Core

Opened 2 months ago

Last modified 9 days ago

#64345 new enhancement

Ability names should support versioning

Reported by: jason_the_adams's profile jason_the_adams Owned by:
Milestone: Awaiting Review Priority: normal
Severity: normal Version: trunk
Component: AI Keywords: has-patch has-unit-tests
Focuses: Cc:

Description

We've discussed that versioning an Ability should be done within the name of the Ability instead of a formal concept. That is, an Ability could be named wp/v1/generate-featured-image to indicate a version. So if a wp/v2/generate-featured-image were created, it would be a different Ability entirely, but conceptually tied to the first — most likely used if the input or output schema changes.

I realized that the regex for validating the Ability name currently enforces the namespace/ability-name structure, throwing an error otherwise. We should continue to support this structure, but allow for an optional middle version.

I'm going to call this an enhancement and not a bug since we this is only something we conceptually intended and not something we tell anyone to do at this point.

Change History (13)

This ticket was mentioned in PR #10593 on WordPress/wordpress-develop by @jason_the_adams.


2 months ago
#1

  • Keywords has-patch has-unit-tests added

Trac ticket: 64345

This adds the ability for an Ability name to have a version. Both of these would be valid names:

  • wp/generate-featured-image
  • wp/v1/generate-featured-image

As noted in the Trac ticket, this was intended, but we didn't catch that the validation regex rejects this.

@jason_the_adams commented on PR #10593:


2 months ago
#2

I'm tempted to also add two methods to WP_Ability since these are enforced conventions.

  • WP_Ability::get_namespace() — returns "wp"
  • WP_Ability::get_version() — returns null for first example and v1 for second

@flixos90 commented on PR #10593:


2 months ago
#3

Copy-pasting from Slack thread:

I don't think we should encourage versioned abilities.

It goes against WordPress Core philosophies, and I think it's going to lead to a somewhat messy ecosystem.
FWIW: REST endpoints tend to have versions, but they're almost never used. In other words, the fact that it's called wp/v2 in Core endpoints is pointless, they haven't been updated ever since they were introduced. Which confirms the point that we shouldn't have versions.

antunjurkovic-collab commented on PR #10593:


2 months ago
#4

Strong +1 to @felixarntz and @justlevine on keeping the Ability identifier stable.

From an agent perspective (MCP tools / AI agents), stable identifiers are critical for autonomy and long-term usability.

Hard versioning risk (wp/v1/generate-featured-image):
If an agent is prompted or fine-tuned to call wp/v1/generate-featured-image and the site later moves to wp/v2/…, the agent just hits “tool not found”. It has no obvious way to discover the new version or know it’s the correct replacement. The namespace becomes brittle.

Schema-evolution approach (wp/generate-featured-image + metadata):
If the identifier stays stable (wp/generate-featured-image) and versioning/deprecation is expressed in metadata (e.g. meta.version, deprecation_reason):

The agent calls the same Ability name.

The system can respond with a warning or schema change (e.g. “field X is deprecated, use Y”).

The agent (or client library) can self-heal by updating the payload and retrying, without changing the identifier it was trained on.

This matches what we’re seeing in most agent/tool ecosystems:

Identifier = intent / capability (stable).

Schema + metadata = what evolves (version, deprecation, preferred args).

Keeping the Ability name evergreen and pushing evolution into the schema/metadata gives us better discovery, fewer hard breaks, and a much cleaner story for AI and non-AI clients alike.

#5 @justlevine
2 months ago

There's two separate points here:

  1. Ability names should not contain versions.

As noted by @flixos90 and @antunjurkovic-collab, at best they're noise, at worst they hurt discovery.

  1. Abilities API as a whole should not encourage or actively support versioning.

This is IMO the more important part here. Versions are an implementation detail; a consumer (human, adapter, aging LLM context) shouldn't need to know or care about it in order to execute an ability, just the input + out schema.

(My assumption is this sort of request comes from folks thinking about Abilities API from the perspective of REST. But the correct mental model should actually be Action/Filter hooks. I think we can agree that add_filter( 'my_plugin/v2/my_hook' ) is bad dx)

My recommendation is to close this as wontfix, and instead focus our efforts on self discoverable ability deprecation, which is what consumers actually need to be aware of. (As mentioned elsewhere, I believe a deprecation_reason: string|callable():string|null added to input/output fields a la WPGraphQL is the ideal approach).


PS: Us choosing not to bake versioning into the API doesn't prevent downstream implementors from doing the same - if they so choose. Instead of putting it into the namespace, they could store it in ::$meta['version' and retrieve it via $ability->get_meta('version').

You could even expose it to the user via an arg if they were so inclined. E.g.

<?php
// pseudocode
wp_register_ability(
 ...$my_ability_args,
 'input_schema' => [
   'type' => 'object',
   'properties' => [
      ...$my_ability_args['input_args'],
      'version' => [
        'type' => 'string',
        'enum' => ['1.0.0', '2.0.0']
        'default' => '2.0.0'
      ],
    ],
  'execute_callback' => static function( $input ) {
      if ( version_compare( $my_ability_args['meta']['version'], $input['version', '>' ) {
        _deprecated_function( __( 'We could have coerced things ourselves, but now it's your problem to upgrade to v2.0.0', 'my-plugin' ) ...);
       return My/V100/Routine::execute( $input );
      }

      return My/V200/Routine::execute( $input );
   },
);

(Separately, I think we should allow sub-namespaces, especially since we don't really have a use case for "category" yet, just not explicitly for versioning. But if we supported them generally and someone wanted to (ab)use. Just like if someone wanted to version-prefix a hook ).

But hopefully we can agree that's an antipattern, since it's really easy even without an explicit deprecations api to keep things evergreen and inline with core's faux back-compat:

<?php
...
'execute_callback' => static function ($input) {
  if ( isset ( $input['old_arg'] ) & ! isset( $input['new_arg'] ) {
    _deprecated_argument( ... __( 'We're not jerks about it, but you should update to the new arg' ) );
    $input['new_arg'] == My::map_old_to_new( $input['old_arg' );
  }

  return My::implementation_detail( $input['new_arg'] );
},
...


Version 0, edited 2 months ago by justlevine (next)

@justlevine commented on PR #10593:


2 months ago
#6

Shared my feedback directly to trac, since PR comments are one-directional: https://core.trac.wordpress.org/ticket/64345#comment:5

@jason_the_adams commented on PR #10593:


4 weeks ago
#7

Thanks for all the really great thoughts! I definitely see where folks are coming from. To help make things concrete, let me frame an example:

In my mind there are two sides to this, and they approach the same issue:

  • Versioning
  • Deprecation

Let's say I'm GiveWP and I have a givewp/create-donation ability. Later, we add (true story) Campaigns, and make it so that every donation must be associated with a campaign via a campaign_id. Ok, so now we need to update the ability. This means two things:

First, I'll need to create a new ability so the old one still works to at least some extent, or for a certain period of time. This presents the new dilemma: what do I name the ability? givewp/create-donation-with-campaign? givewp/cteate-donation-final? It's still the same thing, it just has different input and output parameters.
Second, I want to communicate this change to users. They should switch to the new ability. How do I communicate that an ability will eventually be removed, and how do I communicate what the new ability to use is?

Versioning helps with the first via givewp/v1/create-donation and then givewp/v2/create-donation. Deprecation helps with the second with the ability to mark an ability with deprecation and an ID to the new one.

---

Now, one could argue that in this case GiveWP should somehow make the Ability backwards compatibility. I'm not sure how that would be possible, but in any case it forces architectural philosophy on the developer. We don't know them or their market, so I want to avoid that.

I like @justlevine's (on trac) creative approach to include versioning as part of a single ability, but this actually touches on a point I just made elsewhere today: for maximize an Ability's chances of working well multi-contextually, simplicity is key. Having Abilities that grow in complexity like that hurts discoverability and reduces the chances of it working easily in many contexts.

As a note, it "subdomains" in the Ability name is effectively what's being proposed here, at least in its simplest form. I'm leery about trying to shove in too much of a versioning concept, which is why I'm presenting a "versioned" Ability as actually multiple Abilities. It's not one that's versioned; it's many that include an optional version as part of the namespace. I emphasize optional because if in core, for example, we philosophically do not want to version and commit to backwards compatibility, then we can omit the version and call it a day.

The closest I get to a concept of versioning is (and I definitely won't die on this hill) the idea of a "latest" Ability. That is, if we have givewp/v1/create-donation and givewp/v2/create-donation then givewp/latest/create-donation would always point to the last version. The only reason this comes to mind is for cases like MCP where it would be better to only create a tool for the latest Ability. But, honest, I think this is quite optional.

I'd love to hear from folks using this GiveWP example as reference, or any other scenarios others want to introduce, to help this conversation be as concrete as possible. 😄

@jorgefilipecosta commented on PR #10593:


4 weeks ago
#8

Sharing here for discussion an alternative approach following the same pattern we use for blocks in Gutenberg, which has been working for many years.

The attributes of a block may also change, some removed, some added, but we need to keep supporting blocks created using old versions relying on old attributes, etc. So it is a very similar scenario.

Basically, abilities would not specify a version; they would be registered normally but could have an array with old deprecated abilities:

wp_register_ability(
	'plugin/my-ability',
	array(
		'label'               => __( 'Get Environment Info' ),
		'description'         => __( 'Returns core details about the site\'s runtime context for diagnostics and compatibility (environment, PHP runtime, database server info, WordPress version).' ),
		'category'            => $category_site,
		'input_schema'        => array(...),
		'output_schema'       => array(...),
		'execute_callback'    => ...,
		'permission_callback' => ...,
		'deprecated' => array(
			// Deprecated in version 1.1
			array(
				'input_schema'       => array(...),
				'output_schema'      => array(...),
				'execute_callback'   => ...,
				'permission_callback' => ...,
			),
			// Deprecated in version 1.0
			array(
				'input_schema'       => array(...),
				'output_schema'      => array(...),
				'execute_callback'   => ...,
				'permission_callback' => ...,
			),
		),
	)
);

Consumers of an ability relying on a specific input or output schema: e.g., because they programmatically use the output or because they document the input schema in an LLM prompt, would specify the version of the schemas they are relying on:

$result = wp_execute_ability( 'plugin/my-ability', $args, $schemas );

If the expected schema does not match the current one, the system would look for a deprecated version matching the expected schemas and use the corresponding callbacks. If no schemas are specified, it would use the current version, provided the input args match its schema; otherwise, it would automatically use a deprecated version whose schema matches the input args.

For example renaming an input parameter would be easy deprecation calling the new callback mapping the old name to the new one. For people using the old input things would still automatically work even if they don't specify the schema they are expecting version etc.

@jason_the_adams commented on PR #10593:


4 weeks ago
#9

Ohhh, now that's fascinating, @jorgefilipecosta. I'll have to chew on that. 🤔

#10 @artpi
4 weeks ago

I am honestly conflicted about this.
I can empathize with @justlevine tring to keep things simple, but i also think there is a route where we will need just oldshchool versions.

We have 3 concerns here.

1: When abilities are called in a single shot

When you call the ability in a single shot - like in MCP, in a programatic way. Single shot means that the ability is both discovered and executed in the same session.

  • Don't care about the versions
  • You want to use the newest version of the ability
  • You don't want to deal with versions in the ability id

2: When Marketing your ability

  • You want a recognizeable name for your ability so you can promote it in the ecosystem
  • You don't want the version in the id

3: When dependening on an ability

Let's say plugin B is using an ability provieded by plugin A:
You very often want to be able to pin an ability version that behaves in a predictable way.

  • If you are writing system prompt instructing the LLM to use the ability in a certain way or give it instructions to behave in a certain way
  • You are using ability in a larger workflow where output of one ability flows to the other and you depend on it the shape of input/output.

So really the ideal system would:

  • Keep ids stable
  • Introduce an option to run a legacy ability if provided.

As such, commenting on @jorgefilipecosta comment:

Sharing here for discussion an alternative approach following the same pattern we use for blocks in Gutenberg, which has been working for many years.

I like the idea of trying to have different apis behave in the same way.

So in that scenario:

  • plugin B is using an ability X provieded by plugin A:
  • Developer of plugin A is introducing new behaviour of Ability X.
  • They also attach the old behaviour so that when it is detected, legacy callback gets called instead of default one?

The core difference between abilities and blocks is that the abilities can haev a long dependency chain with cascading failures while the blocks when they fail, they fail locally and the worst that can happen is visual problems.

Lets imagine abilities used for core financial operations: Handling floats in financial operations is something we had to learn the hard way multiple times in WooCommerce:

  • Old: SUM = ( float a + float b ).toString() + "$"
  • New: SUM = ( ( float a * 100 + float b * 100 ) / 100 ).toString() + "$"

If you don't believe me, try it yourself:

const a = 0.1, b = 0.2;

const oldSum = (a + b).toString() + "$";
const newSum = (((a * 100) + (b * 100)) / 100).toString() + "$";

console.log(oldSum); // "0.30000000000000004$"
console.log(newSum); // "0.3$"

Now developer of plugin B has accounted for the old behaviour in their logic, but now developer A introduces this new behaviour and suddenly financial math is off. There was no schema changes, and yet everything changed.

I still think the ability to introduce a breaking change will be needed, and clever duck typing is not going to solve this problem.

It really sounds like a good fit for an additional version field:

  • Keeps ids stable
  • Developer can choose to provide version or not
  • They can choose to bump it if behaviour is breaking
  • When they do, they have to ship all the previous versions too.
  • The developer depending on an ability can choose to pin a version they depend on
wp_get_ability( 'artpi/sum', 1 );

@justlevine commented on PR #10593:


4 weeks ago
#11

Pull Requests [...] are only used for code review.

If possible, can we keep the discussion on the PR scoped to the specific implementation of version that's in the PR, and save the general discusion for Trac?

As a reminder, the connection to trac is one-directional. E.g. there's a lovely reply from @artpi and it would be a shame for others who inadvertently think this is the place to participate miss his replies or other folks 🙇

#12 @justlevine
4 weeks ago

@JasonTheAdams - illustrative examples are always helpful 🙇

I want to highlight that the "two sides" you're describing aren't deprecation and versioning, but rather deprecation and naming things - which as one of the hardest parts about development seems pretty beyond the scope of what we can solve with our API and so IMO shouldn't be given as a reason to compromise on DX.

(I think you get to this in the end when you noted for you it mostly boils down to "subdomains", but I wanted to be explicit and upfront since it's an important distinction).

Now for givewp/create-donation:

First, I'll need to create a new ability so the old one still works to at least some extent, or for a certain period of time.

This feels like a false premise. Instead of a new ability, you could mark the new campaign_id as schematically optional, but note in the description and deprecation message for an empty value that it will be required in the future.

(As a reminder, versionless schemas are a long-existing engineering pattern. It's not about the specific example, the majority of examples have a versionless path of evolution).

However, let's assume that for some reason you choose to break things with the old ability and create a new one.

This presents the new dilemma: what do I name the ability? givewp/create-donation-with-campaign? givewp/cteate-donation-final? It's still the same thing, it just has different input and output parameters.

This is the generic "naming things" problem. It's the same problem when you want to deprecate a public function, a WordPress hook, etc. Versioning does not solve this problem any differently than other forms of prefixing. You communicate the changes the same way you would with any other rename to a "new" thing. So using versioning for this purpose is all of the baggage without IMO any of the benefits.

The difference with a versionless approach is that by design it's easy and encouraged to not create a new ability but rather evolve the existing one, so you avoid needing to make pedantic differentiations in the first place.

Now, one could argue that in this case GiveWP should somehow make the Ability backwards compatibility. I'm not sure how that would be possible, but in any case it forces architectural philosophy on the developer. We don't know them or their market, so I want to avoid that.

I don't think I followed this. Moving beyond the fact that Abilities are literally meant to bring WordPress-levels of backwards compatibility to 3rd party APIs, how does prioritizing backwards compatibility "force architectural philosophy" on the developer? If a dev is gung ho on a versioning antipattern, they can just as easily choose not to back compat and release a new ability as described. Nothing is changed if we allow any number of %s/ segments versus only a singular v%n/.

Having Abilities that grow in complexity like that hurts discoverability and reduces the chances of it working easily in many contexts.

It's because I agree with this that I'm strongly advocating for a versionless approach. Complexity isn't measured in terms of args (beyond general API best practices — if you've deprecated a dozen args, you might want to cut a new ability), it's measured in terms of how many similar but slightly different abilities you (or your context window) need to juggle. Versionless by default means that changes rarely invalidate docs, best practices, tutorials, but are additive to the existing body of knowledge.

As a note, "subdomains" in the Ability name is effectively what's being proposed here, at least in its simplest form. I'm leery about trying to shove in too much of a versioning concept, which is why I'm presenting a "versioned" Ability as actually multiple Abilities.

Agree here (as noted up top). Aligning abilities to block naming and limiting them to {plugin_name}/{block_name} was an arbitrary v1 limitation in part because some folks were trying to carve out a use case for Ability Categories. But letting abilities have subdomains — especially and ironically since categorization is now a distinct concept — lets folks who want to use a versioning antipattern (and many other possible use cases) do so without forcing a specific design pattern on everyone.

IMO we should detwine the topics, and move the versioning discussion out of both the namespace and this ticket, and refocus on generally allowing


@jorgefilipecosta - thanks for this suggestion and making the parallel to blocks.

In addition to addressing a similar need as us, IIRC (so please correct me) the specific API shape there evolved as a result of release pressures and API limitations with the original implementations, as headaches around block deprecations were one of the major pain points developers, agencies, and enterprises cited for years as reasons against migrating to blocks.

So while it's matured very well — and might be worth doing here even just for the sake of consistency — I'd want to take a beat and consider what a holistic approach would look like for Abilities, and their differing needs. (Still very much rather this approach than the versioning ideas being thrown around).

My gut concern with a separate deprecated array of snapshots (? or would we do this composably so you only include the diff in the lower levels?) would boil down to discoverability. Unlike with GB APIs where we just want to map and transform data, the input/output schemas are meant for both human and agentic inference. Would these deprecations map to a one-of (and if so how do we indicate the priority), or are we clobbering them all down to a single schema (which ties back to @jasontheadams's concern about complexity/schema creep and sabotaging our context windows)? How do we indicate (to humans or traditional/inference machines) what the migration path is from deprecated value to current? etc.

We can probably come up with answers to these, or more holistic alternatives might be blocked by 6.9 tech debt, and I hope we explore it further.

---

@artpi as previously noted (and illustrated by the woo example) versioning is a mechanism for communicating behavior — in and of itself it does not guarantee backwards compatibility or predictable behavior, though it can be coopted to do so.

Using that to add some (opinionated) nuance to your helpful breakdown of concerns:

3: When depending on an ability

Let's say plugin B is using an ability provided by plugin A:
You very often want to be able to pin an ability version that behaves in a predictable way.

The hidden premise here is "pin an ability version". I believe it should be rephrased as "You very often want to ensure an ability behaves in a predictable way."

Versionless schemas — and many other API patterns for backcompat (e.g. most parts of WordPress core) — solve this without needing to pin versions.

Lets imagine abilities used for core financial operations: Handling floats in financial operations is something we had to learn the hard way multiple times in WooCommerce:

Perfect example, thanks. I want to highlight that headaches around WooCommerce's breaking ecosystem changes since ditching SemVer was a huge motivator and specific design influence for almost everything about the Abilities API.

The crux of the problem was that There was no schema changes, and yet everything changed. If everything changed, then Woo should have "changed the schema" or provided a method for backcompat. Neither versioning nor the Abilities API can full stop a developer for shipping a breaking change in behavior (that's the purpose of unit tests), but what the Abilities API does — regardless of versioning — is ensure that the only way you can break the API Contract (i.e. consumer expectation) is with an intentional schema change.

Here's btw what that could have looked traditionally (PHP pseudocode) without any Abilities schema enforcement:

<?php
// conditional arg to support legacy behavior.
handle_financial_operation( ...$existing_args, $legacy_float_mode: bool = false ) {
    if ( $legacy_float_mode === false && Woo::initial_install_date( $before_this_change ) ) {
        _deprecated_arg( 'We fixed floats, fix before the breaking release when we change the default behavior.' );
        ...
    }
    // new behavior
}

// or we deprecate the entire method.
handle_financial_operation( ...$existing_args ) {
    // if you're really feeling the PM pressure to version.
    _deprecated_method( 'Use Woo\V2::handle_financial_operation() instead');

    // Same as before we can choose to support the old behavior or after a time coerce to new behavior etc.
}

// or we could just create separate functions.
calculate_financial_operation( ...$new_args ) {
    // new behavior
}

(I hope it's clear how that can be translated conceptually to Abilities, either with @jorgefilipecosta's approach or mine.)

It really sounds like a good fit for an additional version field:
...
wp_get_ability( 'artpi/sum', 1 );

This is the anti-pattern I'm trying to avoid here.

(Although as mentioned higher in the thread, at least it's relegated back to meta and no longer poisoning namespace semantics 😅).

With this change, instead of versions being an optional and voluntary way to handle the "naming things" issue it's now a hard-coded, public API requirement. Every consumer needs to be on the lookout and able to handle versioning on every single ability — people and agents wasting cognitive load (and context window) checking potential breaking v2 whose existence is permanently inferred from the existence of version.


From replies above it seems some folks think I'm proposing a philosophical idea and not a design pattern, so to help visualize my suggestion in terms of code instead of concepts, I'm proposing (pseudocode):

<?php
wp_register_ability(
    'plugin/my-ability',
    array(
        'label'               => __( 'My ability' ),
        'description'         => __( 'My ability.' ),
        'input_schema'        => array(
            ...$unchanged_fields,
            'my_old_input' => array(
                'type'        => 'string',
                'description' => __( 'Old input field, deprecated.' ),
                // One possibility: a minimally viable ?string with a semantic message.
                'deprecation_reason' => __( 'Use "my_new_input" instead. This field will be removed in a future release.' ),
            ),
            'my_new_input' => array(
                ...$field_definition
            )
        ),
        'output_schema'       => array(
            ...$same_concept_as_input_fields
        ),
        'execute_callback'    => static function ( $input ) {
            // Developers can do WHATEVER they want here to route to internal behavior or drive results,
            // yet NONE of it is a breaking change, or anything a downstream consumer needs to worry about.

            // If developers _choose_ to break behavior, use versions (from meta) to route deprecated behavior,
            // or use flags-as-args, it's entirely immaterial to API
        }
    )
);

Obviously we can make this more sophisticated (e.g. a deprecation array with a since, reason, replacement) for more agentic consumption — if justified — I'm just trying to illustrate how simple it is to go the versionless route as best practice while still allowing for developers to build whatever versioning system they want.

@JasonTheAdams - from a discovery and DX perspective, the way GraphQL handles deprecated fields is via progressive disclosure (another way versionless aligns with agentic requirements). Deprecated fields are only visible for introspection, so they'll still work internally, but only show up in tooling or agentic requests if the entire schema with deprecations is requested.

Programmatically, the implementation question for doing the above is how to handle our existing reliance on rest_sanitize_value_for_schema() for validation. While even modern JSON Schema supports at minimum a deprecated field, we're using WP_Rest-flavored JSON. We could do a pre-loop to map/strip our dx to additionalProperties or similar engineering solutions to evaluate.


tl;dr I'm hoping for consensus on the following decision:

Anything versioning offers can be implemented gratis by developers if we take a versionless approach to deprecations.

However, adopting versioning forces all developers to version, and the shortcomings with that are numerous and well documented
(even if you personally disagree with how severe they are or whether the pros outweigh the cons of various approaches some cases).

If/once agreed, we can detangle the two conversations (tickets?) into:

  1. a holistic approach to (versionless) deprecations
  2. Opening up ability to namespace subdomains of any shape. (That it supports folks who want to implement versioning via the name, and not via meta - if at all - is just a "happy" side effect.)

This would let us more effectively handle both in time for 7.0b1

This ticket was mentioned in PR #10848 on WordPress/wordpress-develop by @jorgefilipecosta.


9 days ago
#13

This PR Expands ability name validation from exactly 2 segments (namespace/ability) to 2-4 segments, enabling names like my-plugin/resource/find and my-plugin/resource/sub/find.

## Details

The current ability naming convention only allows a single namespace separator (my-plugin/my-ability). This is too restrictive for organizing abilities into logical resource groups. With this change, plugins can register abilities like core/posts/find, core/posts/create, or my-plugin/resource/sub/action.

The validation regex in WP_Abilities_Registry::register() changes from:

/^[a-z0-9-]+\/[a-z0-9-]+$/

to:

/^[a-z0-9-]+(?:\/[a-z0-9-]+){1,3}$/

This allows the first segment plus 1-3 additional slash-delimited segments (2-4 total). The REST API route patterns in both controllers already accepted multiple slashes, so no route changes were needed.

This PR comes from a suggestion from @justlevine at https://github.com/WordPress/wordpress-develop/pull/10665#issuecomment-3769756790 :

Necessary caveats out of the way, I'd recommend we solve the naming issue holistically with nested namespaces, a pattern that because it's good for human cognitive overload, which just so happens to mean that it aligns with inference best practices too. (related: core.trac.wordpress.org/ticket/64345#comment:5).

Which would turn these into:

core/posts/find or get (As noted inline, I think getting a single post is superfluous, and both use cases can be more intuitively handled with an improved input shape, e.g. by ).

core/posts/create
core/posts/update

And we plan to use this new type of naming for post abilities.

cc: @justlevine, @JasonTheAdams

## Test plan

  • [ ] Run npm run test:php -- --group abilities-api and verify all tests pass
  • [ ] Verify existing 2-segment ability names (core/get-site-info, etc.) continue to work
  • [ ] Verify 3-segment names like test/math/add can be registered and executed via REST
  • [ ] Verify 5-segment names are rejected with a clear error message
Note: See TracTickets for help on using tickets.