Skip to content

feat(myyoast): gradual rollout of the MyYoast OAuth connection#23376

Open
diedexx wants to merge 3 commits into
trunkfrom
DE/rt-1208-gradual-rollout
Open

feat(myyoast): gradual rollout of the MyYoast OAuth connection#23376
diedexx wants to merge 3 commits into
trunkfrom
DE/rt-1208-gradual-rollout

Conversation

@diedexx

@diedexx diedexx commented Jun 19, 2026

Copy link
Copy Markdown
Member

Warning

This PR enabled the MyYoast connection feature flag for a small percentage of sites. Merge when the feature is ready or change the rollout share to 0.

Context

The MyYoast OAuth/OIDC connection ships behind the YOAST_SEO_MYYOAST_CONNECTION feature flag and will roll out to the 10M+ sites running Yoast SEO. Flipping it on globally would let every site attempt Dynamic Client Registration (DCR) against MyYoast on potentially the same day, with no production traffic shape to validate against. This PR makes the rollout gradual and adds the plugin side of the server's emergency brake, so we can enable the connection for a small, slowly widening share of sites and react before a problem reaches the full install base.

This is the plugin half of the issue's recommended hybrid (Option D): a deterministic plugin-side cohort for the broad shape, plus the server-side 503 brake (already shipped in Yoast/Platform#4557) as the real-time panic button. The machinery is finite by design — once the connection reaches 100% with no regressions, the cohort conditional reverts to a plain feature-flag conditional and is removed in a follow-up cleanup.

Summary

This PR can be summarized in the following changelog entry:

  • Adds a gradual, deterministic per-site rollout for the MyYoast OAuth connection and handles the server's temporary registration brake.

Relevant technical choices:

  • Constant is an override, cohort is the default. Gradual_Rollout_Conditional (a new abstract Feature_Flag_Conditional subclass) treats a defined YOAST_SEO_<FEATURE> constant as an explicit override (true forces on, false forces off). When the constant is not defined, a stable hash of the feature name + site_url() (crc32 % 1000) buckets the site into or out of the current rollout share (per-mille, 0–1000). MyYoast_Connection_Conditional extends it and ships at a 1% share.
  • No filter / no remote config. The wp-config constant is the per-site override, so no runtime filter or remote toggle is needed — this keeps the rollout within wordpress.org plugin guidelines (no phoning home for feature state).
  • Feature name in the hash avoids permanently "lucky" sites: a site that buckets low for one feature is not automatically early for every feature.
  • 503 temporarily_unavailable is display-only. do_register() detects the server's registration brake and throws a typed Registration_Temporarily_Unavailable_Exception (a Registration_Failed_Exception subtype, so existing handlers keep working) carrying the Retry-After hint. Nothing schedules or enforces a wait; the HTTP client's existing backoff path is intentionally left untouched.
  • Reusable by design. Gradual_Rollout_Conditional can and should be used for more similar features that we want to roll out to a smaller group first.

Test instructions

Test instructions for the acceptance test before the PR gets merged

