Repository: smithy-lang/smithy
Pull request #2245 (2024-04-15 20:38:55)

Add SigV4 and SigV4A migration diff validation
## Overview

~~***Dependent on #2250, which is the first commit of this PR***~~

These changes add SigV4 (`aws.auth#sigv4`, `sigv4`) and SigV4A (`aws.auth#sigv4a`, `sigv4a`) migration diff validation.

**This is important since not all SigV4 credentials work with SigV4A, so migration from SigV4 to SigV4A is not backward compatible.**

At the time of writing, it is uncertain whether migrating from SigV4A to SigV4 is backward compatible.

Simplified, the SigV4 migration validation events are emitted when:

- `aws.auth#sigv4` is replaced by `aws.auth#sigv4a`, or vice versa
- the order of `aws.auth#sigv4` and `aws.auth#sigv4a` is changed in the effective auth schemes
- `aws.auth#sigv4a` is added before an existing `aws.auth#sigv4`, or vice versa

## Validation Tables

Keywords:

- `sigv4` = `aws.auth#sigv4`
- `sigv4a` = `aws.auth#sigv4a`
- `+` = set addition
- `,` = list addition (order matters)
- `...` = set, e.g. `any`, `no sigv4`, `with sigv4`

For both `@auth` and `@smithy.rules#endpointRuleSet` auth schemes, these are the following validations:

- `SigV4Migration` checks for service and operation `@auth` diffs.
- `EndpointSigV4Migration` checks for service `@auth` and `@smithy.rules#endpointRuleSet` diffs.
  - At the time of writing, `sigv4` also includes `sigv4-` sub-schemes except `sigv4-s3express`.
  - At the time of writing, `sigv4a` also includes `sigv4-s3express`.
  - `beta` auth schemes are not considered during SigV4 migration, and will have to manually be added.

Old Model Auth | New Model Auth | Expectation
--- | --- | ---
[`no sigv4` + `no sigv4a`] | [`any`] | Skips validation since no migration
[`with sigv4` + `no sigv4a`] | [`no sigv4a`] | Skips validation since no migration
[`with sigv4a` + `no sigv4`] | [`no sigv4`] | Skips validation since no migration
[`with sigv4`, `with sigv4a`] | [`with sigv4`, `with sigv4a`] | Skips validation since no migration
[`with sigv4` + `no sigv4a`] | [`with sigv4a` + `no sigv4`] | Danger: `sigv4a` replaced `sigv4`, but not all `sigv4` credentials are compatible with `sigv4a`
[`with sigv4a` + `no sigv4`] | [`with sigv4` + `no sigv4a`] | Danger: `sigv4` replaced `sigv4a`, but signing scope could be narrowed (typically from `*`)
[`with sigv4` + `no sigv4a`] | [`with sigv4`, `with sigv4a`] | Backward compatible: `sigv4` will still resolve before `sigv4a`
[`with sigv4a` + `no sigv4`] | [`with sigv4a`, `with sigv4`] | Backward compatible: `sigv4a` will still resolve before `sigv4`
[`with sigv4` + `no sigv4a`] | [`with sigv4a`, `with sigv4`] | Danger: `sigv4a` will resolve before `sigv4`, but not all `sigv4` credentials are compatible with `sigv4a`
[`with sigv4a` + `no sigv4`] | [`with sigv4`, `with sigv4a`] | Danger: `sigv4` will resolve before `sigv4a`, but signing scope could be narrowed (typically from `*`)
[`with sigv4`, `with sigv4a`] | [`with sigv4a`, `with sigv4`] | Danger: `sigv4` and `sigv4a` order is changed, but not all `sigv4` credentials are compatible with `sigv4a`
[`with sigv4a`, `with sigv4`] | [`with sigv4`, `with sigv4a`] | Danger: `sigv4` and `sigv4a` order is changed, but signing scope could be narrowed (typically from `*`)

## Testing

- [x] Added diff tests according to the validation table
- [x] CI passes

## Related

* AWS Authentication Traits: https://smithy.io/2.0/aws/aws-auth.html
* Endpoint `authSchemes` list property: https://smithy.io/2.0/additional-specs/rules-engine/specification.html#endpoint-authschemes-list-property

---
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.



