Skip to content

Plugin Directory: Treat a new ref at the same version as a new release - #808

Open
obenland wants to merge 1 commit into
WordPress:trunkfrom
obenland:release-newness-includes-stable-tag
Open

Plugin Directory: Treat a new ref at the same version as a new release#808
obenland wants to merge 1 commit into
WordPress:trunkfrom
obenland:release-newness-includes-stable-tag

Conversation

@obenland

Copy link
Copy Markdown
Member

update_single_plugin() decided whether a release was "new" — and therefore whether it had to pass through the cooldown and block gates — from the served version alone:

$is_new_version = $version !== $update_source_version; // stable_tag never compared

So a new ref carrying the same Version header (tag A → tag B, or a tag↔trunk flip, all at the same version) had is_new_version = false, skipped both gates, and wrote its different content straight into update_source. With the release cooldown live in production, this defeated the window the security scan needs: the candidate shipped immediately, and the later high-risk callback couldn't un-ship an already-served version. A one-line SVN action (re-tag the same version) walked past the entire gate.

What it does

  • Newness is now a change to the served version OR the served ref: update_single_plugin() reads update_source.stable_tag alongside version and compares both. A same-version ref change is recognised as a new release and passes through the cooldown and block gates like any other. This aligns the write path with cron_trigger()'s out-of-date query, which already treats a stable_tag change as needing an update.
  • The varchar(128) truncation allowance already applied to the version column now extends to stable_tag too, so a long ref that stores truncated doesn't read as perpetually-new.
  • $is_new_version is renamed $is_new_release at its three use sites to match the broadened meaning; the current stable tag is read once and reused for the row write.

Relationship to #807

This is the newness half of making (version, ref) the canonical release identity; #807 is the resolution half (resolving the held release from the stable tag). They compose: with #807 also in place, this closes the same-version ref-change bypass for arbitrary tag names (where the new tag's name differs from the version, which update_single_plugin's version-keyed get_release() can't otherwise resolve). On its own, this PR already covers the tag↔trunk transitions, where the ref is resolvable by version. It's independently correct and independently mergeable.

Not addressed here (separate, larger work): binding a scan verdict to the scanned bytes (mutable ZIP), gating the direct-download / new-install surface, and the callback/cooldown races.

Testing

tests/Release_Newness_Test.php (3 tests): a new ref at the already-served version is deferred by the cooldown (not written), a blocked new-ref-same-version is held out of the row with its deferred serve cancelled, and an unchanged release (same version and ref) is not spuriously deferred. Verified the first two fail against the previous version-only newness. A pre-existing truncation hold test was made ref-consistent so it still isolates version truncation (and now also exercises the stable_tag truncation path).

🤖 Generated with Claude Code

update_single_plugin() decided newness from the served version alone, so a
new tag (or a tag<->trunk flip) carrying the same Version header skipped the
cooldown and block gates and shipped its different content immediately — the
scan could not hold it. Newness now also compares the served stable tag, so
a same-version ref change passes through the gates like any other release.

The truncation guard extends to the stable_tag column, mirroring the version
column's varchar(128) allowance. A pre-existing hold test that left stable_tag
unset now sets it, so it isolates version truncation as intended.

Combined with the stable-tag release resolution in WordPress#807, this closes the
same-version ref-change bypass for arbitrary tag names; on its own it already
covers tag<->trunk transitions.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Copilot AI lite review requested due to automatic review settings August 13, 2026 21:29

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@github-actions

Copy link
Copy Markdown

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

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

Props obenland.

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants