Skip to content

Plugin Directory: Let reviewers block a release from being served#728

Draft
obenland wants to merge 2 commits into
WordPress:trunkfrom
obenland:feature/reviewer-release-block
Draft

Plugin Directory: Let reviewers block a release from being served#728
obenland wants to merge 2 commits into
WordPress:trunkfrom
obenland:feature/reviewer-release-block

Conversation

@obenland

@obenland obenland commented Jul 22, 2026

Copy link
Copy Markdown
Member

Summary

Reviewers could already skip the release cooldown, but had no way to hold a release back short
of closing the whole plugin. This adds a Block control to the Controls metabox, alongside
the force-release one and sharing a single reason field, so a version still inside its cooldown
can be held out of update_source — the previously served version keeps being served — until a
reviewer force-releases it.

This is the plumbing half of #720, which is now split in two. The scan-driven half is #729.

How it works

  • The holdAPI_Update_Updater::block_release(), the counterpart to force_release(),
    records a release_block on the release row. A single gate in update_single_plugin()
    honours it, so both the deferred cooldown cron and the backup reconciliation cron keep the
    version out of update_source.
  • Only where there's something to hold — a version that's already live can't be un-shipped.
    A block on one is refused rather than recorded as a hold that never takes effect.
  • Unblock — a reviewer force-release clears the block and serves the version; the override
    is recorded in the audit log.
  • Reviewer visibility — the metabox shows a blocked-state notice alongside the
    force-release control, and the Block button appears only while a version is still cooling
    down.

The sync_availability() fix

Holding a version back means update_single_plugin() can't rewrite the row at all — every
version-specific column describes the held version, not the served one.

That was already true of the release cooldown, and it means a status change never reached
sites while one was in flight
: closing a plugin whose next version was deferred left
update_source serving the current version with available = 1. A block makes that unbounded
rather than a few hours, which is what surfaced it.

sync_availability() now writes the availability and closure fields on their own and leaves the
served version alone. Worth reviewing on its own terms — it fixes live behaviour that predates
this feature.

Tests

Reviewer_Release_Block_Test covers the block through the same path the metabox uses: the hold,
the audit notes, the refusals, the metabox save handler including its capability check, and
closing/re-opening a plugin while a version is held. Its fixture lives in
Release_Block_Test_Case, resolved by a small autoloader in the test bootstrap so the shared
bases don't need requiring in dependency order.

170 tests pass locally against environments/plugin-directory.

Notes for reviewers

🤖 Generated with Claude Code

obenland and others added 2 commits July 22, 2026 13:43
The test environment was missing three things the development environment
already had, which between them made whole classes of test impossible:

- `update_source` was never created, so anything touching the update API's
  table failed.
- `wp-content/env-bin` was not mapped, so `database-tables.sql` was not
  reachable from the test container to import.
- `PLUGINS_TABLE_PREFIX` was undefined, so `API\Base::load_routes()` fatalled
  and no `plugins/v1` route was registered.

Shared verbatim with WordPress#717; whichever of the two PRs lands second should drop
this commit.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UNnLdnE7etihGNvsWY1qGE
Reviewers could already skip the release cooldown, but had no way to hold a
release back short of closing the whole plugin. Add a Block control to the
Controls metabox, alongside the force-release one and sharing a single reason
field, so a version still inside its cooldown can be held out of `update_source`
until it's force-released.

- API_Update_Updater::block_release() is the counterpart to force_release(): it
  records a `release_block` on the release row, and a single gate in
  update_single_plugin() honours it, so both the deferred cooldown cron and the
  backup reconciliation cron keep the version out of `update_source`.
- A version that's already live can't be un-shipped, so a block on one is
  refused rather than recorded as a hold that never takes effect.
- force_release() clears the block and notes the override in the audit log.

Holding a version back means update_single_plugin() can't rewrite the row at
all, since every version-specific column describes the held version. That was
already true of the cooldown, and it means a status change never reached sites
while one was in flight: closing a plugin whose next version was deferred left
`update_source` serving the current version with `available = 1`. A block makes
that unbounded rather than a few hours, so sync_availability() now writes the
availability and closure fields on their own and leaves the served version
alone.

Covered by Reviewer_Release_Block_Test, which exercises the block through the
same path the metabox uses, including closing and re-opening a plugin while a
version is held. Its fixture lives in Release_Block_Test_Case, resolved by a
small autoloader in the test bootstrap.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.

1 participant