This PR can be acceptance tested by following these steps:

  • Override path: with define( 'YOAST_SEO_MYYOAST_CONNECTION', true ); in wp-config.php, the connection's WP-CLI commands (run wp yoast auth --help to test) load (constant forces the feature on); with false, they do not.

  • Cohort path: with the constant removed from wp-config.php, the connection is enabled only for sites whose crc32( 'MYYOAST_CONNECTION' . site_url() ) % 1000 is below the 1% share (10). Most dev sites fall outside the cohort, so the connection stays inactive without the constant.

  • Cohort path: edit MyYoast_Connection_Conditional. change the 10 into 1000. Verify that the MyYoast connection features are now available again

  • Registration brake: engage the server brake (per Yoast/Platform#4557) so DCR returns 503 temporarily_unavailable, then run wp yoast auth register. It reports "Registration is temporarily unavailable. Try again in N seconds." (or "Try again later." when no Retry-After is sent) instead of a generic failure. (coordinate with @diedexx or the Platform team).

Relevant test scenarios

  • Changes should be tested with the browser console open
  • Changes should be tested on different posts/pages/taxonomies/custom post types/custom taxonomies
  • Changes should be tested on different editors (Default Block/Gutenberg/Classic/Elementor/other)
  • Changes should be tested on different browsers
  • Changes should be tested on multisite

Multisite: the cohort hashes site_url(), which is per-blog, so each subsite buckets independently (the intended behavior, matching the per-blog DCR registration model).

Test instructions for QA when the code is in the RC

  • QA should use the same steps as above.

QA can test this PR by following these steps:

Impact check

This PR affects the following parts of the plugin, which may require extra testing:

  • The MyYoast_Connection_Conditional now loads its integrations (key-rotation cron, cleanup, WP-CLI) for cohort sites with no YOAST_SEO_MYYOAST_CONNECTION constant set, where previously only an explicit true enabled them. The cohort ships at 1%.
  • The MyYoast Dynamic Client Registration flow (do_register) — a new typed exception on the 503 path; the 201/other-error/transport-failure paths are unchanged.

Other environments

  • This PR also affects Shopify. I have added a changelog entry starting with [shopify-seo], added test instructions for Shopify and attached the Shopify label to this PR.
  • This PR also affects Yoast SEO for Google Docs. I have added a changelog entry starting with [yoast-doc-extension], added test instructions for Yoast SEO for Google Docs and attached the Google Docs Add-on label to this PR.

Documentation

  • I have written documentation for this change. For example, comments in the Relevant technical choices, comments in the code, documentation on Confluence / shared Google Drive / Yoast developer portal, or other.

Quality assurance

  • I have tested this code to the best of my abilities.
  • During testing, I had activated all plugins that Yoast SEO provides integrations for.
  • I have added unit tests to verify the code works as intended.
  • If any part of the code is behind a feature flag, my test instructions also cover cases where the feature flag is switched off.
  • I have written this PR in accordance with my team's definition of done.
  • I have checked that the base branch is correctly set.
  • I have run grunt build:images and committed the results, if my PR introduces or edits images or SVGs.

Innovation

  • No innovation project is applicable for this PR.
  • This PR falls under an innovation project. I have attached the innovation label.
  • I have added my hours to the WBSO document.

Fixes Yoast/reserved-tasks#1208

diedexx and others added 2 commits June 19, 2026 11:57
Adds Gradual_Rollout_Conditional, an abstract feature-flag conditional whose
default state is a deterministic, slowly widening rollout across a share of
sites. The YOAST_SEO_<FEATURE> constant stays an explicit override (defined
true forces on, false forces off); when it is not defined, a stable hash of
the feature name plus the site URL buckets the site into or out of the current
rollout share (per-mille, 0-1000). Mixing the feature name into the hash avoids
permanently "lucky" sites across features.

MyYoast_Connection_Conditional extends it and ships at a 1% share. The machinery
is disposable: at a 100% share the conditional reverts to extending
Feature_Flag_Conditional directly and this class can be removed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
When the MyYoast server engages its registration emergency brake it returns
503 temporarily_unavailable on Dynamic Client Registration. do_register() now
detects this and throws a typed Registration_Temporarily_Unavailable_Exception
(a Registration_Failed_Exception subtype, so existing handlers keep working)
carrying the server's Retry-After hint. The hint is display-only: the WP-CLI
register command shows when to try again, but nothing schedules or enforces a
wait, and the HTTP client's backoff path is intentionally left untouched.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@diedexx diedexx added innovation Innovative issue. Relating to performance, memory or data-flow. changelog: non-user-facing Needs to be included in the 'Non-userfacing' category in the changelog labels Jun 19, 2026
@diedexx diedexx requested a review from Copilot June 19, 2026 10:45

Copilot AI 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.

Pull request overview

Introduces a deterministic, per-site gradual rollout mechanism for the MyYoast OAuth connection (instead of a global flip of YOAST_SEO_MYYOAST_CONNECTION), and adds a typed error path for the server-side “registration brake” (HTTP 503 temporarily_unavailable) including a display-only Retry-After hint surfaced via WP-CLI.

Changes:

  • Added Gradual_Rollout_Conditional to enable feature flags for a stable per-mille cohort when no YOAST_SEO_<FEATURE> constant override is defined.
  • Switched MyYoast_Connection_Conditional to use gradual rollout (currently 1% / 10 per-mille).
  • Added Registration_Temporarily_Unavailable_Exception, wired it into DCR registration handling, and added/updated unit tests (including WP-CLI messaging behavior).

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
src/conditionals/gradual-rollout-conditional.php New conditional implementing deterministic cohort rollout when no constant override is set.
src/conditionals/myyoast-connection-conditional.php Moves MyYoast connection gating to the new gradual rollout conditional and sets rollout share.
src/myyoast-client/infrastructure/registration/client-registration.php Detects 503 temporarily_unavailable, throws typed exception, parses Retry-After.
src/myyoast-client/application/exceptions/registration-temporarily-unavailable-exception.php New exception type carrying optional retry delay.
src/myyoast-client/user-interface/auth-command.php WP-CLI command surfaces “temporarily unavailable” with retry hint.
tests/Unit/MyYoast_Client/Infrastructure/Registration/Client_Registration_Test.php Adds unit coverage for the new 503 brake behavior and Retry-After parsing.
tests/Unit/Conditionals/Gradual_Rollout_Conditional_Test.php New unit tests for rollout cohort logic and constant override behavior.
tests/Unit/Doubles/Gradual_Rollout_Conditional_Double.php Test double used to exercise the abstract rollout conditional.
tests/Unit/Conditionals/MyYoast_Connection_Conditional_Test.php Updates expectation/documentation to reflect cohort fallback behavior.

Comment thread src/conditionals/myyoast-connection-conditional.php
Comment thread tests/Unit/Conditionals/Gradual_Rollout_Conditional_Test.php Outdated
@coveralls

coveralls commented Jun 19, 2026

Copy link
Copy Markdown

Coverage Report for CI Build 1

Coverage decreased (-0.5%) to 53.23%

Details

  • Coverage decreased (-0.5%) from the base build.
  • Patch coverage: 30 uncovered changes across 5 files (6 of 36 lines covered, 16.67%).
  • No coverage regressions found.

Uncovered Changes

File Changed Covered %
src/myyoast-client/infrastructure/registration/client-registration.php 10 0 0.0%
src/conditionals/gradual-rollout-conditional.php 13 5 38.46%
src/myyoast-client/application/exceptions/registration-temporarily-unavailable-exception.php 5 0 0.0%
src/myyoast-client/user-interface/auth-command.php 5 0 0.0%
src/conditionals/myyoast-connection-conditional.php 3 1 33.33%

Coverage Regressions

No coverage regressions found.


Coverage Stats

Coverage Status
Relevant Lines: 67745
Covered Lines: 35839
Line Coverage: 52.9%
Relevant Branches: 16577
Covered Branches: 9046
Branch Coverage: 54.57%
Branches in Coverage %: Yes
Coverage Strength: 44419.48 hits per line

💛 - Coveralls

- Correct the MyYoast_Connection_Conditional docblock: the rollout ships at 1%,
  not 0, matching the returned share.
- Simplify the boundary-test skip guard: the bucket is always 0-999 (% 1000),
  so the only clamp case to skip is a bucket of 0.

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

changelog: non-user-facing Needs to be included in the 'Non-userfacing' category in the changelog innovation Innovative issue. Relating to performance, memory or data-flow.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants