diff --git a/package_manager/tests/fixtures/release-history/drupal.9.8.1-extra.xml b/package_manager/tests/fixtures/release-history/drupal.9.8.1-extra.xml
new file mode 100644
index 0000000000000000000000000000000000000000..0dbdd739ce17566ba99b0b5635ff00bf92f7a51b
--- /dev/null
+++ b/package_manager/tests/fixtures/release-history/drupal.9.8.1-extra.xml
@@ -0,0 +1,157 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+This fixture is used by \Drupal\Tests\automatic_updates\Kernel\StatusCheck\VersionPolicyValidatorTest.
+
+Contains metadata about the following (fake) releases of Drupal core, all of which are secure, in order:
+- 9.8.1-rc3
+- 9.8.1-beta2
+- 9.8.1-alpha1
+- 9.7.1
+- 9.7.0
+-->
+<project xmlns:dc="http://purl.org/dc/elements/1.1/">
+    <title>Drupal</title>
+    <short_name>drupal</short_name>
+    <dc:creator>Drupal</dc:creator>
+    <supported_branches>9.7.,9.8.</supported_branches>
+    <project_status>published</project_status>
+    <link>http://example.com/project/drupal</link>
+    <terms>
+        <term>
+            <name>Projects</name>
+            <value>Drupal project</value>
+        </term>
+    </terms>
+    <releases>
+      <release>
+        <name>Drupal 9.8.1-rc3</name>
+        <version>9.8.1-rc3</version>
+        <status>published</status>
+        <release_link>http://example.com/drupal-9-8-1-rc3-release</release_link>
+        <download_link>http://example.com/drupal-9-8-1-rc3.tar.gz</download_link>
+        <date>1250424521</date>
+        <terms>
+          <term>
+            <name>Release type</name>
+            <value>New features</value>
+          </term>
+          <term>
+            <name>Release type</name>
+            <value>Bug fixes</value>
+          </term>
+        </terms>
+      </release>
+      <release>
+        <name>Drupal 9.8.1-beta2</name>
+        <version>9.8.1-beta2</version>
+        <status>published</status>
+        <release_link>http://example.com/drupal-9-8-1-beta2-release</release_link>
+        <download_link>http://example.com/drupal-9-8-1-beta2.tar.gz</download_link>
+        <date>1250424521</date>
+        <terms>
+          <term>
+            <name>Release type</name>
+            <value>New features</value>
+          </term>
+          <term>
+            <name>Release type</name>
+            <value>Bug fixes</value>
+          </term>
+        </terms>
+      </release>
+        <release>
+            <name>Drupal 9.8.1-alpha1</name>
+            <version>9.8.1-alpha1</version>
+            <status>published</status>
+            <release_link>http://example.com/drupal-9-8-1-alpha1-release</release_link>
+            <download_link>http://example.com/drupal-9-8-1-alpha1.tar.gz</download_link>
+            <date>1250424521</date>
+            <terms>
+                <term>
+                    <name>Release type</name>
+                    <value>New features</value>
+                </term>
+                <term>
+                    <name>Release type</name>
+                    <value>Bug fixes</value>
+                </term>
+            </terms>
+        </release>
+      <release>
+        <name>Drupal 9.8.1-beta2</name>
+        <version>9.8.1-beta2</version>
+        <status>published</status>
+        <release_link>http://example.com/drupal-9-8-1-beta2-release</release_link>
+        <download_link>http://example.com/drupal-9-8-1-beta2.tar.gz</download_link>
+        <date>1250424521</date>
+        <terms>
+          <term>
+            <name>Release type</name>
+            <value>New features</value>
+          </term>
+          <term>
+            <name>Release type</name>
+            <value>Bug fixes</value>
+          </term>
+        </terms>
+      </release>
+      <release>
+        <name>Drupal 9.8.0</name>
+        <version>9.8.0</version>
+        <status>published</status>
+        <release_link>http://example.com/drupal-9-8-0-release</release_link>
+        <download_link>http://example.com/drupal-9-8-0.tar.gz</download_link>
+        <date>1250424521</date>
+        <terms>
+          <term>
+            <name>Release type</name>
+            <value>New features</value>
+          </term>
+          <term>
+            <name>Release type</name>
+            <value>Bug fixes</value>
+          </term>
+          <term>
+            <name>Release type</name>
+            <value>Insecure</value>
+          </term>
+        </terms>
+      </release>
+        <release>
+            <name>Drupal 9.7.1</name>
+            <version>9.7.1</version>
+            <status>published</status>
+            <release_link>http://example.com/drupal-9-7-1-release</release_link>
+            <download_link>http://example.com/drupal-9-7-1.tar.gz</download_link>
+            <date>1250425521</date>
+            <terms>
+                <term>
+                    <name>Release type</name>
+                    <value>New features</value>
+                </term>
+                <term>
+                    <name>Release type</name>
+                    <value>Bug fixes</value>
+                </term>
+            </terms>
+        </release>
+        <release>
+            <name>Drupal 9.7.0</name>
+            <version>9.7.0</version>
+            <status>published</status>
+            <release_link>http://example.com/drupal-9-7-0-release</release_link>
+            <download_link>http://example.com/drupal-9-7-0.tar.gz</download_link>
+            <date>1250424521</date>
+            <terms>
+                <term>
+                    <name>Release type</name>
+                    <value>New features</value>
+                </term>
+                <term>
+                    <name>Release type</name>
+                    <value>Bug fixes</value>
+                </term>
+            </terms>
+        </release>
+    </releases>
+</project>
diff --git a/src/Validator/VersionPolicy/MinorUpdatesEnabled.php b/src/Validator/VersionPolicy/MinorUpdatesEnabled.php
deleted file mode 100644
index 4a375fd17dc2632c7235a8c3e1e0d7d881b28356..0000000000000000000000000000000000000000
--- a/src/Validator/VersionPolicy/MinorUpdatesEnabled.php
+++ /dev/null
@@ -1,88 +0,0 @@
-<?php
-
-declare(strict_types = 1);
-
-namespace Drupal\automatic_updates\Validator\VersionPolicy;
-
-use Drupal\automatic_updates\VersionParsingTrait;
-use Drupal\Core\Config\ConfigFactoryInterface;
-use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
-use Drupal\Core\StringTranslation\StringTranslationTrait;
-use Symfony\Component\DependencyInjection\ContainerInterface;
-
-/**
- * A policy rule that allows minor updates if enabled in configuration.
- *
- * @internal
- *   This is an internal part of Automatic Updates' version policy for
- *   Drupal core. It may be changed or removed at any time without warning.
- *   External code should not interact with this class.
- */
-final class MinorUpdatesEnabled implements ContainerInjectionInterface {
-
-  use StringTranslationTrait;
-  use VersionParsingTrait;
-
-  /**
-   * The config factory service.
-   *
-   * @var \Drupal\Core\Config\ConfigFactoryInterface
-   */
-  private $configFactory;
-
-  /**
-   * Constructs a MinorUpdatesEnabled object.
-   *
-   * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
-   *   The config factory service.
-   */
-  public function __construct(ConfigFactoryInterface $config_factory) {
-    $this->configFactory = $config_factory;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public static function create(ContainerInterface $container) {
-    return new static(
-      $container->get('config.factory')
-    );
-  }
-
-  /**
-   * Checks that the target minor version of Drupal can be updated to.
-   *
-   * The update will only be allowed if the allow_core_minor_updates flag is
-   * set to TRUE in config.
-   *
-   * @param string $installed_version
-   *   The installed version of Drupal.
-   * @param string|null $target_version
-   *   The target version of Drupal, or NULL if not known.
-   *
-   * @return \Drupal\Core\StringTranslation\TranslatableMarkup[]
-   *   The error messages, if any.
-   */
-  public function validate(string $installed_version, ?string $target_version): array {
-    $installed_minor = static::getMajorAndMinorVersion($installed_version);
-    $target_minor = static::getMajorAndMinorVersion($target_version);
-
-    if ($installed_minor === $target_minor) {
-      return [];
-    }
-
-    $minor_updates_allowed = $this->configFactory->get('automatic_updates.settings')
-      ->get('allow_core_minor_updates');
-
-    if (!$minor_updates_allowed) {
-      return [
-        $this->t('Drupal cannot be automatically updated from @installed_version to @target_version because automatic updates from one minor version to another are not supported.', [
-          '@installed_version' => $installed_version,
-          '@target_version' => $target_version,
-        ]),
-      ];
-    }
-    return [];
-  }
-
-}
diff --git a/src/Validator/VersionPolicy/TargetVersionInstallable.php b/src/Validator/VersionPolicy/TargetVersionInstallable.php
index e0a75ae4ed1adf7aa7890f520e62ba363471767d..79ee6a656dcda80292725841d9bed75cc637c499 100644
--- a/src/Validator/VersionPolicy/TargetVersionInstallable.php
+++ b/src/Validator/VersionPolicy/TargetVersionInstallable.php
@@ -4,22 +4,52 @@ declare(strict_types = 1);
 
 namespace Drupal\automatic_updates\Validator\VersionPolicy;
 
+use Drupal\automatic_updates\VersionParsingTrait;
+use Drupal\Core\Config\ConfigFactoryInterface;
+use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
 use Drupal\Core\StringTranslation\StringTranslationTrait;
+use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
- * A policy rule requiring the target version to be an installable release.
+ * A policy rule requiring the target version to be installable.
  *
  * @internal
  *   This is an internal part of Automatic Updates' version policy for
  *   Drupal core. It may be changed or removed at any time without warning.
  *   External code should not interact with this class.
  */
-final class TargetVersionInstallable {
+final class TargetVersionInstallable implements ContainerInjectionInterface {
 
   use StringTranslationTrait;
+  use VersionParsingTrait;
 
   /**
-   * Checks that the target version of Drupal is a known installable release.
+   * Constructs a TargetVersionInstallable object.
+   *
+   * @param \Drupal\Core\Config\ConfigFactoryInterface $configFactory
+   *   The config factory service.
+   */
+  public function __construct(private ConfigFactoryInterface $configFactory) {}
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('config.factory')
+    );
+  }
+
+  /**
+   * Checks that the target version can be installed.
+   *
+   * This means two things must be true:
+   * - The target minor version of Drupal can be updated to. The update will
+   *   only be allowed if the allow_core_minor_updates flag is TRUE in config.
+   * - The target version of Drupal is a known installable release.
+   *
+   * If the first check fails, there is no need to do the second check because
+   * the first check implies that the target version isn't installable.
    *
    * @param string $installed_version
    *   The installed version of Drupal.
@@ -32,6 +62,22 @@ final class TargetVersionInstallable {
    *   The error messages, if any.
    */
   public function validate(string $installed_version, ?string $target_version, array $available_releases): array {
+    $installed_minor = static::getMajorAndMinorVersion($installed_version);
+    $target_minor = static::getMajorAndMinorVersion($target_version);
+
+    if ($installed_minor !== $target_minor) {
+      $minor_updates_allowed = $this->configFactory->get('automatic_updates.settings')
+        ->get('allow_core_minor_updates');
+
+      if (!$minor_updates_allowed) {
+        return [
+          $this->t('Drupal cannot be automatically updated from @installed_version to @target_version because automatic updates from one minor version to another are not supported.', [
+            '@installed_version' => $installed_version,
+            '@target_version' => $target_version,
+          ]),
+        ];
+      }
+    }
     // If the target version isn't in the list of installable releases, we
     // should flag an error.
     if (empty($available_releases) || !array_key_exists($target_version, $available_releases)) {
diff --git a/src/Validator/VersionPolicyValidator.php b/src/Validator/VersionPolicyValidator.php
index 998da5a876c6c58e59ca715fc746d21f00840061..7bf7e7421afe64bdb09638ccaf3b71d34e78e4e9 100644
--- a/src/Validator/VersionPolicyValidator.php
+++ b/src/Validator/VersionPolicyValidator.php
@@ -5,6 +5,7 @@ declare(strict_types = 1);
 namespace Drupal\automatic_updates\Validator;
 
 use Drupal\automatic_updates\CronUpdateStage;
+use Drupal\Component\Utility\NestedArray;
 use Drupal\package_manager\ComposerInspector;
 use Drupal\package_manager\Event\StatusCheckEvent;
 use Drupal\package_manager\PathLocator;
@@ -13,7 +14,6 @@ use Drupal\automatic_updates\UpdateStage;
 use Drupal\automatic_updates\Validator\VersionPolicy\ForbidDowngrade;
 use Drupal\automatic_updates\Validator\VersionPolicy\ForbidMinorUpdates;
 use Drupal\automatic_updates\Validator\VersionPolicy\MajorVersionMatch;
-use Drupal\automatic_updates\Validator\VersionPolicy\MinorUpdatesEnabled;
 use Drupal\automatic_updates\Validator\VersionPolicy\StableReleaseInstalled;
 use Drupal\automatic_updates\Validator\VersionPolicy\ForbidDevSnapshot;
 use Drupal\automatic_updates\Validator\VersionPolicy\SupportedBranchInstalled;
@@ -110,28 +110,56 @@ final class VersionPolicyValidator implements EventSubscriberInterface {
         }
       }
     }
-    // If this is not a cron update, and we know the target version, minor
-    // version updates are allowed if configuration says so.
-    elseif ($target_version) {
-      $rules[] = MinorUpdatesEnabled::class;
-    }
 
     $installed_version = $this->getInstalledVersion();
     $available_releases = $this->getAvailableReleases($stage);
 
-    // Invoke each rule in the order that they were added to $rules, stopping
-    // when one returns error messages.
-    // @todo Return all the error messages in https://www.drupal.org/i/3281379.
+    // Let all the rules flag whatever messages they need to.
+    $messages = [];
     foreach ($rules as $rule) {
-      $messages = $this->classResolver
-        ->getInstanceFromDefinition($rule)
+      $messages[$rule] = $this->classResolver->getInstanceFromDefinition($rule)
         ->validate($installed_version, $target_version, $available_releases);
+    }
+    // Remove any messages that are superseded by other, more specific ones.
+    $filtered_rule_messages = array_filter($messages, fn ($rule) => !self::isRuleSuperseded($rule, $messages), ARRAY_FILTER_USE_KEY);
+    // Collapse all the rules' messages into a single array.
+    return NestedArray::mergeDeepArray($filtered_rule_messages);
+  }
 