Patch:
diff --git a/smithy-aws-endpoints/build.gradle b/smithy-aws-endpoints/build.gradle
index 7eda2cdcd..2fdbcadae 100644
--- a/smithy-aws-endpoints/build.gradle
+++ b/smithy-aws-endpoints/build.gradle
@@ -11,6 +11,8 @@ ext {
 }
 
 dependencies {
+    api project(":smithy-aws-traits")
+    api project(":smithy-diff")
     api project(":smithy-rules-engine")
     api project(":smithy-model")
     api project(":smithy-utils")
diff --git a/smithy-aws-endpoints/src/main/java/software/amazon/smithy/rulesengine/aws/diff/EndpointSigV4Migration.java b/smithy-aws-endpoints/src/main/java/software/amazon/smithy/rulesengine/aws/diff/EndpointSigV4Migration.java
new file mode 100644
index 000000000..1c12b24ef
--- /dev/null
+++ b/smithy-aws-endpoints/src/main/java/software/amazon/smithy/rulesengine/aws/diff/EndpointSigV4Migration.java
@@ -0,0 +1,249 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package software.amazon.smithy.rulesengine.aws.diff;
+
+import static software.amazon.smithy.rulesengine.aws.language.functions.EndpointAuthUtils.isSigV4AEquivalentAuthScheme;
+import static software.amazon.smithy.rulesengine.aws.language.functions.EndpointAuthUtils.isSigV4EquivalentAuthScheme;
+import static software.amazon.smithy.rulesengine.language.EndpointRuleSet.EndpointPathCollector;
+
+import java.util.AbstractMap.SimpleEntry;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Optional;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+import software.amazon.smithy.diff.ChangedShape;
+import software.amazon.smithy.diff.Differences;
+import software.amazon.smithy.diff.evaluators.AbstractDiffEvaluator;
+import software.amazon.smithy.model.Model;
+import software.amazon.smithy.model.knowledge.ServiceIndex;
+import software.amazon.smithy.model.shapes.ServiceShape;
+import software.amazon.smithy.model.shapes.ShapeId;
+import software.amazon.smithy.model.traits.AuthTrait;
+import software.amazon.smithy.model.validation.ValidationEvent;
+import software.amazon.smithy.rulesengine.aws.language.functions.EndpointAuthUtils;
+import software.amazon.smithy.rulesengine.language.Endpoint;
+import software.amazon.smithy.rulesengine.language.syntax.Identifier;
+import software.amazon.smithy.rulesengine.traits.EndpointRuleSetTrait;
+import software.amazon.smithy.utils.Pair;
+import software.amazon.smithy.utils.SmithyInternalApi;
+
+/**
+ * Emit diff validation for SigV4 migration in the {@code @smithy.rules#endpointRuleSet} trait.
+ *
+ * Specifically, SigV4 ({@code aws.auth#sigv4}) to SigV4A ({@code aws.auth#sigv4a}) due to a subset of credentials
+ * usable with SigV4 that are not usable with SigV4A.
+ *
+ * @see <a href="https://smithy.io/2.0/aws/aws-auth.html">AWS Authentication Traits</a>
+ * @see <a href="https://smithy.io/2.0/additional-specs/rules-engine/specification.html#endpoint-authschemes-list-property">Endpoint {@code authSchemes} list property</a>
+ */
+@SmithyInternalApi
+public final class EndpointSigV4Migration extends AbstractDiffEvaluator {
+    private static final Identifier ID_NAME = Identifier.of("name");
+
+    @Override
+    public List<ValidationEvent> evaluate(Differences differences) {
+        List<ValidationEvent> events = new ArrayList<>();
+
+        Model oldModel = differences.getOldModel();
+        ServiceIndex oldServiceIndex = ServiceIndex.of(oldModel);
+        Model newModel = differences.getNewModel();
+        ServiceIndex newServiceIndex = ServiceIndex.of(newModel);
+
+        // Validate Service effective auth schemes
+        List<ChangedShape<ServiceShape>> serviceChanges = differences
+            .changedShapes(ServiceShape.class)
+            .collect(Collectors.toList());
+        for (ChangedShape<ServiceShape> change : serviceChanges) {
+            ServiceShape oldServiceShape = change.getOldShape();
+            ServiceShape newServiceShape = change.getNewShape();
+
+            if (!oldServiceShape.hasTrait(EndpointRuleSetTrait.ID)
+                || !newServiceShape.hasTrait(EndpointRuleSetTrait.ID)) {
+                continue;
+            }
+
+            Optional<Pair<EndpointRuleSetTrait, EndpointRuleSetTrait>> endpointRuleSetOpt =
+                change.getChangedTrait(EndpointRuleSetTrait.class);
+            Optional<Pair<AuthTrait, AuthTrait>> authOpt =
+                change.getChangedTrait(AuthTrait.class);
+            List<String> oldModeledAuthSchemes = getModeledAuthSchemes(oldServiceIndex, oldServiceShape);
+            List<String> newModeledAuthSchemes = getModeledAuthSchemes(newServiceIndex, newServiceShape);
+            // Validate diffs for changes to `@smithy.rules#endpointRuleSet` and `@auth` and effective auth schemes
+            if (!endpointRuleSetOpt.isPresent()
+                && !authOpt.isPresent()
+                // Check modeled auth schemes since they could change without the `@auth` trait present
+                && oldModeledAuthSchemes.equals(newModeledAuthSchemes)) {
+                continue;
+            }
+
+            EndpointRuleSetTrait oldErs = oldServiceShape.expectTrait(EndpointRuleSetTrait.class);
+            Map<String, Endpoint> oldEndpoints = EndpointPathCollector.from(oldErs).collect();
+
+            EndpointRuleSetTrait newErs = newServiceShape.expectTrait(EndpointRuleSetTrait.class);
+            Map<String, Endpoint> newEndpoints = EndpointPathCollector.from(newErs).collect();
+
+            // JSON path -> Endpoint entries that exist in both the old and new model and are changed
+            Map<String, Pair<Endpoint, Endpoint>> changedEndpoints = newEndpoints.entrySet().stream()
+                .filter(e -> oldEndpoints.containsKey(e.getKey()))
+                .map(e -> new SimpleEntry<String, Pair<Endpoint, Endpoint>>(
+                    e.getKey(),
+                    Pair.of(oldEndpoints.get(e.getKey()), e.getValue())))
+                .filter(e -> !e.getValue().getLeft().equals(e.getValue().getRight()))
+                .collect(Collectors.toMap(Entry::getKey, Entry::getValue));
+            for (Entry<String, Pair<Endpoint, Endpoint>> entry : changedEndpoints.entrySet()) {
+                String jsonPath = entry.getKey();
+                Endpoint oldEndpoint = entry.getValue().getLeft();
+                Endpoint newEndpoint = entry.getValue().getRight();
+                List<String> oldAuthSchemes = getAuthSchemes(oldEndpoint, oldModeledAuthSchemes);
+                List<String> newAuthSchemes = getAuthSchemes(newEndpoint, newModeledAuthSchemes);
+
+                boolean isOldSigV4Present = containsSigV4EquivalentAuthScheme(oldAuthSchemes);
+                boolean isOldSigV4APresent = containsSigV4AEquivalentAuthScheme(oldAuthSchemes);
+                boolean isNewSigV4Present = containsSigV4EquivalentAuthScheme(newAuthSchemes);
+                boolean isNewSigV4APresent = containsSigV4AEquivalentAuthScheme(newAuthSchemes);
+
+                boolean isSigV4Replaced = isOldSigV4Present && !isNewSigV4Present
+                    && !isOldSigV4APresent && isNewSigV4APresent;
+                boolean isSigV4AReplaced = !isOldSigV4Present && isNewSigV4Present
+                    && isOldSigV4APresent && !isNewSigV4APresent;
+                boolean noSigV4XRemoved = isOldSigV4Present && isNewSigV4Present
+                    && isOldSigV4APresent && isNewSigV4APresent;
+                boolean isSigV4Added = !isOldSigV4Present && isNewSigV4Present
+                    && isOldSigV4APresent && isNewSigV4APresent;
+                boolean isSigV4AAdded = isOldSigV4Present && isNewSigV4Present
+                    && !isOldSigV4APresent && isNewSigV4APresent;
+                if (isSigV4Replaced) {
+                    events.add(danger(
+                        newServiceShape,
+                        "The `aws.auth#sigv4` authentication scheme was replaced by the `aws.auth#sigv4a` "
+                        + "authentication scheme in the effective auth schemes for an endpoint in the "
+                        + "`@smithy.rules#endpointRuleSet` trait applied to `" + newServiceShape.getId() + "` at: `"
+                        + jsonPath + "`. "
+                        + "Replacing the `aws.auth#sigv4` authentication scheme with the `aws.auth#sigv4a` "
+                        + "authentication scheme directly is not backward compatible since not all credentials usable "
+                        + "by `aws.auth#sigv4` are compatible with `aws.auth#sigv4a`, and can break existing clients' "
+                        + "authentication."));
+                } else if (isSigV4AReplaced) {
+                    events.add(danger(
+                        newServiceShape,
+                        "The `aws.auth#sigv4a` authentication scheme was replaced by the `aws.auth#sigv4` "
+                        + "authentication scheme in the effective auth schemes for an endpoint in the "
+                        + "`@smithy.rules#endpointRuleSet` trait applied to `" + newServiceShape.getId() + "` at: `"
+                        + jsonPath + "`. "
+                        + "Replacing the `aws.auth#sigv4` authentication scheme with the `aws.auth#sigv4a` "
+                        + "authentication scheme directly may not be backward compatible if the signing scope was "
+                        + "narrowed (typically from `*`)."));
+                } else if (noSigV4XRemoved) {
+                    int oldSigV4Index = getIndexOfSigV4AuthScheme(oldAuthSchemes);
+                    int oldSigV4aIndex = getIndexOfSigV4AAuthScheme(oldAuthSchemes);
+                    int sigV4Index = getIndexOfSigV4AuthScheme(newAuthSchemes);
+                    int sigV4aIndex = getIndexOfSigV4AAuthScheme(newAuthSchemes);
+                    boolean isOldSigV4BeforeSigV4A = oldSigV4Index < oldSigV4aIndex;
+                    boolean isSigV4BeforeSigV4A = sigV4Index < sigV4aIndex;
+                    if (isOldSigV4BeforeSigV4A && !isSigV4BeforeSigV4A) {
+                        events.add(danger(
+                            newServiceShape,
+                            "The `aws.auth#sigv4a` authentication scheme was moved before the `aws.auth#sigv4` "
+                            + "authentication scheme in the effective auth schemes for an endpoint in the "
+                            + "`@smithy.rules#endpointRuleSet` trait applied to `" + newServiceShape.getId()
+                            + "` at: `" + jsonPath + "`. "
+                            + "Moving the `aws.auth#sigv4a` authentication scheme before the `aws.auth#sigv4` "
+                            + "authentication scheme is not backward compatible since not all credentials usable by "
+                            + "`aws.auth#sigv4` are compatible with `aws.auth#sigv4a`, and can break existing "
+                            + "clients' authentication."));
+                    }
+                    if (!isOldSigV4BeforeSigV4A && isSigV4BeforeSigV4A) {
+                        events.add(danger(
+                            newServiceShape,
+                            "The `aws.auth#sigv4` authentication scheme was moved before the `aws.auth#sigv4a` "
+                            + "authentication scheme in the effective auth schemes for an endpoint in the "
+                            + "`@smithy.rules#endpointRuleSet` trait applied to `" + newServiceShape.getId()
+                            + "` at: `" + jsonPath + "`. "
+                            + "Moving the `aws.auth#sigv4` authentication scheme before the `aws.auth#sigv4a` "
+                            + "authentication scheme may not be backward compatible if the signing scope was narrowed "
+                            + "(typically from `*`)."));
+                    }
+                } else if (isSigV4Added) {
+                    int sigV4Index = getIndexOfSigV4AuthScheme(newAuthSchemes);
+                    int sigV4aIndex = getIndexOfSigV4AAuthScheme(newAuthSchemes);
+                    boolean isSigV4AddedBeforeSigV4A = sigV4Index < sigV4aIndex;
+                    if (isSigV4AddedBeforeSigV4A) {
+                        events.add(danger(
+                            newServiceShape,
+                            "The `aws.auth#sigv4` authentication scheme was added before the `aws.auth#sigv4a` "
+                            + "authentication scheme in the effective auth schemes for an endpoint in the "
+                            + "`@smithy.rules#endpointRuleSet` trait applied to `" + newServiceShape.getId()
+                            + "` at: `" + jsonPath + "`. "
+                            + "Adding the `aws.auth#sigv4` authentication scheme before an existing `aws.auth#sigv4a` "
+                            + "authentication scheme may not be backward compatible if the signing scope was narrowed "
+                            + "(typically from `*`)."));
+                    }
+                } else if (isSigV4AAdded) {
+                    int sigV4Index = getIndexOfSigV4AuthScheme(newAuthSchemes);
+                    int sigV4aIndex = getIndexOfSigV4AAuthScheme(newAuthSchemes);
+                    boolean isSigV4AAddedBeforeSigV4 = sigV4aIndex < sigV4Index;
+                    if (isSigV4AAddedBeforeSigV4) {
+                        events.add(danger(
+                            newServiceShape,
+                            "The `aws.auth#sigv4a` authentication scheme was added before the `aws.auth#sigv4` "
+                            + "authentication scheme in the effective auth schemes for an endpoint in the "
+                            + "`@smithy.rules#endpointRuleSet` trait applied to `" + newServiceShape.getId()
+                            + "` at: `" + jsonPath + "`. "
+                            + "Adding the `aws.auth#sigv4a` authentication scheme before an existing `aws.auth#sigv4` "
+                            + "authentication scheme is not backward compatible since not all credentials usable by "
+                            + "`aws.auth#sigv4` are compatible with `aws.auth#sigv4a`, and can break existing clients' "
+                            + "authentication."));
+                    }
+                }
+            }
+        }
+
+        return events;
+    }
+
+    private static List<String> getAuthSchemes(Endpoint endpoint, List<String> modeledAuthSchemes) {
+        List<String> endpointAuthSchemes = endpoint.getEndpointAuthSchemes().stream()
+            .map(a -> a.get(ID_NAME).asStringLiteral().get().expectLiteral())
+            .collect(Collectors.toList());
+        return endpointAuthSchemes.size() == 0
+            ? modeledAuthSchemes
+            : endpointAuthSchemes;
+    }
+
+    private static List<String> getModeledAuthSchemes(ServiceIndex serviceIndex, ServiceShape serviceShape) {
+        return serviceIndex.getEffectiveAuthSchemes(serviceShape).keySet().stream()
+            .map(ShapeId::toString)
+            .collect(Collectors.toList());
+    }
+
+    private static boolean containsSigV4EquivalentAuthScheme(List<String> authSchemes) {
+        return getIndexOfSigV4AuthScheme(authSchemes) != -1;
+    }
+
+    private static boolean containsSigV4AEquivalentAuthScheme(List<String> authSchemes) {
+        return getIndexOfSigV4AAuthScheme(authSchemes) != -1;
+    }
+
+    private static int getIndexOfSigV4AuthScheme(List<String> authSchemes) {
+        return getIndexOfAuthScheme(authSchemes, EndpointAuthUtils::isSigV4EquivalentAuthScheme);
+    }
+
+    private static int getIndexOfSigV4AAuthScheme(List<String> authSchemes) {
+        return getIndexOfAuthScheme(authSchemes, EndpointAuthUtils::isSigV4AEquivalentAuthScheme);
+    }
+
+    private static int getIndexOfAuthScheme(List<String> authSchemes, Predicate<String> identifier) {
+        for (int i = 0; i < authSchemes.size(); i++) {
+            if (identifier.test(authSchemes.get(i))) {
+                return i;
+            }
+        }
+        return -1;
+    }
+}
diff --git a/smithy-aws-endpoints/src/main/java/software/amazon/smithy/rulesengine/aws/language/functions/EndpointAuthUtils.java b/smithy-aws-endpoints/src/main/java/software/amazon/smithy/rulesengine/aws/language/functions/EndpointAuthUtils.java
index 1c25ea94b..32544ee4a 100644
--- a/smithy-aws-endpoints/src/main/java/software/amazon/smithy/rulesengine/aws/language/functions/EndpointAuthUtils.java
+++ b/smithy-aws-endpoints/src/main/java/software/amazon/smithy/rulesengine/aws/language/functions/EndpointAuthUtils.java
@@ -11,6 +11,8 @@ import java.util.Map;
 import java.util.Optional;
 import java.util.function.BiFunction;
 import java.util.function.Function;
+import software.amazon.smithy.aws.traits.auth.SigV4ATrait;
+import software.amazon.smithy.aws.traits.auth.SigV4Trait;
 import software.amazon.smithy.model.FromSourceLocation;
 import software.amazon.smithy.model.validation.Severity;
 import software.amazon.smithy.model.validation.ValidationEvent;
@@ -26,15 +28,19 @@ import software.amazon.smithy.utils.MapUtils;
  * Utilities for constructing and validating AWS-specific authentication components for rule-sets.
  */
 public final class EndpointAuthUtils {
-    private static final String SIGV_4 = "sigv4";
-    private static final String SIG_V4A = "sigv4a";
+    private static final String SIGV4 = "sigv4";
+    private static final String SIGV4_SHAPE_ID = SigV4Trait.ID.toString();
+    private static final String SIGV4_SUB = "sigv4-";
+    private static final String SIGV4_S3EXPRESS = "sigv4-s3express";
+    private static final String SIGV4A = "sigv4a";
+    private static final String SIGV4A_SHAPE_ID = SigV4ATrait.ID.toString();
     private static final String SIGNING_NAME = "signingName";
     private static final String SIGNING_REGION = "signingRegion";
     private static final String SIGNING_REGION_SET = "signingRegionSet";
 
-    private static final Identifier ID_SIGNING_NAME = Identifier.of("signingName");
-    private static final Identifier ID_SIGNING_REGION = Identifier.of("signingRegion");
-    private static final Identifier ID_SIGNING_REGION_SET = Identifier.of("signingRegionSet");
+    private static final Identifier ID_SIGNING_NAME = Identifier.of(SIGNING_NAME);
+    private static final Identifier ID_SIGNING_REGION = Identifier.of(SIGNING_REGION);
+    private static final Identifier ID_SIGNING_REGION_SET = Identifier.of(SIGNING_REGION_SET);
     private static final Identifier ID_DISABLE_DOUBLE_ENCODING = Identifier.of("disableDoubleEncoding");
     private static final Identifier ID_DISABLE_NORMALIZE_PATH = Identifier.of("disableNormalizePath");
 
@@ -49,7 +55,7 @@ public final class EndpointAuthUtils {
      * @return the updated endpoint builder.
      */
     public static Endpoint.Builder sigv4(Endpoint.Builder builder, Literal signingRegion, Literal signingService) {
-        return builder.addAuthScheme(SIGV_4, MapUtils.of(
+        return builder.addAuthScheme(SIGV4, MapUtils.of(
                 SIGNING_NAME, signingService,
                 SIGNING_REGION, signingRegion));
     }
@@ -67,17 +73,49 @@ public final class EndpointAuthUtils {
             List<Literal> signingRegionSet,
             Literal signingService
     ) {
-        return builder.addAuthScheme(SIG_V4A, MapUtils.of(
+        return builder.addAuthScheme(SIGV4A, MapUtils.of(
                 SIGNING_NAME, signingService,
                 SIGNING_REGION_SET, Literal.tupleLiteral(signingRegionSet)));
     }
 
+    /**
+     * Returns if a given auth scheme is equivalent to {@code aws.auth#sigv4}.
+     *
+     * @param authScheme name of the auth scheme.
+     * @return whether the auth scheme is equivalent to {@code aws.auth#sigv4}.
+     */
+    public static boolean isSigV4EquivalentAuthScheme(String authScheme) {
+        if (authScheme.equals(SIGV4) || authScheme.equals(SIGV4_SHAPE_ID)) {
+            return true;
+        }
+        if (authScheme.startsWith(SIGV4_SUB) && !authScheme.equals(SIGV4_S3EXPRESS)) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Returns if a given auth scheme is equivalent to {@code aws.auth#sigv4a}.
+     *
+     * @param authScheme name of the auth scheme.
+     * @return whether the auth scheme is equivalent to {@code aws.auth#sigv4a}.
+     */
+    public static boolean isSigV4AEquivalentAuthScheme(String authScheme) {
+        if (authScheme.equals(SIGV4A) || authScheme.equals(SIGV4A_SHAPE_ID)) {
+            return true;
+        }
+        if (authScheme.equals(SIGV4_S3EXPRESS)) {
+            return true;
+        }
+        return false;
+    }
+
     static final class SigV4SchemeValidator implements AuthSchemeValidator {
         SigV4SchemeValidator() {}
 
         @Override
         public boolean test(String name) {
-            return name.equals("sigv4");
+            return name.equals(SIGV4);
         }
 
         @Override
@@ -126,7 +164,7 @@ public final class EndpointAuthUtils {
 
         @Override
         public boolean test(String name) {
-            return name.equals("sigv4a");
+            return name.equals(SIGV4A);
         }
 
         @Override
@@ -173,7 +211,7 @@ public final class EndpointAuthUtils {
 
         @Override
         public boolean test(String name) {
-            return name.startsWith("sigv4-");
+            return name.startsWith(SIGV4_SUB);
         }
 
         @Override
diff --git a/smithy-aws-endpoints/src/main/resources/META-INF/services/software.amazon.smithy.diff.DiffEvaluator b/smithy-aws-endpoints/src/main/resources/META-INF/services/software.amazon.smithy.diff.DiffEvaluator
new file mode 100644
index 000000000..e16810eea
--- /dev/null
+++ b/smithy-aws-endpoints/src/main/resources/META-INF/services/software.amazon.smithy.diff.DiffEvaluator
@@ -0,0 +1,1 @@
+software.amazon.smithy.rulesengine.aws.diff.EndpointSigV4Migration
diff --git a/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/diffs/with-sigv4-no-migration.events b/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/diffs/with-sigv4-no-migration.events
new file mode 100644
index 000000000..e69de29bb
diff --git a/smithy-aws-traits/build.gradle b/smithy-aws-traits/build.gradle
index 151dd98db..af2ac03b0 100644
--- a/smithy-aws-traits/build.gradle
+++ b/smithy-aws-traits/build.gradle
@@ -21,5 +21,6 @@ ext {
 }
 
 dependencies {
+    api project(":smithy-diff")
     api project(":smithy-model")
 }
diff --git a/smithy-aws-traits/src/main/java/software/amazon/smithy/aws/traits/auth/diff/SigV4Migration.java b/smithy-aws-traits/src/main/java/software/amazon/smithy/aws/traits/auth/diff/SigV4Migration.java
new file mode 100644
index 000000000..890c9fda8
--- /dev/null
+++ b/smithy-aws-traits/src/main/java/software/amazon/smithy/aws/traits/auth/diff/SigV4Migration.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package software.amazon.smithy.aws.traits.auth.diff;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+import software.amazon.smithy.aws.traits.auth.SigV4ATrait;
+import software.amazon.smithy.aws.traits.auth.SigV4Trait;
+import software.amazon.smithy.diff.ChangedShape;
+import software.amazon.smithy.diff.Differences;
+import software.amazon.smithy.diff.evaluators.AbstractDiffEvaluator;
+import software.amazon.smithy.model.Model;
+import software.amazon.smithy.model.knowledge.ServiceIndex;
+import software.amazon.smithy.model.knowledge.TopDownIndex;
+import software.amazon.smithy.model.shapes.OperationShape;
+import software.amazon.smithy.model.shapes.ServiceShape;
+import software.amazon.smithy.model.shapes.Shape;
+import software.amazon.smithy.model.shapes.ShapeId;
+import software.amazon.smithy.model.validation.ValidationEvent;
+import software.amazon.smithy.utils.SmithyInternalApi;
+
+/**
+ * Emit diff validation for SigV4 migration in the {@code @auth} trait.
+ *
+ * Specifically, SigV4 ({@code aws.auth#sigv4}) to SigV4A ({@code aws.auth#sigv4a}) due to a subset of credentials
+ * usable with SigV4 that are not usable with SigV4A.
+ *
+ * @see <a href="https://smithy.io/2.0/aws/aws-auth.html">AWS Authentication Traits</a>
+ */
+@SmithyInternalApi
+public final class SigV4Migration extends AbstractDiffEvaluator {
+    @Override
+    public List<ValidationEvent> evaluate(Differences differences) {
+        List<ValidationEvent> events = new ArrayList<>();
+
+        Model oldModel = differences.getOldModel();
+        ServiceIndex oldServiceIndex = ServiceIndex.of(oldModel);
+        Model newModel = differences.getNewModel();
+        ServiceIndex newServiceIndex = ServiceIndex.of(newModel);
+
+        // Validate Service effective auth schemes
+        List<ChangedShape<ServiceShape>> serviceChanges = differences
+            .changedShapes(ServiceShape.class)
+            .collect(Collectors.toList());
+        for (ChangedShape<ServiceShape> change : serviceChanges) {
+            ServiceShape oldServiceShape = change.getOldShape();
+            List<ShapeId> oldAuthSchemes = oldServiceIndex
+                .getEffectiveAuthSchemes(oldServiceShape)
+                .keySet()
+                .stream()
+                .collect(Collectors.toList());
+            ServiceShape newServiceShape = change.getNewShape();
+            List<ShapeId> newAuthSchemes = newServiceIndex
+                .getEffectiveAuthSchemes(newServiceShape)
+                .keySet()
+                .stream()
+                .collect(Collectors.toList());
+            validateMigration(
+                newServiceShape,
+                oldAuthSchemes,
+                newAuthSchemes,
+                events);
+        }
+
+        Map<OperationShape, Set<ServiceShape>> operationToContainedServiceBindings =
+            computeOperationToContainedServiceBindings(newModel);
+        List<ChangedShape<OperationShape>> operationChanges = differences
+            .changedShapes(OperationShape.class)
+            .collect(Collectors.toList());
+        for (ChangedShape<OperationShape> change : operationChanges) {
+            OperationShape newOperationShape = change.getNewShape();
+            Set<ServiceShape> newOperationServiceBindings = operationToContainedServiceBindings.get(newOperationShape);
+            if (newOperationServiceBindings == null) {
+                continue;
+            }
+            // Validate Operation effective auth schemes
+            for (ServiceShape newServiceShape : newOperationServiceBindings) {
+                ServiceShape oldServiceShape = oldModel.expectShape(newServiceShape.getId(), ServiceShape.class);
+                OperationShape oldOperationShape = change.getOldShape();
+                List<ShapeId> oldAuthSchemes = oldServiceIndex
+                    .getEffectiveAuthSchemes(oldServiceShape, oldOperationShape)
+                    .keySet()
+                    .stream()
+                    .collect(Collectors.toList());
+                List<ShapeId> newAuthSchemes = newServiceIndex
+                    .getEffectiveAuthSchemes(newServiceShape, newOperationShape)
+                    .keySet()
+                    .stream()
+                    .collect(Collectors.toList());
+                validateMigration(
+                    newOperationShape,
+                    oldAuthSchemes,
+                    newAuthSchemes,
+                    events);
+            }
+        }
+
+        return events;
+    }
+
+    private void validateMigration(
+        Shape shape,
+        List<ShapeId> oldAuthSchemes,
+        List<ShapeId> newAuthSchemes,
+        List<ValidationEvent> events
+    ) {
+        boolean isOldSigV4Present = oldAuthSchemes.contains(SigV4Trait.ID);
+        boolean isOldSigV4APresent = oldAuthSchemes.contains(SigV4ATrait.ID);
+        boolean isNewSigV4Present = newAuthSchemes.contains(SigV4Trait.ID);
+        boolean isNewSigV4APresent = newAuthSchemes.contains(SigV4ATrait.ID);
+
+        boolean isSigV4Replaced = isOldSigV4Present && !isNewSigV4Present && !isOldSigV4APresent && isNewSigV4APresent;
+        boolean isSigV4AReplaced = !isOldSigV4Present && isNewSigV4Present && isOldSigV4APresent && !isNewSigV4APresent;
+        boolean noSigV4XRemoved = isOldSigV4Present && isNewSigV4Present && isOldSigV4APresent && isNewSigV4APresent;
+        boolean isSigV4Added = !isOldSigV4Present && isNewSigV4Present && isOldSigV4APresent && isNewSigV4APresent;
+        boolean isSigV4AAdded = isOldSigV4Present && isNewSigV4Present && !isOldSigV4APresent && isNewSigV4APresent;
+        if (isSigV4Replaced) {
+            events.add(danger(
+                shape,
+                "The `aws.auth#sigv4` authentication scheme was replaced by the `aws.auth#sigv4a` authentication "
+                + "scheme in the effective auth schemes for `" + shape.getId() + "`. "
+                + "Replacing the `aws.auth#sigv4` authentication scheme with the `aws.auth#sigv4a` authentication "
+                + "scheme directly is not backward compatible since not all credentials usable by `aws.auth#sigv4` are "
+                + "compatible with `aws.auth#sigv4a`, and can break existing clients' authentication."));
+        } else if (isSigV4AReplaced) {
+            events.add(danger(
+                shape,
+                "The `aws.auth#sigv4a` authentication scheme was replaced by the `aws.auth#sigv4` authentication "
+                + "scheme in the effective auth schemes for `" + shape.getId() + "`. "
+                + "Replacing the `aws.auth#sigv4` authentication scheme with the `aws.auth#sigv4a` authentication "
+                + "scheme directly may not be backward compatible if the signing scope was narrowed (typically from "
+                + "`*`)."));
+        } else if (noSigV4XRemoved) {
+            int oldSigV4Index = oldAuthSchemes.indexOf(SigV4Trait.ID);
+            int oldSigV4aIndex = oldAuthSchemes.indexOf(SigV4ATrait.ID);
+            int sigV4Index = newAuthSchemes.indexOf(SigV4Trait.ID);
+            int sigV4aIndex = newAuthSchemes.indexOf(SigV4ATrait.ID);
+            boolean isOldSigV4BeforeSigV4A = oldSigV4Index < oldSigV4aIndex;
+            boolean isSigV4BeforeSigV4A = sigV4Index < sigV4aIndex;
+            if (isOldSigV4BeforeSigV4A && !isSigV4BeforeSigV4A) {
+                events.add(danger(
+                    shape,
+                    "The `aws.auth#sigv4a` authentication scheme was moved before the `aws.auth#sigv4` authentication "
+                    + "scheme in the effective auth schemes for `" + shape.getId() + "`. "
+                    + "Moving the `aws.auth#sigv4a` authentication scheme before the `aws.auth#sigv4` authentication "
+                    + "scheme is not backward compatible since not all credentials usable by `aws.auth#sigv4` are "
+                    + "compatible with `aws.auth#sigv4a`, and can break existing clients' authentication."));
+            }
+            if (!isOldSigV4BeforeSigV4A && isSigV4BeforeSigV4A) {
+                events.add(danger(
+                    shape,
+                    "The `aws.auth#sigv4` authentication scheme was moved before the `aws.auth#sigv4a` authentication "
+                    + "scheme in the effective auth schemes for `" + shape.getId() + "`. "
+                    + "Moving the `aws.auth#sigv4` authentication scheme before the `aws.auth#sigv4a` authentication "
+                    + "scheme may not be backward compatible if the signing scope was narrowed (typically from `*`)."));
+            }
+        } else if (isSigV4Added) {
+            int sigV4Index = newAuthSchemes.indexOf(SigV4Trait.ID);
+            int sigV4aIndex = newAuthSchemes.indexOf(SigV4ATrait.ID);
+            boolean isSigV4AddedBeforeSigV4A = sigV4Index < sigV4aIndex;
+            if (isSigV4AddedBeforeSigV4A) {
+                events.add(danger(
+                    shape,
+                    "The `aws.auth#sigv4` authentication scheme was added before the `aws.auth#sigv4a` authentication "
+                    + "scheme in the effective auth schemes for `" + shape.getId() + "`. "
+                    + "Adding the `aws.auth#sigv4` authentication scheme before an existing `aws.auth#sigv4a` "
+                    + "authentication scheme may not be backward compatible if the signing scope was narrowed "
+                    + "(typically from `*`)."));
+            }
+        } else if (isSigV4AAdded) {
+            int sigV4Index = newAuthSchemes.indexOf(SigV4Trait.ID);
+            int sigV4aIndex = newAuthSchemes.indexOf(SigV4ATrait.ID);
+            boolean isSigV4AAddedBeforeSigV4 = sigV4aIndex < sigV4Index;
+            if (isSigV4AAddedBeforeSigV4) {
+                events.add(danger(
+                    shape,
+                    "The `aws.auth#sigv4a` authentication scheme was added before the `aws.auth#sigv4` authentication "
+                    + "scheme in the effective auth schemes for `" + shape.getId() + "`. "
+                    + "Adding the `aws.auth#sigv4a` authentication scheme before an existing `aws.auth#sigv4` "
+                    + "authentication scheme is not backward compatible since not all credentials usable by "
+                    + "`aws.auth#sigv4` are compatible with `aws.auth#sigv4a`, and can break existing clients' "
+                    + "authentication."));
+            }
+        }
+    }
+
+    private static Map<OperationShape, Set<ServiceShape>> computeOperationToContainedServiceBindings(Model model) {
+        Map<OperationShape, Set<ServiceShape>> operationToContainedServiceBindings = new HashMap<>();
+        TopDownIndex topDownIndex = TopDownIndex.of(model);
+        for (OperationShape operationShape : model.getOperationShapes()) {
+            Set<ServiceShape> operationEntry = operationToContainedServiceBindings
+                .getOrDefault(operationShape, new HashSet());
+            for (ServiceShape serviceShape : model.getServiceShapes()) {
+                if (topDownIndex.getContainedOperations(serviceShape).contains(operationShape)) {
+                    operationEntry.add(serviceShape);
+                }
+            }
+            operationToContainedServiceBindings.put(operationShape, operationEntry);
+        }
+        return operationToContainedServiceBindings;
+    }
+}
diff --git a/smithy-aws-traits/src/main/resources/META-INF/services/software.amazon.smithy.diff.DiffEvaluator b/smithy-aws-traits/src/main/resources/META-INF/services/software.amazon.smithy.diff.DiffEvaluator
new file mode 100644
index 000000000..5bbacbffb
--- /dev/null
+++ b/smithy-aws-traits/src/main/resources/META-INF/services/software.amazon.smithy.diff.DiffEvaluator
@@ -0,0 +1,1 @@
+software.amazon.smithy.aws.traits.auth.diff.SigV4Migration
diff --git a/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/diffs/with-sigv4-no-migration.events b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/diffs/with-sigv4-no-migration.events
new file mode 100644
index 000000000..e69de29bb
diff --git a/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/Endpoint.java b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/Endpoint.java
index dbaa28211..0f0d3010f 100644
--- a/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/Endpoint.java
+++ b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/Endpoint.java
@@ -12,8 +12,11 @@ import java.util.Comparator;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Map.Entry;
 import java.util.Objects;
+import java.util.Optional;
 import java.util.TreeMap;
+import java.util.stream.Collectors;
 import software.amazon.smithy.model.FromSourceLocation;
 import software.amazon.smithy.model.SourceLocation;
 import software.amazon.smithy.model.node.ArrayNode;
@@ -42,6 +45,8 @@ public final class Endpoint implements FromSourceLocation, ToNode, ToSmithyBuild
     private static final String URL = "url";
     private static final String PROPERTIES = "properties";
     private static final String HEADERS = "headers";
+    private static final Identifier ID_AUTH_SCHEMES = Identifier.of("authSchemes");
+    private static final Identifier ID_NAME = Identifier.of("name");
 
     private final Map<String, List<Expression>> headers;
     private final Map<Identifier, Literal> properties;
@@ -57,12 +62,12 @@ public final class Endpoint implements FromSourceLocation, ToNode, ToSmithyBuild
         List<Literal> authSchemes = new ArrayList<>();
         for (Map.Entry<Identifier, Map<Identifier, Literal>> authScheme : builder.authSchemes.get().entrySet()) {
             Map<Identifier, Literal> base = new TreeMap<>(Comparator.comparing(Identifier::toString));
-            base.put(Identifier.of("name"), Literal.of(authScheme.getKey().toString()));
+            base.put(ID_NAME, Literal.of(authScheme.getKey().toString()));
             base.putAll(authScheme.getValue());
             authSchemes.add(Literal.recordLiteral(base));
         }
         if (!authSchemes.isEmpty()) {
-            builder.putProperty(Identifier.of("authSchemes"), Literal.tupleLiteral(authSchemes));
+            builder.putProperty(ID_AUTH_SCHEMES, Literal.tupleLiteral(authSchemes));
         }
 
         this.properties = builder.properties.copy();
@@ -139,6 +144,19 @@ public final class Endpoint implements FromSourceLocation, ToNode, ToSmithyBuild
         return properties;
     }
 
+    /**
+     * Get the endpoint {@code authSchemes} property as a map of {@link Identifier} to {@link Literal} values.
+     *
+     * @return the list of endpoint {@code authSchemes}.
+     */
+    public List<Map<Identifier, Literal>> getEndpointAuthSchemes() {
+        return Optional.ofNullable(getProperties().get(ID_AUTH_SCHEMES))
+            .map(a -> a.asTupleLiteral().get().stream()
+                .map(l -> l.asRecordLiteral().get())
+                .collect(Collectors.toList()))
+            .orElse(Collections.emptyList());
+    }
+
     @Override
     public Builder toBuilder() {
         return builder().sourceLocation(sourceLocation).url(url).headers(headers).properties(properties);
diff --git a/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/EndpointRuleSet.java b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/EndpointRuleSet.java
index fb97cd836..860c3e52b 100644
--- a/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/EndpointRuleSet.java
+++ b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/language/EndpointRuleSet.java
@@ -8,7 +8,10 @@ package software.amazon.smithy.rulesengine.language;
 import static software.amazon.smithy.rulesengine.language.error.RuleError.context;
 
 import java.util.Collection;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
 import java.util.Objects;
 import java.util.Optional;
 import java.util.function.Function;
@@ -17,6 +20,7 @@ import software.amazon.smithy.model.SourceLocation;
 import software.amazon.smithy.model.node.ArrayNode;
 import software.amazon.smithy.model.node.Node;
 import software.amazon.smithy.model.node.ObjectNode;
+import software.amazon.smithy.model.node.StringNode;
 import software.amazon.smithy.model.node.ToNode;
 import software.amazon.smithy.rulesengine.language.error.RuleError;
 import software.amazon.smithy.rulesengine.language.evaluation.Scope;
@@ -27,6 +31,7 @@ import software.amazon.smithy.rulesengine.language.syntax.expressions.functions.
 import software.amazon.smithy.rulesengine.language.syntax.parameters.Parameters;
 import software.amazon.smithy.rulesengine.language.syntax.rule.EndpointRule;
 import software.amazon.smithy.rulesengine.language.syntax.rule.Rule;
+import software.amazon.smithy.rulesengine.traits.EndpointRuleSetTrait;
 import software.amazon.smithy.rulesengine.validators.AuthSchemeValidator;
 import software.amazon.smithy.utils.BuilderRef;
 import software.amazon.smithy.utils.SmithyBuilder;
@@ -57,10 +62,10 @@ public final class EndpointRuleSet implements FromSourceLocation, ToNode, ToSmit
 
     private EndpointRuleSet(Builder builder) {
         super();
-        parameters = SmithyBuilder.requiredState("parameters", builder.parameters);
+        parameters = SmithyBuilder.requiredState(PARAMETERS, builder.parameters);
         rules = builder.rules.copy();
         sourceLocation = SmithyBuilder.requiredState("source", builder.getSourceLocation());
-        version = SmithyBuilder.requiredState("version", builder.version);
+        version = SmithyBuilder.requiredState(VERSION, builder.version);
     }
 
     /**
@@ -313,4 +318,89 @@ public final class EndpointRuleSet implements FromSourceLocation, ToNode, ToSmit
             return ruleSet;
         }
     }
+
+    /**
+     * {@link EndpointRuleSet} visitor that collects a map of JSON pointer paths to {@link Endpoint}s.
+     */
+    @SmithyInternalApi
+    public static final class EndpointPathCollector {
+        private static final String ENDPOINT = "endpoint";
+        private static final String TYPE = "type";
+        private final Map<String, Endpoint> visitedEndpoints = new HashMap();
+        private final ObjectNode endpointRuleSet;
+
+        private EndpointPathCollector(EndpointRuleSetTrait endpointRuleSetTrait) {
+            this.endpointRuleSet = endpointRuleSetTrait.getRuleSet().expectObjectNode();
+        }
+
+        /**
+         * Creates a collector from an {@link EndpointRuleSetTrait}.
+         *
+         * @param endpointRuleSetTrait EndpointRuleSet trait instance.
+         */
+        @SmithyInternalApi
+        public static EndpointPathCollector from(EndpointRuleSetTrait endpointRuleSetTrait) {
+            return new EndpointPathCollector(endpointRuleSetTrait);
+        }
+
+        /**
+         * Collects the mapped JSON pointer paths to {@link Endpoint}s.
+         *
+         * @return a map of JSON pointer paths to {@link Endpoint}s.
+         */
+        @SmithyInternalApi
+        public Map<String, Endpoint> collect() {
+            arrayNode(endpointRuleSet.expectArrayMember(RULES), "/" + RULES);
+            return visitedEndpoints;
+        }
+
+        private void objectNode(ObjectNode node, String parentPath) {
+            boolean isEndpointRuleObject = node
+                .getMember(TYPE)
+                .map(n -> n.asStringNode()
+                    .map(s -> s.getValue().equals(ENDPOINT))
+                    .orElse(false))
+                .orElse(false);
+            if (isEndpointRuleObject) {
+                Endpoint endpoint = Endpoint.fromNode(node.expectMember(ENDPOINT));
+                visitedEndpoints.put(parentPath + "/" + ENDPOINT, endpoint);
+                return;
+            }
+            for (Entry<StringNode, Node> member : node.getMembers().entrySet()) {
+                String key = member.getKey().getValue();
+                Node value = member.getValue();
+                switch (value.getType()) {
+                    case OBJECT: {
+                        objectNode(value.expectObjectNode(), parentPath + "/" + key);
+                        break;
+                    }
+                    case ARRAY: {
+                        arrayNode(value.expectArrayNode(), parentPath + "/" + key);
+                        break;
+                    }
+                    default:
+                        break;
+                }
+            }
+        }
+
+        private void arrayNode(ArrayNode node, String parentPath) {
+            List<Node> elements = node.getElements();
+            for (int i = 0; i < elements.size(); i++) {
+                Node element = elements.get(i);
+                switch (element.getType()) {
+                    case OBJECT: {
+                        objectNode(element.expectObjectNode(), parentPath + "/" + i);
+                        break;
+                    }
+                    case ARRAY: {
+                        arrayNode(element.expectArrayNode(), parentPath + "/" + i);
+                        break;
+                    }
+                    default:
+                        break;
+                }
+            }
+        }
+    }
 }


Test patch:
diff --git a/smithy-aws-endpoints/src/test/java/software/amazon/smithy/rulesengine/aws/DiffTest.java b/smithy-aws-endpoints/src/test/java/software/amazon/smithy/rulesengine/aws/DiffTest.java
new file mode 100644
index 000000000..36a0a1d6b
--- /dev/null
+++ b/smithy-aws-endpoints/src/test/java/software/amazon/smithy/rulesengine/aws/DiffTest.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package software.amazon.smithy.rulesengine.aws;
+
+import static java.lang.String.format;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.io.File;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Arrays;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.Callable;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
+import software.amazon.smithy.diff.ModelDiff;
+import software.amazon.smithy.diff.testrunner.SmithyDiffTestCase;
+import software.amazon.smithy.diff.testrunner.SmithyDiffTestSuite;
+import software.amazon.smithy.model.Model;
+import software.amazon.smithy.model.SourceLocation;
+import software.amazon.smithy.model.shapes.ShapeId;
+import software.amazon.smithy.model.validation.Severity;
+import software.amazon.smithy.model.validation.ValidationEvent;
+import software.amazon.smithy.utils.IoUtils;
+import software.amazon.smithy.utils.Pair;
+
+public class DiffTest {
+    @ParameterizedTest(name = "{0}")
+    @MethodSource("source")
+    public void testRunner(String filename, Callable<SmithyDiffTestCase.Result> callable) throws Exception {
+        callable.call();
+    }
+
+    public static Stream<?> source() {
+        return SmithyDiffTestSuite.defaultParameterizedTestSource(DiffTest.class);
+    }
+}
diff --git a/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/diffs/either-or-both-no-endpointRuleSet.a.smithy b/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/diffs/either-or-both-no-endpointRuleSet.a.smithy
new file mode 100644
index 000000000..ca42804e5
--- /dev/null
+++ b/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/diffs/either-or-both-no-endpointRuleSet.a.smithy
@@ -0,0 +1,28 @@
+$version: "2.0"
+
+metadata suppressions = [
+    {
+        id: "ModifiedTrait.Add.smithy.rules#endpointRuleSet"
+        namespace: "ns.foo"
+    }
+    {
+        id: "ModifiedTrait.Remove.smithy.rules#endpointRuleSet"
+        namespace: "ns.foo"
+    }
+]
+
+namespace ns.foo
+
+use smithy.rules#endpointRuleSet
+
+service Service1 {}
+
+@endpointRuleSet(
+    version: "1.0"
+    serviceId: "service"
+    parameters: {}
+    rules: []
+)
+service Service2 {}
+
+service Service3 {}
diff --git a/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/diffs/either-or-both-no-endpointRuleSet.b.smithy b/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/diffs/either-or-both-no-endpointRuleSet.b.smithy
new file mode 100644
index 000000000..cf6f413d0
--- /dev/null
+++ b/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/diffs/either-or-both-no-endpointRuleSet.b.smithy
@@ -0,0 +1,28 @@
+$version: "2.0"
+
+metadata suppressions = [
+    {
+        id: "ModifiedTrait.Add.smithy.rules#endpointRuleSet"
+        namespace: "ns.foo"
+    }
+    {
+        id: "ModifiedTrait.Remove.smithy.rules#endpointRuleSet"
+        namespace: "ns.foo"
+    }
+]
+
+namespace ns.foo
+
+use smithy.rules#endpointRuleSet
+
+service Service1 {}
+
+service Service2 {}
+
+@endpointRuleSet(
+    version: "1.0"
+    serviceId: "service"
+    parameters: {}
+    rules: []
+)
+service Service3 {}
diff --git a/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/diffs/either-or-both-no-endpointRuleSet.events b/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/diffs/either-or-both-no-endpointRuleSet.events
new file mode 100644
index 000000000..e69de29bb
diff --git a/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/diffs/no-old-sigv4.a.smithy b/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/diffs/no-old-sigv4.a.smithy
new file mode 100644
index 000000000..dbec9b9d7
--- /dev/null
+++ b/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/diffs/no-old-sigv4.a.smithy
@@ -0,0 +1,74 @@
+$version: "2.0"
+
+metadata suppressions = [
+    {
+        id: "ModifiedTrait.Add.smithy.rules#endpointRuleSet"
+        namespace: "ns.foo"
+    }
+    {
+        id: "ModifiedTrait.Remove.smithy.rules#endpointRuleSet"
+        namespace: "ns.foo"
+    }
+]
+
+namespace ns.foo
+
+use smithy.rules#endpointRuleSet
+
+@endpointRuleSet(
+    version: "1.0"
+    serviceId: "service"
+    parameters: {}
+    rules: [
+        {
+            type: "endpoint"
+            conditions: []
+            endpoint: {
+                url: "https://abc.service.com"
+                properties: {
+                    authSchemes: [
+                        {
+                            name: "example"
+                        }
+                    ]
+                }
+            }
+        }
+    ]
+)
+@auth([httpBearerAuth])
+@httpBearerAuth
+service Service1 {}
+
+@endpointRuleSet(
+    version: "1.0"
+    serviceId: "service"
+    parameters: {}
+    rules: [
+        {
+            type: "endpoint"
+            conditions: []
+            endpoint: {
+                url: "https://abc.service.com"
+                properties: {
+                    authSchemes: [
+                        {
+                            name: "example"
+                        }
+                    ]
+                }
+            }
+        }
+    ]
+)
+@auth([httpBearerAuth])
+@httpBearerAuth
+service Service2 {}
+
+@auth([httpBearerAuth])
+@httpBearerAuth
+service Service3 {}
+
+@auth([httpBearerAuth])
+@httpBearerAuth
+service Service4 {}
diff --git a/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/diffs/no-old-sigv4.b.smithy b/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/diffs/no-old-sigv4.b.smithy
new file mode 100644
index 000000000..71844773b
--- /dev/null
+++ b/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/diffs/no-old-sigv4.b.smithy
@@ -0,0 +1,74 @@
+$version: "2.0"
+
+metadata suppressions = [
+    {
+        id: "ModifiedTrait.Add.smithy.rules#endpointRuleSet"
+        namespace: "ns.foo"
+    }
+    {
+        id: "ModifiedTrait.Remove.smithy.rules#endpointRuleSet"
+        namespace: "ns.foo"
+    }
+]
+
+namespace ns.foo
+
+use smithy.rules#endpointRuleSet
+
+@endpointRuleSet(
+    version: "1.0"
+    serviceId: "service"
+    parameters: {}
+    rules: [
+        {
+            type: "endpoint"
+            conditions: []
+            endpoint: {
+                url: "https://abc.service.com"
+                properties: {
+                    authSchemes: [
+                        {
+                            name: "example"
+                        }
+                    ]
+                }
+            }
+        }
+    ]
+)
+@auth([httpBearerAuth])
+@httpBearerAuth
+service Service1 {}
+
+@auth([httpBearerAuth])
+@httpBearerAuth
+service Service2 {}
+
+@endpointRuleSet(
+    version: "1.0"
+    serviceId: "service"
+    parameters: {}
+    rules: [
+        {
+            type: "endpoint"
+            conditions: []
+            endpoint: {
+                url: "https://abc.service.com"
+                properties: {
+                    authSchemes: [
+                        {
+                            name: "example"
+                        }
+                    ]
+                }
+            }
+        }
+    ]
+)
+@auth([httpBearerAuth])
+@httpBearerAuth
+service Service3 {}
+
+@auth([httpBearerAuth])
+@httpBearerAuth
+service Service4 {}
diff --git a/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/diffs/no-old-sigv4.events b/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/diffs/no-old-sigv4.events
new file mode 100644
index 000000000..e69de29bb
diff --git a/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/diffs/sigv4-after-sigv4a-migration.a.smithy b/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/diffs/sigv4-after-sigv4a-migration.a.smithy
new file mode 100644
index 000000000..0cca3ba92
--- /dev/null
+++ b/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/diffs/sigv4-after-sigv4a-migration.a.smithy
@@ -0,0 +1,159 @@
+$version: "2.0"
+
+metadata suppressions = [
+    {
+        id: "ModifiedTrait.Update.smithy.api#auth"
+        namespace: "ns.foo"
+    }
+    {
+        id: "ModifiedTrait.Update.smithy.rules#endpointRuleSet"
+        namespace: "ns.foo"
+    }
+]
+
+namespace ns.foo
+
+use aws.auth#sigv4
+use aws.auth#sigv4a
+use smithy.rules#endpointRuleSet
+
+@endpointRuleSet(
+    version: "1.0"
+    serviceId: "service"
+    parameters: {}
+    rules: [
+        {
+            type: "endpoint"
+            conditions: []
+            endpoint: {
+                url: "https://abc.service.com"
+                properties: {
+                    authSchemes: [
+                        {
+                            name: "sigv4a"
+                            signingRegionSet: ["*"]
+                        }
+                    ]
+                }
+            }
+        }
+    ]
+)
+@auth([sigv4a])
+@sigv4(name: "service1")
+@sigv4a(name: "service1")
+service Service1 {}
+
+@endpointRuleSet(
+    version: "1.0"
+    serviceId: "service"
+    parameters: {}
+    rules: [
+        {
+            type: "endpoint"
+            conditions: []
+            endpoint: {
+                url: "https://abc.service.com"
+                properties: {
+                    authSchemes: [
+                        {
+                            name: "sigv4a"
+                            signingRegionSet: ["*"]
+                        }
+                    ]
+                }
+            }
+        }
+    ]
+)
+@auth([sigv4a])
+@sigv4(name: "service2")
+@sigv4a(name: "service2")
+service Service2 {}
+
+@endpointRuleSet(
+    version: "1.0"
+    serviceId: "service"
+    parameters: {}
+    rules: [
+        {
+            type: "endpoint"
+            conditions: []
+            endpoint: {
+                url: "https://abc.service.com"
+                properties: {
+                    authSchemes: [
+                        {
+                            name: "sigv4a"
+                            signingRegionSet: ["*"]
+                        }
+                    ]
+                }
+            }
+        }
+    ]
+)
+@auth([sigv4a])
+@sigv4(name: "service3")
+@sigv4a(name: "service3")
+service Service3 {}
+
+@endpointRuleSet(
+    version: "1.0"
+    serviceId: "service"
+    parameters: {}
+    rules: [
+        {
+            type: "endpoint"
+            conditions: []
+            endpoint: {
+                url: "https://abc.service.com"
+                properties: {}
+            }
+        }
+    ]
+)
+@auth([sigv4a])
+@sigv4(name: "service4")
+@sigv4a(name: "service4")
+service Service4 {}
+
+@endpointRuleSet(
+    version: "1.0"
+    serviceId: "service"
+    parameters: {}
+    rules: [
+        {
+            type: "endpoint"
+            conditions: []
+            endpoint: {
+                url: "https://abc.service.com"
+                properties: {}
+            }
+        }
+    ]
+)
+@auth([sigv4a])
+@sigv4(name: "service5")
+@sigv4a(name: "service5")
+service Service5 {}
+
+@endpointRuleSet(
+    version: "1.0"
+    serviceId: "service"
+    parameters: {}
+    rules: [
+        {
+            type: "endpoint"
+            conditions: []
+            endpoint: {
+                url: "https://abc.service.com"
+                properties: {}
+            }
+        }
+    ]
+)
+@auth([sigv4a])
+@sigv4(name: "service6")
+@sigv4a(name: "service6")
+service Service6 {}
diff --git a/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/diffs/sigv4-after-sigv4a-migration.b.smithy b/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/diffs/sigv4-after-sigv4a-migration.b.smithy
new file mode 100644
index 000000000..226fa83a0
--- /dev/null
+++ b/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/diffs/sigv4-after-sigv4a-migration.b.smithy
@@ -0,0 +1,178 @@
+$version: "2.0"
+
+metadata suppressions = [
+    {
+        id: "ModifiedTrait.Update.smithy.api#auth"
+        namespace: "ns.foo"
+    }
+    {
+        id: "ModifiedTrait.Update.smithy.rules#endpointRuleSet"
+        namespace: "ns.foo"
+    }
+]
+
+namespace ns.foo
+
+use aws.auth#sigv4
+use aws.auth#sigv4a
+use smithy.rules#endpointRuleSet
+
+@endpointRuleSet(
+    version: "1.0"
+    serviceId: "service"
+    parameters: {}
+    rules: [
+        {
+            type: "endpoint"
+            conditions: []
+            endpoint: {
+                url: "https://abc.service.com"
+                properties: {
+                    authSchemes: [
+                        {
+                            name: "sigv4a"
+                            signingRegionSet: ["*"]
+                        }
+                        {
+                            name: "sigv4"
+                        }
+                    ]
+                }
+            }
+        }
+    ]
+)
+@auth([sigv4a])
+@sigv4(name: "service1")
+@sigv4a(name: "service1")
+service Service1 {}
+
+@endpointRuleSet(
+    version: "1.0"
+    serviceId: "service"
+    parameters: {}
+    rules: [
+        {
+            type: "endpoint"
+            conditions: []
+            endpoint: {
+                url: "https://abc.service.com"
+                properties: {
+                    authSchemes: [
+                        {
+                            name: "sigv4a"
+                            signingRegionSet: ["*"]
+                        }
+                        {
+                            name: "sigv4"
+                        }
+                    ]
+                }
+            }
+        }
+    ]
+)
+@auth([sigv4a])
+@sigv4(name: "service2")
+@sigv4a(name: "service2")
+service Service2 {}
+
+@endpointRuleSet(
+    version: "1.0"
+    serviceId: "service"
+    parameters: {}
+    rules: [
+        {
+            type: "endpoint"
+            conditions: []
+            endpoint: {
+                url: "https://abc.service.com"
+                properties: {}
+            }
+        }
+    ]
+)
+@auth([sigv4a, sigv4])
+@sigv4(name: "service3")
+@sigv4a(name: "service3")
+service Service3 {}
+
+@endpointRuleSet(
+    version: "1.0"
+    serviceId: "service"
+    parameters: {}
+    rules: [
+        {
+            type: "endpoint"
+            conditions: []
+            endpoint: {
+                url: "https://abc.service.com"
+                properties: {
+                    authSchemes: [
+                        {
+                            name: "sigv4a"
+                            signingRegionSet: ["*"]
+                        }
+                        {
+                            name: "sigv4"
+                        }
+                    ]
+                }
+            }
+        }
+    ]
+)
+@auth([sigv4a])
+@sigv4(name: "service4")
+@sigv4a(name: "service4")
+service Service4 {}
+
+@endpointRuleSet(
+    version: "1.0"
+    serviceId: "service"
+    parameters: {}
+    rules: [
+        {
+            type: "endpoint"
+            conditions: []
+            endpoint: {
+                url: "https://abc.service.com"
+                properties: {
+                    authSchemes: [
+                        {
+                            name: "sigv4a"
+                            signingRegionSet: ["*"]
+                        }
+                        {
+                            name: "sigv4"
+                        }
+                    ]
+                }
+            }
+        }
+    ]
+)
+@auth([sigv4a])
+@sigv4(name: "service5")
+@sigv4a(name: "service5")
+service Service5 {}
+
+@endpointRuleSet(
+    version: "1.0"
+    serviceId: "service"
+    parameters: {}
+    rules: [
+        {
+            type: "endpoint"
+            conditions: []
+            endpoint: {
+                url: "https://abc.service.com"
+                properties: {}
+            }
+        }
+    ]
+)
+@auth([sigv4a, sigv4])
+@sigv4(name: "service6")
+@sigv4a(name: "service6")
+service Service6 {}
diff --git a/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/diffs/sigv4-after-sigv4a-migration.events b/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/diffs/sigv4-after-sigv4a-migration.events
new file mode 100644
index 000000000..e69de29bb
diff --git a/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/diffs/sigv4-before-sigv4a-migration.a.smithy b/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/diffs/sigv4-before-sigv4a-migration.a.smithy
new file mode 100644
index 000000000..26184bb53
--- /dev/null
+++ b/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/diffs/sigv4-before-sigv4a-migration.a.smithy
@@ -0,0 +1,183 @@
+$version: "2.0"
+
+metadata suppressions = [
+    {
+        id: "ModifiedTrait.Update.smithy.api#auth"
+        namespace: "ns.foo"
+    }
+    {
+        id: "ModifiedTrait.Update.smithy.rules#endpointRuleSet"
+        namespace: "ns.foo"
+    }
+    {
+        id: "SigV4Migration"
+        namespace: "ns.foo"
+    }
+]
+
+namespace ns.foo
+
+use aws.auth#sigv4
+use aws.auth#sigv4a
+use smithy.rules#endpointRuleSet
+
+@endpointRuleSet(
+    version: "1.0"
+    serviceId: "service"
+    parameters: {}
+    rules: [
+        {
+            type: "endpoint"
+            conditions: []
+            endpoint: {
+                url: "https://abc.service.com"
+                properties: {
+                    authSchemes: [
+                        {
+                            name: "sigv4a"
+                            signingRegionSet: ["*"]
+                        }
+                    ]
+                }
+            }
+        }
+    ]
+)
+@auth([sigv4a])
+@sigv4(name: "service1")
+@sigv4a(name: "service1")
+service Service1 {}
+
+@endpointRuleSet(
+    version: "1.0"
+    serviceId: "service"
+    parameters: {}
+    rules: [
+        {
+            type: "endpoint"
+            conditions: []
+            endpoint: {
+                url: "https://abc.service.com"
+                properties: {
+                    authSchemes: [
+                        {
+                            name: "sigv4a"
+                            signingRegionSet: ["*"]
+                        }
+                    ]
+                }
+            }
+        }
+    ]
+)
+@auth([sigv4a])
+@sigv4(name: "service2")
+@sigv4a(name: "service2")
+service Service2 {}
+
+@endpointRuleSet(
+    version: "1.0"
+    serviceId: "service"
+    parameters: {}
+    rules: [
+        {
+            type: "endpoint"
+            conditions: []
+            endpoint: {
+                url: "https://abc.service.com"
+                properties: {
+                    authSchemes: [
+                        {
+                            name: "sigv4a"
+                            signingRegionSet: ["*"]
+                        }
+                    ]
+                }
+            }
+        }
+    ]
+)
+@auth([sigv4a])
+@sigv4(name: "service3")
+@sigv4a(name: "service3")
+service Service3 {}
+
+@endpointRuleSet(
+    version: "1.0"
+    serviceId: "service"
+    parameters: {}
+    rules: [
+        {
+            type: "endpoint"
+            conditions: []
+            endpoint: {
+                url: "https://abc.service.com"
+                properties: {}
+            }
+        }
+    ]
+)
+@auth([sigv4a])
+@sigv4(name: "service4")
+@sigv4a(name: "service4")
+service Service4 {}
+
+@endpointRuleSet(
+    version: "1.0"
+    serviceId: "service"
+    parameters: {}
+    rules: [
+        {
+            type: "endpoint"
+            conditions: []
+            endpoint: {
+                url: "https://abc.service.com"
+                properties: {}
+            }
+        }
+    ]
+)
+@auth([sigv4a])
+@sigv4(name: "service5")
+@sigv4a(name: "service5")
+service Service5 {}
+
+@endpointRuleSet(
+    version: "1.0"
+    serviceId: "service"
+    parameters: {}
+    rules: [
+        {
+            type: "endpoint"
+            conditions: []
+            endpoint: {
+                url: "https://abc.service.com"
+                properties: {}
+            }
+        }
+    ]
+)
+@auth([sigv4a])
+@sigv4(name: "service6")
+@sigv4a(name: "service6")
+service Service6 {}
+
+@endpointRuleSet(
+    version: "1.0"
+    serviceId: "service"
+    parameters: {}
+    rules: [
+        {
+            type: "endpoint"
+            conditions: []
+            endpoint: {
+                url: "https://abc.service.com"
+                properties: {}
+            }
+        }
+    ]
+)
+@auth([sigv4a])
+@sigv4(name: "service7")
+@sigv4a(name: "service7")
+service Service7 {}
diff --git a/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/diffs/sigv4-before-sigv4a-migration.b.smithy b/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/diffs/sigv4-before-sigv4a-migration.b.smithy
new file mode 100644
index 000000000..ea80dd82f
--- /dev/null
+++ b/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/diffs/sigv4-before-sigv4a-migration.b.smithy
@@ -0,0 +1,202 @@
+$version: "2.0"
+
+metadata suppressions = [
+    {
+        id: "ModifiedTrait.Update.smithy.api#auth"
+        namespace: "ns.foo"
+    }
+    {
+        id: "ModifiedTrait.Update.smithy.rules#endpointRuleSet"
+        namespace: "ns.foo"
+    }
+    {
+        id: "SigV4Migration"
+        namespace: "ns.foo"
+    }
+]
+
+namespace ns.foo
+
+use aws.auth#sigv4
+use aws.auth#sigv4a
+use smithy.rules#endpointRuleSet
+
+@endpointRuleSet(
+    version: "1.0"
+    serviceId: "service"
+    parameters: {}
+    rules: [
+        {
+            type: "endpoint"
+            conditions: []
+            endpoint: {
+                url: "https://abc.service.com"
+                properties: {
+                    authSchemes: [
+                        {
+                            name: "sigv4"
+                        }
+                        {
+                            name: "sigv4a"
+                            signingRegionSet: ["*"]
+                        }
+                    ]
+                }
+            }
+        }
+    ]
+)
+@auth([sigv4a])
+@sigv4(name: "service1")
+@sigv4a(name: "service1")
+service Service1 {}
+
+@endpointRuleSet(
+    version: "1.0"
+    serviceId: "service"
+    parameters: {}
+    rules: [
+        {
+            type: "endpoint"
+            conditions: []
+            endpoint: {
+                url: "https://abc.service.com"
+                properties: {
+                    authSchemes: [
+                        {
+                            name: "sigv4"
+                        }
+                        {
+                            name: "sigv4a"
+                            signingRegionSet: ["*"]
+                        }
+                    ]
+                }
+            }
+        }
+    ]
+)
+@auth([sigv4a])
+@sigv4(name: "service2")
+@sigv4a(name: "service2")
+service Service2 {}
+
+@endpointRuleSet(
+    version: "1.0"
+    serviceId: "service"
+    parameters: {}
+    rules: [
+        {
+            type: "endpoint"
+            conditions: []
+            endpoint: {
+                url: "https://abc.service.com"
+                properties: {}
+            }
+        }
+    ]
+)
+@auth([sigv4, sigv4a])
+@sigv4(name: "service3")
+@sigv4a(name: "service3")
+service Service3 {}
+
+@endpointRuleSet(
+    version: "1.0"
+    serviceId: "service"
+    parameters: {}
+    rules: [
+        {
+            type: "endpoint"
+            conditions: []
+            endpoint: {
+                url: "https://abc.service.com"
+                properties: {
+                    authSchemes: [
+                        {
+                            name: "sigv4"
+                        }
+                        {
+                            name: "sigv4a"
+                            signingRegionSet: ["*"]
+                        }
+                    ]
+                }
+            }
+        }
+    ]
+)
+@auth([sigv4a])
+@sigv4(name: "service4")
+@sigv4a(name: "service4")
+service Service4 {}
+
+@endpointRuleSet(
+    version: "1.0"
+    serviceId: "service"
+    parameters: {}
+    rules: [
+        {
+            type: "endpoint"
+            conditions: []
+            endpoint: {
+                url: "https://abc.service.com"
+                properties: {
+                    authSchemes: [
+                        {
+                            name: "sigv4"
+                        }
+                        {
+                            name: "sigv4a"
+                            signingRegionSet: ["*"]
+                        }
+                    ]
+                }
+            }
+        }
+    ]
+)
+@auth([sigv4a])
+@sigv4(name: "service5")
+@sigv4a(name: "service5")
+service Service5 {}
+
+@endpointRuleSet(
+    version: "1.0"
+    serviceId: "service"
+    parameters: {}
+    rules: [
+        {
+            type: "endpoint"
+            conditions: []
+            endpoint: {
+                url: "https://abc.service.com"
+                properties: {}
+            }
+        }
+    ]
+)
+@auth([sigv4, sigv4a])
+@sigv4(name: "service6")
+@sigv4a(name: "service6")
+service Service6 {}
+
+@endpointRuleSet(
+    version: "1.0"
+    serviceId: "service"
+    parameters: {}
+    rules: [
+        {
+            type: "endpoint"
+            conditions: []
+            endpoint: {
+                url: "https://abc.service.com"
+                properties: { hello: "world" }
+            }
+        }
+    ]
+)
+@auth([sigv4, sigv4a])
+@sigv4(name: "service7")
+@sigv4a(name: "service7")
+service Service7 {}
diff --git a/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/diffs/sigv4-before-sigv4a-migration.events b/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/diffs/sigv4-before-sigv4a-migration.events
new file mode 100644
index 000000000..70b9b43e1
--- /dev/null
+++ b/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/diffs/sigv4-before-sigv4a-migration.events
@@ -0,0 +1,11 @@
+[DANGER] ns.foo#Service1: The `aws.auth#sigv4` authentication scheme was added before the `aws.auth#sigv4a` authentication scheme in the effective auth schemes for an endpoint in the `@smithy.rules#endpointRuleSet` trait applied to `ns.foo#Service1` at: `/rules/0/endpoint`. Adding the `aws.auth#sigv4` authentication scheme before an existing `aws.auth#sigv4a` authentication scheme may not be backward compatible if the signing scope was narrowed (typically from `*`). | EndpointSigV4Migration
+-----
+[DANGER] ns.foo#Service2: The `aws.auth#sigv4` authentication scheme was added before the `aws.auth#sigv4a` authentication scheme in the effective auth schemes for an endpoint in the `@smithy.rules#endpointRuleSet` trait applied to `ns.foo#Service2` at: `/rules/0/endpoint`. Adding the `aws.auth#sigv4` authentication scheme before an existing `aws.auth#sigv4a` authentication scheme may not be backward compatible if the signing scope was narrowed (typically from `*`). | EndpointSigV4Migration
+-----
+[DANGER] ns.foo#Service3: The `aws.auth#sigv4` authentication scheme was added before the `aws.auth#sigv4a` authentication scheme in the effective auth schemes for an endpoint in the `@smithy.rules#endpointRuleSet` trait applied to `ns.foo#Service3` at: `/rules/0/endpoint`. Adding the `aws.auth#sigv4` authentication scheme before an existing `aws.auth#sigv4a` authentication scheme may not be backward compatible if the signing scope was narrowed (typically from `*`). | EndpointSigV4Migration
+-----
+[DANGER] ns.foo#Service4: The `aws.auth#sigv4` authentication scheme was added before the `aws.auth#sigv4a` authentication scheme in the effective auth schemes for an endpoint in the `@smithy.rules#endpointRuleSet` trait applied to `ns.foo#Service4` at: `/rules/0/endpoint`. Adding the `aws.auth#sigv4` authentication scheme before an existing `aws.auth#sigv4a` authentication scheme may not be backward compatible if the signing scope was narrowed (typically from `*`). | EndpointSigV4Migration
+-----
+[DANGER] ns.foo#Service5: The `aws.auth#sigv4` authentication scheme was added before the `aws.auth#sigv4a` authentication scheme in the effective auth schemes for an endpoint in the `@smithy.rules#endpointRuleSet` trait applied to `ns.foo#Service5` at: `/rules/0/endpoint`. Adding the `aws.auth#sigv4` authentication scheme before an existing `aws.auth#sigv4a` authentication scheme may not be backward compatible if the signing scope was narrowed (typically from `*`). | EndpointSigV4Migration
+-----
+[DANGER] ns.foo#Service7: The `aws.auth#sigv4` authentication scheme was added before the `aws.auth#sigv4a` authentication scheme in the effective auth schemes for an endpoint in the `@smithy.rules#endpointRuleSet` trait applied to `ns.foo#Service7` at: `/rules/0/endpoint`. Adding the `aws.auth#sigv4` authentication scheme before an existing `aws.auth#sigv4a` authentication scheme may not be backward compatible if the signing scope was narrowed (typically from `*`). | EndpointSigV4Migration
diff --git a/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/diffs/sigv4-replace-sigv4a-migration.a.smithy b/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/diffs/sigv4-replace-sigv4a-migration.a.smithy
new file mode 100644
index 000000000..3b2b090c0
--- /dev/null
+++ b/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/diffs/sigv4-replace-sigv4a-migration.a.smithy
@@ -0,0 +1,116 @@
+$version: "2.0"
+
+metadata suppressions = [
+    {
+        id: "ModifiedTrait.Update.smithy.api#auth"
+        namespace: "ns.foo"
+    }
+    {
+        id: "ModifiedTrait.Update.smithy.rules#endpointRuleSet"
+        namespace: "ns.foo"
+    }
+    {
+        id: "SigV4Migration"
+        namespace: "ns.foo"
+    }
+]
+
+namespace ns.foo
+
+use aws.auth#sigv4
+use aws.auth#sigv4a
+use smithy.rules#endpointRuleSet
+
+@endpointRuleSet(
+    version: "1.0"
+    serviceId: "service"
+    parameters: {}
+    rules: [
+        {
+            type: "endpoint"
+            conditions: []
+            endpoint: {
+                url: "https://abc.service.com"
+                properties: {
+                    authSchemes: [
+                        {
+                            name: "sigv4a"
+                            signingRegionSet: ["*"]
+                        }
+                    ]
+                }
+            }
+        }
+    ]
+)
+@auth([sigv4a])
+@sigv4(name: "service1")
+@sigv4a(name: "service1")
+service Service1 {}
+
+@endpointRuleSet(
+    version: "1.0"
+    serviceId: "service"
+    parameters: {}
+    rules: [
+        {
+            type: "endpoint"
+            conditions: []
+            endpoint: {
+                url: "https://abc.service.com"
+                properties: {
+                    authSchemes: [
+                        {
+                            name: "sigv4a"
+                            signingRegionSet: ["*"]
+                        }
+                    ]
+                }
+            }
+        }
+    ]
+)
+@auth([sigv4a])
+@sigv4(name: "service2")
+@sigv4a(name: "service2")
+service Service2 {}
+
+@endpointRuleSet(
+    version: "1.0"
+    serviceId: "service"
+    parameters: {}
+    rules: [
+        {
+            type: "endpoint"
+            conditions: []
+            endpoint: {
+                url: "https://abc.service.com"
+                properties: {}
+            }
+        }
+    ]
+)
+@auth([sigv4a])
+@sigv4(name: "service3")
+@sigv4a(name: "service3")
+service Service3 {}
+
+@endpointRuleSet(
+    version: "1.0"
+    serviceId: "service"
+    parameters: {}
+    rules: [
+        {
+            type: "endpoint"
+            conditions: []
+            endpoint: {
+                url: "https://abc.service.com"
+                properties: {}
+            }
+        }
+    ]
+)
+@auth([sigv4a])
+@sigv4(name: "service4")
+@sigv4a(name: "service4")
+service Service4 {}
diff --git a/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/diffs/sigv4-replace-sigv4a-migration.b.smithy b/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/diffs/sigv4-replace-sigv4a-migration.b.smithy
new file mode 100644
index 000000000..0eb05adcd
--- /dev/null
+++ b/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/diffs/sigv4-replace-sigv4a-migration.b.smithy
@@ -0,0 +1,114 @@
+$version: "2.0"
+
+metadata suppressions = [
+    {
+        id: "ModifiedTrait.Update.smithy.api#auth"
+        namespace: "ns.foo"
+    }
+    {
+        id: "ModifiedTrait.Update.smithy.rules#endpointRuleSet"
+        namespace: "ns.foo"
+    }
+    {
+        id: "SigV4Migration"
+        namespace: "ns.foo"
+    }
+]
+
+namespace ns.foo
+
+use aws.auth#sigv4
+use aws.auth#sigv4a
+use smithy.rules#endpointRuleSet
+
+@endpointRuleSet(
+    version: "1.0"
+    serviceId: "service"
+    parameters: {}
+    rules: [
+        {
+            type: "endpoint"
+            conditions: []
+            endpoint: {
+                url: "https://abc.service.com"
+                properties: {
+                    authSchemes: [
+                        {
+                            name: "sigv4"
+                        }
+                    ]
+                }
+            }
+        }
+    ]
+)
+@auth([sigv4a])
+@sigv4(name: "service1")
+@sigv4a(name: "service1")
+service Service1 {}
+
+@endpointRuleSet(
+    version: "1.0"
+    serviceId: "service"
+    parameters: {}
+    rules: [
+        {
+            type: "endpoint"
+            conditions: []
+            endpoint: {
+                url: "https://abc.service.com"
+                properties: {}
+            }
+        }
+    ]
+)
+@auth([sigv4])
+@sigv4(name: "service2")
+@sigv4a(name: "service2")
+service Service2 {}
+
+@endpointRuleSet(
+    version: "1.0"
+    serviceId: "service"
+    parameters: {}
+    rules: [
+        {
+            type: "endpoint"
+            conditions: []
+            endpoint: {
+                url: "https://abc.service.com"
+                properties: {
+                    authSchemes: [
+                        {
+                            name: "sigv4"
+                        }
+                    ]
+                }
+            }
+        }
+    ]
+)
+@auth([sigv4a])
+@sigv4(name: "service3")
+@sigv4a(name: "service3")
+service Service3 {}
+
+@endpointRuleSet(
+    version: "1.0"
+    serviceId: "service"
+    parameters: {}
+    rules: [
+        {
+            type: "endpoint"
+            conditions: []
+            endpoint: {
+                url: "https://abc.service.com"
+                properties: {}
+            }
+        }
+    ]
+)
+@auth([sigv4])
+@sigv4(name: "service4")
+@sigv4a(name: "service4")
+service Service4 {}
diff --git a/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/diffs/sigv4-replace-sigv4a-migration.events b/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/diffs/sigv4-replace-sigv4a-migration.events
new file mode 100644
index 000000000..81f3a779d
--- /dev/null
+++ b/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/diffs/sigv4-replace-sigv4a-migration.events
@@ -0,0 +1,5 @@
+[DANGER] ns.foo#Service1: The `aws.auth#sigv4a` authentication scheme was replaced by the `aws.auth#sigv4` authentication scheme in the effective auth schemes for an endpoint in the `@smithy.rules#endpointRuleSet` trait applied to `ns.foo#Service1` at: `/rules/0/endpoint`. Replacing the `aws.auth#sigv4` authentication scheme with the `aws.auth#sigv4a` authentication scheme directly may not be backward compatible if the signing scope was narrowed (typically from `*`). | EndpointSigV4Migration
+-----
+[DANGER] ns.foo#Service2: The `aws.auth#sigv4a` authentication scheme was replaced by the `aws.auth#sigv4` authentication scheme in the effective auth schemes for an endpoint in the `@smithy.rules#endpointRuleSet` trait applied to `ns.foo#Service2` at: `/rules/0/endpoint`. Replacing the `aws.auth#sigv4` authentication scheme with the `aws.auth#sigv4a` authentication scheme directly may not be backward compatible if the signing scope was narrowed (typically from `*`). | EndpointSigV4Migration
+-----
+[DANGER] ns.foo#Service3: The `aws.auth#sigv4a` authentication scheme was replaced by the `aws.auth#sigv4` authentication scheme in the effective auth schemes for an endpoint in the `@smithy.rules#endpointRuleSet` trait applied to `ns.foo#Service3` at: `/rules/0/endpoint`. Replacing the `aws.auth#sigv4` authentication scheme with the `aws.auth#sigv4a` authentication scheme directly may not be backward compatible if the signing scope was narrowed (typically from `*`). | EndpointSigV4Migration
diff --git a/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/diffs/sigv4-sigv4a-in-order.a.smithy b/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/diffs/sigv4-sigv4a-in-order.a.smithy
new file mode 100644
index 000000000..ac3a78168
--- /dev/null
+++ b/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/diffs/sigv4-sigv4a-in-order.a.smithy
@@ -0,0 +1,210 @@
+$version: "2.0"
+
+metadata suppressions = [
+    {
+        id: "ModifiedTrait.Update.smithy.rules#endpointRuleSet"
+        namespace: "ns.foo"
+    }
+    {
+        id: "ModifiedTrait.Update.smithy.api#auth"
+        namespace: "ns.foo"
+    }
+]
+
+namespace ns.foo
+
+use aws.auth#sigv4
+use aws.auth#sigv4a
+use smithy.rules#endpointRuleSet
+
+@endpointRuleSet(
+    version: "1.0"
+    serviceId: "service"
+    parameters: {}
+    rules: [
+        {
+            type: "endpoint"
+            conditions: []
+            endpoint: {
+                url: "https://abc.service.com"
+                properties: {
+                    authSchemes: [
+                        {
+                            name: "sigv4"
+                        }
+                        {
+                            name: "sigv4a"
+                            signingRegionSet: ["*"]
+                        }
+                    ]
+                }
+            }
+        }
+    ]
+)
+@auth([sigv4])
+@sigv4(name: "service1")
+@sigv4a(name: "service1")
+service Service1 {}
+
+@endpointRuleSet(
+    version: "1.0"
+    serviceId: "service"
+    parameters: {}
+    rules: [
+        {
+            type: "endpoint"
+            conditions: []
+            endpoint: {
+                url: "https://abc.service.com"
+                properties: {
+                    authSchemes: [
+                        {
+                            name: "sigv4"
+                        }
+                        {
+                            name: "sigv4a"
+                            signingRegionSet: ["*"]
+                        }
+                    ]
+                }
+            }
+        }
+    ]
+)
+@auth([sigv4])
+@sigv4(name: "service2")
+@sigv4a(name: "service2")
+service Service2 {}
+
+@endpointRuleSet(
+    version: "1.0"
+    serviceId: "service"
+    parameters: {}
+    rules: [
+        {
+            type: "endpoint"
+            conditions: []
+            endpoint: {
+                url: "https://abc.service.com"
+                properties: {
+                    authSchemes: [
+                        {
+                            name: "sigv4"
+                        }
+                        {
+                            name: "sigv4a"
+                            signingRegionSet: ["*"]
+                        }
+                    ]
+                }
+            }
+        }
+    ]
+)
+@auth([sigv4])
+@sigv4(name: "service3")
+@sigv4a(name: "service3")
+service Service3 {}
+
+@endpointRuleSet(
+    version: "1.0"
+    serviceId: "service"
+    parameters: {}
+    rules: [
+        {
+            type: "endpoint"
+            conditions: []
+            endpoint: {
+                url: "https://abc.service.com"
+                properties: {
+                    authSchemes: [
+                        {
+                            name: "sigv4"
+                        }
+                        {
+                            name: "sigv4a"
+                            signingRegionSet: ["*"]
+                        }
+                    ]
+                }
+            }
+        }
+    ]
+)
+@auth([sigv4])
+@sigv4(name: "service4")
+@sigv4a(name: "service4")
+service Service4 {}
+
+@endpointRuleSet(
+    version: "1.0"
+    serviceId: "service"
+    parameters: {}
+    rules: [
+        {
+            type: "endpoint"
+            conditions: []
+            endpoint: {
+                url: "https://abc.service.com"
+                properties: {
+                    authSchemes: [
+                        {
+                            name: "sigv4"
+                        }
+                        {
+                            name: "sigv4a"
+                            signingRegionSet: ["*"]
+                        }
+                    ]
+                }
+            }
+        }
+    ]
+)
+@auth([sigv4])
+@httpBearerAuth
+@sigv4(name: "service5")
+@sigv4a(name: "service5")
+service Service5 {}
+
+@endpointRuleSet(
+    version: "1.0"
+    serviceId: "service"
+    parameters: {}
+    rules: [
+        {
+            type: "endpoint"
+            conditions: []
+            endpoint: {
+                url: "https://abc.service.com"
+                properties: {}
+            }
+        }
+    ]
+)
+@auth([sigv4, sigv4a])
+@httpBearerAuth
+@sigv4(name: "service6")
+@sigv4a(name: "service6")
+service Service6 {}
+
+@endpointRuleSet(
+    version: "1.0"
+    serviceId: "service"
+    parameters: {}
+    rules: [
+        {
+            type: "endpoint"
+            conditions: []
+            endpoint: {
+                url: "https://abc.service.com"
+                properties: {}
+            }
+        }
+    ]
+)
+@auth([sigv4, sigv4a])
+@sigv4(name: "service7")
+@sigv4a(name: "service7")
+service Service7 {}
diff --git a/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/diffs/sigv4-sigv4a-in-order.b.smithy b/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/diffs/sigv4-sigv4a-in-order.b.smithy
new file mode 100644
index 000000000..29f88f1a4
--- /dev/null
+++ b/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/diffs/sigv4-sigv4a-in-order.b.smithy
@@ -0,0 +1,212 @@
+$version: "2.0"
+
+metadata suppressions = [
+    {
+        id: "ModifiedTrait.Update.smithy.rules#endpointRuleSet"
+        namespace: "ns.foo"
+    }
+    {
+        id: "ModifiedTrait.Update.smithy.api#auth"
+        namespace: "ns.foo"
+    }
+]
+
+namespace ns.foo
+
+use aws.auth#sigv4
+use aws.auth#sigv4a
+use smithy.rules#endpointRuleSet
+
+@endpointRuleSet(
+    version: "1.0"
+    serviceId: "service"
+    parameters: {}
+    rules: [
+        {
+            type: "endpoint"
+            conditions: []
+            endpoint: {
+                url: "https://abc.service.com"
+                properties: {
+                    authSchemes: [
+                        {
+                            name: "sigv4"
+                        }
+                        {
+                            name: "example"
+                        }
+                        {
+                            name: "sigv4a"
+                            signingRegionSet: ["*"]
+                        }
+                    ]
+                }
+            }
+        }
+    ]
+)
+@auth([sigv4])
+@sigv4(name: "service1")
+@sigv4a(name: "service1")
+service Service1 {}
+
+@endpointRuleSet(
+    version: "1.0"
+    serviceId: "service"
+    parameters: {}
+    rules: [
+        {
+            type: "endpoint"
+            conditions: []
+            endpoint: {
+                url: "https://abc.service.com"
+                properties: {
+                    authSchemes: [
+                        {
+                            name: "example"
+                        }
+                        {
+                            name: "sigv4"
+                        }
+                        {
+                            name: "sigv4a"
+                            signingRegionSet: ["*"]
+                        }
+                    ]
+                }
+            }
+        }
+    ]
+)
+@auth([sigv4])
+@sigv4(name: "service2")
+@sigv4a(name: "service2")
+service Service2 {}
+
+@endpointRuleSet(
+    version: "1.0"
+    serviceId: "service"
+    parameters: {}
+    rules: [
+        {
+            type: "endpoint"
+            conditions: []
+            endpoint: {
+                url: "https://abc.service.com"
+                properties: {
+                    authSchemes: [
+                        {
+                            name: "sigv4"
+                        }
+                        {
+                            name: "sigv4a"
+                            signingRegionSet: ["*"]
+                        }
+                        {
+                            name: "example"
+                        }
+                    ]
+                }
+            }
+        }
+    ]
+)
+@auth([sigv4])
+@sigv4(name: "service3")
+@sigv4a(name: "service3")
+service Service3 {}
+
+@endpointRuleSet(
+    version: "1.0"
+    serviceId: "service"
+    parameters: {}
+    rules: [
+        {
+            type: "endpoint"
+            conditions: []
+            endpoint: {
+                url: "https://abc.service.com"
+                properties: {}
+            }
+        }
+    ]
+)
+@auth([sigv4, sigv4a])
+@sigv4(name: "service4")
+@sigv4a(name: "service4")
+service Service4 {}
+
+@endpointRuleSet(
+    version: "1.0"
+    serviceId: "service"
+    parameters: {}
+    rules: [
+        {
+            type: "endpoint"
+            conditions: []
+            endpoint: {
+                url: "https://abc.service.com"
+                properties: {}
+            }
+        }
+    ]
+)
+@auth([sigv4, httpBearerAuth, sigv4a])
+@httpBearerAuth
+@sigv4(name: "service5")
+@sigv4a(name: "service5")
+service Service5 {}
+
+@endpointRuleSet(
+    version: "1.0"
+    serviceId: "service"
+    parameters: {}
+    rules: [
+        {
+            type: "endpoint"
+            conditions: []
+            endpoint: {
+                url: "https://abc.service.com"
+                properties: { hello: "world" }
+            }
+        }
+    ]
+)
+@auth([sigv4, httpBearerAuth, sigv4a])
+@httpBearerAuth
+@sigv4(name: "service6")
+@sigv4a(name: "service6")
+service Service6 {}
+
+@endpointRuleSet(
+    version: "1.0"
+    serviceId: "service"
+    parameters: {}
+    rules: [
+        {
+            type: "endpoint"
+            conditions: []
+            endpoint: {
+                url: "https://abc.service.com"
+                properties: {
+                    authSchemes: [
+                        {
+                            name: "sigv4"
+                        }
+                        {
+                            name: "example"
+                        }
+                        {
+                            name: "sigv4a"
+                            signingRegionSet: ["*"]
+                        }
+                    ]
+                }
+            }
+        }
+    ]
+)
+@auth([sigv4, sigv4a])
+@sigv4(name: "service7")
+@sigv4a(name: "service7")
+service Service7 {}
diff --git a/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/diffs/sigv4-sigv4a-in-order.events b/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/diffs/sigv4-sigv4a-in-order.events
new file mode 100644
index 000000000..e69de29bb
diff --git a/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/diffs/sigv4-sigv4a-swap-order.a.smithy b/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/diffs/sigv4-sigv4a-swap-order.a.smithy
new file mode 100644
index 000000000..daa9b730b
--- /dev/null
+++ b/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/diffs/sigv4-sigv4a-swap-order.a.smithy
@@ -0,0 +1,226 @@
+$version: "2.0"
+
+metadata suppressions = [
+    {
+        id: "ModifiedTrait.Add.smithy.api#auth"
+        namespace: "ns.foo"
+    }
+    {
+        id: "ModifiedTrait.Update.smithy.api#auth"
+        namespace: "ns.foo"
+    }
+    {
+        id: "ModifiedTrait.Update.smithy.rules#endpointRuleSet"
+        namespace: "ns.foo"
+    }
+    {
+        id: "SigV4Migration"
+        namespace: "ns.foo"
+    }
+]
+
+namespace ns.foo
+
+use aws.auth#sigv4
+use aws.auth#sigv4a
+use smithy.rules#endpointRuleSet
+
+@endpointRuleSet(
+    version: "1.0"
+    serviceId: "service"
+    parameters: {}
+    rules: [
+        {
+            type: "endpoint"
+            conditions: []
+            endpoint: {
+                url: "https://abc.service.com"
+                properties: {
+                    authSchemes: [
+                        {
+                            name: "sigv4"
+                        }
+                        {
+                            name: "sigv4a"
+                            signingRegionSet: ["*"]
+                        }
+                    ]
+                }
+            }
+        }
+    ]
+)
+@auth([sigv4, sigv4a])
+@sigv4(name: "service1")
+@sigv4a(name: "service1")
+service Service1 {}
+
+@endpointRuleSet(
+    version: "1.0"
+    serviceId: "service"
+    parameters: {}
+    rules: [
+        {
+            type: "endpoint"
+            conditions: []
+            endpoint: {
+                url: "https://abc.service.com"
+                properties: {
+                    authSchemes: [
+                        {
+                            name: "sigv4"
+                        }
+                        {
+                            name: "sigv4a"
+                            signingRegionSet: ["*"]
+                        }
+                    ]
+                }
+            }
+        }
+    ]
+)
+@auth([sigv4, sigv4a])
+@sigv4(name: "service2")
+@sigv4a(name: "service2")
+service Service2 {}
+
+@endpointRuleSet(
+    version: "1.0"
+    serviceId: "service"
+    parameters: {}
+    rules: [
+        {
+            type: "endpoint"
+            conditions: []
+            endpoint: {
+                url: "https://abc.service.com"
+                properties: {}
+            }
+        }
+    ]
+)
+@auth([sigv4, sigv4a])
+@sigv4(name: "service3")
+@sigv4a(name: "service3")
+service Service3 {}
+
+@endpointRuleSet(
+    version: "1.0"
+    serviceId: "service"
+    parameters: {}
+    rules: [
+        {
+            type: "endpoint"
+            conditions: []
+            endpoint: {
+                url: "https://abc.service.com"
+                properties: {}
+            }
+        }
+    ]
+)
+@auth([sigv4, sigv4a])
+@sigv4(name: "service4")
+@sigv4a(name: "service4")
+service Service4 {}
+
+@endpointRuleSet(
+    version: "1.0"
+    serviceId: "service"
+    parameters: {}
+    rules: [
+        {
+            type: "endpoint"
+            conditions: []
+            endpoint: {
+                url: "https://abc.service.com"
+                properties: {
+                    authSchemes: [
+                        {
+                            name: "sigv4a"
+                            signingRegionSet: ["*"]
+                        }
+                        {
+                            name: "sigv4"
+                        }
+                    ]
+                }
+            }
+        }
+    ]
+)
+@auth([sigv4a, sigv4])
+@sigv4(name: "service5")
+@sigv4a(name: "service5")
+service Service5 {}
+
+@endpointRuleSet(
+    version: "1.0"
+    serviceId: "service"
+    parameters: {}
+    rules: [
+        {
+            type: "endpoint"
+            conditions: []
+            endpoint: {
+                url: "https://abc.service.com"
+                properties: {
+                    authSchemes: [
+                        {
+                            name: "sigv4a"
+                            signingRegionSet: ["*"]
+                        }
+                        {
+                            name: "sigv4"
+                        }
+                    ]
+                }
+            }
+        }
+    ]
+)
+@auth([sigv4a, sigv4])
+@sigv4(name: "service6")
+@sigv4a(name: "service6")
+service Service6 {}
+
+@endpointRuleSet(
+    version: "1.0"
+    serviceId: "service"
+    parameters: {}
+    rules: [
+        {
+            type: "endpoint"
+            conditions: []
+            endpoint: {
+                url: "https://abc.service.com"
+                properties: {}
+            }
+        }
+    ]
+)
+@auth([sigv4a, sigv4])
+@sigv4(name: "service7")
+@sigv4a(name: "service7")
+service Service7 {}
+
+@endpointRuleSet(
+    version: "1.0"
+    serviceId: "service"
+    parameters: {}
+    rules: [
+        {
+            type: "endpoint"
+            conditions: []
+            endpoint: {
+                url: "https://abc.service.com"
+                properties: {}
+            }
+        }
+    ]
+)
+@auth([sigv4a, sigv4])
+@sigv4(name: "service8")
+@sigv4a(name: "service8")
+service Service8 {}
diff --git a/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/diffs/sigv4-sigv4a-swap-order.b.smithy b/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/diffs/sigv4-sigv4a-swap-order.b.smithy
new file mode 100644
index 000000000..4cad3982b
--- /dev/null
+++ b/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/diffs/sigv4-sigv4a-swap-order.b.smithy
@@ -0,0 +1,226 @@
+$version: "2.0"
+
+metadata suppressions = [
+    {
+        id: "ModifiedTrait.Add.smithy.api#auth"
+        namespace: "ns.foo"
+    }
+    {
+        id: "ModifiedTrait.Update.smithy.api#auth"
+        namespace: "ns.foo"
+    }
+    {
+        id: "ModifiedTrait.Update.smithy.rules#endpointRuleSet"
+        namespace: "ns.foo"
+    }
+    {
+        id: "SigV4Migration"
+        namespace: "ns.foo"
+    }
+]
+
+namespace ns.foo
+
+use aws.auth#sigv4
+use aws.auth#sigv4a
+use smithy.rules#endpointRuleSet
+
+@endpointRuleSet(
+    version: "1.0"
+    serviceId: "service"
+    parameters: {}
+    rules: [
+        {
+            type: "endpoint"
+            conditions: []
+            endpoint: {
+                url: "https://abc.service.com"
+                properties: {
+                    authSchemes: [
+                        {
+                            name: "sigv4a"
+                            signingRegionSet: ["*"]
+                        }
+                        {
+                            name: "sigv4"
+                        }
+                    ]
+                }
+            }
+        }
+    ]
+)
+@auth([sigv4, sigv4a])
+@sigv4(name: "service1")
+@sigv4a(name: "service1")
+service Service1 {}
+
+@endpointRuleSet(
+    version: "1.0"
+    serviceId: "service"
+    parameters: {}
+    rules: [
+        {
+            type: "endpoint"
+            conditions: []
+            endpoint: {
+                url: "https://abc.service.com"
+                properties: {}
+            }
+        }
+    ]
+)
+@auth([sigv4a, sigv4])
+@sigv4(name: "service2")
+@sigv4a(name: "service2")
+service Service2 {}
+
+@endpointRuleSet(
+    version: "1.0"
+    serviceId: "service"
+    parameters: {}
+    rules: [
+        {
+            type: "endpoint"
+            conditions: []
+            endpoint: {
+                url: "https://abc.service.com"
+                properties: {
+                    authSchemes: [
+                        {
+                            name: "sigv4a"
+                            signingRegionSet: ["*"]
+                        }
+                        {
+                            name: "sigv4"
+                        }
+                    ]
+                }
+            }
+        }
+    ]
+)
+@auth([sigv4, sigv4a])
+@sigv4(name: "service3")
+@sigv4a(name: "service3")
+service Service3 {}
+
+@endpointRuleSet(
+    version: "1.0"
+    serviceId: "service"
+    parameters: {}
+    rules: [
+        {
+            type: "endpoint"
+            conditions: []
+            endpoint: {
+                url: "https://abc.service.com"
+                properties: {}
+            }
+        }
+    ]
+)
+@auth([sigv4a, sigv4])
+@sigv4(name: "service4")
+@sigv4a(name: "service4")
+service Service4 {}
+
+@endpointRuleSet(
+    version: "1.0"
+    serviceId: "service"
+    parameters: {}
+    rules: [
+        {
+            type: "endpoint"
+            conditions: []
+            endpoint: {
+                url: "https://abc.service.com"
+                properties: {
+                    authSchemes: [
+                        {
+                            name: "sigv4"
+                        }
+                        {
+                            name: "sigv4a"
+                            signingRegionSet: ["*"]
+                        }
+                    ]
+                }
+            }
+        }
+    ]
+)
+@auth([sigv4a, sigv4])
+@sigv4(name: "service5")
+@sigv4a(name: "service5")
+service Service5 {}
+
+@endpointRuleSet(
+    version: "1.0"
+    serviceId: "service"
+    parameters: {}
+    rules: [
+        {
+            type: "endpoint"
+            conditions: []
+            endpoint: {
+                url: "https://abc.service.com"
+                properties: {}
+            }
+        }
+    ]
+)
+@auth([sigv4, sigv4a])
+@sigv4(name: "service6")
+@sigv4a(name: "service6")
+service Service6 {}
+
+@endpointRuleSet(
+    version: "1.0"
+    serviceId: "service"
+    parameters: {}
+    rules: [
+        {
+            type: "endpoint"
+            conditions: []
+            endpoint: {
+                url: "https://abc.service.com"
+                properties: {
+                    authSchemes: [
+                        {
+                            name: "sigv4"
+                        }
+                        {
+                            name: "sigv4a"
+                            signingRegionSet: ["*"]
+                        }
+                    ]
+                }
+            }
+        }
+    ]
+)
+@auth([sigv4a, sigv4])
+@sigv4(name: "service7")
+@sigv4a(name: "service7")
+service Service7 {}
+
+@endpointRuleSet(
+    version: "1.0"
+    serviceId: "service"
+    parameters: {}
+    rules: [
+        {
+            type: "endpoint"
+            conditions: []
+            endpoint: {
+                url: "https://abc.service.com"
+                properties: {}
+            }
+        }
+    ]
+)
+@auth([sigv4, sigv4a])
+@sigv4(name: "service8")
+@sigv4a(name: "service8")
+service Service8 {}
diff --git a/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/diffs/sigv4-sigv4a-swap-order.events b/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/diffs/sigv4-sigv4a-swap-order.events
new file mode 100644
index 000000000..b7bac41d2
--- /dev/null
+++ b/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/diffs/sigv4-sigv4a-swap-order.events
@@ -0,0 +1,11 @@
+[DANGER] ns.foo#Service1: The `aws.auth#sigv4a` authentication scheme was moved before the `aws.auth#sigv4` authentication scheme in the effective auth schemes for an endpoint in the `@smithy.rules#endpointRuleSet` trait applied to `ns.foo#Service1` at: `/rules/0/endpoint`. Moving the `aws.auth#sigv4a` authentication scheme before the `aws.auth#sigv4` authentication scheme is not backward compatible since not all credentials usable by `aws.auth#sigv4` are compatible with `aws.auth#sigv4a`, and can break existing clients' authentication. | EndpointSigV4Migration
+-----
+[DANGER] ns.foo#Service2: The `aws.auth#sigv4a` authentication scheme was moved before the `aws.auth#sigv4` authentication scheme in the effective auth schemes for an endpoint in the `@smithy.rules#endpointRuleSet` trait applied to `ns.foo#Service2` at: `/rules/0/endpoint`. Moving the `aws.auth#sigv4a` authentication scheme before the `aws.auth#sigv4` authentication scheme is not backward compatible since not all credentials usable by `aws.auth#sigv4` are compatible with `aws.auth#sigv4a`, and can break existing clients' authentication. | EndpointSigV4Migration
+-----
+[DANGER] ns.foo#Service3: The `aws.auth#sigv4a` authentication scheme was moved before the `aws.auth#sigv4` authentication scheme in the effective auth schemes for an endpoint in the `@smithy.rules#endpointRuleSet` trait applied to `ns.foo#Service3` at: `/rules/0/endpoint`. Moving the `aws.auth#sigv4a` authentication scheme before the `aws.auth#sigv4` authentication scheme is not backward compatible since not all credentials usable by `aws.auth#sigv4` are compatible with `aws.auth#sigv4a`, and can break existing clients' authentication. | EndpointSigV4Migration
+-----
+[DANGER] ns.foo#Service5: The `aws.auth#sigv4` authentication scheme was moved before the `aws.auth#sigv4a` authentication scheme in the effective auth schemes for an endpoint in the `@smithy.rules#endpointRuleSet` trait applied to `ns.foo#Service5` at: `/rules/0/endpoint`. Moving the `aws.auth#sigv4` authentication scheme before the `aws.auth#sigv4a` authentication scheme may not be backward compatible if the signing scope was narrowed (typically from `*`). | EndpointSigV4Migration
+-----
+[DANGER] ns.foo#Service6: The `aws.auth#sigv4` authentication scheme was moved before the `aws.auth#sigv4a` authentication scheme in the effective auth schemes for an endpoint in the `@smithy.rules#endpointRuleSet` trait applied to `ns.foo#Service6` at: `/rules/0/endpoint`. Moving the `aws.auth#sigv4` authentication scheme before the `aws.auth#sigv4a` authentication scheme may not be backward compatible if the signing scope was narrowed (typically from `*`). | EndpointSigV4Migration
+-----
+[DANGER] ns.foo#Service7: The `aws.auth#sigv4` authentication scheme was moved before the `aws.auth#sigv4a` authentication scheme in the effective auth schemes for an endpoint in the `@smithy.rules#endpointRuleSet` trait applied to `ns.foo#Service7` at: `/rules/0/endpoint`. Moving the `aws.auth#sigv4` authentication scheme before the `aws.auth#sigv4a` authentication scheme may not be backward compatible if the signing scope was narrowed (typically from `*`). | EndpointSigV4Migration
diff --git a/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/diffs/sigv4a-after-sigv4-migration.a.smithy b/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/diffs/sigv4a-after-sigv4-migration.a.smithy
new file mode 100644
index 000000000..4c2b91b22
--- /dev/null
+++ b/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/diffs/sigv4a-after-sigv4-migration.a.smithy
@@ -0,0 +1,156 @@
+$version: "2.0"
+
+metadata suppressions = [
+    {
+        id: "ModifiedTrait.Update.smithy.api#auth"
+        namespace: "ns.foo"
+    }
+    {
+        id: "ModifiedTrait.Update.smithy.rules#endpointRuleSet"
+        namespace: "ns.foo"
+    }
+]
+
+namespace ns.foo
+
+use aws.auth#sigv4
+use aws.auth#sigv4a
+use smithy.rules#endpointRuleSet
+
+@endpointRuleSet(
+    version: "1.0"
+    serviceId: "service"
+    parameters: {}
+    rules: [
+        {
+            type: "endpoint"
+            conditions: []
+            endpoint: {
+                url: "https://abc.service.com"
+                properties: {
+                    authSchemes: [
+                        {
+                            name: "sigv4"
+                        }
+                    ]
+                }
+            }
+        }
+    ]
+)
+@auth([sigv4])
+@sigv4(name: "service1")
+@sigv4a(name: "service1")
+service Service1 {}
+
+@endpointRuleSet(
+    version: "1.0"
+    serviceId: "service"
+    parameters: {}
+    rules: [
+        {
+            type: "endpoint"
+            conditions: []
+            endpoint: {
+                url: "https://abc.service.com"
+                properties: {
+                    authSchemes: [
+                        {
+                            name: "sigv4"
+                        }
+                    ]
+                }
+            }
+        }
+    ]
+)
+@auth([sigv4])
+@sigv4(name: "service2")
+@sigv4a(name: "service2")
+service Service2 {}
+
+@endpointRuleSet(
+    version: "1.0"
+    serviceId: "service"
+    parameters: {}
+    rules: [
+        {
+            type: "endpoint"
+            conditions: []
+            endpoint: {
+                url: "https://abc.service.com"
+                properties: {
+                    authSchemes: [
+                        {
+                            name: "sigv4"
+                        }
+                    ]
+                }
+            }
+        }
+    ]
+)
+@auth([sigv4])
+@sigv4(name: "service3")
+@sigv4a(name: "service3")
+service Service3 {}
+
+@endpointRuleSet(
+    version: "1.0"
+    serviceId: "service"
+    parameters: {}
+    rules: [
+        {
+            type: "endpoint"
+            conditions: []
+            endpoint: {
+                url: "https://abc.service.com"
+                properties: {}
+            }
+        }
+    ]
+)
+@auth([sigv4])
+@sigv4(name: "service4")
+@sigv4a(name: "service4")
+service Service4 {}
+
+@endpointRuleSet(
+    version: "1.0"
+    serviceId: "service"
+    parameters: {}
+    rules: [
+        {
+            type: "endpoint"
+            conditions: []
+            endpoint: {
+                url: "https://abc.service.com"
+                properties: {}
+            }
+        }
+    ]
+)
+@auth([sigv4])
+@sigv4(name: "service5")
+@sigv4a(name: "service5")
+service Service5 {}
+
+@endpointRuleSet(
+    version: "1.0"
+    serviceId: "service"
+    parameters: {}
+    rules: [
+        {
+            type: "endpoint"
+            conditions: []
+            endpoint: {
+                url: "https://abc.service.com"
+                properties: {}
+            }
+        }
+    ]
+)
+@auth([sigv4])
+@sigv4(name: "service6")
+@sigv4a(name: "service6")
+service Service6 {}
diff --git a/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/diffs/sigv4a-after-sigv4-migration.b.smithy b/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/diffs/sigv4a-after-sigv4-migration.b.smithy
new file mode 100644
index 000000000..fc289c1c2
--- /dev/null
+++ b/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/diffs/sigv4a-after-sigv4-migration.b.smithy
@@ -0,0 +1,178 @@
+$version: "2.0"
+
+metadata suppressions = [
+    {
+        id: "ModifiedTrait.Update.smithy.api#auth"
+        namespace: "ns.foo"
+    }
+    {
+        id: "ModifiedTrait.Update.smithy.rules#endpointRuleSet"
+        namespace: "ns.foo"
+    }
+]
+
+namespace ns.foo
+
+use aws.auth#sigv4
+use aws.auth#sigv4a
+use smithy.rules#endpointRuleSet
+
+@endpointRuleSet(
+    version: "1.0"
+    serviceId: "service"
+    parameters: {}
+    rules: [
+        {
+            type: "endpoint"
+            conditions: []
+            endpoint: {
+                url: "https://abc.service.com"
+                properties: {
+                    authSchemes: [
+                        {
+                            name: "sigv4"
+                        }
+                        {
+                            name: "sigv4a"
+                            signingRegionSet: ["*"]
+                        }
+                    ]
+                }
+            }
+        }
+    ]
+)
+@auth([sigv4, sigv4a])
+@sigv4(name: "service1")
+@sigv4a(name: "service1")
+service Service1 {}
+
+@endpointRuleSet(
+    version: "1.0"
+    serviceId: "service"
+    parameters: {}
+    rules: [
+        {
+            type: "endpoint"
+            conditions: []
+            endpoint: {
+                url: "https://abc.service.com"
+                properties: {
+                    authSchemes: [
+                        {
+                            name: "sigv4"
+                        }
+                        {
+                            name: "sigv4a"
+                            signingRegionSet: ["*"]
+                        }
+                    ]
+                }
+            }
+        }
+    ]
+)
+@auth([sigv4])
+@sigv4(name: "service2")
+@sigv4a(name: "service2")
+service Service2 {}
+
+@endpointRuleSet(
+    version: "1.0"
+    serviceId: "service"
+    parameters: {}
+    rules: [
+        {
+            type: "endpoint"
+            conditions: []
+            endpoint: {
+                url: "https://abc.service.com"
+                properties: {}
+            }
+        }
+    ]
+)
+@auth([sigv4, sigv4a])
+@sigv4(name: "service3")
+@sigv4a(name: "service3")
+service Service3 {}
+
+@endpointRuleSet(
+    version: "1.0"
+    serviceId: "service"
+    parameters: {}
+    rules: [
+        {
+            type: "endpoint"
+            conditions: []
+            endpoint: {
+                url: "https://abc.service.com"
+                properties: {
+                    authSchemes: [
+                        {
+                            name: "sigv4"
+                        }
+                        {
+                            name: "sigv4a"
+                            signingRegionSet: ["*"]
+                        }
+                    ]
+                }
+            }
+        }
+    ]
+)
+@auth([sigv4])
+@sigv4(name: "service4")
+@sigv4a(name: "service4")
+service Service4 {}
+
+@endpointRuleSet(
+    version: "1.0"
+    serviceId: "service"
+    parameters: {}
+    rules: [
+        {
+            type: "endpoint"
+            conditions: []
+            endpoint: {
+                url: "https://abc.service.com"
+                properties: {
+                    authSchemes: [
+                        {
+                            name: "sigv4"
+                        }
+                        {
+                            name: "sigv4a"
+                            signingRegionSet: ["*"]
+                        }
+                    ]
+                }
+            }
+        }
+    ]
+)
+@auth([sigv4, sigv4a])
+@sigv4(name: "service5")
+@sigv4a(name: "service5")
+service Service5 {}
+
+@endpointRuleSet(
+    version: "1.0"
+    serviceId: "service"
+    parameters: {}
+    rules: [
+        {
+            type: "endpoint"
+            conditions: []
+            endpoint: {
+                url: "https://abc.service.com"
+                properties: {}
+            }
+        }
+    ]
+)
+@auth([sigv4, sigv4a])
+@sigv4(name: "service6")
+@sigv4a(name: "service6")
+service Service6 {}
diff --git a/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/diffs/sigv4a-after-sigv4-migration.events b/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/diffs/sigv4a-after-sigv4-migration.events
new file mode 100644
index 000000000..e69de29bb
diff --git a/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/diffs/sigv4a-before-sigv4-migration.a.smithy b/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/diffs/sigv4a-before-sigv4-migration.a.smithy
new file mode 100644
index 000000000..cffe9fd9e
--- /dev/null
+++ b/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/diffs/sigv4a-before-sigv4-migration.a.smithy
@@ -0,0 +1,180 @@
+$version: "2.0"
+
+metadata suppressions = [
+    {
+        id: "ModifiedTrait.Update.smithy.api#auth"
+        namespace: "ns.foo"
+    }
+    {
+        id: "ModifiedTrait.Update.smithy.rules#endpointRuleSet"
+        namespace: "ns.foo"
+    }
+    {
+        id: "SigV4Migration"
+        namespace: "ns.foo"
+    }
+]
+
+namespace ns.foo
+
+use aws.auth#sigv4
+use aws.auth#sigv4a
+use smithy.rules#endpointRuleSet
+
+@endpointRuleSet(
+    version: "1.0"
+    serviceId: "service"
+    parameters: {}
+    rules: [
+        {
+            type: "endpoint"
+            conditions: []
+            endpoint: {
+                url: "https://abc.service.com"
+                properties: {
+                    authSchemes: [
+                        {
+                            name: "sigv4"
+                        }
+                    ]
+                }
+            }
+        }
+    ]
+)
+@auth([sigv4])
+@sigv4(name: "service1")
+@sigv4a(name: "service1")
+service Service1 {}
+
+@endpointRuleSet(
+    version: "1.0"
+    serviceId: "service"
+    parameters: {}
+    rules: [
+        {
+            type: "endpoint"
+            conditions: []
+            endpoint: {
+                url: "https://abc.service.com"
+                properties: {
+                    authSchemes: [
+                        {
+                            name: "sigv4"
+                        }
+                    ]
+                }
+            }
+        }
+    ]
+)
+@auth([sigv4])
+@sigv4(name: "service2")
+@sigv4a(name: "service2")
+service Service2 {}
+
+@endpointRuleSet(
+    version: "1.0"
+    serviceId: "service"
+    parameters: {}
+    rules: [
+        {
+            type: "endpoint"
+            conditions: []
+            endpoint: {
+                url: "https://abc.service.com"
+                properties: {
+                    authSchemes: [
+                        {
+                            name: "sigv4"
+                        }
+                    ]
+                }
+            }
+        }
+    ]
+)
+@auth([sigv4])
+@sigv4(name: "service3")
+@sigv4a(name: "service3")
+service Service3 {}
+
+@endpointRuleSet(
+    version: "1.0"
+    serviceId: "service"
+    parameters: {}
+    rules: [
+        {
+            type: "endpoint"
+            conditions: []
+            endpoint: {
+                url: "https://abc.service.com"
+                properties: {}
+            }
+        }
+    ]
+)
+@auth([sigv4])
+@sigv4(name: "service4")
+@sigv4a(name: "service4")
+service Service4 {}
+
+@endpointRuleSet(
+    version: "1.0"
+    serviceId: "service"
+    parameters: {}
+    rules: [
+        {
+            type: "endpoint"
+            conditions: []
+            endpoint: {
+                url: "https://abc.service.com"
+                properties: {}
+            }
+        }
+    ]
+)
+@auth([sigv4])
+@sigv4(name: "service5")
+@sigv4a(name: "service5")
+service Service5 {}
+
+@endpointRuleSet(
+    version: "1.0"
+    serviceId: "service"
+    parameters: {}
+    rules: [
+        {
+            type: "endpoint"
+            conditions: []
+            endpoint: {
+                url: "https://abc.service.com"
+                properties: {}
+            }
+        }
+    ]
+)
+@auth([sigv4])
+@sigv4(name: "service6")
+@sigv4a(name: "service6")
+service Service6 {}
+
+@endpointRuleSet(
+    version: "1.0"
+    serviceId: "service"
+    parameters: {}
+    rules: [
+        {
+            type: "endpoint"
+            conditions: []
+            endpoint: {
+                url: "https://abc.service.com"
+                properties: {}
+            }
+        }
+    ]
+)
+@auth([sigv4])
+@sigv4(name: "service7")
+@sigv4a(name: "service7")
+service Service7 {}
diff --git a/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/diffs/sigv4a-before-sigv4-migration.b.smithy b/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/diffs/sigv4a-before-sigv4-migration.b.smithy
new file mode 100644
index 000000000..f6288ff77
--- /dev/null
+++ b/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/diffs/sigv4a-before-sigv4-migration.b.smithy
@@ -0,0 +1,202 @@
+$version: "2.0"
+
+metadata suppressions = [
+    {
+        id: "ModifiedTrait.Update.smithy.api#auth"
+        namespace: "ns.foo"
+    }
+    {
+        id: "ModifiedTrait.Update.smithy.rules#endpointRuleSet"
+        namespace: "ns.foo"
+    }
+    {
+        id: "SigV4Migration"
+        namespace: "ns.foo"
+    }
+]
+
+namespace ns.foo
+
+use aws.auth#sigv4
+use aws.auth#sigv4a
+use smithy.rules#endpointRuleSet
+
+@endpointRuleSet(
+    version: "1.0"
+    serviceId: "service"
+    parameters: {}
+    rules: [
+        {
+            type: "endpoint"
+            conditions: []
+            endpoint: {
+                url: "https://abc.service.com"
+                properties: {
+                    authSchemes: [
+                        {
+                            name: "sigv4a"
+                            signingRegionSet: ["*"]
+                        }
+                        {
+                            name: "sigv4"
+                        }
+                    ]
+                }
+            }
+        }
+    ]
+)
+@auth([sigv4a, sigv4])
+@sigv4(name: "service1")
+@sigv4a(name: "service1")
+service Service1 {}
+
+@endpointRuleSet(
+    version: "1.0"
+    serviceId: "service"
+    parameters: {}
+    rules: [
+        {
+            type: "endpoint"
+            conditions: []
+            endpoint: {
+                url: "https://abc.service.com"
+                properties: {
+                    authSchemes: [
+                        {
+                            name: "sigv4a"
+                            signingRegionSet: ["*"]
+                        }
+                        {
+                            name: "sigv4"
+                        }
+                    ]
+                }
+            }
+        }
+    ]
+)
+@auth([sigv4])
+@sigv4(name: "service2")
+@sigv4a(name: "service2")
+service Service2 {}
+
+@endpointRuleSet(
+    version: "1.0"
+    serviceId: "service"
+    parameters: {}
+    rules: [
+        {
+            type: "endpoint"
+            conditions: []
+            endpoint: {
+                url: "https://abc.service.com"
+                properties: {}
+            }
+        }
+    ]
+)
+@auth([sigv4a, sigv4])
+@sigv4(name: "service3")
+@sigv4a(name: "service3")
+service Service3 {}
+
+@endpointRuleSet(
+    version: "1.0"
+    serviceId: "service"
+    parameters: {}
+    rules: [
+        {
+            type: "endpoint"
+            conditions: []
+            endpoint: {
+                url: "https://abc.service.com"
+                properties: {
+                    authSchemes: [
+                        {
+                            name: "sigv4a"
+                            signingRegionSet: ["*"]
+                        }
+                        {
+                            name: "sigv4"
+                        }
+                    ]
+                }
+            }
+        }
+    ]
+)
+@auth([sigv4])
+@sigv4(name: "service4")
+@sigv4a(name: "service4")
+service Service4 {}
+
+@endpointRuleSet(
+    version: "1.0"
+    serviceId: "service"
+    parameters: {}
+    rules: [
+        {
+            type: "endpoint"
+            conditions: []
+            endpoint: {
+                url: "https://abc.service.com"
+                properties: {
+                    authSchemes: [
+                        {
+                            name: "sigv4a"
+                            signingRegionSet: ["*"]
+                        }
+                        {
+                            name: "sigv4"
+                        }
+                    ]
+                }
+            }
+        }
+    ]
+)
+@auth([sigv4a, sigv4])
+@sigv4(name: "service5")
+@sigv4a(name: "service5")
+service Service5 {}
+
+@endpointRuleSet(
+    version: "1.0"
+    serviceId: "service"
+    parameters: {}
+    rules: [
+        {
+            type: "endpoint"
+            conditions: []
+            endpoint: {
+                url: "https://abc.service.com"
+                properties: {}
+            }
+        }
+    ]
+)
+@auth([sigv4a, sigv4])
+@sigv4(name: "service6")
+@sigv4a(name: "service6")
+service Service6 {}
+
+@endpointRuleSet(
+    version: "1.0"
+    serviceId: "service"
+    parameters: {}
+    rules: [
+        {
+            type: "endpoint"
+            conditions: []
+            endpoint: {
+                url: "https://abc.service.com"
+                properties: { hello: "world" }
+            }
+        }
+    ]
+)
+@auth([sigv4a, sigv4])
+@sigv4(name: "service7")
+@sigv4a(name: "service7")
+service Service7 {}
diff --git a/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/diffs/sigv4a-before-sigv4-migration.events b/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/diffs/sigv4a-before-sigv4-migration.events
new file mode 100644
index 000000000..cafd3b9f5
--- /dev/null
+++ b/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/diffs/sigv4a-before-sigv4-migration.events
@@ -0,0 +1,11 @@
+[DANGER] ns.foo#Service1: The `aws.auth#sigv4a` authentication scheme was added before the `aws.auth#sigv4` authentication scheme in the effective auth schemes for an endpoint in the `@smithy.rules#endpointRuleSet` trait applied to `ns.foo#Service1` at: `/rules/0/endpoint`. Adding the `aws.auth#sigv4a` authentication scheme before an existing `aws.auth#sigv4` authentication scheme is not backward compatible since not all credentials usable by `aws.auth#sigv4` are compatible with `aws.auth#sigv4a`, and can break existing clients' authentication. | EndpointSigV4Migration
+-----
+[DANGER] ns.foo#Service2: The `aws.auth#sigv4a` authentication scheme was added before the `aws.auth#sigv4` authentication scheme in the effective auth schemes for an endpoint in the `@smithy.rules#endpointRuleSet` trait applied to `ns.foo#Service2` at: `/rules/0/endpoint`. Adding the `aws.auth#sigv4a` authentication scheme before an existing `aws.auth#sigv4` authentication scheme is not backward compatible since not all credentials usable by `aws.auth#sigv4` are compatible with `aws.auth#sigv4a`, and can break existing clients' authentication. | EndpointSigV4Migration
+-----
+[DANGER] ns.foo#Service3: The `aws.auth#sigv4a` authentication scheme was added before the `aws.auth#sigv4` authentication scheme in the effective auth schemes for an endpoint in the `@smithy.rules#endpointRuleSet` trait applied to `ns.foo#Service3` at: `/rules/0/endpoint`. Adding the `aws.auth#sigv4a` authentication scheme before an existing `aws.auth#sigv4` authentication scheme is not backward compatible since not all credentials usable by `aws.auth#sigv4` are compatible with `aws.auth#sigv4a`, and can break existing clients' authentication. | EndpointSigV4Migration
+-----
+[DANGER] ns.foo#Service4: The `aws.auth#sigv4a` authentication scheme was added before the `aws.auth#sigv4` authentication scheme in the effective auth schemes for an endpoint in the `@smithy.rules#endpointRuleSet` trait applied to `ns.foo#Service4` at: `/rules/0/endpoint`. Adding the `aws.auth#sigv4a` authentication scheme before an existing `aws.auth#sigv4` authentication scheme is not backward compatible since not all credentials usable by `aws.auth#sigv4` are compatible with `aws.auth#sigv4a`, and can break existing clients' authentication. | EndpointSigV4Migration
+-----
+[DANGER] ns.foo#Service5: The `aws.auth#sigv4a` authentication scheme was added before the `aws.auth#sigv4` authentication scheme in the effective auth schemes for an endpoint in the `@smithy.rules#endpointRuleSet` trait applied to `ns.foo#Service5` at: `/rules/0/endpoint`. Adding the `aws.auth#sigv4a` authentication scheme before an existing `aws.auth#sigv4` authentication scheme is not backward compatible since not all credentials usable by `aws.auth#sigv4` are compatible with `aws.auth#sigv4a`, and can break existing clients' authentication. | EndpointSigV4Migration
+-----
+[DANGER] ns.foo#Service7: The `aws.auth#sigv4a` authentication scheme was added before the `aws.auth#sigv4` authentication scheme in the effective auth schemes for an endpoint in the `@smithy.rules#endpointRuleSet` trait applied to `ns.foo#Service7` at: `/rules/0/endpoint`. Adding the `aws.auth#sigv4a` authentication scheme before an existing `aws.auth#sigv4` authentication scheme is not backward compatible since not all credentials usable by `aws.auth#sigv4` are compatible with `aws.auth#sigv4a`, and can break existing clients' authentication. | EndpointSigV4Migration
diff --git a/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/diffs/sigv4a-replace-sigv4-migration.a.smithy b/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/diffs/sigv4a-replace-sigv4-migration.a.smithy
new file mode 100644
index 000000000..6ebc52228
--- /dev/null
+++ b/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/diffs/sigv4a-replace-sigv4-migration.a.smithy
@@ -0,0 +1,114 @@
+$version: "2.0"
+
+metadata suppressions = [
+    {
+        id: "ModifiedTrait.Update.smithy.api#auth"
+        namespace: "ns.foo"
+    }
+    {
+        id: "ModifiedTrait.Update.smithy.rules#endpointRuleSet"
+        namespace: "ns.foo"
+    }
+    {
+        id: "SigV4Migration"
+        namespace: "ns.foo"
+    }
+]
+
+namespace ns.foo
+
+use aws.auth#sigv4
+use aws.auth#sigv4a
+use smithy.rules#endpointRuleSet
+
+@endpointRuleSet(
+    version: "1.0"
+    serviceId: "service"
+    parameters: {}
+    rules: [
+        {
+            type: "endpoint"
+            conditions: []
+            endpoint: {
+                url: "https://abc.service.com"
+                properties: {
+                    authSchemes: [
+                        {
+                            name: "sigv4"
+                        }
+                    ]
+                }
+            }
+        }
+    ]
+)
+@auth([sigv4])
+@sigv4(name: "service1")
+@sigv4a(name: "service1")
+service Service1 {}
+
+@endpointRuleSet(
+    version: "1.0"
+    serviceId: "service"
+    parameters: {}
+    rules: [
+        {
+            type: "endpoint"
+            conditions: []
+            endpoint: {
+                url: "https://abc.service.com"
+                properties: {
+                    authSchemes: [
+                        {
+                            name: "sigv4"
+                        }
+                    ]
+                }
+            }
+        }
+    ]
+)
+@auth([sigv4])
+@sigv4(name: "service2")
+@sigv4a(name: "service2")
+service Service2 {}
+
+@endpointRuleSet(
+    version: "1.0"
+    serviceId: "service"
+    parameters: {}
+    rules: [
+        {
+            type: "endpoint"
+            conditions: []
+            endpoint: {
+                url: "https://abc.service.com"
+                properties: {}
+            }
+        }
+    ]
+)
+@auth([sigv4])
+@sigv4(name: "service3")
+@sigv4a(name: "service3")
+service Service3 {}
+
+@endpointRuleSet(
+    version: "1.0"
+    serviceId: "service"
+    parameters: {}
+    rules: [
+        {
+            type: "endpoint"
+            conditions: []
+            endpoint: {
+                url: "https://abc.service.com"
+                properties: {}
+            }
+        }
+    ]
+)
+@auth([sigv4])
+@sigv4(name: "service4")
+@sigv4a(name: "service4")
+service Service4 {}
diff --git a/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/diffs/sigv4a-replace-sigv4-migration.b.smithy b/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/diffs/sigv4a-replace-sigv4-migration.b.smithy
new file mode 100644
index 000000000..b22141497
--- /dev/null
+++ b/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/diffs/sigv4a-replace-sigv4-migration.b.smithy
@@ -0,0 +1,116 @@
+$version: "2.0"
+
+metadata suppressions = [
+    {
+        id: "ModifiedTrait.Update.smithy.api#auth"
+        namespace: "ns.foo"
+    }
+    {
+        id: "ModifiedTrait.Update.smithy.rules#endpointRuleSet"
+        namespace: "ns.foo"
+    }
+    {
+        id: "SigV4Migration"
+        namespace: "ns.foo"
+    }
+]
+
+namespace ns.foo
+
+use aws.auth#sigv4
+use aws.auth#sigv4a
+use smithy.rules#endpointRuleSet
+
+@endpointRuleSet(
+    version: "1.0"
+    serviceId: "service"
+    parameters: {}
+    rules: [
+        {
+            type: "endpoint"
+            conditions: []
+            endpoint: {
+                url: "https://abc.service.com"
+                properties: {
+                    authSchemes: [
+                        {
+                            name: "sigv4a"
+                            signingRegionSet: ["*"]
+                        }
+                    ]
+                }
+            }
+        }
+    ]
+)
+@auth([sigv4a])
+@sigv4(name: "service1")
+@sigv4a(name: "service1")
+service Service1 {}
+
+@endpointRuleSet(
+    version: "1.0"
+    serviceId: "service"
+    parameters: {}
+    rules: [
+        {
+            type: "endpoint"
+            conditions: []
+            endpoint: {
+                url: "https://abc.service.com"
+                properties: {}
+            }
+        }
+    ]
+)
+@auth([sigv4a])
+@sigv4(name: "service2")
+@sigv4a(name: "service2")
+service Service2 {}
+
+@endpointRuleSet(
+    version: "1.0"
+    serviceId: "service"
+    parameters: {}
+    rules: [
+        {
+            type: "endpoint"
+            conditions: []
+            endpoint: {
+                url: "https://abc.service.com"
+                properties: {
+                    authSchemes: [
+                        {
+                            name: "sigv4a"
+                            signingRegionSet: ["*"]
+                        }
+                    ]
+                }
+            }
+        }
+    ]
+)
+@auth([sigv4a])
+@sigv4(name: "service3")
+@sigv4a(name: "service3")
+service Service3 {}
+
+@endpointRuleSet(
+    version: "1.0"
+    serviceId: "service"
+    parameters: {}
+    rules: [
+        {
+            type: "endpoint"
+            conditions: []
+            endpoint: {
+                url: "https://abc.service.com"
+                properties: {}
+            }
+        }
+    ]
+)
+@auth([sigv4a])
+@sigv4(name: "service4")
+@sigv4a(name: "service4")
+service Service4 {}
diff --git a/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/diffs/sigv4a-replace-sigv4-migration.events b/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/diffs/sigv4a-replace-sigv4-migration.events
new file mode 100644
index 000000000..2b31c4e0a
--- /dev/null
+++ b/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/diffs/sigv4a-replace-sigv4-migration.events
@@ -0,0 +1,5 @@
+[DANGER] ns.foo#Service1: The `aws.auth#sigv4` authentication scheme was replaced by the `aws.auth#sigv4a` authentication scheme in the effective auth schemes for an endpoint in the `@smithy.rules#endpointRuleSet` trait applied to `ns.foo#Service1` at: `/rules/0/endpoint`. Replacing the `aws.auth#sigv4` authentication scheme with the `aws.auth#sigv4a` authentication scheme directly is not backward compatible since not all credentials usable by `aws.auth#sigv4` are compatible with `aws.auth#sigv4a`, and can break existing clients' authentication. | EndpointSigV4Migration
+-----
+[DANGER] ns.foo#Service2: The `aws.auth#sigv4` authentication scheme was replaced by the `aws.auth#sigv4a` authentication scheme in the effective auth schemes for an endpoint in the `@smithy.rules#endpointRuleSet` trait applied to `ns.foo#Service2` at: `/rules/0/endpoint`. Replacing the `aws.auth#sigv4` authentication scheme with the `aws.auth#sigv4a` authentication scheme directly is not backward compatible since not all credentials usable by `aws.auth#sigv4` are compatible with `aws.auth#sigv4a`, and can break existing clients' authentication. | EndpointSigV4Migration
+-----
+[DANGER] ns.foo#Service3: The `aws.auth#sigv4` authentication scheme was replaced by the `aws.auth#sigv4a` authentication scheme in the effective auth schemes for an endpoint in the `@smithy.rules#endpointRuleSet` trait applied to `ns.foo#Service3` at: `/rules/0/endpoint`. Replacing the `aws.auth#sigv4` authentication scheme with the `aws.auth#sigv4a` authentication scheme directly is not backward compatible since not all credentials usable by `aws.auth#sigv4` are compatible with `aws.auth#sigv4a`, and can break existing clients' authentication. | EndpointSigV4Migration
diff --git a/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/diffs/with-sigv4-no-migration.a.smithy b/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/diffs/with-sigv4-no-migration.a.smithy
new file mode 100644
index 000000000..0ec0391bc
--- /dev/null
+++ b/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/diffs/with-sigv4-no-migration.a.smithy
@@ -0,0 +1,84 @@
+$version: "2.0"
+
+metadata suppressions = [
+    {
+        id: "ModifiedTrait.Update.smithy.api#auth"
+        namespace: "ns.foo"
+    }
+    {
+        id: "ModifiedTrait.Update.smithy.rules#endpointRuleSet"
+        namespace: "ns.foo"
+    }
+]
+
+namespace ns.foo
+
+use aws.auth#sigv4
+use aws.auth#sigv4a
+use smithy.rules#endpointRuleSet
+
+@endpointRuleSet(
+    version: "1.0"
+    serviceId: "service"
+    parameters: {}
+    rules: [
+        {
+            type: "endpoint"
+            conditions: []
+            endpoint: {
+                url: "https://abc.service.com"
+                properties: {
+                    authSchemes: [
+                        {
+                            name: "sigv4"
+                        }
+                    ]
+                }
+            }
+        }
+    ]
+)
+@auth([sigv4])
+@httpBearerAuth
+@sigv4(name: "service1")
+@sigv4a(name: "service1")
+service Service1 {}
+
+@endpointRuleSet(
+    version: "1.0"
+    serviceId: "service"
+    parameters: {}
+    rules: [
+        {
+            type: "endpoint"
+            conditions: []
+            endpoint: {
+                url: "https://abc.service.com"
+                properties: {
+                    authSchemes: [
+                        {
+                            name: "sigv4"
+                        }
+                    ]
+                }
+            }
+        }
+    ]
+)
+@auth([sigv4])
+@httpBearerAuth
+@sigv4(name: "service2")
+@sigv4a(name: "service2")
+service Service2 {}
+
+@auth([sigv4])
+@httpBearerAuth
+@sigv4(name: "service3")
+@sigv4a(name: "service3")
+service Service3 {}
+
+@auth([sigv4])
+@httpBearerAuth
+@sigv4(name: "service4")
+@sigv4a(name: "service4")
+service Service4 {}
diff --git a/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/diffs/with-sigv4-no-migration.b.smithy b/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/diffs/with-sigv4-no-migration.b.smithy
new file mode 100644
index 000000000..f89b193fd
--- /dev/null
+++ b/smithy-aws-endpoints/src/test/resources/software/amazon/smithy/rulesengine/aws/diffs/with-sigv4-no-migration.b.smithy
@@ -0,0 +1,90 @@
+$version: "2.0"
+
+metadata suppressions = [
+    {
+        id: "ModifiedTrait.Update.smithy.api#auth"
+        namespace: "ns.foo"
+    }
+    {
+        id: "ModifiedTrait.Update.smithy.rules#endpointRuleSet"
+        namespace: "ns.foo"
+    }
+]
+
+namespace ns.foo
+
+use aws.auth#sigv4
+use aws.auth#sigv4a
+use smithy.rules#endpointRuleSet
+
+@endpointRuleSet(
+    version: "1.0"
+    serviceId: "service"
+    parameters: {}
+    rules: [
+        {
+            type: "endpoint"
+            conditions: []
+            endpoint: {
+                url: "https://abc.service.com"
+                properties: {
+                    authSchemes: [
+                        {
+                            name: "example"
+                        }
+                        {
+                            name: "sigv4"
+                        }
+                    ]
+                }
+            }
+        }
+    ]
+)
+@auth([sigv4, httpBearerAuth])
+@httpBearerAuth
+@sigv4(name: "service1")
+@sigv4a(name: "service1")
+service Service1 {}
+
+@endpointRuleSet(
+    version: "1.0"
+    serviceId: "service"
+    parameters: {}
+    rules: [
+        {
+            type: "endpoint"
+            conditions: []
+            endpoint: {
+                url: "https://abc.service.com"
+                properties: {
+                    authSchemes: [
+                        {
+                            name: "sigv4"
+                        }
+                        {
+                            name: "example"
+                        }
+                    ]
+                }
+            }
+        }
+    ]
+)
+@auth([sigv4, httpBearerAuth])
+@httpBearerAuth
+@sigv4(name: "service2")
+@sigv4a(name: "service2")
+service Service2 {}
+
+@auth([sigv4, httpBearerAuth])
+@httpBearerAuth
+@sigv4(name: "service3")
+@sigv4a(name: "service3")
+service Service3 {}
+
+@auth([httpBearerAuth, sigv4])
+@httpBearerAuth
+@sigv4(name: "service4")
+@sigv4a(name: "service4")
+service Service4 {}
diff --git a/smithy-aws-traits/src/test/java/software/amazon/smithy/aws/traits/DiffTest.java b/smithy-aws-traits/src/test/java/software/amazon/smithy/aws/traits/DiffTest.java
new file mode 100644
index 000000000..e001bff25
--- /dev/null
+++ b/smithy-aws-traits/src/test/java/software/amazon/smithy/aws/traits/DiffTest.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package software.amazon.smithy.aws.traits;
+
+import static java.lang.String.format;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.io.File;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Arrays;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.Callable;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
+import software.amazon.smithy.diff.ModelDiff;
+import software.amazon.smithy.diff.testrunner.SmithyDiffTestCase;
+import software.amazon.smithy.diff.testrunner.SmithyDiffTestSuite;
+import software.amazon.smithy.model.Model;
+import software.amazon.smithy.model.SourceLocation;
+import software.amazon.smithy.model.shapes.ShapeId;
+import software.amazon.smithy.model.validation.Severity;
+import software.amazon.smithy.model.validation.ValidationEvent;
+import software.amazon.smithy.utils.IoUtils;
+import software.amazon.smithy.utils.Pair;
+
+public class DiffTest {
+    @ParameterizedTest(name = "{0}")
+    @MethodSource("source")
+    public void testRunner(String filename, Callable<SmithyDiffTestCase.Result> callable) throws Exception {
+        callable.call();
+    }
+
+    public static Stream<?> source() {
+        return SmithyDiffTestSuite.defaultParameterizedTestSource(DiffTest.class);
+    }
+}
diff --git a/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/diffs/no-old-sigv4.a.smithy b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/diffs/no-old-sigv4.a.smithy
new file mode 100644
index 000000000..e75d0177d
--- /dev/null
+++ b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/diffs/no-old-sigv4.a.smithy
@@ -0,0 +1,21 @@
+$version: "2.0"
+
+metadata suppressions = [
+    {
+        id: "ModifiedTrait.Update.smithy.api#auth"
+        namespace: "ns.foo"
+    }
+]
+
+namespace ns.foo
+
+@auth([])
+@httpBearerAuth
+service Service {
+    operations: [
+        Operation
+    ]
+}
+
+@auth([])
+operation Operation {}
diff --git a/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/diffs/no-old-sigv4.b.smithy b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/diffs/no-old-sigv4.b.smithy
new file mode 100644
index 000000000..a2815d8d1
--- /dev/null
+++ b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/diffs/no-old-sigv4.b.smithy
@@ -0,0 +1,21 @@
+$version: "2.0"
+
+metadata suppressions = [
+    {
+        id: "ModifiedTrait.Update.smithy.api#auth"
+        namespace: "ns.foo"
+    }
+]
+
+namespace ns.foo
+
+@auth([httpBearerAuth])
+@httpBearerAuth
+service Service {
+    operations: [
+        Operation
+    ]
+}
+
+@auth([httpBearerAuth])
+operation Operation {}
diff --git a/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/diffs/no-old-sigv4.events b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/diffs/no-old-sigv4.events
new file mode 100644
index 000000000..e69de29bb
diff --git a/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/diffs/sigv4-after-sigv4a-migration.a.smithy b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/diffs/sigv4-after-sigv4a-migration.a.smithy
new file mode 100644
index 000000000..8514baee5
--- /dev/null
+++ b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/diffs/sigv4-after-sigv4a-migration.a.smithy
@@ -0,0 +1,25 @@
+$version: "2.0"
+
+metadata suppressions = [
+    {
+        id: "ModifiedTrait.Update.smithy.api#auth"
+        namespace: "ns.foo"
+    }
+]
+
+namespace ns.foo
+
+use aws.auth#sigv4
+use aws.auth#sigv4a
+
+@auth([sigv4a])
+@sigv4(name: "service")
+@sigv4a(name: "service")
+service Service {
+    operations: [
+        Operation
+    ]
+}
+
+@auth([sigv4a])
+operation Operation {}
diff --git a/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/diffs/sigv4-after-sigv4a-migration.b.smithy b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/diffs/sigv4-after-sigv4a-migration.b.smithy
new file mode 100644
index 000000000..028d92375
--- /dev/null
+++ b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/diffs/sigv4-after-sigv4a-migration.b.smithy
@@ -0,0 +1,25 @@
+$version: "2.0"
+
+metadata suppressions = [
+    {
+        id: "ModifiedTrait.Update.smithy.api#auth"
+        namespace: "ns.foo"
+    }
+]
+
+namespace ns.foo
+
+use aws.auth#sigv4
+use aws.auth#sigv4a
+
+@auth([sigv4a, sigv4])
+@sigv4(name: "service")
+@sigv4a(name: "service")
+service Service {
+    operations: [
+        Operation
+    ]
+}
+
+@auth([sigv4a, sigv4])
+operation Operation {}
diff --git a/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/diffs/sigv4-after-sigv4a-migration.events b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/diffs/sigv4-after-sigv4a-migration.events
new file mode 100644
index 000000000..e69de29bb
diff --git a/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/diffs/sigv4-before-sigv4a-migration.a.smithy b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/diffs/sigv4-before-sigv4a-migration.a.smithy
new file mode 100644
index 000000000..8514baee5
--- /dev/null
+++ b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/diffs/sigv4-before-sigv4a-migration.a.smithy
@@ -0,0 +1,25 @@
+$version: "2.0"
+
+metadata suppressions = [
+    {
+        id: "ModifiedTrait.Update.smithy.api#auth"
+        namespace: "ns.foo"
+    }
+]
+
+namespace ns.foo
+
+use aws.auth#sigv4
+use aws.auth#sigv4a
+
+@auth([sigv4a])
+@sigv4(name: "service")
+@sigv4a(name: "service")
+service Service {
+    operations: [
+        Operation
+    ]
+}
+
+@auth([sigv4a])
+operation Operation {}
diff --git a/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/diffs/sigv4-before-sigv4a-migration.b.smithy b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/diffs/sigv4-before-sigv4a-migration.b.smithy
new file mode 100644
index 000000000..d6ad5d01f
--- /dev/null
+++ b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/diffs/sigv4-before-sigv4a-migration.b.smithy
@@ -0,0 +1,25 @@
+$version: "2.0"
+
+metadata suppressions = [
+    {
+        id: "ModifiedTrait.Update.smithy.api#auth"
+        namespace: "ns.foo"
+    }
+]
+
+namespace ns.foo
+
+use aws.auth#sigv4
+use aws.auth#sigv4a
+
+@auth([sigv4, sigv4a])
+@sigv4(name: "service")
+@sigv4a(name: "service")
+service Service {
+    operations: [
+        Operation
+    ]
+}
+
+@auth([sigv4, sigv4a])
+operation Operation {}
diff --git a/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/diffs/sigv4-before-sigv4a-migration.events b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/diffs/sigv4-before-sigv4a-migration.events
new file mode 100644
index 000000000..ed584854c
--- /dev/null
+++ b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/diffs/sigv4-before-sigv4a-migration.events
@@ -0,0 +1,3 @@
+[DANGER] ns.foo#Service: The `aws.auth#sigv4` authentication scheme was added before the `aws.auth#sigv4a` authentication scheme in the effective auth schemes for `ns.foo#Service`. Adding the `aws.auth#sigv4` authentication scheme before an existing `aws.auth#sigv4a` authentication scheme may not be backward compatible if the signing scope was narrowed (typically from `*`). | SigV4Migration
+-----
+[DANGER] ns.foo#Operation: The `aws.auth#sigv4` authentication scheme was added before the `aws.auth#sigv4a` authentication scheme in the effective auth schemes for `ns.foo#Operation`. Adding the `aws.auth#sigv4` authentication scheme before an existing `aws.auth#sigv4a` authentication scheme may not be backward compatible if the signing scope was narrowed (typically from `*`). | SigV4Migration
diff --git a/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/diffs/sigv4-replace-sigv4a-migration.a.smithy b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/diffs/sigv4-replace-sigv4a-migration.a.smithy
new file mode 100644
index 000000000..8514baee5
--- /dev/null
+++ b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/diffs/sigv4-replace-sigv4a-migration.a.smithy
@@ -0,0 +1,25 @@
+$version: "2.0"
+
+metadata suppressions = [
+    {
+        id: "ModifiedTrait.Update.smithy.api#auth"
+        namespace: "ns.foo"
+    }
+]
+
+namespace ns.foo
+
+use aws.auth#sigv4
+use aws.auth#sigv4a
+
+@auth([sigv4a])
+@sigv4(name: "service")
+@sigv4a(name: "service")
+service Service {
+    operations: [
+        Operation
+    ]
+}
+
+@auth([sigv4a])
+operation Operation {}
diff --git a/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/diffs/sigv4-replace-sigv4a-migration.b.smithy b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/diffs/sigv4-replace-sigv4a-migration.b.smithy
new file mode 100644
index 000000000..84b82e83d
--- /dev/null
+++ b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/diffs/sigv4-replace-sigv4a-migration.b.smithy
@@ -0,0 +1,25 @@
+$version: "2.0"
+
+metadata suppressions = [
+    {
+        id: "ModifiedTrait.Update.smithy.api#auth"
+        namespace: "ns.foo"
+    }
+]
+
+namespace ns.foo
+
+use aws.auth#sigv4
+use aws.auth#sigv4a
+
+@auth([sigv4])
+@sigv4(name: "service")
+@sigv4a(name: "service")
+service Service {
+    operations: [
+        Operation
+    ]
+}
+
+@auth([sigv4])
+operation Operation {}
diff --git a/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/diffs/sigv4-replace-sigv4a-migration.events b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/diffs/sigv4-replace-sigv4a-migration.events
new file mode 100644
index 000000000..6340e3e54
--- /dev/null
+++ b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/diffs/sigv4-replace-sigv4a-migration.events
@@ -0,0 +1,3 @@
+[DANGER] ns.foo#Service: The `aws.auth#sigv4a` authentication scheme was replaced by the `aws.auth#sigv4` authentication scheme in the effective auth schemes for `ns.foo#Service`. Replacing the `aws.auth#sigv4` authentication scheme with the `aws.auth#sigv4a` authentication scheme directly may not be backward compatible if the signing scope was narrowed (typically from `*`). | SigV4Migration
+-----
+[DANGER] ns.foo#Operation: The `aws.auth#sigv4a` authentication scheme was replaced by the `aws.auth#sigv4` authentication scheme in the effective auth schemes for `ns.foo#Operation`. Replacing the `aws.auth#sigv4` authentication scheme with the `aws.auth#sigv4a` authentication scheme directly may not be backward compatible if the signing scope was narrowed (typically from `*`). | SigV4Migration
diff --git a/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/diffs/sigv4-sigv4a-in-order.a.smithy b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/diffs/sigv4-sigv4a-in-order.a.smithy
new file mode 100644
index 000000000..5c797829d
--- /dev/null
+++ b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/diffs/sigv4-sigv4a-in-order.a.smithy
@@ -0,0 +1,26 @@
+$version: "2.0"
+
+metadata suppressions = [
+    {
+        id: "ModifiedTrait.Update.smithy.api#auth"
+        namespace: "ns.foo"
+    }
+]
+
+namespace ns.foo
+
+use aws.auth#sigv4
+use aws.auth#sigv4a
+
+@auth([httpBearerAuth, sigv4, sigv4a])
+@httpBearerAuth
+@sigv4(name: "service")
+@sigv4a(name: "service")
+service Service {
+    operations: [
+        Operation
+    ]
+}
+
+@auth([httpBearerAuth, sigv4, sigv4a])
+operation Operation {}
diff --git a/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/diffs/sigv4-sigv4a-in-order.b.smithy b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/diffs/sigv4-sigv4a-in-order.b.smithy
new file mode 100644
index 000000000..6b7dca49d
--- /dev/null
+++ b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/diffs/sigv4-sigv4a-in-order.b.smithy
@@ -0,0 +1,26 @@
+$version: "2.0"
+
+metadata suppressions = [
+    {
+        id: "ModifiedTrait.Update.smithy.api#auth"
+        namespace: "ns.foo"
+    }
+]
+
+namespace ns.foo
+
+use aws.auth#sigv4
+use aws.auth#sigv4a
+
+@auth([sigv4, httpBearerAuth, sigv4a])
+@httpBearerAuth
+@sigv4(name: "service")
+@sigv4a(name: "service")
+service Service {
+    operations: [
+        Operation
+    ]
+}
+
+@auth([sigv4, httpBearerAuth, sigv4a])
+operation Operation {}
diff --git a/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/diffs/sigv4-sigv4a-in-order.events b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/diffs/sigv4-sigv4a-in-order.events
new file mode 100644
index 000000000..e69de29bb
diff --git a/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/diffs/sigv4-sigv4a-swap-order.a.smithy b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/diffs/sigv4-sigv4a-swap-order.a.smithy
new file mode 100644
index 000000000..200a2690a
--- /dev/null
+++ b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/diffs/sigv4-sigv4a-swap-order.a.smithy
@@ -0,0 +1,37 @@
+$version: "2.0"
+
+metadata suppressions = [
+    {
+        id: "ModifiedTrait.Update.smithy.api#auth"
+        namespace: "ns.foo"
+    }
+]
+
+namespace ns.foo
+
+use aws.auth#sigv4
+use aws.auth#sigv4a
+
+@auth([sigv4, sigv4a])
+@sigv4(name: "service1")
+@sigv4a(name: "service1")
+service Service1 {
+    operations: [
+        Operation1
+    ]
+}
+
+@auth([sigv4, sigv4a])
+operation Operation1 {}
+
+@auth([sigv4a, sigv4])
+@sigv4(name: "service2")
+@sigv4a(name: "service2")
+service Service2 {
+    operations: [
+        Operation2
+    ]
+}
+
+@auth([sigv4a, sigv4])
+operation Operation2 {}
diff --git a/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/diffs/sigv4-sigv4a-swap-order.b.smithy b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/diffs/sigv4-sigv4a-swap-order.b.smithy
new file mode 100644
index 000000000..ec607fa19
--- /dev/null
+++ b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/diffs/sigv4-sigv4a-swap-order.b.smithy
@@ -0,0 +1,37 @@
+$version: "2.0"
+
+metadata suppressions = [
+    {
+        id: "ModifiedTrait.Update.smithy.api#auth"
+        namespace: "ns.foo"
+    }
+]
+
+namespace ns.foo
+
+use aws.auth#sigv4
+use aws.auth#sigv4a
+
+@auth([sigv4a, sigv4])
+@sigv4(name: "service1")
+@sigv4a(name: "service1")
+service Service1 {
+    operations: [
+        Operation1
+    ]
+}
+
+@auth([sigv4a, sigv4])
+operation Operation1 {}
+
+@auth([sigv4, sigv4a])
+@sigv4(name: "service2")
+@sigv4a(name: "service2")
+service Service2 {
+    operations: [
+        Operation2
+    ]
+}
+
+@auth([sigv4, sigv4a])
+operation Operation2 {}
diff --git a/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/diffs/sigv4-sigv4a-swap-order.events b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/diffs/sigv4-sigv4a-swap-order.events
new file mode 100644
index 000000000..57888ae11
--- /dev/null
+++ b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/diffs/sigv4-sigv4a-swap-order.events
@@ -0,0 +1,7 @@
+[DANGER] ns.foo#Service1: The `aws.auth#sigv4a` authentication scheme was moved before the `aws.auth#sigv4` authentication scheme in the effective auth schemes for `ns.foo#Service1`. Moving the `aws.auth#sigv4a` authentication scheme before the `aws.auth#sigv4` authentication scheme is not backward compatible since not all credentials usable by `aws.auth#sigv4` are compatible with `aws.auth#sigv4a`, and can break existing clients' authentication. | SigV4Migration
+-----
+[DANGER] ns.foo#Operation1: The `aws.auth#sigv4a` authentication scheme was moved before the `aws.auth#sigv4` authentication scheme in the effective auth schemes for `ns.foo#Operation1`. Moving the `aws.auth#sigv4a` authentication scheme before the `aws.auth#sigv4` authentication scheme is not backward compatible since not all credentials usable by `aws.auth#sigv4` are compatible with `aws.auth#sigv4a`, and can break existing clients' authentication. | SigV4Migration
+-----
+[DANGER] ns.foo#Service2: The `aws.auth#sigv4` authentication scheme was moved before the `aws.auth#sigv4a` authentication scheme in the effective auth schemes for `ns.foo#Service2`. Moving the `aws.auth#sigv4` authentication scheme before the `aws.auth#sigv4a` authentication scheme may not be backward compatible if the signing scope was narrowed (typically from `*`). | SigV4Migration
+-----
+[DANGER] ns.foo#Operation2: The `aws.auth#sigv4` authentication scheme was moved before the `aws.auth#sigv4a` authentication scheme in the effective auth schemes for `ns.foo#Operation2`. Moving the `aws.auth#sigv4` authentication scheme before the `aws.auth#sigv4a` authentication scheme may not be backward compatible if the signing scope was narrowed (typically from `*`). | SigV4Migration
diff --git a/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/diffs/sigv4a-after-sigv4-migration.a.smithy b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/diffs/sigv4a-after-sigv4-migration.a.smithy
new file mode 100644
index 000000000..84b82e83d
--- /dev/null
+++ b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/diffs/sigv4a-after-sigv4-migration.a.smithy
@@ -0,0 +1,25 @@
+$version: "2.0"
+
+metadata suppressions = [
+    {
+        id: "ModifiedTrait.Update.smithy.api#auth"
+        namespace: "ns.foo"
+    }
+]
+
+namespace ns.foo
+
+use aws.auth#sigv4
+use aws.auth#sigv4a
+
+@auth([sigv4])
+@sigv4(name: "service")
+@sigv4a(name: "service")
+service Service {
+    operations: [
+        Operation
+    ]
+}
+
+@auth([sigv4])
+operation Operation {}
diff --git a/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/diffs/sigv4a-after-sigv4-migration.b.smithy b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/diffs/sigv4a-after-sigv4-migration.b.smithy
new file mode 100644
index 000000000..d6ad5d01f
--- /dev/null
+++ b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/diffs/sigv4a-after-sigv4-migration.b.smithy
@@ -0,0 +1,25 @@
+$version: "2.0"
+
+metadata suppressions = [
+    {
+        id: "ModifiedTrait.Update.smithy.api#auth"
+        namespace: "ns.foo"
+    }
+]
+
+namespace ns.foo
+
+use aws.auth#sigv4
+use aws.auth#sigv4a
+
+@auth([sigv4, sigv4a])
+@sigv4(name: "service")
+@sigv4a(name: "service")
+service Service {
+    operations: [
+        Operation
+    ]
+}
+
+@auth([sigv4, sigv4a])
+operation Operation {}
diff --git a/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/diffs/sigv4a-after-sigv4-migration.events b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/diffs/sigv4a-after-sigv4-migration.events
new file mode 100644
index 000000000..e69de29bb
diff --git a/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/diffs/sigv4a-before-sigv4-migration.a.smithy b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/diffs/sigv4a-before-sigv4-migration.a.smithy
new file mode 100644
index 000000000..84b82e83d
--- /dev/null
+++ b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/diffs/sigv4a-before-sigv4-migration.a.smithy
@@ -0,0 +1,25 @@
+$version: "2.0"
+
+metadata suppressions = [
+    {
+        id: "ModifiedTrait.Update.smithy.api#auth"
+        namespace: "ns.foo"
+    }
+]
+
+namespace ns.foo
+
+use aws.auth#sigv4
+use aws.auth#sigv4a
+
+@auth([sigv4])
+@sigv4(name: "service")
+@sigv4a(name: "service")
+service Service {
+    operations: [
+        Operation
+    ]
+}
+
+@auth([sigv4])
+operation Operation {}
diff --git a/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/diffs/sigv4a-before-sigv4-migration.b.smithy b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/diffs/sigv4a-before-sigv4-migration.b.smithy
new file mode 100644
index 000000000..028d92375
--- /dev/null
+++ b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/diffs/sigv4a-before-sigv4-migration.b.smithy
@@ -0,0 +1,25 @@
+$version: "2.0"
+
+metadata suppressions = [
+    {
+        id: "ModifiedTrait.Update.smithy.api#auth"
+        namespace: "ns.foo"
+    }
+]
+
+namespace ns.foo
+
+use aws.auth#sigv4
+use aws.auth#sigv4a
+
+@auth([sigv4a, sigv4])
+@sigv4(name: "service")
+@sigv4a(name: "service")
+service Service {
+    operations: [
+        Operation
+    ]
+}
+
+@auth([sigv4a, sigv4])
+operation Operation {}
diff --git a/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/diffs/sigv4a-before-sigv4-migration.events b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/diffs/sigv4a-before-sigv4-migration.events
new file mode 100644
index 000000000..dab1a6130
--- /dev/null
+++ b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/diffs/sigv4a-before-sigv4-migration.events
@@ -0,0 +1,3 @@
+[DANGER] ns.foo#Service: The `aws.auth#sigv4a` authentication scheme was added before the `aws.auth#sigv4` authentication scheme in the effective auth schemes for `ns.foo#Service`. Adding the `aws.auth#sigv4a` authentication scheme before an existing `aws.auth#sigv4` authentication scheme is not backward compatible since not all credentials usable by `aws.auth#sigv4` are compatible with `aws.auth#sigv4a`, and can break existing clients' authentication. | SigV4Migration
+-----
+[DANGER] ns.foo#Operation: The `aws.auth#sigv4a` authentication scheme was added before the `aws.auth#sigv4` authentication scheme in the effective auth schemes for `ns.foo#Operation`. Adding the `aws.auth#sigv4a` authentication scheme before an existing `aws.auth#sigv4` authentication scheme is not backward compatible since not all credentials usable by `aws.auth#sigv4` are compatible with `aws.auth#sigv4a`, and can break existing clients' authentication. | SigV4Migration
diff --git a/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/diffs/sigv4a-replace-sigv4-migration.a.smithy b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/diffs/sigv4a-replace-sigv4-migration.a.smithy
new file mode 100644
index 000000000..84b82e83d
--- /dev/null
+++ b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/diffs/sigv4a-replace-sigv4-migration.a.smithy
@@ -0,0 +1,25 @@
+$version: "2.0"
+
+metadata suppressions = [
+    {
+        id: "ModifiedTrait.Update.smithy.api#auth"
+        namespace: "ns.foo"
+    }
+]
+
+namespace ns.foo
+
+use aws.auth#sigv4
+use aws.auth#sigv4a
+
+@auth([sigv4])
+@sigv4(name: "service")
+@sigv4a(name: "service")
+service Service {
+    operations: [
+        Operation
+    ]
+}
+
+@auth([sigv4])
+operation Operation {}
diff --git a/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/diffs/sigv4a-replace-sigv4-migration.b.smithy b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/diffs/sigv4a-replace-sigv4-migration.b.smithy
new file mode 100644
index 000000000..8514baee5
--- /dev/null
+++ b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/diffs/sigv4a-replace-sigv4-migration.b.smithy
@@ -0,0 +1,25 @@
+$version: "2.0"
+
+metadata suppressions = [
+    {
+        id: "ModifiedTrait.Update.smithy.api#auth"
+        namespace: "ns.foo"
+    }
+]
+
+namespace ns.foo
+
+use aws.auth#sigv4
+use aws.auth#sigv4a
+
+@auth([sigv4a])
+@sigv4(name: "service")
+@sigv4a(name: "service")
+service Service {
+    operations: [
+        Operation
+    ]
+}
+
+@auth([sigv4a])
+operation Operation {}
diff --git a/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/diffs/sigv4a-replace-sigv4-migration.events b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/diffs/sigv4a-replace-sigv4-migration.events
new file mode 100644
index 000000000..9b81ece18
--- /dev/null
+++ b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/diffs/sigv4a-replace-sigv4-migration.events
@@ -0,0 +1,3 @@
+[DANGER] ns.foo#Service: The `aws.auth#sigv4` authentication scheme was replaced by the `aws.auth#sigv4a` authentication scheme in the effective auth schemes for `ns.foo#Service`. Replacing the `aws.auth#sigv4` authentication scheme with the `aws.auth#sigv4a` authentication scheme directly is not backward compatible since not all credentials usable by `aws.auth#sigv4` are compatible with `aws.auth#sigv4a`, and can break existing clients' authentication. | SigV4Migration
+-----
+[DANGER] ns.foo#Operation: The `aws.auth#sigv4` authentication scheme was replaced by the `aws.auth#sigv4a` authentication scheme in the effective auth schemes for `ns.foo#Operation`. Replacing the `aws.auth#sigv4` authentication scheme with the `aws.auth#sigv4a` authentication scheme directly is not backward compatible since not all credentials usable by `aws.auth#sigv4` are compatible with `aws.auth#sigv4a`, and can break existing clients' authentication. | SigV4Migration
diff --git a/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/diffs/with-sigv4-no-migration.a.smithy b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/diffs/with-sigv4-no-migration.a.smithy
new file mode 100644
index 000000000..df9bb9120
--- /dev/null
+++ b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/diffs/with-sigv4-no-migration.a.smithy
@@ -0,0 +1,24 @@
+$version: "2.0"
+
+metadata suppressions = [
+    {
+        id: "ModifiedTrait.Update.smithy.api#auth"
+        namespace: "ns.foo"
+    }
+]
+
+namespace ns.foo
+
+use aws.auth#sigv4
+
+@auth([sigv4])
+@httpBearerAuth
+@sigv4(name: "service")
+service Service {
+    operations: [
+        Operation
+    ]
+}
+
+@auth([sigv4])
+operation Operation {}
diff --git a/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/diffs/with-sigv4-no-migration.b.smithy b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/diffs/with-sigv4-no-migration.b.smithy
new file mode 100644
index 000000000..31d687fc9
--- /dev/null
+++ b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/diffs/with-sigv4-no-migration.b.smithy
@@ -0,0 +1,24 @@
+$version: "2.0"
+
+metadata suppressions = [
+    {
+        id: "ModifiedTrait.Update.smithy.api#auth"
+        namespace: "ns.foo"
+    }
+]
+
+namespace ns.foo
+
+use aws.auth#sigv4
+
+@auth([httpBearerAuth, sigv4])
+@httpBearerAuth
+@sigv4(name: "service")
+service Service {
+    operations: [
+        Operation
+    ]
+}
+
+@auth([httpBearerAuth, sigv4])
+operation Operation {}
diff --git a/smithy-rules-engine/src/test/java/software/amazon/smithy/rulesengine/language/EndpointRuleSetTest.java b/smithy-rules-engine/src/test/java/software/amazon/smithy/rulesengine/language/EndpointRuleSetTest.java
index db87a0630..623573d13 100644
--- a/smithy-rules-engine/src/test/java/software/amazon/smithy/rulesengine/language/EndpointRuleSetTest.java
+++ b/smithy-rules-engine/src/test/java/software/amazon/smithy/rulesengine/language/EndpointRuleSetTest.java
@@ -1,8 +1,14 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
 package software.amazon.smithy.rulesengine.language;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 
 import java.util.Collections;
+import java.util.Map;
 import org.junit.jupiter.api.Test;
 import software.amazon.smithy.model.SourceLocation;
 import software.amazon.smithy.model.node.Node;
@@ -10,15 +16,25 @@ import software.amazon.smithy.rulesengine.language.evaluation.RuleEvaluator;
 import software.amazon.smithy.rulesengine.language.evaluation.value.EndpointValue;
 import software.amazon.smithy.rulesengine.language.evaluation.value.Value;
 import software.amazon.smithy.rulesengine.language.syntax.Identifier;
+import software.amazon.smithy.rulesengine.language.syntax.expressions.Reference;
 import software.amazon.smithy.rulesengine.language.syntax.expressions.literal.Literal;
 import software.amazon.smithy.rulesengine.language.syntax.parameters.Parameter;
 import software.amazon.smithy.rulesengine.language.syntax.parameters.ParameterType;
 import software.amazon.smithy.rulesengine.language.syntax.parameters.Parameters;
 import software.amazon.smithy.rulesengine.language.syntax.rule.Rule;
+import software.amazon.smithy.rulesengine.traits.EndpointRuleSetTrait;
 import software.amazon.smithy.utils.IoUtils;
 import software.amazon.smithy.utils.MapUtils;
 
 public class EndpointRuleSetTest {
+    private static final Endpoint MINIMAL_ENDPOINT = Endpoint.builder()
+        .sourceLocation(SourceLocation.none())
+        .url(Literal.of("https://{Region}.amazonaws.com"))
+        .addAuthScheme(Identifier.of("sigv4"), MapUtils.of(
+                Identifier.of("signingRegion"), Literal.of("{Region}"),
+                Identifier.of("signingName"), Literal.of("serviceName")))
+        .build();
+
     @Test
     public void testRuleEval() {
         EndpointRuleSet actual = TestRunnerTest.getMinimalEndpointRuleSet();
@@ -61,13 +77,62 @@ public class EndpointRuleSetTest {
                 .addRule(Rule
                         .builder()
                         .description("base rule")
-                        .endpoint(Endpoint.builder()
-                                .sourceLocation(SourceLocation.none())
-                                .url(Literal.of("https://{Region}.amazonaws.com"))
-                                .addAuthScheme(Identifier.of("sigv4"), MapUtils.of(
-                                        Identifier.of("signingRegion"), Literal.of("{Region}"),
-                                        Identifier.of("signingName"), Literal.of("serviceName")))
-                                .build()))
+                        .endpoint(MINIMAL_ENDPOINT))
                 .build(), actual);
     }
+
+    @Test
+    public void testEndpointCollectorMinimalRuleset() {
+        EndpointRuleSet ers = TestRunnerTest.getMinimalEndpointRuleSet();
+        Map<String, Endpoint> actual = EndpointRuleSet.EndpointPathCollector
+            .from(EndpointRuleSetTrait.builder()
+                .ruleSet(ers.toNode())
+                .build())
+            .collect();
+        Map<String, Endpoint> expected = MapUtils.of("/rules/0/endpoint", MINIMAL_ENDPOINT);
+        assertEquals(expected, actual);
+    }
+
+    @Test
+    public void testEndpointCollectorEmptyRuleset() {
+        EndpointRuleSet ers = EndpointRuleSet.builder()
+            .parameters(Parameters.builder().build())
+            .build();
+        Map<String, Endpoint> actual = EndpointRuleSet.EndpointPathCollector
+            .from(EndpointRuleSetTrait.builder()
+                .ruleSet(ers.toNode())
+                .build())
+            .collect();
+        Map<String, Endpoint> expected = Collections.emptyMap();
+        assertEquals(expected, actual);
+    }
+
+    @Test
+    public void testEndpointCollectorComplexRuleset() {
+        EndpointRuleSet ers = TestRunnerTest.getEndpointRuleSet(
+            EndpointRuleSetTest.class, "example-complex-ruleset.json");
+        Map<String, Endpoint> actual = EndpointRuleSet.EndpointPathCollector
+            .from(EndpointRuleSetTrait.builder()
+                .ruleSet(ers.toNode())
+                .build())
+            .collect();
+        Map<String, Endpoint> expected = MapUtils.of(
+            "/rules/1/rules/0/rules/0/rules/1/endpoint", Endpoint.builder()
+                .url(Literal.of("https://example.{Region}.dual-stack-dns-suffix.com"))
+                .build(),
+            "/rules/1/rules/0/rules/1/rules/0/rules/0/endpoint", Endpoint.builder()
+                .url(Literal.of("https://example-fips.{Region}.dual-stack-dns-suffix.com"))
+                .build(),
+            "/rules/1/rules/0/rules/2/endpoint", Endpoint.builder()
+                .url(Literal.of("https://example.{Region}.dual-stack-dns-suffix.com"))
+                .build(),
+            "/rules/0/rules/1/endpoint", Endpoint.builder()
+                .url(new Reference(Identifier.of("Endpoint"), SourceLocation.NONE))
+                .build(),
+            "/rules/1/rules/0/rules/0/rules/0/rules/0/rules/0/endpoint", Endpoint.builder()
+                .url(Literal.of("https://example-fips.{Region}.dual-stack-dns-suffix.com"))
+                .build()
+        );
+        assertEquals(expected, actual);
+    }
 }
diff --git a/smithy-rules-engine/src/test/java/software/amazon/smithy/rulesengine/language/EndpointTest.java b/smithy-rules-engine/src/test/java/software/amazon/smithy/rulesengine/language/EndpointTest.java
new file mode 100644
index 000000000..24f24d6b4
--- /dev/null
+++ b/smithy-rules-engine/src/test/java/software/amazon/smithy/rulesengine/language/EndpointTest.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package software.amazon.smithy.rulesengine.language;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+import org.junit.jupiter.api.Test;
+import software.amazon.smithy.rulesengine.language.syntax.Identifier;
+import software.amazon.smithy.rulesengine.language.syntax.expressions.literal.Literal;
+import software.amazon.smithy.utils.ListUtils;
+import software.amazon.smithy.utils.MapUtils;
+import software.amazon.smithy.utils.Pair;
+
+public class EndpointTest {
+    private static final Pair<String, Map<Identifier, Literal>> SIGV4_AUTH_SCHEME = Pair.of(
+        "sigv4",
+        Collections.emptyMap());
+    private static final Pair<String, Map<Identifier, Literal>> SIGV4A_AUTH_SCHEME = Pair.of(
+        "sigv4a",
+        MapUtils.of(
+            Identifier.of("signingRegionSet"),
+            Literal.tupleLiteral(ListUtils.of(Literal.of("*")))));
+
+    @Test
+    public void testGetEndpointAuthSchemes() {
+        Endpoint endpoint = Endpoint.builder()
+            .url(Literal.of("https://abc.service.com"))
+            .addAuthScheme(Identifier.of(SIGV4A_AUTH_SCHEME.getLeft()), SIGV4A_AUTH_SCHEME.getRight())
+            .addAuthScheme(Identifier.of(SIGV4_AUTH_SCHEME.getLeft()), SIGV4_AUTH_SCHEME.getRight())
+            .build();
+        List<Map<Identifier, Literal>> actual = endpoint.getEndpointAuthSchemes();
+        List<Map<Identifier, Literal>> expected = ListUtils.of(
+            convertAuthSchemeToMap(SIGV4A_AUTH_SCHEME),
+            convertAuthSchemeToMap(SIGV4_AUTH_SCHEME));
+        assertEquals(expected, actual);
+    }
+
+    @Test
+    public void testEmptyGetEndpointAuthSchemes() {
+        Endpoint endpoint = Endpoint.builder()
+            .url(Literal.of("https://abc.service.com"))
+            .build();
+        List<Map<Identifier, Literal>> actual = endpoint.getEndpointAuthSchemes();
+        List<Map<Identifier, Literal>> expected = Collections.emptyList();
+        assertEquals(expected, actual);
+    }
+
+    private static Map<Identifier, Literal> convertAuthSchemeToMap(Pair<String, Map<Identifier, Literal>> authScheme) {
+        Map<Identifier, Literal> base = new TreeMap<>(Comparator.comparing(Identifier::toString));
+        base.put(Identifier.of("name"), Literal.of(authScheme.getLeft()));
+        base.putAll(authScheme.getRight());
+        return base;
+    }
+}
diff --git a/smithy-rules-engine/src/test/java/software/amazon/smithy/rulesengine/language/TestRunnerTest.java b/smithy-rules-engine/src/test/java/software/amazon/smithy/rulesengine/language/TestRunnerTest.java
index a216a1fb1..553f9c62b 100644
--- a/smithy-rules-engine/src/test/java/software/amazon/smithy/rulesengine/language/TestRunnerTest.java
+++ b/smithy-rules-engine/src/test/java/software/amazon/smithy/rulesengine/language/TestRunnerTest.java
@@ -15,8 +15,11 @@ public class TestRunnerTest {
     }
 
     public static EndpointRuleSet getMinimalEndpointRuleSet() {
-        return EndpointRuleSet.fromNode(Node.parse(IoUtils.readUtf8Resource(
-                TestRunnerTest.class, "minimal-ruleset.json")));
+        return getEndpointRuleSet(TestRunnerTest.class, "minimal-ruleset.json");
+    }
+
+    public static EndpointRuleSet getEndpointRuleSet(Class klass, String file) {
+        return EndpointRuleSet.fromNode(Node.parseJsonWithComments(IoUtils.readUtf8Resource(klass, file)));
     }
 
     @ParameterizedTest(name = "{0}")
diff --git a/smithy-rules-engine/src/test/resources/software/amazon/smithy/rulesengine/language/example-complex-ruleset.json b/smithy-rules-engine/src/test/resources/software/amazon/smithy/rulesengine/language/example-complex-ruleset.json
new file mode 100644
index 000000000..c08bdb314
--- /dev/null
+++ b/smithy-rules-engine/src/test/resources/software/amazon/smithy/rulesengine/language/example-complex-ruleset.json
@@ -0,0 +1,243 @@
+// Modified AWS service ruleset (may not actually resolve any endpoints correctly)
+{
+    "version": "1.0",
+    "parameters": {
+        "Region": {
+            "required": false,
+            "type": "String"
+        },
+        "UseFIPS": {
+            "required": true,
+            "type": "Boolean"
+        },
+        "Endpoint": {
+            "builtIn": "SDK::Endpoint",
+            "required": false,
+            "documentation": "Override the endpoint used to send this request",
+            "type": "String"
+        }
+    },
+    "rules": [
+        {
+            "conditions": [
+                {
+                    "fn": "isSet",
+                    "argv": [
+                        {
+                            "ref": "Endpoint"
+                        }
+                    ]
+                }
+            ],
+            "rules": [
+                {
+                    "conditions": [
+                        {
+                            "fn": "booleanEquals",
+                            "argv": [
+                                {
+                                    "ref": "UseFIPS"
+                                },
+                                true
+                            ]
+                        }
+                    ],
+                    "error": "Invalid Configuration: FIPS and custom endpoint are not supported",
+                    "type": "error"
+                },
+                {
+                    "conditions": [],
+                    "endpoint": {
+                        "url": {
+                            "ref": "Endpoint"
+                        },
+                        "properties": {},
+                        "headers": {}
+                    },
+                    "type": "endpoint"
+                }
+            ],
+            "type": "tree"
+        },
+        {
+            "conditions": [
+                {
+                    "fn": "isSet",
+                    "argv": [
+                        {
+                            "ref": "Region"
+                        }
+                    ]
+                }
+            ],
+            "rules": [
+                {
+                    "conditions": [
+                        {
+                            "fn": "parseURL",
+                            "argv": [
+                                {
+                                    "ref": "Region"
+                                }
+                            ],
+                            "assign": "PartitionResult"
+                        }
+                    ],
+                    "rules": [
+                        {
+                            "conditions": [
+                                {
+                                    "fn": "booleanEquals",
+                                    "argv": [
+                                        true,
+                                        {
+                                            "fn": "getAttr",
+                                            "argv": [
+                                                {
+                                                    "ref": "PartitionResult"
+                                                },
+                                                "isIp"
+                                            ]
+                                        }
+                                    ]
+                                }
+                            ],
+                            "rules": [
+                                {
+                                    "conditions": [
+                                        {
+                                            "fn": "booleanEquals",
+                                            "argv": [
+                                                {
+                                                    "ref": "UseFIPS"
+                                                },
+                                                true
+                                            ]
+                                        }
+                                    ],
+                                    "rules": [
+                                        {
+                                            "conditions": [
+                                                {
+                                                    "fn": "booleanEquals",
+                                                    "argv": [
+                                                        {
+                                                            "fn": "getAttr",
+                                                            "argv": [
+                                                                {
+                                                                    "ref": "PartitionResult"
+                                                                },
+                                                                "isIp"
+                                                            ]
+                                                        },
+                                                        true
+                                                    ]
+                                                }
+                                            ],
+                                            "rules": [
+                                                {
+                                                    "conditions": [],
+                                                    "endpoint": {
+                                                        "url": "https://example-fips.{Region}.dual-stack-dns-suffix.com",
+                                                        "properties": {},
+                                                        "headers": {}
+                                                    },
+                                                    "type": "endpoint"
+                                                }
+                                            ],
+                                            "type": "tree"
+                                        },
+                                        {
+                                            "conditions": [],
+                                            "error": "FIPS is enabled but this partition does not support FIPS",
+                                            "type": "error"
+                                        }
+                                    ],
+                                    "type": "tree"
+                                },
+                                {
+                                    "conditions": [],
+                                    "endpoint": {
+                                        "url": "https://example.{Region}.dual-stack-dns-suffix.com",
+                                        "properties": {},
+                                        "headers": {}
+                                    },
+                                    "type": "endpoint"
+                                }
+                            ],
+                            "type": "tree"
+                        },
+                        {
+                            "conditions": [
+                                {
+                                    "fn": "booleanEquals",
+                                    "argv": [
+                                        {
+                                            "ref": "UseFIPS"
+                                        },
+                                        true
+                                    ]
+                                }
+                            ],
+                            "rules": [
+                                {
+                                    "conditions": [
+                                        {
+                                            "fn": "booleanEquals",
+                                            "argv": [
+                                                {
+                                                    "fn": "getAttr",
+                                                    "argv": [
+                                                        {
+                                                            "ref": "PartitionResult"
+                                                        },
+                                                        "isIp"
+                                                    ]
+                                                },
+                                                true
+                                            ]
+                                        }
+                                    ],
+                                    "rules": [
+                                        {
+                                            "conditions": [],
+                                            "endpoint": {
+                                                "url": "https://example-fips.{Region}.dual-stack-dns-suffix.com",
+                                                "properties": {},
+                                                "headers": {}
+                                            },
+                                            "type": "endpoint"
+                                        }
+                                    ],
+                                    "type": "tree"
+                                },
+                                {
+                                    "conditions": [],
+                                    "error": "FIPS is enabled but this partition does not support FIPS",
+                                    "type": "error"
+                                }
+                            ],
+                            "type": "tree"
+                        },
+                        {
+                            "conditions": [],
+                            "endpoint": {
+                                "url": "https://example.{Region}.dual-stack-dns-suffix.com",
+                                "properties": {},
+                                "headers": {}
+                            },
+                            "type": "endpoint"
+                        }
+                    ],
+                    "type": "tree"
+                }
+            ],
+            "type": "tree"
+        },
+        {
+            "conditions": [],
+            "error": "Invalid Configuration: Missing Region",
+            "type": "error"
+        }
+    ]
+}