Commit bf74658ba5c for woocommerce

commit bf74658ba5ce3806b0694d1bca4414d1ad69c62f
Author: Karol Manijak <20098064+kmanijak@users.noreply.github.com>
Date:   Fri Aug 21 14:47:41 2026 +0200

    Enable variation galleries for all stores (#67884)

    * Enable variation galleries for all stores

    * Add changelog entry for variation gallery rollout

    * Correct variation gallery rollout version

    * Preserve variation gallery rollout telemetry

    * Preserve variation gallery compatibility while enforcing rollout

    * Simplify variation gallery rollout compatibility

    * Restore variation gallery package declaration order

    * Report effective variation gallery state in telemetry

    * Fix variation gallery migration test isolation

    * Force enable variation gallery on upgrade

    * Refine variation gallery rollout migration

    * Update variation gallery rollout changelog significance

    * Fix variation gallery migration test isolation

    * Preserve variation gallery feature compatibility

    * Restore variation gallery legacy option mapping

    * Handle network-active merged feature plugins

    * Normalize active plugin option before merging

    * Remove deprecated variation gallery feature option

    * Remove obsolete variation gallery enablement checks

    * Remove obsolete variation gallery option checks

diff --git a/plugins/woocommerce/changelog/codex-variation-gallery-100 b/plugins/woocommerce/changelog/codex-variation-gallery-100
new file mode 100644
index 00000000000..050220b6ed6
--- /dev/null
+++ b/plugins/woocommerce/changelog/codex-variation-gallery-100
@@ -0,0 +1,4 @@
+Significance: major
+Type: update
+
+Enable variation galleries for all stores and remove the experimental feature toggle.
diff --git a/plugins/woocommerce/includes/class-wc-install.php b/plugins/woocommerce/includes/class-wc-install.php
index 61f76e0445f..86cfde7f0f9 100644
--- a/plugins/woocommerce/includes/class-wc-install.php
+++ b/plugins/woocommerce/includes/class-wc-install.php
@@ -347,6 +347,9 @@ class WC_Install {
 			'wc_update_1110_cleanup_block_email_posts',
 			'wc_update_1110_flush_product_count_cache',
 		),
+		'11.1.0-1' => array(
+			'wc_update_11101_remove_deprecated_variation_gallery_option',
+		),
 	);

 	/**
diff --git a/plugins/woocommerce/includes/class-wc-product-variable.php b/plugins/woocommerce/includes/class-wc-product-variable.php
index c0a418c5dc8..9c0bb2a8b87 100644
--- a/plugins/woocommerce/includes/class-wc-product-variable.php
+++ b/plugins/woocommerce/includes/class-wc-product-variable.php
@@ -11,7 +11,6 @@
 use Automattic\WooCommerce\Enums\ProductStockStatus;
 use Automattic\WooCommerce\Enums\ProductType;
 use Automattic\WooCommerce\Internal\Utilities\ProductUtil;
-use Automattic\WooCommerce\Internal\VariationGallery\Package as VariationGalleryPackage;

 defined( 'ABSPATH' ) || exit;

@@ -442,18 +441,14 @@ class WC_Product_Variable extends WC_Product {
 		$parent_featured_id       = (int) $this->get_image_id();
 		$parent_featured_valid    = $parent_featured_id && wp_attachment_is_image( $parent_featured_id );

-		$variation_gallery_image_ids = array();
+		$variation_gallery_image_ids = array_values(
+			array_filter(
+				array_map( 'intval', $variation->get_gallery_image_ids() ),
+				'wp_attachment_is_image'
+			)
+		);
 		$variation_gallery_html      = '';

-		if ( VariationGalleryPackage::is_enabled() ) {
-			$variation_gallery_image_ids = array_values(
-				array_filter(
-					array_map( 'intval', $variation->get_gallery_image_ids() ),
-					'wp_attachment_is_image'
-				)
-			);
-		}
-
 		// Prefer variation-owned images over the parent fallback.
 		if ( $variation_featured_valid ) {
 			$selected_image_id = $variation_featured_id;
diff --git a/plugins/woocommerce/includes/wc-template-functions.php b/plugins/woocommerce/includes/wc-template-functions.php
index 4deae04646e..0307f89d3a1 100644
--- a/plugins/woocommerce/includes/wc-template-functions.php
+++ b/plugins/woocommerce/includes/wc-template-functions.php
@@ -2211,11 +2211,8 @@ if ( ! function_exists( 'woocommerce_variable_add_to_cart' ) ) {
 		// Enqueue variation scripts.
 		wp_enqueue_script( 'wc-add-to-cart-variation' );

-		// Attach a reset snapshot only when variation-gallery swaps are enabled.
-		if (
-			\Automattic\WooCommerce\Internal\VariationGallery\Package::is_enabled() &&
-			! isset( $attached_gallery_defaults[ $product->get_id() ] )
-		) {
+		// Attach the reset snapshot once per product.
+		if ( ! isset( $attached_gallery_defaults[ $product->get_id() ] ) ) {
 			wp_add_inline_script(
 				'wc-add-to-cart-variation',
 				sprintf(
diff --git a/plugins/woocommerce/includes/wc-update-functions.php b/plugins/woocommerce/includes/wc-update-functions.php
index dff8e3a251a..06b5fda6f14 100644
--- a/plugins/woocommerce/includes/wc-update-functions.php
+++ b/plugins/woocommerce/includes/wc-update-functions.php
@@ -40,6 +40,7 @@ use Automattic\WooCommerce\Internal\ProductDownloads\ApprovedDirectories\Synchro
 use Automattic\WooCommerce\Internal\Utilities\DatabaseUtil;
 use Automattic\WooCommerce\Internal\Utilities\FilesystemUtil;
 use Automattic\WooCommerce\Internal\Utilities\ProductUtil;
+use Automattic\WooCommerce\Internal\VariationGallery\Package as VariationGalleryPackage;
 use Automattic\WooCommerce\Utilities\StringUtil;
 use Automattic\WooCommerce\Blocks\Options as BlockOptions;
 use Automattic\WooCommerce\Blocks\Utils\BlockTemplateUtils;
@@ -3584,6 +3585,21 @@ function wc_update_1100_enable_point_of_sale_feature() {
 	update_option( 'woocommerce_feature_point_of_sale_enabled', 'yes' );
 }

+/**
+ * Remove the deprecated variation gallery feature option from the database.
+ *
+ * The variation gallery feature flag is deprecated as of 11.1.0 and is now always enabled.
+ * The option is no longer needed as FeaturesUtil::feature_is_enabled('variation_gallery')
+ * returns the deprecated_value directly without reading from the database.
+ *
+ * @since 11.1.0
+ *
+ * @return void
+ */
+function wc_update_11101_remove_deprecated_variation_gallery_option(): void {
+	delete_option( VariationGalleryPackage::ENABLE_OPTION_NAME );
+}
+
 /**
  * Delete the cached dashboard out-of-stock product count.
  *
diff --git a/plugins/woocommerce/src/Blocks/Utils/ProductGalleryUtils.php b/plugins/woocommerce/src/Blocks/Utils/ProductGalleryUtils.php
index 0c1082d767b..422cc2a8bab 100644
--- a/plugins/woocommerce/src/Blocks/Utils/ProductGalleryUtils.php
+++ b/plugins/woocommerce/src/Blocks/Utils/ProductGalleryUtils.php
@@ -2,7 +2,6 @@
 namespace Automattic\WooCommerce\Blocks\Utils;

 use Automattic\WooCommerce\Internal\ProductGallery\ProductMediaGallery;
-use Automattic\WooCommerce\Internal\VariationGallery\Package as VariationGalleryPackage;

 /**
  * Utility methods used for the Product Gallery block.
@@ -442,7 +441,7 @@ class ProductGalleryUtils {
 	 * Decision tree (variation chosen):
 	 * - no variation images → parent featured + parent gallery
 	 * - own featured only → variation featured + parent gallery extras
-	 * - own featured + gallery (flag on) → variation images only
+	 * - own featured + gallery → variation images only
 	 * - gallery only, no own featured (potential AVI shape) → parent featured + variation gallery
 	 *
 	 * @param int   $variation_id          Variation post ID.
@@ -460,12 +459,9 @@ class ProductGalleryUtils {
 		$featured_id    = (int) $variation->get_image_id();
 		$featured_valid = $featured_id && wp_attachment_is_image( $featured_id );

-		$variation_gallery_ids = array();
-		if ( VariationGalleryPackage::is_enabled() ) {
-			$variation_gallery_ids = array_map( 'intval', $variation->get_gallery_image_ids() );
-			$variation_gallery_ids = array_filter( $variation_gallery_ids, 'wp_attachment_is_image' );
-			$variation_gallery_ids = array_values( $variation_gallery_ids );
-		}
+		$variation_gallery_ids = array_map( 'intval', $variation->get_gallery_image_ids() );
+		$variation_gallery_ids = array_filter( $variation_gallery_ids, 'wp_attachment_is_image' );
+		$variation_gallery_ids = array_values( $variation_gallery_ids );

 		// No images from variation - full parent fallback.
 		if ( ! $featured_valid && empty( $variation_gallery_ids ) ) {
diff --git a/plugins/woocommerce/src/Internal/Features/FeaturesController.php b/plugins/woocommerce/src/Internal/Features/FeaturesController.php
index 4fdbacdbc87..d61a80d585b 100644
--- a/plugins/woocommerce/src/Internal/Features/FeaturesController.php
+++ b/plugins/woocommerce/src/Internal/Features/FeaturesController.php
@@ -541,15 +541,14 @@ class FeaturesController {
 			),
 			\Automattic\WooCommerce\Internal\VariationGallery\Package::FEATURE_ID => array(
 				'name'                         => __( 'Variation gallery', 'woocommerce' ),
-				'description'                  => __(
-					'Add multiple images per product variation. Once enabled, the Additional Variation Images extension will be deactivated and its data migrated.',
-					'woocommerce'
-				),
-				'option_key'                   => \Automattic\WooCommerce\Internal\VariationGallery\Package::ENABLE_OPTION_NAME,
-				'is_experimental'              => true,
-				'enabled_by_default'           => \Automattic\WooCommerce\Internal\VariationGallery\Package::is_in_canary_cohort(),
+				'description'                  => __( 'Add multiple images per product variation.', 'woocommerce' ),
+				'is_experimental'              => false,
+				'enabled_by_default'           => true,
+				'disable_ui'                   => true,
 				'skip_compatibility_checks'    => true,
 				'default_plugin_compatibility' => FeaturePluginCompatibility::COMPATIBLE,
+				'deprecated_since'             => '11.1.0',
+				'deprecated_value'             => true,
 			),
 			'wc-visual-attribute'                  => array(
 				'name'                         => __( 'Color swatches for attributes', 'woocommerce' ),
diff --git a/plugins/woocommerce/src/Internal/VariationGallery/Package.php b/plugins/woocommerce/src/Internal/VariationGallery/Package.php
index 286d9fc5142..700697abbd2 100644
--- a/plugins/woocommerce/src/Internal/VariationGallery/Package.php
+++ b/plugins/woocommerce/src/Internal/VariationGallery/Package.php
@@ -39,48 +39,30 @@ class Package {
 	public const ENABLE_OPTION_NAME = 'wc_feature_woocommerce_additional_variation_images_enabled';

 	/**
-	 * `woocommerce_remote_variant_assignment` option name.
-	 */
-	private const REMOTE_VARIANT_OPTION_NAME = 'woocommerce_remote_variant_assignment';
-
-	/**
-	 * Highest variant bucket in the canary cohort. Range is 1-120, so
-	 * `<= 6` gets exactly 5%. Matches the Brands merge precedent.
+	 * Highest variant bucket in the former canary cohort.
+	 *
+	 * @deprecated 11.1.0 The variation gallery is enabled for all users.
 	 */
 	public const CANARY_MAX_VARIANT = 6;

 	/**
-	 * Whether the current store is in the canary cohort.
+	 * Whether the current store is in the former canary cohort.
 	 *
-	 * @internal Removable once the feature is at 100% rollout.
+	 * @deprecated 11.1.0 Use Package::is_enabled() instead.
 	 * @return bool
 	 */
 	public static function is_in_canary_cohort(): bool {
-		$variant_assignment = (int) get_option( self::REMOTE_VARIANT_OPTION_NAME, 0 );
-		return $variant_assignment > 0 && $variant_assignment <= self::CANARY_MAX_VARIANT;
+		wc_deprecated_function( __METHOD__, '11.1.0', __CLASS__ . '::is_enabled' );
+		return self::is_enabled();
 	}

 	/**
-	 * Whether the merged variation gallery feature is enabled for the current
-	 * request.
-	 *
-	 * Explicit `yes`/`no` on the option wins; unset falls back to the canary
-	 * cohort.
+	 * As of WooCommerce 11.1, the variation gallery is enabled for all users.
 	 *
 	 * @return bool
 	 */
 	public static function is_enabled() {
-		$option_value = get_option( self::ENABLE_OPTION_NAME, '' );
-
-		if ( 'yes' === $option_value ) {
-			return true;
-		}
-
-		if ( 'no' === $option_value ) {
-			return false;
-		}
-
-		return self::is_in_canary_cohort();
+		return true;
 	}

 	/**
@@ -98,10 +80,6 @@ class Package {
 	 * @internal
 	 */
 	final public static function init(): void {
-		if ( ! self::is_enabled() ) {
-			return;
-		}
-
 		$container = wc_get_container();
 		$container->get( ClassicVariationGalleryAdmin::class )->register();
 		$container->get( LegacyVariationGalleryCompatibility::class )->register();
diff --git a/plugins/woocommerce/src/Internal/VariationGallery/Telemetry.php b/plugins/woocommerce/src/Internal/VariationGallery/Telemetry.php
index 940c5d656a0..7d5523c1af4 100644
--- a/plugins/woocommerce/src/Internal/VariationGallery/Telemetry.php
+++ b/plugins/woocommerce/src/Internal/VariationGallery/Telemetry.php
@@ -50,9 +50,6 @@ class Telemetry implements RegisterHooksInterface {
 	public static function collect_snapshot(): array {
 		global $wpdb;

-		$option_value         = get_option( Package::ENABLE_OPTION_NAME, '' );
-		$variant_assignment   = (int) get_option( 'woocommerce_remote_variant_assignment', 0 );
-		$cohort               = Package::is_in_canary_cohort() ? 'treatment' : 'control';
 		$legacy_plugin_active = self::is_legacy_plugin_active();
 		$legacy_plugin_file   = WP_PLUGIN_DIR . '/' . self::LEGACY_PLUGIN_FILE;

@@ -105,10 +102,7 @@ class Telemetry implements RegisterHooksInterface {
 		);

 		return array(
-			'feature_enabled'               => Package::is_enabled() ? 'yes' : 'no',
-			'feature_option_explicit'       => '' === $option_value ? 'no' : 'yes',
-			'remote_variant_assignment'     => $variant_assignment,
-			'remote_variant_cohort'         => $cohort,
+			'feature_enabled'               => 'yes',
 			'legacy_avi_plugin_active'      => $legacy_plugin_active ? 'yes' : 'no',
 			'legacy_avi_plugin_installed'   => file_exists( $legacy_plugin_file ) ? 'yes' : 'no',
 			'migrated_variation_count'      => $migrated_variation_count,
diff --git a/plugins/woocommerce/src/Packages.php b/plugins/woocommerce/src/Packages.php
index 9952fd64b69..45d1b52c120 100644
--- a/plugins/woocommerce/src/Packages.php
+++ b/plugins/woocommerce/src/Packages.php
@@ -193,7 +193,16 @@ class Packages {
 		}

 		// Scroll through all of the active plugins and disable them if they're merged packages.
-		$active_plugins = get_option( 'active_plugins', array() );
+		$active_plugins = (array) get_option( 'active_plugins', array() );
+		if ( is_multisite() ) {
+			$active_plugins = array_unique(
+				array_merge(
+					$active_plugins,
+					array_keys( (array) get_site_option( 'active_sitewide_plugins', array() ) )
+				)
+			);
+		}
+
 		// Deactivate the plugin if possible so that there are no conflicts.
 		foreach ( $active_plugins as $active_plugin_path ) {
 			$plugin_file = basename( plugin_basename( $active_plugin_path ), '.php' );
diff --git a/plugins/woocommerce/templates/single-product/add-to-cart/variable.php b/plugins/woocommerce/templates/single-product/add-to-cart/variable.php
index e2291b43b14..f262ac5c613 100644
--- a/plugins/woocommerce/templates/single-product/add-to-cart/variable.php
+++ b/plugins/woocommerce/templates/single-product/add-to-cart/variable.php
@@ -12,7 +12,7 @@
  *
  * @see https://woocommerce.com/document/template-structure/
  * @package WooCommerce\Templates
- * @version 10.9.0
+ * @version 11.1.0
  */

 defined( 'ABSPATH' ) || exit;
@@ -62,12 +62,8 @@ do_action( 'woocommerce_before_add_to_cart_form' ); ?>
 		<div class="reset_variations_alert screen-reader-text" role="alert" aria-live="polite" aria-relevant="all"></div>
 		<?php
 		// Reset snapshot for cases where a theme/plugin loads the variation form later, like quick-view modals.
-		if ( \Automattic\WooCommerce\Internal\VariationGallery\Package::is_enabled() ) :
-			?>
-			<script type="text/template" class="wc-product-gallery-default-template"><?php echo wc_get_product_gallery_html( $product ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></script>
-			<?php
-		endif;
 		?>
+		<script type="text/template" class="wc-product-gallery-default-template"><?php echo wc_get_product_gallery_html( $product ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></script>
 		<?php do_action( 'woocommerce_after_variations_table' ); ?>

 		<div class="single_variation_wrap">
diff --git a/plugins/woocommerce/tests/legacy/bootstrap.php b/plugins/woocommerce/tests/legacy/bootstrap.php
index 40fac14d320..faa162bfbff 100644
--- a/plugins/woocommerce/tests/legacy/bootstrap.php
+++ b/plugins/woocommerce/tests/legacy/bootstrap.php
@@ -7,6 +7,7 @@
  */

 use Automattic\WooCommerce\Internal\Admin\FeaturePlugin;
+use Automattic\WooCommerce\Internal\VariationGallery\Migration as VariationGalleryMigration;
 use Automattic\WooCommerce\Testing\Tools\CodeHacking\CodeHacker;
 use Automattic\WooCommerce\Testing\Tools\CodeHacking\Hacks\StaticMockerHack;
 use Automattic\WooCommerce\Testing\Tools\CodeHacking\Hacks\FunctionsMockerHack;
@@ -70,6 +71,9 @@ class WC_Unit_Tests_Bootstrap {
 		// Set up WC-Admin config.
 		tests_add_filter( 'woocommerce_admin_get_feature_config', array( $this, 'add_development_features' ) );

+		// Keep the shared DB update queue clean outside the variation gallery package tests.
+		tests_add_filter( 'init', array( $this, 'cancel_variation_gallery_migration_action' ), 21 );
+
 		// Speed things up by turning down the password hashing cost.
 		tests_add_filter(
 			'wp_hash_password_options',
@@ -307,6 +311,20 @@ class WC_Unit_Tests_Bootstrap {
 		echo esc_html( 'Installing WooCommerce...' . PHP_EOL );
 	}

+	/**
+	 * Cancel the variation gallery migration scheduled during test bootstrap.
+	 *
+	 * The package scheduler is covered directly by its own tests. Leaving its
+	 * bootstrap action pending leaks into unrelated tests of the shared DB update queue.
+	 */
+	public function cancel_variation_gallery_migration_action(): void {
+		WC()->queue()->cancel_all(
+			'woocommerce_run_update_callback',
+			array( 'update_callback' => array( VariationGalleryMigration::class, 'run' ) ),
+			'woocommerce-db-updates'
+		);
+	}
+
 	/**
 	 * Load WC-specific test cases and factories.
 	 *
diff --git a/plugins/woocommerce/tests/php/includes/class-wc-product-variable-test.php b/plugins/woocommerce/tests/php/includes/class-wc-product-variable-test.php
index 17ea8395de8..da5e45fbd42 100644
--- a/plugins/woocommerce/tests/php/includes/class-wc-product-variable-test.php
+++ b/plugins/woocommerce/tests/php/includes/class-wc-product-variable-test.php
@@ -1,19 +1,9 @@
 <?php

-use Automattic\WooCommerce\Internal\VariationGallery\Package;
-
 /**
  * Tests for WC_Product_Variable.
  */
 class WC_Product_Variable_Test extends \WC_Unit_Test_Case {
-	/**
-	 * Reset variation gallery feature-flag option leaked by individual tests.
-	 */
-	public function tearDown(): void {
-		delete_option( Package::ENABLE_OPTION_NAME );
-		parent::tearDown();
-	}
-
 	/**
 	 * @testdox 'get_available_variations' returns the variations as arrays if no parameters is passed.
 	 */
@@ -177,8 +167,6 @@ class WC_Product_Variable_Test extends \WC_Unit_Test_Case {
 	 * @testdox 'get_available_variations' with 'array' return includes image and gallery data for variations that have images set.
 	 */
 	public function test_get_available_variations_array_includes_image_data_when_variation_has_images(): void {
-		update_option( Package::ENABLE_OPTION_NAME, 'yes' );
-
 		$image_id   = $this->create_image_attachment( 'Variation Image', 'variation-image.jpg' );
 		$gallery_id = $this->create_image_attachment( 'Variation Gallery Image', 'variation-gallery.jpg' );

@@ -200,8 +188,6 @@ class WC_Product_Variable_Test extends \WC_Unit_Test_Case {
 	 * @testdox 'get_available_variation' exposes typed variation gallery image IDs.
 	 */
 	public function test_get_available_variation_includes_gallery_image_ids() {
-		update_option( Package::ENABLE_OPTION_NAME, 'yes' );
-
 		$product   = WC_Helper_Product::create_variation_product();
 		$variation = wc_get_product( $product->get_children()[0] );
 		$image_id  = wp_insert_attachment(
@@ -243,57 +229,10 @@ class WC_Product_Variable_Test extends \WC_Unit_Test_Case {
 		$this->assertNotEmpty( $available_variation['gallery_images_html'] );
 	}

-	/**
-	 * @testdox 'get_available_variation' omits multi-image gallery data when the variation gallery feature flag is disabled.
-	 */
-	public function test_get_available_variation_returns_single_image_shape_when_feature_flag_disabled() {
-		update_option( Package::ENABLE_OPTION_NAME, 'no' );
-
-		$product   = WC_Helper_Product::create_variation_product();
-		$variation = wc_get_product( $product->get_children()[0] );
-		$image_id  = wp_insert_attachment(
-			array(
-				'post_title'     => 'Variation Image',
-				'post_type'      => 'attachment',
-				'post_mime_type' => 'image/jpeg',
-			)
-		);
-		$image_ids = array(
-			wp_insert_attachment(
-				array(
-					'post_title'     => 'Variation Gallery Image 1',
-					'post_type'      => 'attachment',
-					'post_mime_type' => 'image/jpeg',
-				)
-			),
-			wp_insert_attachment(
-				array(
-					'post_title'     => 'Variation Gallery Image 2',
-					'post_type'      => 'attachment',
-					'post_mime_type' => 'image/jpeg',
-				)
-			),
-		);
-
-		update_post_meta( $image_id, '_wp_attached_file', 'variation-disabled.jpg' );
-
-		$variation->set_image_id( $image_id );
-		$variation->set_gallery_image_ids( $image_ids );
-		$variation->save();
-
-		$available_variation = $product->get_available_variation( $variation );
-
-		$this->assertSame( array(), $available_variation['gallery_image_ids'] );
-		$this->assertSame( '', $available_variation['gallery_images_html'] );
-		$this->assertSame( $image_id, $available_variation['image_id'] );
-	}
-
 	/**
 	 * @testdox 'get_available_variation' falls back to the variation's own gallery when the variation featured image is stale.
 	 */
 	public function test_get_available_variation_falls_back_to_variation_gallery_when_featured_is_stale() {
-		update_option( Package::ENABLE_OPTION_NAME, 'yes' );
-
 		$product              = WC_Helper_Product::create_variation_product();
 		$variation            = wc_get_product( $product->get_children()[0] );
 		$parent_featured_id   = $this->create_image_attachment( 'Parent Featured Image', 'parent-featured.jpg' );
@@ -325,8 +264,6 @@ class WC_Product_Variable_Test extends \WC_Unit_Test_Case {
 	 * @testdox 'get_available_variation' falls back to the parent featured image when both the variation featured image and gallery are absent.
 	 */
 	public function test_get_available_variation_falls_back_to_parent_featured_when_variation_has_no_images() {
-		update_option( Package::ENABLE_OPTION_NAME, 'yes' );
-
 		$product            = WC_Helper_Product::create_variation_product();
 		$variation          = wc_get_product( $product->get_children()[0] );
 		$parent_featured_id = $this->create_image_attachment( 'Parent Featured Image', 'parent-featured.jpg' );
diff --git a/plugins/woocommerce/tests/php/includes/wc-product-functions-test.php b/plugins/woocommerce/tests/php/includes/wc-product-functions-test.php
index ea2930da408..d475f8f5d5a 100644
--- a/plugins/woocommerce/tests/php/includes/wc-product-functions-test.php
+++ b/plugins/woocommerce/tests/php/includes/wc-product-functions-test.php
@@ -30,12 +30,9 @@ class WC_Product_Functions_Tests extends \WC_Unit_Test_Case {
 	private $reviews_setting_changed = false;

 	/**
-	 * Reset the variation gallery feature-flag option after each test so
-	 * individual cases that flip it on don't leak global state.
+	 * Restore settings modified by tests.
 	 */
 	public function tearDown(): void {
-		delete_option( \Automattic\WooCommerce\Internal\VariationGallery\Package::ENABLE_OPTION_NAME );
-
 		if ( $this->reviews_setting_changed ) {
 			delete_option( 'woocommerce_product_lookup_table_is_generating' );
 			as_unschedule_all_actions( '', array(), 'wc_update_product_lookup_tables' );
@@ -1543,11 +1540,9 @@ class WC_Product_Functions_Tests extends \WC_Unit_Test_Case {
 	}

 	/**
-	 * @testdox Variable add-to-cart attaches a pristine gallery snapshot to the variation script when the feature is on.
+	 * @testdox Variable add-to-cart attaches a pristine gallery snapshot to the variation script.
 	 */
 	public function test_woocommerce_variable_add_to_cart_attaches_gallery_snapshot() {
-		update_option( \Automattic\WooCommerce\Internal\VariationGallery\Package::ENABLE_OPTION_NAME, 'yes' );
-
 		$inline_js = $this->capture_variable_add_to_cart_inline_js();

 		$this->assertStringContainsString( 'wc_variation_gallery_defaults', $inline_js );
@@ -1560,21 +1555,6 @@ class WC_Product_Functions_Tests extends \WC_Unit_Test_Case {
 		$this->assertStringContainsString( 'woocommerce-product-gallery', $decoded_snapshot );
 	}

-	/**
-	 * @testdox Variable add-to-cart skips the gallery snapshot when the feature is off.
-	 */
-	public function test_woocommerce_variable_add_to_cart_skips_gallery_snapshot_when_feature_off() {
-		// Set the option to 'no' explicitly rather than deleting it: an empty option makes
-		// Package::is_enabled() fall through to the canary cohort, which reads a separate
-		// option (woocommerce_remote_variant_assignment) that other tests in the same worker
-		// may have left set, non-deterministically re-enabling the feature. 'no' short-circuits.
-		update_option( \Automattic\WooCommerce\Internal\VariationGallery\Package::ENABLE_OPTION_NAME, 'no' );
-
-		$inline_js = $this->capture_variable_add_to_cart_inline_js();
-
-		$this->assertStringNotContainsString( 'wc_variation_gallery_defaults', $inline_js );
-	}
-
 	/**
 	 * Render the variable add-to-cart template and return the inline JS
 	 * attached to the variation script.
diff --git a/plugins/woocommerce/tests/php/includes/wc-update-functions-test.php b/plugins/woocommerce/tests/php/includes/wc-update-functions-test.php
index 85fada1a252..70455142608 100644
--- a/plugins/woocommerce/tests/php/includes/wc-update-functions-test.php
+++ b/plugins/woocommerce/tests/php/includes/wc-update-functions-test.php
@@ -7,6 +7,7 @@

 use Automattic\WooCommerce\Blocks\Options as BlockOptions;
 use Automattic\WooCommerce\Blocks\Utils\BlockTemplateUtils;
+use Automattic\WooCommerce\Internal\VariationGallery\Package as VariationGalleryPackage;

 /**
  * Class WC_Core_Functions_Test
@@ -339,6 +340,29 @@ class WC_Update_Functions_Test extends \WC_Unit_Test_Case {
 		$this->assertSame( 'yes', get_option( 'woocommerce_feature_point_of_sale_enabled' ) );
 	}

+	/**
+	 * @testdox Migration registers and removes the deprecated variation gallery feature option.
+	 */
+	public function test_wc_update_11101_remove_deprecated_variation_gallery_option(): void {
+		include_once WC_ABSPATH . 'includes/wc-update-functions.php';
+
+		$db_updates = WC_Install::get_db_update_callbacks();
+		$this->assertArrayHasKey( '11.1.0-1', $db_updates );
+		$this->assertContains( 'wc_update_11101_remove_deprecated_variation_gallery_option', $db_updates['11.1.0-1'] );
+
+		delete_option( VariationGalleryPackage::ENABLE_OPTION_NAME );
+		wc_update_11101_remove_deprecated_variation_gallery_option();
+		$this->assertFalse( get_option( VariationGalleryPackage::ENABLE_OPTION_NAME ) );
+
+		update_option( VariationGalleryPackage::ENABLE_OPTION_NAME, 'no' );
+		wc_update_11101_remove_deprecated_variation_gallery_option();
+		$this->assertFalse( get_option( VariationGalleryPackage::ENABLE_OPTION_NAME ) );
+
+		update_option( VariationGalleryPackage::ENABLE_OPTION_NAME, 'yes' );
+		wc_update_11101_remove_deprecated_variation_gallery_option();
+		$this->assertFalse( get_option( VariationGalleryPackage::ENABLE_OPTION_NAME ) );
+	}
+
 	/**
 	 * @testdox Migration registers and deletes the cached dashboard out-of-stock count.
 	 */
diff --git a/plugins/woocommerce/tests/php/src/Blocks/Utils/ProductGalleryUtilsTest.php b/plugins/woocommerce/tests/php/src/Blocks/Utils/ProductGalleryUtilsTest.php
index b8e4a58c756..130345c8603 100644
--- a/plugins/woocommerce/tests/php/src/Blocks/Utils/ProductGalleryUtilsTest.php
+++ b/plugins/woocommerce/tests/php/src/Blocks/Utils/ProductGalleryUtilsTest.php
@@ -16,7 +16,6 @@ class ProductGalleryUtilsTest extends \WP_UnitTestCase {
 	 */
 	public function tearDown(): void {
 		delete_option( ProductMediaGallery::ENABLE_OPTION_NAME );
-		delete_option( \Automattic\WooCommerce\Internal\VariationGallery\Package::ENABLE_OPTION_NAME );
 		parent::tearDown();
 	}

@@ -24,8 +23,6 @@ class ProductGalleryUtilsTest extends \WP_UnitTestCase {
 	 * Test get_product_gallery_image_data method.
 	 */
 	public function test_get_product_gallery_image_data() {
-		update_option( \Automattic\WooCommerce\Internal\VariationGallery\Package::ENABLE_OPTION_NAME, 'yes' );
-
 		// Create the variable product.
 		$variable_product = \WC_Helper_Product::create_variation_product();

@@ -140,59 +137,10 @@ class ProductGalleryUtilsTest extends \WP_UnitTestCase {
 		}
 	}

-	/**
-	 * Test that get_product_variation_gallery_data returns the single-image
-	 * shape when the variation gallery feature flag is disabled, even when
-	 * the variation has multiple gallery images saved.
-	 */
-	public function test_get_product_variation_gallery_data_returns_single_image_when_feature_flag_disabled() {
-		update_option( \Automattic\WooCommerce\Internal\VariationGallery\Package::ENABLE_OPTION_NAME, 'no' );
-
-		$variable_product = \WC_Helper_Product::create_variation_product();
-
-		$variation_image_id = wp_insert_attachment(
-			array(
-				'post_title'     => 'Variation Featured Image',
-				'post_type'      => 'attachment',
-				'post_mime_type' => 'image/jpeg',
-			)
-		);
-		update_post_meta( $variation_image_id, '_wp_attached_file', 'variation-featured.jpg' );
-
-		$variation_gallery_image_ids = array(
-			wp_insert_attachment(
-				array(
-					'post_title'     => 'Variation Gallery Image 1',
-					'post_type'      => 'attachment',
-					'post_mime_type' => 'image/jpeg',
-				)
-			),
-			wp_insert_attachment(
-				array(
-					'post_title'     => 'Variation Gallery Image 2',
-					'post_type'      => 'attachment',
-					'post_mime_type' => 'image/jpeg',
-				)
-			),
-		);
-
-		$variation = wc_get_product( $variable_product->get_children()[0] );
-		$variation->set_image_id( $variation_image_id );
-		$variation->set_gallery_image_ids( $variation_gallery_image_ids );
-		$variation->save();
-
-		$variation_entry = ProductGalleryUtils::get_product_variation_gallery_data( $variable_product )[ $variation->get_id() ];
-
-		$this->assertSame( $variation_image_id, $variation_entry['image_id'] );
-		$this->assertSame( array( $variation_image_id ), $variation_entry['image_ids'] );
-	}
-
 	/**
 	 * Test that variation gallery data falls back to the variation's own gallery when the variation featured image is stale.
 	 */
 	public function test_get_product_variation_gallery_data_falls_back_to_variation_gallery_when_featured_is_stale() {
-		update_option( \Automattic\WooCommerce\Internal\VariationGallery\Package::ENABLE_OPTION_NAME, 'yes' );
-
 		$variable_product     = \WC_Helper_Product::create_variation_product();
 		$parent_featured_id   = $this->create_image_attachment( 'Parent Featured Image', 'parent-featured.jpg' );
 		$stale_featured_id    = $this->create_image_attachment( 'Stale Variation Image', 'stale-featured.jpg' );
@@ -224,7 +172,7 @@ class ProductGalleryUtilsTest extends \WP_UnitTestCase {
 	/**
 	 * Variation has only its own featured image (no gallery) → the
 	 * variation featured replaces the parent's hero, parent gallery extras
-	 * stay. Applies whether the feature flag is on or off.
+	 * stay.
 	 */
 	public function test_get_product_variation_gallery_data_case_3_single_image_appends_parent_gallery_extras() {
 		$parent_featured_id     = $this->create_image_attachment( 'Parent Featured', 'parent-featured.jpg' );
@@ -246,8 +194,8 @@ class ProductGalleryUtilsTest extends \WP_UnitTestCase {
 	}

 	/**
-	 * Variation has its own featured plus gallery images (feature flag
-	 * on) → the variation's images replace the parent's entirely.
+	 * Variation has its own featured plus gallery images, so the variation's
+	 * images replace the parent's entirely.
 	 */
 	public function test_get_product_variation_gallery_data_case_4_multiple_images_replaces_parent_set() {
 		$parent_featured_id     = $this->create_image_attachment( 'Parent Featured', 'parent-featured.jpg' );
@@ -315,31 +263,6 @@ class ProductGalleryUtilsTest extends \WP_UnitTestCase {
 		);
 	}

-	/**
-	 * Feature flag off: Variation gallery is treated as empty even if rows exist in postmeta,
-	 * so the single-image rule applies (variation featured + parent gallery extras).
-	 */
-	public function test_get_product_variation_gallery_data_case_3_applies_with_feature_flag_off() {
-		$parent_featured_id     = $this->create_image_attachment( 'Parent Featured', 'parent-featured.jpg' );
-		$parent_gallery_extra   = $this->create_image_attachment( 'Parent Gallery Extra', 'parent-gallery-extra.jpg' );
-		$variation_featured_id  = $this->create_image_attachment( 'Variation Featured', 'variation-featured.jpg' );
-		$variation_gallery_id_a = $this->create_image_attachment( 'Variation Gallery A (ignored)', 'variation-gallery-a.jpg' );
-
-		$entry = $this->create_variation_gallery_entry(
-			$parent_featured_id,
-			array( $parent_gallery_extra ),
-			$variation_featured_id,
-			array( $variation_gallery_id_a ),
-			'no'
-		);
-
-		$this->assertSame( $variation_featured_id, $entry['image_id'] );
-		$this->assertSame(
-			array( $variation_featured_id, $parent_gallery_extra ),
-			$entry['image_ids']
-		);
-	}
-
 	/**
 	 * The variation featured is also present in the parent gallery — output
 	 * must dedup so the image doesn't render twice in a row.
@@ -365,22 +288,18 @@ class ProductGalleryUtilsTest extends \WP_UnitTestCase {
 	/**
 	 * Create a variation gallery fixture and return the selected variation entry.
 	 *
-	 * @param int    $parent_featured_id    Parent product featured image ID.
-	 * @param int[]  $parent_gallery_ids    Parent product gallery image IDs.
-	 * @param int    $variation_featured_id Variation featured image ID.
-	 * @param int[]  $variation_gallery_ids Variation gallery image IDs.
-	 * @param string $feature_flag          Variation gallery feature flag value.
+	 * @param int   $parent_featured_id    Parent product featured image ID.
+	 * @param int[] $parent_gallery_ids    Parent product gallery image IDs.
+	 * @param int   $variation_featured_id Variation featured image ID.
+	 * @param int[] $variation_gallery_ids Variation gallery image IDs.
 	 * @return array<string, mixed>
 	 */
 	private function create_variation_gallery_entry(
 		int $parent_featured_id,
 		array $parent_gallery_ids = array(),
 		int $variation_featured_id = 0,
-		array $variation_gallery_ids = array(),
-		string $feature_flag = 'yes'
+		array $variation_gallery_ids = array()
 	): array {
-		update_option( \Automattic\WooCommerce\Internal\VariationGallery\Package::ENABLE_OPTION_NAME, $feature_flag );
-
 		$variable_product = \WC_Helper_Product::create_variation_product();
 		$variable_product->set_image_id( $parent_featured_id );
 		$variable_product->set_gallery_image_ids( $parent_gallery_ids );
diff --git a/plugins/woocommerce/tests/php/src/Internal/VariationGallery/PackageTest.php b/plugins/woocommerce/tests/php/src/Internal/VariationGallery/PackageTest.php
index 3b2286d15b0..2e07f10b32d 100644
--- a/plugins/woocommerce/tests/php/src/Internal/VariationGallery/PackageTest.php
+++ b/plugins/woocommerce/tests/php/src/Internal/VariationGallery/PackageTest.php
@@ -11,11 +11,29 @@ use Automattic\WooCommerce\Internal\VariationGallery\Package;
  */
 class PackageTest extends \WC_Unit_Test_Case {

+	/**
+	 * Reset migration-related state before each test.
+	 */
+	public function setUp(): void {
+		parent::setUp();
+
+		$this->reset_migration_state();
+	}
+
 	/**
 	 * Reset migration-related state between tests so action queue and
 	 * completion option don't leak across cases.
 	 */
 	public function tearDown(): void {
+		$this->reset_migration_state();
+
+		parent::tearDown();
+	}
+
+	/**
+	 * Reset migration-related options and scheduled actions.
+	 */
+	private function reset_migration_state(): void {
 		WC()->queue()->cancel_all(
 			'woocommerce_run_update_callback',
 			$this->get_migration_action_args(),
@@ -28,84 +46,24 @@ class PackageTest extends \WC_Unit_Test_Case {
 		);
 		delete_option( Migration::COMPLETED_OPTION );
 		delete_option( Package::ENABLE_OPTION_NAME );
-		delete_option( 'woocommerce_remote_variant_assignment' );
-
-		parent::tearDown();
 	}

 	/**
-	 * @testdox is_enabled honors an explicit 'yes' on the feature option.
+	 * @testdox is_enabled returns true now that the variation gallery is fully rolled out.
 	 */
-	public function test_is_enabled_returns_true_when_option_explicitly_yes(): void {
-		update_option( Package::ENABLE_OPTION_NAME, 'yes' );
-		update_option( 'woocommerce_remote_variant_assignment', 99 );
-
+	public function test_is_enabled_returns_true(): void {
 		$this->assertTrue( Package::is_enabled() );
 	}

 	/**
-	 * @testdox is_enabled honors an explicit 'no' on the feature option even when the store is in the canary cohort.
+	 * @testdox is_enabled returns true for a former explicit opt-out.
 	 */
-	public function test_is_enabled_returns_false_when_option_explicitly_no(): void {
+	public function test_is_enabled_returns_true_for_former_explicit_opt_out(): void {
 		update_option( Package::ENABLE_OPTION_NAME, 'no' );
-		update_option( 'woocommerce_remote_variant_assignment', 1 );
-
-		$this->assertFalse( Package::is_enabled() );
-	}
-
-	/**
-	 * @testdox is_enabled includes stores in the canary cohort when the option is unset.
-	 */
-	public function test_is_enabled_returns_true_for_canary_cohort_when_option_unset(): void {
-		delete_option( Package::ENABLE_OPTION_NAME );
-		update_option( 'woocommerce_remote_variant_assignment', 1 );

 		$this->assertTrue( Package::is_enabled() );
 	}

-	/**
-	 * @testdox is_enabled includes the last variant bucket in the canary cohort when the option is unset.
-	 */
-	public function test_is_enabled_returns_true_for_canary_cohort_boundary_when_option_unset(): void {
-		delete_option( Package::ENABLE_OPTION_NAME );
-		update_option( 'woocommerce_remote_variant_assignment', Package::CANARY_MAX_VARIANT );
-
-		$this->assertTrue( Package::is_enabled() );
-	}
-
-	/**
-	 * @testdox is_enabled excludes stores outside the canary cohort when the option is unset.
-	 */
-	public function test_is_enabled_returns_false_for_control_cohort_when_option_unset(): void {
-		delete_option( Package::ENABLE_OPTION_NAME );
-		update_option( 'woocommerce_remote_variant_assignment', Package::CANARY_MAX_VARIANT + 1 );
-
-		$this->assertFalse( Package::is_enabled() );
-	}
-
-	/**
-	 * @testdox is_enabled excludes stores with no variant assignment when the option is unset.
-	 */
-	public function test_is_enabled_returns_false_when_option_and_variant_unset(): void {
-		delete_option( Package::ENABLE_OPTION_NAME );
-		delete_option( 'woocommerce_remote_variant_assignment' );
-
-		$this->assertFalse( Package::is_enabled() );
-	}
-
-	/**
-	 * @testdox is_in_canary_cohort reports membership independently of the feature option.
-	 */
-	public function test_is_in_canary_cohort_is_independent_of_option_value(): void {
-		update_option( 'woocommerce_remote_variant_assignment', 1 );
-		update_option( Package::ENABLE_OPTION_NAME, 'no' );
-		$this->assertTrue( Package::is_in_canary_cohort() );
-
-		update_option( 'woocommerce_remote_variant_assignment', Package::CANARY_MAX_VARIANT + 1 );
-		update_option( Package::ENABLE_OPTION_NAME, 'yes' );
-		$this->assertFalse( Package::is_in_canary_cohort() );
-	}
-
 	/**
 	 * @testdox maybe_schedule_migration queues the migration.
 	 */