-      if ($messages) {
-        return $messages;
+  /**
+   * Check if a given rule's messages are superseded by a more specific rule.
+   *
+   * @param string $rule
+   *   The rule to check.
+   * @param array[] $rule_messages
+   *   The messages that were returned by the various rules, keyed by the name
+   *   of the rule that returned them.
+   *
+   * @return bool
+   *   TRUE if the given rule is superseded by another rule, FALSE otherwise.
+   */
+  private static function isRuleSuperseded(string $rule, array $rule_messages): bool {
+    // Some rules' messages are more specific than other rules' messages. For
+    // example, if the message "… automatic updates from one major version to
+    // another are not supported" is returned, then the message "… not in the
+    // list of installable releases" is not needed because the new major version
+    // will not be in the list of installable releases. The keys of this array
+    // are the rules which supersede messages from the values, which are the
+    // less specific rules.
+    $more_specific_rule_sets = [
+      ForbidDowngrade::class => [TargetVersionInstallable::class, MajorVersionMatch::class],
+      ForbidDevSnapshot::class => [StableReleaseInstalled::class],
+      MajorVersionMatch::class => [TargetVersionInstallable::class],
+      ForbidMinorUpdates::class => [TargetVersionInstallable::class],
+    ];
+    foreach ($more_specific_rule_sets as $more_specific_rule => $less_specific_rules) {
+      // If the more specific rule flagged any messages, the given rule is
+      // superseded.
+      if (!empty($rule_messages[$more_specific_rule]) && in_array($rule, $less_specific_rules, TRUE)) {
+        return TRUE;
       }
     }
-    return [];
+    return FALSE;
   }
 
   /**
diff --git a/tests/src/Kernel/AutomaticUpdatesKernelTestBase.php b/tests/src/Kernel/AutomaticUpdatesKernelTestBase.php
index a7832fd96a5c48dd95ed9edc9fe46f4d50915a2a..9ac2612e13d94f8a511b40b321c2c4e7256bb5bd 100644
--- a/tests/src/Kernel/AutomaticUpdatesKernelTestBase.php
+++ b/tests/src/Kernel/AutomaticUpdatesKernelTestBase.php
@@ -112,4 +112,11 @@ class TestCronUpdateStage extends CronUpdateStage {
     $this->handlePostApply($stage_id, $start_version, $target_version);
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function setMetadata(string $key, $data): void {
+    parent::setMetadata($key, $data);
+  }
+
 }
diff --git a/tests/src/Kernel/StatusCheck/VersionPolicyValidatorTest.php b/tests/src/Kernel/StatusCheck/VersionPolicyValidatorTest.php
index 9d8519c868869321f0ab5d93686d61366f6137d3..c9c8c2e0bc454a48514cd4e19ba194970d7a6dfa 100644
--- a/tests/src/Kernel/StatusCheck/VersionPolicyValidatorTest.php
+++ b/tests/src/Kernel/StatusCheck/VersionPolicyValidatorTest.php
@@ -31,7 +31,67 @@ class VersionPolicyValidatorTest extends AutomaticUpdatesKernelTestBase {
    * @return mixed[][]
    *   The test cases.
    */
-  public function providerStatusCheck(): array {
+  public function providerStatusCheckSpecific(): array {
+    $metadata_dir = __DIR__ . '/../../../../package_manager/tests/fixtures/release-history';
+
+    return [
+      // This case proves that, if a stable release is installed, there is no
+      // error generated when if the next available release is a normal (i.e.,
+      // non-security) release. If unattended updates are only enabled for
+      // security releases, the next available release will be ignored, and
+      // therefore generate no validation errors, because it's not a security
+      // release.
+      'update to normal release' => [
+        '9.8.1',
+        NULL,
+        "$metadata_dir/drupal.9.8.2.xml",
+        [CronUpdateStage::DISABLED, CronUpdateStage::SECURITY, CronUpdateStage::ALL],
+        [],
+      ],
+      // These three cases prove that updating from an unsupported minor version
+      // will raise an error if unattended updates are enabled. Furthermore, if
+      // an error is raised, the messaging will vary depending on whether
+      // attended updates across minor versions are allowed. (Note that the
+      // target version will not be automatically detected because the release
+      // metadata used in these cases doesn't have any 9.7.x releases.)
+      'update from unsupported minor, cron disabled' => [
+        '9.7.1',
+        NULL,
+        "$metadata_dir/drupal.9.8.1-security.xml",
+        [CronUpdateStage::DISABLED],
+        [],
+      ],
+      'update from unsupported minor, cron enabled, minor updates forbidden' => [
+        '9.7.1',
+        NULL,
+        "$metadata_dir/drupal.9.8.1-security.xml",
+        [CronUpdateStage::SECURITY, CronUpdateStage::ALL],
+        [
+          t('The currently installed version of Drupal core, 9.7.1, is not in a supported minor version. Your site will not be automatically updated during cron until it is updated to a supported minor version.'),
+          t('See the <a href="/admin/reports/updates">available updates page</a> for available updates.'),
+        ],
+      ],
+      'update from unsupported minor, cron enabled, minor updates allowed' => [
+        '9.7.1',
+        NULL,
+        "$metadata_dir/drupal.9.8.1-security.xml",
+        [CronUpdateStage::SECURITY, CronUpdateStage::ALL],
+        [
+          t('The currently installed version of Drupal core, 9.7.1, is not in a supported minor version. Your site will not be automatically updated during cron until it is updated to a supported minor version.'),
+          t('Use the <a href="/admin/modules/update">update form</a> to update to a supported version.'),
+        ],
+        TRUE,
+      ],
+    ];
+  }
+
+  /**
+   * Data provider for testStatusCheck() and testCronPreCreate().
+   *
+   * @return mixed[][]
+   *   The test cases.
+   */
+  public function providerGeneric(): array {
     $metadata_dir = __DIR__ . '/../../../../package_manager/tests/fixtures/release-history';
 
     return [
@@ -40,6 +100,7 @@ class VersionPolicyValidatorTest extends AutomaticUpdatesKernelTestBase {
       // patch-level update from a stable release never raises an error.
       'stable release installed' => [
         '9.8.0',
+        '9.8.1',
         "$metadata_dir/drupal.9.8.1-security.xml",
         [CronUpdateStage::DISABLED, CronUpdateStage::SECURITY, CronUpdateStage::ALL],
         [],
@@ -48,108 +109,261 @@ class VersionPolicyValidatorTest extends AutomaticUpdatesKernelTestBase {
       // regardless of configuration.
       'dev snapshot installed' => [
         '9.8.0-dev',
+        '9.8.1',
         "$metadata_dir/drupal.9.8.1-security.xml",
         [CronUpdateStage::DISABLED, CronUpdateStage::SECURITY, CronUpdateStage::ALL],
         [
-          $this->createVersionPolicyValidationResult('9.8.0-dev', NULL, [
-            t('Drupal cannot be automatically updated from the installed version, 9.8.0-dev, because automatic updates from a dev version to any other version are not supported.'),
-          ]),
+          t('Drupal cannot be automatically updated from the installed version, 9.8.0-dev, because automatic updates from a dev version to any other version are not supported.'),
         ],
       ],
       // The next six cases prove that updating from an alpha, beta, or RC
       // release raises an error if unattended updates are enabled.
       'alpha installed, cron disabled' => [
         '9.8.0-alpha1',
+        '9.8.1',
         "$metadata_dir/drupal.9.8.1-security.xml",
         [CronUpdateStage::DISABLED],
         [],
       ],
       'alpha installed, cron enabled' => [
         '9.8.0-alpha1',
+        '9.8.1',
         "$metadata_dir/drupal.9.8.1-security.xml",
         [CronUpdateStage::SECURITY, CronUpdateStage::ALL],
         [
-          $this->createVersionPolicyValidationResult('9.8.0-alpha1', NULL, [
-            t('Drupal cannot be automatically updated during cron from its current version, 9.8.0-alpha1, because it is not a stable version.'),
-          ]),
+          t('Drupal cannot be automatically updated during cron from its current version, 9.8.0-alpha1, because it is not a stable version.'),
         ],
       ],
       'beta installed, cron disabled' => [
         '9.8.0-beta2',
+        '9.8.1',
         "$metadata_dir/drupal.9.8.1-security.xml",
         [CronUpdateStage::DISABLED],
         [],
       ],
       'beta installed, cron enabled' => [
         '9.8.0-beta2',
+        '9.8.1',
         "$metadata_dir/drupal.9.8.1-security.xml",
         [CronUpdateStage::SECURITY, CronUpdateStage::ALL],
         [
-          $this->createVersionPolicyValidationResult('9.8.0-beta2', NULL, [
-            t('Drupal cannot be automatically updated during cron from its current version, 9.8.0-beta2, because it is not a stable version.'),
-          ]),
+          t('Drupal cannot be automatically updated during cron from its current version, 9.8.0-beta2, because it is not a stable version.'),
         ],
       ],
       'rc installed, cron disabled' => [
         '9.8.0-rc3',
+        '9.8.1',
         "$metadata_dir/drupal.9.8.1-security.xml",
         [CronUpdateStage::DISABLED],
         [],
       ],
       'rc installed, cron enabled' => [
         '9.8.0-rc3',
+        '9.8.1',
         "$metadata_dir/drupal.9.8.1-security.xml",
         [CronUpdateStage::SECURITY, CronUpdateStage::ALL],
         [
-          $this->createVersionPolicyValidationResult('9.8.0-rc3', NULL, [
-            t('Drupal cannot be automatically updated during cron from its current version, 9.8.0-rc3, because it is not a stable version.'),
-          ]),
+          t('Drupal cannot be automatically updated during cron from its current version, 9.8.0-rc3, because it is not a stable version.'),
         ],
       ],
-      // This case proves that, if a stable release is installed, there is no
+    ];
+  }
+
+  /**
+   * Tests target version validation during status checks.
+   *
+   * @param string $installed_version
+   *   The installed version of Drupal core.
+   * @param string|null $target_version
+   *   The target version of Drupal core.
+   * @param string $release_metadata
+   *   The path of the core release metadata to serve to the update system.
+   * @param string[] $cron_modes
+   *   The modes for unattended updates. Can contain any of
+   *   \Drupal\automatic_updates\CronUpdateStage::DISABLED,
+   *   \Drupal\automatic_updates\CronUpdateStage::SECURITY, and
+   *   \Drupal\automatic_updates\CronUpdateStage::ALL.
+   * @param \Drupal\Core\StringTranslation\TranslatableMarkup[] $expected_validation_messages
+   *   The expected validation messages.
+   * @param bool $allow_minor_updates
+   *   (optional) Whether or not attended updates across minor updates are
+   *   allowed. Defaults to FALSE.
+   *
+   * @dataProvider providerGeneric
+   * @dataProvider providerStatusCheckSpecific
+   */
+  public function testStatusCheck(string $installed_version, ?string $target_version, string $release_metadata, array $cron_modes, array $expected_validation_messages, bool $allow_minor_updates = FALSE): void {
+    $this->setCoreVersion($installed_version);
+    $this->setReleaseMetadata(['drupal' => $release_metadata]);
+
+    foreach ($cron_modes as $cron_mode) {
+      $this->config('automatic_updates.settings')
+        ->set('cron', $cron_mode)
+        ->set('allow_core_minor_updates', $allow_minor_updates)
+        ->save();
+
+      $expected_results = [];
+      if ($expected_validation_messages) {
+        // If we're doing a status check, the stage isn't created, and the
+        // requested package versions are not recorded during begin(), so the
+        // error message won't contain the target version.
+        $expected_results[] = $this->createVersionPolicyValidationResult($installed_version, NULL, $expected_validation_messages);
+      }
+      $this->assertCheckerResultsFromManager($expected_results, TRUE);
+    }
+  }
+
+  /**
+   * Data provider for testCronPreCreate().
+   *
+   * @return mixed[][]
+   *   The test cases.
+   */
+  public function providerCronPreCreateSpecific(): array {
+    $metadata_dir = __DIR__ . '/../../../../package_manager/tests/fixtures/release-history';
+
+    return [
+      // The next three cases prove that update to an alpha, beta, or RC release
+      // doesn't raise any error if unattended updates are disabled.
+      'update to alpha, cron disabled' => [
+        '9.8.0',
+        '9.8.1-alpha1',
+        "$metadata_dir/drupal.9.8.1-extra.xml",
+        [CronUpdateStage::DISABLED],
+        [],
+      ],
+      'update to beta, cron disabled' => [
+        '9.8.0',
+        '9.8.1-beta2',
+        "$metadata_dir/drupal.9.8.1-extra.xml",
+        [CronUpdateStage::DISABLED],
+        [],
+      ],
+      'update to rc, cron disabled' => [
+        '9.8.0',
+        '9.8.1-rc3',
+        "$metadata_dir/drupal.9.8.1-extra.xml",
+        [CronUpdateStage::DISABLED],
+        [],
+      ],
+      // This case proves that, if a stable release is installed, there is an
       // error generated when if the next available release is a normal (i.e.,
-      // non-security) release. If unattended updates are only enabled for
-      // security releases, the next available release will be ignored, and
-      // therefore generate no validation errors, because it's not a security
-      // release.
-      'update to normal release' => [
+      // non-security) release, if unattended updates are only enabled for
+      // security releases.
+      'update to normal release, cron enabled for security releases' => [
         '9.8.1',
+        '9.8.2',
         "$metadata_dir/drupal.9.8.2.xml",
-        [CronUpdateStage::DISABLED, CronUpdateStage::SECURITY, CronUpdateStage::ALL],
-        [],
+        [CronUpdateStage::SECURITY],
+        [
+          t('Drupal cannot be automatically updated during cron from 9.8.1 to 9.8.2 because 9.8.2 is not a security release.'),
+        ],
+      ],
+      // The next three cases prove that normal (i.e., non-security) update to
+      // an alpha, beta, or RC release raises multiple errors if unattended
+      // updates are enabled only for security releases.
+      'normal update to alpha, cron enabled for security releases' => [
+        '9.8.0',
+        '9.8.1-alpha1',
+        "$metadata_dir/drupal.9.8.1-extra.xml",
+        [CronUpdateStage::SECURITY],
+        [
+          t('Drupal cannot be automatically updated during cron to the recommended version, 9.8.1-alpha1, because it is not a stable version.'),
+          t('Drupal cannot be automatically updated during cron from 9.8.0 to 9.8.1-alpha1 because 9.8.1-alpha1 is not a security release.'),
+        ],
+      ],
+      'normal update to beta, cron enabled for security releases' => [
+        '9.8.0',
+        '9.8.1-beta2',
+        "$metadata_dir/drupal.9.8.1-extra.xml",
+        [CronUpdateStage::SECURITY],
+        [
+          t('Drupal cannot be automatically updated during cron to the recommended version, 9.8.1-beta2, because it is not a stable version.'),
+          t('Drupal cannot be automatically updated during cron from 9.8.0 to 9.8.1-beta2 because 9.8.1-beta2 is not a security release.'),
+        ],
+      ],
+      'normal update to rc, cron enabled for security releases' => [
+        '9.8.0',
+        '9.8.1-rc3',
+        "$metadata_dir/drupal.9.8.1-extra.xml",
+        [CronUpdateStage::SECURITY],
+        [
+          t('Drupal cannot be automatically updated during cron to the recommended version, 9.8.1-rc3, because it is not a stable version.'),
+          t('Drupal cannot be automatically updated during cron from 9.8.0 to 9.8.1-rc3 because 9.8.1-rc3 is not a security release.'),
+        ],
+      ],
+      // The next three cases prove that normal (i.e., non-security) minor
+      // updates to an alpha, beta, or RC release raises multiple errors if
+      // unattended updates are enabled only for security releases.
+      'update to alpha of next minor, cron enabled for security releases, minor updates forbidden' => [
+        '9.7.0',
+        '9.8.1-alpha1',
+        "$metadata_dir/drupal.9.8.1-extra.xml",
+        [CronUpdateStage::SECURITY],
+        [
+          t('Drupal cannot be automatically updated during cron to the recommended version, 9.8.1-alpha1, because it is not a stable version.'),
+          t('Drupal cannot be automatically updated from 9.7.0 to 9.8.1-alpha1 because automatic updates from one minor version to another are not supported during cron.'),
+          t('Drupal cannot be automatically updated during cron from 9.7.0 to 9.8.1-alpha1 because 9.8.1-alpha1 is not a security release.'),
+        ],
+      ],
+      'update to beta of next minor, cron enabled for security releases, minor updates forbidden' => [
+        '9.7.0',
+        '9.8.1-beta2',
+        "$metadata_dir/drupal.9.8.1-extra.xml",
+        [CronUpdateStage::SECURITY],
+        [
+          t('Drupal cannot be automatically updated during cron to the recommended version, 9.8.1-beta2, because it is not a stable version.'),
+          t('Drupal cannot be automatically updated from 9.7.0 to 9.8.1-beta2 because automatic updates from one minor version to another are not supported during cron.'),
+          t('Drupal cannot be automatically updated during cron from 9.7.0 to 9.8.1-beta2 because 9.8.1-beta2 is not a security release.'),
+        ],
+      ],
+      'update to rc of next minor, cron enabled for security releases, minor updates forbidden' => [
+        '9.7.0',
+        '9.8.1-rc3',
+        "$metadata_dir/drupal.9.8.1-extra.xml",
+        [CronUpdateStage::SECURITY],
+        [
+          t('Drupal cannot be automatically updated during cron to the recommended version, 9.8.1-rc3, because it is not a stable version.'),
+          t('Drupal cannot be automatically updated from 9.7.0 to 9.8.1-rc3 because automatic updates from one minor version to another are not supported during cron.'),
+          t('Drupal cannot be automatically updated during cron from 9.7.0 to 9.8.1-rc3 because 9.8.1-rc3 is not a security release.'),
+        ],
       ],
       // These three cases prove that updating from an unsupported minor version
-      // will raise an error if unattended updates are enabled. Furthermore, if
-      // an error is raised, the messaging will vary depending on whether
-      // attended updates across minor versions are allowed. (Note that the
-      // target version will not be automatically detected because the release
-      // metadata used in these cases doesn't have any 9.7.x releases.)
-      'update from unsupported minor, cron disabled' => [
+      // will raise an error for unattended updates, if unattended updates are
+      // enabled. Furthermore, if an error is raised, the messaging will vary
+      // depending on whether attended updates across minor versions are
+      // allowed. (Note that the target version will not be automatically
+      // detected because the release metadata used in these cases doesn't have
+      // any 9.7.x releases.)
+      'update from unsupported minor, cron disabled, minor updates forbidden' => [
         '9.7.1',
+        '9.8.1',
         "$metadata_dir/drupal.9.8.1-security.xml",
         [CronUpdateStage::DISABLED],
-        [],
+        [
+          t('Drupal cannot be automatically updated from 9.7.1 to 9.8.1 because automatic updates from one minor version to another are not supported.'),
+        ],
       ],
       'update from unsupported minor, cron enabled, minor updates forbidden' => [
         '9.7.1',
+        '9.8.1',
         "$metadata_dir/drupal.9.8.1-security.xml",
         [CronUpdateStage::SECURITY, CronUpdateStage::ALL],
         [
-          $this->createVersionPolicyValidationResult('9.7.1', NULL, [
-            t('The currently installed version of Drupal core, 9.7.1, is not in a supported minor version. Your site will not be automatically updated during cron until it is updated to a supported minor version.'),
-            t('See the <a href="/admin/reports/updates">available updates page</a> for available updates.'),
-          ]),
+          t('The currently installed version of Drupal core, 9.7.1, is not in a supported minor version. Your site will not be automatically updated during cron until it is updated to a supported minor version.'),
+          t('See the <a href="/admin/reports/updates">available updates page</a> for available updates.'),
+          t('Drupal cannot be automatically updated from 9.7.1 to 9.8.1 because automatic updates from one minor version to another are not supported during cron.'),
         ],
       ],
       'update from unsupported minor, cron enabled, minor updates allowed' => [
         '9.7.1',
+        '9.8.1',
         "$metadata_dir/drupal.9.8.1-security.xml",
         [CronUpdateStage::SECURITY, CronUpdateStage::ALL],
         [
-          $this->createVersionPolicyValidationResult('9.7.1', NULL, [
-            t('The currently installed version of Drupal core, 9.7.1, is not in a supported minor version. Your site will not be automatically updated during cron until it is updated to a supported minor version.'),
-            t('Use the <a href="/admin/modules/update">update form</a> to update to a supported version.'),
-          ]),
+          t('The currently installed version of Drupal core, 9.7.1, is not in a supported minor version. Your site will not be automatically updated during cron until it is updated to a supported minor version.'),
+          t('Use the <a href="/admin/modules/update">update form</a> to update to a supported version.'),
+          t('Drupal cannot be automatically updated from 9.7.1 to 9.8.1 because automatic updates from one minor version to another are not supported during cron.'),
         ],
         TRUE,
       ],
@@ -157,10 +371,12 @@ class VersionPolicyValidatorTest extends AutomaticUpdatesKernelTestBase {
   }
 
   /**
-   * Tests target version validation during status checks.
+   * Tests target version validation during pre-create.
    *
    * @param string $installed_version
    *   The installed version of Drupal core.
+   * @param string $target_version
+   *   The target version of Drupal core.
    * @param string $release_metadata
    *   The path of the core release metadata to serve to the update system.
    * @param string[] $cron_modes
@@ -168,25 +384,58 @@ class VersionPolicyValidatorTest extends AutomaticUpdatesKernelTestBase {
    *   \Drupal\automatic_updates\CronUpdateStage::DISABLED,
    *   \Drupal\automatic_updates\CronUpdateStage::SECURITY, and
    *   \Drupal\automatic_updates\CronUpdateStage::ALL.
-   * @param \Drupal\package_manager\ValidationResult[] $expected_results
-   *   The expected validation results.
+   * @param \Drupal\Core\StringTranslation\TranslatableMarkup[] $expected_validation_messages
+   *   The expected validation messages.
    * @param bool $allow_minor_updates
    *   (optional) Whether or not attended updates across minor updates are
    *   allowed. Defaults to FALSE.
    *
-   * @dataProvider providerStatusCheck
+   * @dataProvider providerGeneric
+   * @dataProvider providerCronPreCreateSpecific
    */
-  public function testStatusCheck(string $installed_version, string $release_metadata, array $cron_modes, array $expected_results, bool $allow_minor_updates = FALSE): void {
+  public function testCronPreCreate(string $installed_version, string $target_version, string $release_metadata, array $cron_modes, array $expected_validation_messages, bool $allow_minor_updates = FALSE): void {
     $this->setCoreVersion($installed_version);
     $this->setReleaseMetadata(['drupal' => $release_metadata]);
 
+    // On pre-create, make the stage think that we're updating
+    // drupal/core-recommended to $target_version. We need to do this to test
+    // version validation during pre-create of an unattended update. We can't
+    // use StageFixtureManipulator::setCorePackageVersion() for this, because
+    // that would get executed after pre-create.
+    // @see \Drupal\automatic_updates\Validator\VersionPolicyValidator::validateVersion()
+    $this->addEventTestListener(function (PreCreateEvent $event) use ($target_version): void {
+      /** @var \Drupal\Tests\automatic_updates\Kernel\TestCronUpdateStage $stage */
+      $stage = $event->stage;
+      $stage->setMetadata('packages', [
+        'production' => [
+          'drupal/core-recommended' => $target_version,
+        ],
+      ]);
+    }, PreCreateEvent::class);
+
+    $expected_results = [];
+    if ($expected_validation_messages) {
+      $expected_results[] = $this->createVersionPolicyValidationResult($installed_version, $target_version, $expected_validation_messages);
+    }
+
     foreach ($cron_modes as $cron_mode) {
       $this->config('automatic_updates.settings')
         ->set('cron', $cron_mode)
         ->set('allow_core_minor_updates', $allow_minor_updates)
         ->save();
 
-      $this->assertCheckerResultsFromManager($expected_results, TRUE);
+      $stage = $this->container->get(CronUpdateStage::class);
+      try {
+        $stage->create();
+        // If we did not get an exception, ensure we didn't expect any results.
+        $this->assertEmpty($expected_results);
+      }
+      catch (StageEventException $e) {
+        $this->assertExpectedResultsFromException($expected_results, $e);
+      }
+      finally {
+        $stage->destroy(TRUE);
+      }
     }
   }
 
@@ -210,6 +459,72 @@ class VersionPolicyValidatorTest extends AutomaticUpdatesKernelTestBase {
           ]),
         ],
       ],
+      'unsupported target, minor version upgrade' => [
+        '9.7.1',
+        "$metadata_dir/drupal.9.8.2-unsupported_unpublished.xml",
+        ['drupal' => '9.8.1'],
+        [
+          $this->createVersionPolicyValidationResult('9.7.1', '9.8.1', [
+            t('Drupal cannot be automatically updated from 9.7.1 to 9.8.1 because automatic updates from one minor version to another are not supported.'),
+          ]),
+        ],
+      ],
+      'unsupported target, major version upgrade' => [
+        '8.9.1',
+        "$metadata_dir/drupal.9.8.2-unsupported_unpublished.xml",
+        ['drupal' => '9.8.1'],
+        [
+          $this->createVersionPolicyValidationResult('8.9.1', '9.8.1', [
+            t('Drupal cannot be automatically updated from 8.9.1 to 9.8.1 because automatic updates from one major version to another are not supported.'),
+          ]),
+        ],
+      ],
+      // The following cases are used to test every combination if a dev
+      // snapshot is installed.
+      'insecure target, dev snapshot installed' => [
+        '9.8.0-dev',
+        "$metadata_dir/drupal.9.8.1-security.xml",
+        ['drupal' => '9.8.0'],
+        [
+          $this->createVersionPolicyValidationResult('9.8.0-dev', '9.8.0', [
+            t('Drupal cannot be automatically updated from the installed version, 9.8.0-dev, because automatic updates from a dev version to any other version are not supported.'),
+            t('Cannot update Drupal core to 9.8.0 because it is not in the list of installable releases.'),
+          ]),
+        ],
+      ],
+      'downgrade major, dev snapshot installed' => [
+        '9.8.0-dev',
+        "$metadata_dir/drupal.9.8.1-security.xml",
+        ['drupal' => '8.7.1'],
+        [
+          $this->createVersionPolicyValidationResult('9.8.0-dev', '8.7.1', [
+            t('Drupal cannot be automatically updated from the installed version, 9.8.0-dev, because automatic updates from a dev version to any other version are not supported.'),
+            t('Update version 8.7.1 is lower than 9.8.0-dev, downgrading is not supported.'),
+          ]),
+        ],
+      ],
+      'downgrade minor, dev snapshot installed' => [
+        '9.8.0-dev',
+        "$metadata_dir/drupal.9.8.1-security.xml",
+        ['drupal' => '9.7.0'],
+        [
+          $this->createVersionPolicyValidationResult('9.8.0-dev', '9.7.0', [
+            t('Drupal cannot be automatically updated from the installed version, 9.8.0-dev, because automatic updates from a dev version to any other version are not supported.'),
+            t('Update version 9.7.0 is lower than 9.8.0-dev, downgrading is not supported.'),
+          ]),
+        ],
+      ],
+      'patch downgrade, dev snapshot installed' => [
+        '9.8.1-dev',
+        "$metadata_dir/drupal.9.8.1-security.xml",
+        ['drupal' => '9.8.0'],
+        [
+          $this->createVersionPolicyValidationResult('9.8.1-dev', '9.8.0', [
+            t('Drupal cannot be automatically updated from the installed version, 9.8.1-dev, because automatic updates from a dev version to any other version are not supported.'),
+            t('Update version 9.8.0 is lower than 9.8.1-dev, downgrading is not supported.'),
+          ]),
+        ],
+      ],
       // The following cases can only happen by explicitly supplying the
       // update stage with an invalid target version.
       'downgrade' => [
@@ -332,7 +647,7 @@ class VersionPolicyValidatorTest extends AutomaticUpdatesKernelTestBase {
    *   The installed version of Drupal core.
    * @param string|null $target_version
    *   The target version of Drupal core, or NULL if it's not known.
-   * @param string[] $messages
+   * @param \Drupal\Core\StringTranslation\TranslatableMarkup[] $messages
    *   The error messages that the result should contain.
    *
    * @return \Drupal\package_manager\ValidationResult
diff --git a/tests/src/Unit/VersionPolicy/MinorUpdatesEnabledTest.php b/tests/src/Unit/VersionPolicy/MinorUpdatesEnabledTest.php
deleted file mode 100644
index 9059ef64a93365e5033bfd8926e42bb2545e9695..0000000000000000000000000000000000000000
--- a/tests/src/Unit/VersionPolicy/MinorUpdatesEnabledTest.php
+++ /dev/null
@@ -1,139 +0,0 @@
-<?php
-
-declare(strict_types = 1);
-
-namespace Drupal\Tests\automatic_updates\Unit\VersionPolicy;
-
-use Drupal\automatic_updates\Validator\VersionPolicy\MinorUpdatesEnabled;
-use Drupal\Tests\automatic_updates\Traits\VersionPolicyTestTrait;
-use Drupal\Tests\UnitTestCase;
-
-/**
- * @covers \Drupal\automatic_updates\Validator\VersionPolicy\MinorUpdatesEnabled
- * @group automatic_updates
- * @internal
- */
-class MinorUpdatesEnabledTest extends UnitTestCase {
-
-  use VersionPolicyTestTrait;
-
-  /**
-   * Data provider for testMinorUpdatesEnabled().
-   *
-   * @return mixed[][]
-   *   The test cases.
-   */
-  public function providerMinorUpdatesEnabled(): array {
-    return [
-      'same versions, minor updates forbidden' => [
-        FALSE,
-        '9.8.0',
-        '9.8.0',
-        [],
-      ],
-      'same versions, minor updates allowed' => [
-        TRUE,
-        '9.8.0',
-        '9.8.0',
-        [],
-      ],
-      'target version newer in same minor, minor updates forbidden' => [
-        FALSE,
-        '9.8.0',
-        '9.8.2',
-        [],
-      ],
-      'target version newer in same minor, minor updates allowed' => [
-        TRUE,
-        '9.8.0',
-        '9.8.2',
-        [],
-      ],
-      'target version in newer minor, minor updates forbidden' => [
-        FALSE,
-        '9.8.0',
-        '9.9.2',
-        ['Drupal cannot be automatically updated from 9.8.0 to 9.9.2 because automatic updates from one minor version to another are not supported.'],
-      ],
-      'target version in newer minor, minor updates allowed' => [
-        TRUE,
-        '9.8.0',
-        '9.9.2',
-        [],
-      ],
-      'target version older in same minor, minor updates forbidden' => [
-        FALSE,
-        '9.8.2',
-        '9.8.0',
-        [],
-      ],
-      'target version older in same minor, minor updates allowed' => [
-        TRUE,
-        '9.8.2',
-        '9.8.0',
-        [],
-      ],
-      'target version in older minor, minor updates forbidden' => [
-        FALSE,
-        '9.8.0',
-        '9.7.2',
-        ['Drupal cannot be automatically updated from 9.8.0 to 9.7.2 because automatic updates from one minor version to another are not supported.'],
-      ],
-      'target version in older minor, minor updates allowed' => [
-        TRUE,
-        '9.8.0',
-        '9.7.2',
-        [],
-      ],
-      'target version in older major, minor updates forbidden' => [
-        FALSE,
-        '9.8.0',
-        '8.8.0',
-        ['Drupal cannot be automatically updated from 9.8.0 to 8.8.0 because automatic updates from one minor version to another are not supported.'],
-      ],
-      'target version in older major, minor updates allowed' => [
-        FALSE,
-        '9.8.0',
-        '8.8.0',
-        ['Drupal cannot be automatically updated from 9.8.0 to 8.8.0 because automatic updates from one minor version to another are not supported.'],
-      ],
-      'target version in newer major, minor updates forbidden' => [
-        FALSE,
-        '9.8.0',
-        '10.8.0',
-        ['Drupal cannot be automatically updated from 9.8.0 to 10.8.0 because automatic updates from one minor version to another are not supported.'],
-      ],
-      'target version in newer major, minor updates allowed' => [
-        FALSE,
-        '9.8.0',
-        '10.8.0',
-        ['Drupal cannot be automatically updated from 9.8.0 to 10.8.0 because automatic updates from one minor version to another are not supported.'],
-      ],
-    ];
-  }
-
-  /**
-   * Tests that trying to update across minor versions depends on configuration.
-   *
-   * @param bool $allowed
-   *   Whether or not updating across minor versions is allowed.
-   * @param string $installed_version
-   *   The installed version of Drupal core.
-   * @param string|null $target_version
-   *   The target version of Drupal core, or NULL if not known.
-   * @param string[] $expected_errors
-   *   The expected error messages, if any.
-   *
-   * @dataProvider providerMinorUpdatesEnabled
-   */
-  public function testMinorUpdatesEnabled(bool $allowed, string $installed_version, ?string $target_version, array $expected_errors): void {
-    $config_factory = $this->getConfigFactoryStub([
-      'automatic_updates.settings' => [
-        'allow_core_minor_updates' => $allowed,
-      ],
-    ]);
-    $rule = new MinorUpdatesEnabled($config_factory);
-    $this->assertPolicyErrors($rule, $installed_version, $target_version, $expected_errors);
-  }
-
-}
diff --git a/tests/src/Unit/VersionPolicy/TargetVersionInstallableTest.php b/tests/src/Unit/VersionPolicy/TargetVersionInstallableTest.php
index 42fd26d56856759232cb0242d20c65034bb522dd..435ba5390320afda772b546f0ae039d1a39ca5ea 100644
--- a/tests/src/Unit/VersionPolicy/TargetVersionInstallableTest.php
+++ b/tests/src/Unit/VersionPolicy/TargetVersionInstallableTest.php
@@ -27,10 +27,16 @@ class TargetVersionInstallableTest extends UnitTestCase {
   public function providerTargetVersionInstallable(): array {
     return [
       'no available releases' => [
+        [TRUE, FALSE],
+        '9.8.1',
+        '9.8.2',
         [],
         ['Cannot update Drupal core to 9.8.2 because it is not in the list of installable releases.'],
       ],
       'unknown target' => [
+        [TRUE, FALSE],
+        '9.8.1',
+        '9.8.2',
         [
           '9.8.1' => ProjectRelease::createFromArray([
             'status' => 'published',
@@ -41,6 +47,9 @@ class TargetVersionInstallableTest extends UnitTestCase {
         ['Cannot update Drupal core to 9.8.2 because it is not in the list of installable releases.'],
       ],
       'valid target' => [
+        [TRUE, FALSE],
+        '9.8.1',
+        '9.8.2',
         [
           '9.8.2' => ProjectRelease::createFromArray([
             'status' => 'published',
@@ -50,12 +59,126 @@ class TargetVersionInstallableTest extends UnitTestCase {
         ],
         [],
       ],
+      'installed version and target version are the same' => [
+        [TRUE, FALSE],
+        '9.8.0',
+        '9.8.0',
+        [],
+        ['Cannot update Drupal core to 9.8.0 because it is not in the list of installable releases.'],
+      ],
+      'unknown patch update' => [
+        [TRUE, FALSE],
+        '9.8.0',
+        '9.8.2',
+        [],
+        ['Cannot update Drupal core to 9.8.2 because it is not in the list of installable releases.'],
+      ],
+      'valid target version newer in same minor' => [
+        [TRUE, FALSE],
+        '9.8.0',
+        '9.8.2',
+        [
+          '9.8.2' => ProjectRelease::createFromArray([
+            'status' => 'published',
+            'release_link' => 'http://example.com/drupal-9-8-2-release',
+            'version' => '9.8.2',
+          ]),
+        ],
+        [],
+      ],
+      'target version in newer minor, minor updates forbidden' => [
+        [FALSE],
+        '9.8.0',
+        '9.9.2',
+        [],
+        ['Drupal cannot be automatically updated from 9.8.0 to 9.9.2 because automatic updates from one minor version to another are not supported.'],
+      ],
+      'unknown target version in newer minor, minor updates allowed' => [
+        [TRUE],
+        '9.8.0',
+        '9.9.2',
+        [],
+        ['Cannot update Drupal core to 9.9.2 because it is not in the list of installable releases.'],
+      ],
+      'valid target version in newer minor, minor updates allowed' => [
+        [TRUE],
+        '9.8.0',
+        '9.9.2',
+        [
+          '9.9.2' => ProjectRelease::createFromArray([
+            'status' => 'published',
+            'release_link' => 'http://example.com/drupal-9-9-2-release',
+            'version' => '9.9.2',
+          ]),
+        ],
+        [],
+      ],
+      'target version older in same minor' => [
+        [TRUE, FALSE],
+        '9.8.2',
+        '9.8.0',
+        [],
+        ['Cannot update Drupal core to 9.8.0 because it is not in the list of installable releases.'],
+      ],
+      'target version in older minor, minor updates forbidden' => [
+        [FALSE],
+        '9.8.0',
+        '9.7.2',
+        [],
+        ['Drupal cannot be automatically updated from 9.8.0 to 9.7.2 because automatic updates from one minor version to another are not supported.'],
+      ],
+      'target version in older minor, minor updates allowed' => [
+        [TRUE],
+        '9.8.0',
+        '9.7.2',
+        [],
+        ['Cannot update Drupal core to 9.7.2 because it is not in the list of installable releases.'],
+      ],
+      // In practice, the message produced by the next four cases will be
+      // superseded by the MajorVersionMatch rule.
+      // @see \Drupal\automatic_updates\Validator\VersionPolicy\MajorVersionMatch
+      // @see \Drupal\automatic_updates\Validator\VersionPolicyValidator::isRuleSuperseded()
+      'target version in older major, minor updates forbidden' => [
+        [FALSE],
+        '9.8.0',
+        '8.8.0',
+        [],
+        ['Drupal cannot be automatically updated from 9.8.0 to 8.8.0 because automatic updates from one minor version to another are not supported.'],
+      ],
+      'target version in older major, minor updates allowed' => [
+        [TRUE],
+        '9.8.0',
+        '8.8.0',
+        [],
+        ['Cannot update Drupal core to 8.8.0 because it is not in the list of installable releases.'],
+      ],
+      'target version in newer major, minor updates forbidden' => [
+        [FALSE],
+        '9.8.0',
+        '10.8.0',
+        [],
+        ['Drupal cannot be automatically updated from 9.8.0 to 10.8.0 because automatic updates from one minor version to another are not supported.'],
+      ],
+      'target version in newer major, minor updates allowed' => [
+        [TRUE],
+        '9.8.0',
+        '10.8.0',
+        [],
+        ['Cannot update Drupal core to 10.8.0 because it is not in the list of installable releases.'],
+      ],
     ];
   }
 
   /**
    * Tests that the target version must be a known, installable release.
    *
+   * @param bool[] $minor_updates_allowed
+   *   The values of the allow_core_minor_updates config flag. The rule will be
+   *   tested separately with each value.
+   * @param string $installed_version
+   *   The installed version of Drupal core.
+   * @param string $target_version
+   *   The target version of Drupal core, or NULL if not known.
    * @param \Drupal\update\ProjectRelease[] $available_releases
    *   The available releases of Drupal core, keyed by version.
    * @param string[] $expected_errors
@@ -63,9 +186,16 @@ class TargetVersionInstallableTest extends UnitTestCase {
    *
    * @dataProvider providerTargetVersionInstallable
    */
-  public function testTargetVersionInstallable(array $available_releases, array $expected_errors): void {
-    $rule = new TargetVersionInstallable();
-    $this->assertPolicyErrors($rule, '9.8.1', '9.8.2', $expected_errors, $available_releases);
+  public function testTargetVersionInstallable(array $minor_updates_allowed, string $installed_version, string $target_version, array $available_releases, array $expected_errors): void {
+    foreach ($minor_updates_allowed as $value) {
+      $config_factory = $this->getConfigFactoryStub([
+        'automatic_updates.settings' => [
+          'allow_core_minor_updates' => $value,
+        ],
+      ]);
+      $rule = new TargetVersionInstallable($config_factory);
+      $this->assertPolicyErrors($rule, $installed_version, $target_version, $expected_errors, $available_releases);
+    }
   }
 
 }