[wp-trac] [WordPress Trac] #64345: Ability names should support versioning

WordPress Trac noreply at wordpress.org
Sat Jan 17 17:32:59 UTC 2026


#64345: Ability names should support versioning
--------------------------------------+------------------------------
 Reporter:  jason_the_adams           |       Owner:  (none)
     Type:  enhancement               |      Status:  new
 Priority:  normal                    |   Milestone:  Awaiting Review
Component:  AI                        |     Version:  trunk
 Severity:  normal                    |  Resolution:
 Keywords:  has-patch has-unit-tests  |     Focuses:
--------------------------------------+------------------------------

Comment (by justlevine):

 @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 [https://www.namingthings.co/ 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
 <?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
 <?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

-- 
Ticket URL: <https://core.trac.wordpress.org/ticket/64345#comment:12>
WordPress Trac <https://core.trac.wordpress.org/>
WordPress publishing platform


More information about the wp-trac mailing list