Repository: smithy-lang/smithy
Pull request #2072 (2023-12-12 22:37:56)

Change line break formatting in brackets
This commit updates how the formatter decides whether or not to put a line break after an opening bracket and before the closing bracket (bracket meaning `()`, `{}`, and `[]`). Previously, all bracket types followed the same logic - which included breaking when there are any array or object descendants. This lead to trait bodies being formatted like:
```
@someTrait(
    [
        {
	    key: "value"
	}
    ]
)
structure SomeStruct {}
```
With this change, it will be formatted like:
```
@someTrait([
    {
        key: "value"
    }
])
structure SomeStruct {}
```
the same also applies if the trait has a single object member:
```
@someTrait({
    foo: "bar"
    // ... more members to cause break onto separate lines
})
structure SomeStruct {}
```
Object nodes now also have a single space of padding inside the brackets when on a single line. Previously, it was like:
```
metadata foo = {bar: "baz"}
```
now:
```
metadata foo = { bar: "baz" }
```
Finally, when object nodes are elements of an array, they now always line break if not empty. Previously:
```
metadata foo = [
    {bar: "baz"}
]
```
now (with above padding changes):
```
metadata foo = [
    {
        bar: "baz"
    }
]
```
A few test cases were updated for this behavior, and a few more added to test the nuances of this formatting method.

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/CHANGELOG.md b/CHANGELOG.md
index c0c9c79d6..a3985fff9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,34 @@
 # Smithy Changelog
 
+## 1.43.0 (2024-01-05)
+
+### Features
+
+* Updated `RemovedShape` diff event severity from `ERROR` to `WARNING` for scalar shapes. ([#2037](https://github.com/smithy-lang/smithy/pull/2037))
+* Made `parameterizedTestSource` public, allowing users to use a customized suite as a source for JUnit parameterized tests. ([#2087](https://github.com/smithy-lang/smithy/pull/2087))
+* Refactored `ReplaceShapes` transform to improve efficiency. ([#2082](https://github.com/smithy-lang/smithy/pull/2082))
+* Added validation for endpoint patterns used by `standardRegionalEndpoints` and `standardPartitionalEndpoints`. ([#2069](https://github.com/smithy-lang/smithy/pull/2069))
+* Added support for CLI dependency resolution via proxy. ([#2076](https://github.com/smithy-lang/smithy/pull/2076))
+* Improved efficiency of `ReplaceShapes` transform by only building container shapes once when multiple members are changed. ([#2081](https://github.com/smithy-lang/smithy/pull/2081))
+* Moved `allowOptionalNull` to `NodeValidationVisitor.Feature`. ([#2080](https://github.com/smithy-lang/smithy/pull/2080))
+* Added rules engine built-in for `AccountIdEndpointMode`. ([#2065](https://github.com/smithy-lang/smithy/pull/2065))
+* Added [JReleaser](https://jreleaser.org/) config. ([#2059](https://github.com/smithy-lang/smithy/pull/2059))
+* Added ability to find all operations for which a shape is used as an input, output, or error. ([#2064](https://github.com/smithy-lang/smithy/pull/2064))
+* Split InputOutput shapes into separate request and response shapes for `restXml` protocol tests. ([#2063](https://github.com/smithy-lang/smithy/pull/2063))
+
+### Bug Fixes
+
+* Fixed an issue where `@iamAction` wasn't reflected in CFN resource schema creation. ([#2091](https://github.com/smithy-lang/smithy/pull/2091)) 
+* Fixed tree node start and end locations. ([#2084](https://github.com/smithy-lang/smithy/pull/2084))
+* Fixed several minor build warnings. ([2089](https://github.com/smithy-lang/smithy/pull/2089))
+* Fixed protocol test service signing name for `awsJson1_1` protocol. ([#2089](https://github.com/smithy-lang/smithy/pull/2089))
+* Updated member removal for `ReplaceShapes` transform to ensure enum and intEnum members are correctly removed. ([#2082](https://github.com/smithy-lang/smithy/pull/2082))
+* Corrected erroneous outer tags in `restXml` protocol tests ([#2071](https://github.com/smithy-lang/smithy/pull/2071))
+
+### Documentation
+
+*  Added documentation for configuring CLI dependency resolution via proxy. ([#2083](https://github.com/smithy-lang/smithy/pull/2083))
+
 ## 1.42.0 (2023-12-07)
 
 ### Features
diff --git a/VERSION b/VERSION
index a50908ca3..b978278f0 100644
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-1.42.0
+1.43.0
diff --git a/docs/requirements.txt b/docs/requirements.txt
index 998c28219..9e7b654c0 100644
--- a/docs/requirements.txt
+++ b/docs/requirements.txt
@@ -1,4 +1,4 @@
-sphinx>=4.2.0,<4.3.0
+sphinx>=5.0.0
 pygments==2.15.0
 furo==2022.6.21
 
@@ -7,6 +7,6 @@ sphinx-inline-tabs==2022.1.2b11
 Sphinx-Substitution-Extensions==2022.2.16
 
 # Use by old docs.
-sphinx-tabs==3.2.0
+sphinx-tabs>=3.4.4
 
 sphinx_copybutton==0.5.0
diff --git a/docs/setup.py b/docs/setup.py
index 517c9c956..0c391b2ab 100644
--- a/docs/setup.py
+++ b/docs/setup.py
@@ -3,13 +3,13 @@
 from setuptools import setup, find_packages
 
 requires = [
-    "sphinx==4.2.0",
+    "sphinx>=5.0.0",
     "pygments==2.15.0",
     "sphinx_copybutton==0.5.0",
     # Used by new docs.
     "sphinx-inline-tabs==2022.1.2b11",
     # Used by old docs.
-    "sphinx-tabs==3.2.0"
+    "sphinx-tabs>=3.4.4"
 ]
 
 # Register the custom Smithy loader with Pygments.
diff --git a/docs/source-2.0/aws/aws-iam.rst b/docs/source-2.0/aws/aws-iam.rst
index ee39923db..91377a1fe 100644
--- a/docs/source-2.0/aws/aws-iam.rst
+++ b/docs/source-2.0/aws/aws-iam.rst
@@ -18,12 +18,17 @@ Smithy are automatically inferred. These can be disabled or augmented. For
 more information, see :ref:`deriving-condition-keys`.
 
 
+.. _aws-iam_traits-principal:
+
+----------------
+Principal traits
+----------------
+
 .. smithy-trait:: aws.iam#supportedPrincipalTypes
 .. _aws.iam#supportedPrincipalTypes-trait:
 
------------------------------------------
 ``aws.iam#supportedPrincipalTypes`` trait
------------------------------------------
+=========================================
 
 Summary
     The `IAM principal types`_ that can use the service or operation.
@@ -64,68 +69,6 @@ The following example defines two operations:
     operation OperationB {}
 
 
-.. _aws-iam_traits-resources:
-
----------------
-Resource traits
----------------
-
-.. smithy-trait:: aws.iam#iamResource
-.. _aws.iam#iamResource-trait:
-
-``aws.iam#iamResource`` trait
-=============================
-
-Summary
-    Indicates properties of a Smithy resource in AWS IAM.
-Trait selector
-    ``resource``
-Value type
-    ``structure``
-
-The ``aws.iam#iamResource`` trait is a structure that supports the following
-members:
-
-.. list-table::
-    :header-rows: 1
-    :widths:  10 20 70
-
-    * - Property
-      - Type
-      - Description
-    * - name
-      - ``string``
-      - The name of the resource in AWS IAM.
-    * - relativeDocumentation
-      - ``string``
-      - A relative URL path that defines more information about the resource
-        within a set of IAM-related documentation.
-    * - disableConditionKeyInheritance
-      - ``boolean``
-      - When set to ``true``, decouples this IAM resource's condition keys from
-        those of its parent resource(s). This can be used in combination with
-        the :ref:`aws.iam#conditionKeys-trait` trait to isolate a resource's
-        condition keys from those of its parent(s).
-
-The following example defines a simple resource with a name in AWS IAM that
-deviates from the :ref:`shape name of the shape ID <shape-id>` of the resource.
-
-.. code-block:: smithy
-
-    $version: "2"
-
-    namespace smithy.example
-
-    use aws.iam#iamResource
-
-    @iamResource(name: "super")
-    resource SuperResource {
-        identifiers: {
-            superId: String,
-        }
-    }
-
-
 .. _aws-iam_traits-actions:
 
 -------------
@@ -246,11 +189,16 @@ contains the following properties:
       - The condition keys used for authorizing against this resource.
 
 
+.. _aws-iam_traits-actions-deprecated-traits:
+
+Deprecated action traits
+========================
+
 .. smithy-trait:: aws.iam#actionName
 .. _aws.iam#actionName-trait:
 
 ``aws.iam#actionName`` trait
-============================
+----------------------------
 
 .. danger::
     This trait is deprecated. The ``name`` property of the
@@ -295,7 +243,7 @@ The following example defines two operations:
 .. _aws.iam#actionPermissionDescription-trait:
 
 ``aws.iam#actionPermissionDescription`` trait
-=============================================
+---------------------------------------------
 
 .. danger::
     This trait is deprecated. The ``documentation`` property of the
@@ -325,7 +273,7 @@ Value type
 .. _aws.iam#requiredActions-trait:
 
 ``aws.iam#requiredActions`` trait
-=================================
+---------------------------------
 
 .. danger::
     This trait is deprecated. The ``requiredActions`` property of the
@@ -369,6 +317,68 @@ operation for it to complete successfully.
     @requiredActions(["otherservice:OtherOperation"])
     operation MyOperation {}
 
+.. _aws-iam_traits-resources:
+
+---------------
+Resource Traits
+---------------
+
+.. smithy-trait:: aws.iam#iamResource
+.. _aws.iam#iamResource-trait:
+
+``aws.iam#iamResource`` trait
+=============================
+
+Summary
+    Indicates properties of a Smithy resource in AWS IAM.
+Trait selector
+    ``resource``
+Value type
+    ``structure``
+
+The ``aws.iam#iamResource`` trait is a structure that supports the following
+members:
+
+.. list-table::
+    :header-rows: 1
+    :widths:  10 20 70
+
+    * - Property
+      - Type
+      - Description
+    * - name
+      - ``string``
+      - The name of the resource in AWS IAM.
+    * - relativeDocumentation
+      - ``string``
+      - A relative URL path that defines more information about the resource
+        within a set of IAM-related documentation.
+    * - disableConditionKeyInheritance
+      - ``boolean``
+      - When set to ``true``, decouples this IAM resource's condition keys from
+        those of its parent resource(s). This can be used in combination with
+        the :ref:`aws.iam#conditionKeys-trait` trait to isolate a resource's
+        condition keys from those of its parent(s).
+
+The following example defines a simple resource with a name in AWS IAM that
+deviates from the :ref:`shape name of the shape ID <shape-id>` of the resource.
+
+.. code-block:: smithy
+
+    $version: "2"
+
+    namespace smithy.example
+
+    use aws.iam#iamResource
+
+    @iamResource(name: "super")
+    resource SuperResource {
+        identifiers: {
+            superId: String,
+        }
+    }
+
+
 
 .. _aws-iam_traits-condition-keys:
 
@@ -384,7 +394,8 @@ Condition key traits
 
 Summary
     Defines the set of condition keys that appear within a service in
-    addition to inferred and global condition keys.
+    addition to :ref:`inferred <deriving-condition-keys>` and global condition
+    keys.
 Trait selector
     ``service``
 Value type
@@ -433,7 +444,7 @@ Each condition key structure supports the following members:
 
     @service(sdkId: "My Value", arnNamespace: "myservice")
     @defineConditionKeys(
-        "otherservice:Bar": {
+        "myservice:Bar": {
             type: "String"
             documentation: "The Bar string"
             externalDocumentation: "http://example.com"
@@ -449,49 +460,6 @@ Each condition key structure supports the following members:
     defined on the service.
 
 
-.. _condition-key-types:
-
-Condition Key Types
--------------------
-
-The following table describes the available types a condition key can have.
-Condition keys in IAM policies can be evaluated with `condition operators`_.
-
-.. list-table::
-    :header-rows: 1
-    :widths: 20 80
-
-    * - Type
-      - Description
-    * - ``ARN``
-      - A String type that contains an `Amazon Resource Name (ARN)`_.
-    * - ``Binary``
-      - A String type that contains base-64 encoded binary data.
-    * - ``Bool``
-      - A general boolean type.
-    * - ``Date``
-      - A String type that conforms to the ``datetime`` profile of `ISO 8601`_.
-    * - ``IPAddress``
-      - A String type that conforms to :rfc:`4632`.
-    * - ``Numeric``
-      - A general type for integers and floats.
-    * - ``String``
-      - A general string type.
-    * - ``ArrayOfARN``
-      - An unordered list of ARN types.
-    * - ``ArrayOfBinary``
-      - An unordered list of Binary types.
-    * - ``ArrayOfBool``
-      - An unordered list of Bool types.
-    * - ``ArrayOfDate``
-      - An unordered list of Date types.
-    * - ``ArrayOfIPAddress``
-      - An unordered list of IPAddress types.
-    * - ``ArrayOfNumeric``
-      - An unordered list of Numeric types.
-    * - ``ArrayOfString``
-      - An unordered list of String types.
-
 
 .. smithy-trait:: aws.iam#conditionKeys
 .. _aws.iam#conditionKeys-trait:
@@ -507,11 +475,11 @@ Value type
     ``list<string>``
 
 Condition keys derived automatically can be applied to a resource or operation
-explicitly. Condition keys applied this way MUST be either inferred or
-explicitly defined via the :ref:`aws.iam#defineConditionKeys-trait` trait.
+explicitly. Condition keys applied this way MUST be either :ref:`inferred <deriving-condition-keys>`
+or explicitly defined via the :ref:`aws.iam#defineConditionKeys-trait` trait.
 
 The following example's ``MyResource`` resource has the
-``myservice:MyResourceFoo`` and  ``otherservice:Bar`` condition keys. The
+``myservice:MyResourceFoo`` and  ``myservice:Bar`` condition keys. The
 ``MyOperation`` operation has the ``aws:region`` condition key.
 
 .. code-block:: smithy
@@ -525,13 +493,13 @@ The following example's ``MyResource`` resource has the
     use aws.iam#conditionKeys
 
     @service(sdkId: "My Value", arnNamespace: "myservice")
-    @defineConditionKeys("otherservice:Bar": { type: "String" })
+    @defineConditionKeys("myservice:Bar": { type: "String" })
     service MyService {
         version: "2017-02-11"
         resources: [MyResource]
     }
 
-    @conditionKeys(["otherservice:Bar"])
+    @conditionKeys(["myservice:Bar"])
     resource MyResource {
         identifiers: {foo: String}
         operations: [MyOperation]
@@ -546,85 +514,6 @@ The following example's ``MyResource`` resource has the
     without being defined on the service.
 
 
-.. smithy-trait:: aws.iam#disableConditionKeyInference
-.. _aws.iam#disableConditionKeyInference-trait:
-
-``aws.iam#disableConditionKeyInference`` trait
-==============================================
-
-Summary
-    Declares that the condition keys of a resource should not be inferred.
-Trait selector
-    ``:test(service, resource)``
-Value type
-    Annotation trait
-
-When a service is marked with the ``aws.iam#disableConditionKeyInference``
-trait, all the resources bound to the service will not have condition
-keys automatically inferred from its identifiers and the identifiers
-of its ancestors.
-
-The following example shows resources ``MyResource1`` and ``MyResource2``
-have had condition key inference disabled because they are bound to a
-service marked with ``aws.iam#disableConditionKeyInference`` trait.
-
-.. code-block:: smithy
-
-    $version: "2"
-
-    namespace smithy.example
-
-    use aws.api#service
-    use aws.iam#disableConditionKeyInference
-
-    @service(sdkId: "My Value", arnNamespace: "myservice")
-    @disableConditionKeyInference
-    service MyService {
-        version: "2017-02-11"
-        resources: [MyResource1, MyResource2]
-    }
-
-    resource MyResource1 {
-        identifiers: {
-            foo: String
-        }
-    }
-
-    resource MyResource2 {
-        identifiers: {
-            foo: String
-        }
-    }
-
-A resource marked with the ``aws.iam#disableConditionKeyInference`` trait will
-not have its condition keys automatically inferred from its identifiers and
-the identifiers of its ancestors (if present.)
-
-The following example shows a resource, ``MyResource``, that has had its
-condition key inference disabled.
-
-.. code-block:: smithy
-
-    $version: "2"
-
-    namespace smithy.example
-
-    use aws.api#service
-    use aws.iam#disableConditionKeyInference
-
-    @service(sdkId: "My Value", arnNamespace: "myservice")
-    service MyService {
-        version: "2017-02-11"
-        resources: [MyResource]
-    }
-
-    @disableConditionKeyInference
-    resource MyResource {
-        identifiers: {
-            foo: String
-            bar: String
-        }
-    }
 
 .. smithy-trait:: aws.iam#serviceResolvedConditionKeys
 .. _aws.iam#serviceResolvedConditionKeys-trait:
@@ -642,8 +531,8 @@ Value type
 
 All condition keys defined with the ``serviceResolvedConditionKeys`` trait
 MUST also be defined via the :ref:`aws.iam#defineConditionKeys-trait` trait.
-Derived resource condition keys MUST NOT be included
-with the ``serviceResolvedConditionKeys`` trait.
+:ref:`Inferred resource condition keys <deriving-condition-keys>` MUST NOT be
+included with the ``serviceResolvedConditionKeys`` trait.
 
 The following example defines two service-specific condition keys:
 
@@ -715,9 +604,134 @@ explicitly binds ``ActionContextKey1`` to the field ``key``.
     }
 
 
+.. smithy-trait:: aws.iam#disableConditionKeyInference
+.. _aws.iam#disableConditionKeyInference-trait:
+
+``aws.iam#disableConditionKeyInference`` trait
+==============================================
+
+Summary
+    Declares that the condition keys of a resource should not be
+    :ref:`inferred <deriving-condition-keys>`.
+Trait selector
+    ``:test(service, resource)``
+Value type
+    Annotation trait
+
+When a service is marked with the ``aws.iam#disableConditionKeyInference``
+trait, all the resources bound to the service will not have condition
+keys automatically inferred from its identifiers and the identifiers
+of its ancestors.
+
+The following example shows resources ``MyResource1`` and ``MyResource2``
+have had condition key inference disabled because they are bound to a
+service marked with ``aws.iam#disableConditionKeyInference`` trait.
+
+.. code-block:: smithy
+
+    $version: "2"
+
+    namespace smithy.example
+
+    use aws.api#service
+    use aws.iam#disableConditionKeyInference
+
+    @service(sdkId: "My Value", arnNamespace: "myservice")
+    @disableConditionKeyInference
+    service MyService {
+        version: "2017-02-11"
+        resources: [MyResource1, MyResource2]
+    }
+
+    resource MyResource1 {
+        identifiers: {
+            foo: String
+        }
+    }
+
+    resource MyResource2 {
+        identifiers: {
+            foo: String
+        }
+    }
+
+A resource marked with the ``aws.iam#disableConditionKeyInference`` trait will
+not have its condition keys automatically inferred from its identifiers and
+the identifiers of its ancestors. if present.
+
+The following example shows a resource, ``MyResource``, that has condition key
+inference disabled.
+
+.. code-block:: smithy
+
+    $version: "2"
+
+    namespace smithy.example
+
+    use aws.api#service
+    use aws.iam#disableConditionKeyInference
+
+    @service(sdkId: "My Value", arnNamespace: "myservice")
+    service MyService {
+        version: "2017-02-11"
+        resources: [MyResource]
+    }
+
+    @disableConditionKeyInference
+    resource MyResource {
+        identifiers: {
+            foo: String
+            bar: String
+        }
+    }
+
+
+.. _condition-key-types:
+
+Condition Key Types
+=======================
+
+The following table describes the available types a condition key can have.
+Condition keys in IAM policies can be evaluated with `condition operators`_.
+
+.. list-table::
+    :header-rows: 1
+    :widths: 20 80
+
+    * - Type
+      - Description
+    * - ``ARN``
+      - A String type that contains an `Amazon Resource Name (ARN)`_.
+    * - ``Binary``
+      - A String type that contains base-64 encoded binary data.
+    * - ``Bool``
+      - A general boolean type.
+    * - ``Date``
+      - A String type that conforms to the ``datetime`` profile of `ISO 8601`_.
+    * - ``IPAddress``
+      - A String type that conforms to :rfc:`4632`.
+    * - ``Numeric``
+      - A general type for integers and floats.
+    * - ``String``
+      - A general string type.
+    * - ``ArrayOfARN``
+      - An unordered list of ARN types.
+    * - ``ArrayOfBinary``
+      - An unordered list of Binary types.
+    * - ``ArrayOfBool``
+      - An unordered list of Bool types.
+    * - ``ArrayOfDate``
+      - An unordered list of Date types.
+    * - ``ArrayOfIPAddress``
+      - An unordered list of IPAddress types.
+    * - ``ArrayOfNumeric``
+      - An unordered list of Numeric types.
+    * - ``ArrayOfString``
+      - An unordered list of String types.
+
 .. _deriving-condition-keys:
 
-Deriving Condition Keys
+Deriving condition keys
 =======================
 
 Smithy will automatically derive condition key information for a service, as
@@ -747,13 +761,13 @@ Given the following model,
     use aws.iam#iamResource
 
     @service(sdkId: "My Value", arnNamespace: "myservice")
-    @defineConditionKeys("otherservice:Bar": { type: "String" })
+    @defineConditionKeys("myservice:Bar": { type: "String" })
     service MyService {
         version: "2017-02-11"
         resources: [MyResource]
     }
 
-    @conditionKeys(["otherservice:Bar"])
+    @conditionKeys(["myservice:Bar"])
     resource MyResource {
         identifiers: {foo: String}
         operations: [MyOperation]
@@ -792,11 +806,11 @@ The computed condition keys for the service are:
     * - ``MyResource``
       -
           * ``myservice:MyResourceFoo``
-          * ``otherservice:Bar``
+          * ``myservice:Bar``
     * - ``InnerResource``
       -
           * ``myservice:MyResourceFoo``
-          * ``otherservice:Bar``
+          * ``myservice:Bar``
           * ``myservice:InnerResourceYum``
     * - ``MyDetachedResource``
       - None
@@ -807,7 +821,6 @@ The computed condition keys for the service are:
       -
           * ``aws:region``
 
-
 .. _AWS Identity and Access Management: https://aws.amazon.com/iam/
 .. _Condition keys: https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_condition-keys.html
 .. _Actions: https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_action.html
diff --git a/docs/source-2.0/guides/building-codegen/index.rst b/docs/source-2.0/guides/building-codegen/index.rst
index e65155ee3..e00e93665 100644
--- a/docs/source-2.0/guides/building-codegen/index.rst
+++ b/docs/source-2.0/guides/building-codegen/index.rst
@@ -1,6 +1,6 @@
---------------------------------
-Creating a Smithy Code Generator
---------------------------------
+-------------------------
+Creating a Code Generator
+-------------------------
 
 This guide describes how to structure a new Smithy code generator so that
 the generator can be used to build generic clients for any web service
diff --git a/docs/source-2.0/guides/building-models/index.rst b/docs/source-2.0/guides/building-models/index.rst
deleted file mode 100644
index a38ecdd11..000000000
--- a/docs/source-2.0/guides/building-models/index.rst
+++ /dev/null
@@ -1,20 +0,0 @@
-.. _building-models:
-
-======================
-Building Smithy Models
-======================
-
-Building a Smithy model is the process of validating a model and
-creating build artifacts like other models, JARs,
-OpenAPI specifications, code, etc. Smithy models are built using the
-:doc:`Smithy Gradle plugin <gradle-plugin>`. The Gradle plugin is a
-wrapper around a :doc:`smithy-build <build-config>` and ``smithy-build.json``
-files, which is where Smithy code generation is configured and most
-build logic is implemented.
-
-.. toctree::
-    :maxdepth: 1
-    :caption: Build system guides
-
-    gradle-plugin
-    build-config
diff --git a/docs/source-2.0/guides/glossary.rst b/docs/source-2.0/guides/glossary.rst
index 49829395e..6e3dade65 100644
--- a/docs/source-2.0/guides/glossary.rst
+++ b/docs/source-2.0/guides/glossary.rst
@@ -62,7 +62,7 @@ Glossary
         A specific view of a Smithy model that has added, removed, or
         transformed model components.
 
-        .. seealso:: :doc:`building-models/build-config`
+        .. seealso:: :doc:`smithy-build-json`
 
     Reserved words
         Identifiers and words that cannot be used in a
@@ -104,7 +104,7 @@ Glossary
         :term:`Code generators <code generator>` are configured and executed
         by adding plugins to smithy-build.json files in various projections.
 
-        .. seealso:: :doc:`building-models/build-config`
+        .. seealso:: :doc:`smithy-build-json`
 
     Smithy model
         Smithy models define services, operations, resources, and shapes.
diff --git a/docs/source-2.0/guides/building-models/gradle-plugin.rst b/docs/source-2.0/guides/gradle-plugin.rst
similarity index 100%
rename from docs/source-2.0/guides/building-models/gradle-plugin.rst
rename to docs/source-2.0/guides/gradle-plugin.rst
diff --git a/docs/source-2.0/guides/index.rst b/docs/source-2.0/guides/index.rst
index 53415b0eb..dee6887df 100644
--- a/docs/source-2.0/guides/index.rst
+++ b/docs/source-2.0/guides/index.rst
@@ -6,14 +6,13 @@ Guides
     :maxdepth: 1
 
     smithy-cli/index
-    building-models/index
+    smithy-build-json
+    gradle-plugin
     using-code-generation/index
     model-linters
     model-validation-examples
     evolving-models
     style-guide
-    converting-to-openapi
-    generating-cloudformation-resources
-    migrating-idl-1-to-2
+    model-translations/index
     building-codegen/index
     glossary
diff --git a/docs/source-2.0/guides/model-linters.rst b/docs/source-2.0/guides/model-linters.rst
index 4901d0abc..50d600e82 100644
--- a/docs/source-2.0/guides/model-linters.rst
+++ b/docs/source-2.0/guides/model-linters.rst
@@ -1,6 +1,6 @@
-=============
-Model linters
-=============
+==============
+Linting Models
+==============
 
 This guide describes how to apply optional linters to a Smithy model.
 
diff --git a/docs/source-2.0/guides/converting-to-openapi.rst b/docs/source-2.0/guides/model-translations/converting-to-openapi.rst
similarity index 100%
rename from docs/source-2.0/guides/converting-to-openapi.rst
rename to docs/source-2.0/guides/model-translations/converting-to-openapi.rst
diff --git a/docs/source-2.0/guides/generating-cloudformation-resources.rst b/docs/source-2.0/guides/model-translations/generating-cloudformation-resources.rst
similarity index 98%
rename from docs/source-2.0/guides/generating-cloudformation-resources.rst
rename to docs/source-2.0/guides/model-translations/generating-cloudformation-resources.rst
index 412bfaee5..2953b2d22 100644
--- a/docs/source-2.0/guides/generating-cloudformation-resources.rst
+++ b/docs/source-2.0/guides/model-translations/generating-cloudformation-resources.rst
@@ -292,9 +292,10 @@ disableHandlerPermissionGeneration (``boolean``)
     Sets whether to disable generating ``handler`` ``permission`` lists for
     Resource Schemas. By default, handler permissions lists are automatically
     added to schemas based on :ref:`lifecycle-operations` and permissions
-    listed in the :ref:`aws.iam#requiredActions-trait` on the operation. See
-    `the handlers section`_ in the CloudFormation Resource Schemas
-    documentation for more information.
+    listed in the :ref:`requiredActions property of the aws.iam#iamAction
+    trait <aws.iam#iamAction-trait>` on the operation. See `the handlers
+    section`_ in the CloudFormation Resource Schemas documentation for more
+    information.
 
     .. code-block:: json
 
@@ -312,7 +313,8 @@ disableHandlerPermissionGeneration (``boolean``)
     CloudFormation Resource Schema handlers determine what provisioning actions
     can be performed for the resource. The handlers utilized by CloudFormation
     align with some :ref:`lifecycle-operations`. These operations can also
-    define other permission actions required to invoke them with the :ref:`aws.iam#requiredActions-trait`.
+    define other permission actions required to invoke them with the :ref:`requiredActions
+    property of the aws.iam#iamAction trait <aws.iam#iamAction-trait>`
 
     When handler permission generation is enabled, all the actions required to
     invoke the operations related to the handler, including the actions for the
diff --git a/docs/source-2.0/guides/model-translations/index.rst b/docs/source-2.0/guides/model-translations/index.rst
new file mode 100644
index 000000000..e92ca181a
--- /dev/null
+++ b/docs/source-2.0/guides/model-translations/index.rst
@@ -0,0 +1,16 @@
+==================
+Model Translations
+==================
+
+These guides document how Smithy models can be converted to other model
+formats.
+
+.. toctree::
+    :maxdepth: 1
+
+    converting-to-openapi
+    migrating-idl-1-to-2
+    generating-cloudformation-resources
+
+More model conversion tooling can be found in the
+`Awesome Smithy repo on GitHub <https://github.com/smithy-lang/awesome-smithy>`_.
diff --git a/docs/source-2.0/guides/migrating-idl-1-to-2.rst b/docs/source-2.0/guides/model-translations/migrating-idl-1-to-2.rst
similarity index 100%
rename from docs/source-2.0/guides/migrating-idl-1-to-2.rst
rename to docs/source-2.0/guides/model-translations/migrating-idl-1-to-2.rst
diff --git a/docs/source-2.0/guides/building-models/build-config.rst b/docs/source-2.0/guides/smithy-build-json.rst
similarity index 99%
rename from docs/source-2.0/guides/building-models/build-config.rst
rename to docs/source-2.0/guides/smithy-build-json.rst
index fa8145fa4..ee6d919d9 100644
--- a/docs/source-2.0/guides/building-models/build-config.rst
+++ b/docs/source-2.0/guides/smithy-build-json.rst
@@ -1,8 +1,8 @@
 .. _smithy-build:
 
-============
-smithy-build
-============
+=================
+smithy-build.json
+=================
 
 Building a Smithy model requires constructing a configuration file,
 ``smithy-build.json``. This file is used to describe how a model is created
diff --git a/docs/source-2.0/guides/using-code-generation/index.rst b/docs/source-2.0/guides/using-code-generation/index.rst
index 54cd9b262..066b9726a 100644
--- a/docs/source-2.0/guides/using-code-generation/index.rst
+++ b/docs/source-2.0/guides/using-code-generation/index.rst
@@ -1,8 +1,8 @@
 .. _using-code-generation:
 
-=====================
-Using Code Generation
-=====================
+===============
+Code Generation
+===============
 
 One of Smithy's greatest strengths is code generation. Smithy models can be used to
 generate clients and servers in a variety of programming languages. The generated
diff --git a/docs/source-2.0/quickstart.rst b/docs/source-2.0/quickstart.rst
index bb9e79763..c99297472 100644
--- a/docs/source-2.0/quickstart.rst
+++ b/docs/source-2.0/quickstart.rst
@@ -495,13 +495,18 @@ That's it! We just created a simple, read-only, ``Weather`` service.
 5. Follow the :ref:`Using Code Generation Guide <using-code-generation>`
    to generate code for the ``Weather`` service.
 
-There's plenty more to explore in Smithy. The
-:ref:`Smithy specification <smithy-specification>` can teach you everything you
-need to know about Smithy models. :ref:`Building Smithy Models <building-models>`
-can teach you more about the build process, including how to use
-transformations, projections, plugins, and more. For more sample build
-configurations, see the `examples directory`_ of the Smithy Gradle plugin
-repository.
+There's plenty more to explore in Smithy.
+
+* The :ref:`Smithy specification <smithy-specification>` can teach you
+  everything you need to know about Smithy models.
+* :ref:`The Smithy CLI <smithy-cli>` is the easiest way to do interesting
+  things with Smithy models like code generation, creating different
+  versions of a model for different audiences, and more.
+* The `Smithy Examples <https://github.com/smithy-lang/smithy-examples>`_
+  repo on GitHub provides various example models and package layouts that
+  show how to use tools like the Smithy CLI or
+  :ref:`Gradle plugin <smithy-gradle-plugin>`.
+
 
 Complete example
 ================
diff --git a/docs/source-2.0/redirects b/docs/source-2.0/redirects
index 6eee45124..a2ebb38c2 100644
--- a/docs/source-2.0/redirects
+++ b/docs/source-2.0/redirects
@@ -1,1 +1,6 @@
 implementations.rst https://github.com/smithy-lang/awesome-smithy
+guides/building-models/gradle-plugin.rst guides/gradle-plugin.html
+guides/building-models/build-config.rst guides/smithy-build-json.html
+guides/converting-to-openapi.rst guides/model-translations/converting-to-openapi.html
+guides/migrating-idl-1-to-2.rst guides/model-translations/migrating-idl-1-to-2.html
+guides/generating-cloudformation-resources.rst guides/model-translations/generating-cloudformation-resources.html
diff --git a/smithy-aws-cloudformation/src/main/java/software/amazon/smithy/aws/cloudformation/schema/fromsmithy/mappers/HandlerPermissionMapper.java b/smithy-aws-cloudformation/src/main/java/software/amazon/smithy/aws/cloudformation/schema/fromsmithy/mappers/HandlerPermissionMapper.java
index 971067258..a32bbc516 100644
--- a/smithy-aws-cloudformation/src/main/java/software/amazon/smithy/aws/cloudformation/schema/fromsmithy/mappers/HandlerPermissionMapper.java
+++ b/smithy-aws-cloudformation/src/main/java/software/amazon/smithy/aws/cloudformation/schema/fromsmithy/mappers/HandlerPermissionMapper.java
@@ -22,7 +22,7 @@ import software.amazon.smithy.aws.cloudformation.schema.fromsmithy.CfnMapper;
 import software.amazon.smithy.aws.cloudformation.schema.fromsmithy.Context;
 import software.amazon.smithy.aws.cloudformation.schema.model.Handler;
 import software.amazon.smithy.aws.cloudformation.schema.model.ResourceSchema;
-import software.amazon.smithy.aws.iam.traits.RequiredActionsTrait;
+import software.amazon.smithy.aws.iam.traits.IamActionTrait;
 import software.amazon.smithy.aws.traits.ServiceTrait;
 import software.amazon.smithy.model.Model;
 import software.amazon.smithy.model.shapes.OperationShape;
@@ -112,9 +112,7 @@ public final class HandlerPermissionMapper implements CfnMapper {
         permissionsEntries.add(operationActionName);
 
         // Add all the other required actions for the operation.
-        operation.getTrait(RequiredActionsTrait.class)
-                .map(RequiredActionsTrait::getValues)
-                .map(permissionsEntries::addAll);
+        permissionsEntries.addAll(IamActionTrait.resolveRequiredActions(operation));
         return permissionsEntries;
     }
 }
diff --git a/smithy-aws-endpoints/src/main/java/software/amazon/smithy/rulesengine/aws/language/functions/AwsPartition.java b/smithy-aws-endpoints/src/main/java/software/amazon/smithy/rulesengine/aws/language/functions/AwsPartition.java
index c04116f7e..c4d93d5cc 100644
--- a/smithy-aws-endpoints/src/main/java/software/amazon/smithy/rulesengine/aws/language/functions/AwsPartition.java
+++ b/smithy-aws-endpoints/src/main/java/software/amazon/smithy/rulesengine/aws/language/functions/AwsPartition.java
@@ -5,6 +5,7 @@
 
 package software.amazon.smithy.rulesengine.aws.language.functions;
 
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.LinkedHashMap;
@@ -24,6 +25,7 @@ import software.amazon.smithy.rulesengine.language.syntax.expressions.functions.
 import software.amazon.smithy.rulesengine.language.syntax.expressions.functions.FunctionNode;
 import software.amazon.smithy.rulesengine.language.syntax.expressions.functions.LibraryFunction;
 import software.amazon.smithy.utils.MapUtils;
+import software.amazon.smithy.utils.SmithyInternalApi;
 import software.amazon.smithy.utils.SmithyUnstableApi;
 
 /**
@@ -41,13 +43,39 @@ public final class AwsPartition extends LibraryFunction {
     public static final Identifier INFERRED = Identifier.of("inferred");
 
     private static final Definition DEFINITION = new Definition();
-    private static final List<Partition> PARTITIONS;
+
+    // The following are mutable to allow for overriding the contents
+    // of the PARTITIONS list for test use cases. They MUST NOT have
+    // contents exposed directly, only through copies as is done in
+    // the `evaluate` method below.
+    private static final List<Partition> PARTITIONS = new ArrayList<>();
     private static final Map<String, Partition> REGION_MAP = new HashMap<>();
 
     static {
-        PARTITIONS = Partitions.fromNode(Node.parse(Partitions.class.getResourceAsStream("partitions.json")))
-                .getPartitions();
+        PARTITIONS.addAll(Partitions.fromNode(
+                        Node.parse(Partitions.class.getResourceAsStream("partitions.json")))
+                .getPartitions());
+        initializeRegionMap();
+    }
+
+    private AwsPartition(FunctionNode functionNode) {
+        super(DEFINITION, functionNode);
+    }
 
+    /**
+     * Overrides the partitions provided by default.
+     *
+     * @param partitions A list of partitions to set.
+     */
+    @SmithyInternalApi
+    public static void overridePartitions(Partitions partitions) {
+        PARTITIONS.clear();
+        PARTITIONS.addAll(partitions.getPartitions());
+        initializeRegionMap();
+    }
+
+    private static void initializeRegionMap() {
+        REGION_MAP.clear();
         for (Partition partition : PARTITIONS) {
             for (String region : partition.getRegions().keySet()) {
                 REGION_MAP.put(region, partition);
@@ -55,10 +83,6 @@ public final class AwsPartition extends LibraryFunction {
         }
     }
 
-    private AwsPartition(FunctionNode functionNode) {
-        super(DEFINITION, functionNode);
-    }
-
     /**
      * Gets the {@link FunctionDefinition} implementation.
      *
diff --git a/smithy-aws-endpoints/src/main/java/software/amazon/smithy/rulesengine/aws/traits/EndpointModifierIndex.java b/smithy-aws-endpoints/src/main/java/software/amazon/smithy/rulesengine/aws/traits/EndpointModifierIndex.java
index ff8ccbc59..3d566876d 100644
--- a/smithy-aws-endpoints/src/main/java/software/amazon/smithy/rulesengine/aws/traits/EndpointModifierIndex.java
+++ b/smithy-aws-endpoints/src/main/java/software/amazon/smithy/rulesengine/aws/traits/EndpointModifierIndex.java
@@ -7,7 +7,9 @@ package software.amazon.smithy.rulesengine.aws.traits;
 
 import java.util.HashMap;
 import java.util.Map;
+import java.util.Optional;
 import java.util.TreeMap;
+import java.util.logging.Logger;
 import software.amazon.smithy.model.Model;
 import software.amazon.smithy.model.knowledge.KnowledgeIndex;
 import software.amazon.smithy.model.shapes.ServiceShape;
@@ -22,6 +24,7 @@ import software.amazon.smithy.model.traits.Trait;
  * Endpoint modifier traits are traits that are marked by {@link EndpointModifierTrait}
  */
 public final class EndpointModifierIndex implements KnowledgeIndex {
+    private static final Logger LOGGER = Logger.getLogger(EndpointModifierIndex.class.getName());
 
     private final Map<ShapeId, Map<ShapeId, Trait>> endpointModifierTraits = new HashMap<>();
 
@@ -29,8 +32,14 @@ public final class EndpointModifierIndex implements KnowledgeIndex {
         for (ServiceShape serviceShape : model.getServiceShapes()) {
             Map<ShapeId, Trait> result = new TreeMap<>();
             for (Trait trait : serviceShape.getAllTraits().values()) {
-                Shape traitShape = model.expectShape(trait.toShapeId());
-                if (traitShape.hasTrait(EndpointModifierTrait.ID)) {
+                Optional<Shape> traitShape = model.getShape(trait.toShapeId());
+                if (!traitShape.isPresent()) {
+                    LOGGER.warning(String.format(
+                        "`%s` trait found in service `%s`, but the trait definition is missing",
+                            trait.toShapeId(), serviceShape.toShapeId()));
+                    continue;
+                }
+                if (traitShape.get().hasTrait(EndpointModifierTrait.ID)) {
                     result.put(trait.toShapeId(), trait);
                 }
             }
diff --git a/smithy-aws-endpoints/src/main/java/software/amazon/smithy/rulesengine/aws/validators/AwsSpecialCaseEndpointValidator.java b/smithy-aws-endpoints/src/main/java/software/amazon/smithy/rulesengine/aws/validators/AwsSpecialCaseEndpointValidator.java
index a5591c87a..5294b04f6 100644
--- a/smithy-aws-endpoints/src/main/java/software/amazon/smithy/rulesengine/aws/validators/AwsSpecialCaseEndpointValidator.java
+++ b/smithy-aws-endpoints/src/main/java/software/amazon/smithy/rulesengine/aws/validators/AwsSpecialCaseEndpointValidator.java
@@ -115,7 +115,7 @@ public final class AwsSpecialCaseEndpointValidator extends AbstractValidator {
                     "InvalidEndpointPatternScheme"));
         }
 
-        if (!isValidURL(endpoint)) {
+        if (!isValidUrl(endpoint)) {
             events.add(error(
                     serviceShape, location,
                     String.format("Endpoint `%s` should be a valid URL.",
@@ -126,16 +126,14 @@ public final class AwsSpecialCaseEndpointValidator extends AbstractValidator {
         return events;
     }
 
-    private boolean isValidURL(String endpointPattern) {
+    private boolean isValidUrl(String endpointPattern) {
         String url = endpointPattern
                 .replace("{", "")
                 .replace("}", "");
         try {
             new URL(url).toURI();
             return true;
-        } catch (MalformedURLException e) {
-            return false;
-        } catch (URISyntaxException e) {
+        } catch (MalformedURLException | URISyntaxException e) {
             return false;
         }
     }
diff --git a/smithy-aws-iam-traits/src/main/java/software/amazon/smithy/aws/iam/traits/ActionNameTrait.java b/smithy-aws-iam-traits/src/main/java/software/amazon/smithy/aws/iam/traits/ActionNameTrait.java
index fcf491aca..c0afede91 100644
--- a/smithy-aws-iam-traits/src/main/java/software/amazon/smithy/aws/iam/traits/ActionNameTrait.java
+++ b/smithy-aws-iam-traits/src/main/java/software/amazon/smithy/aws/iam/traits/ActionNameTrait.java
@@ -10,6 +10,12 @@ import software.amazon.smithy.model.SourceLocation;
 import software.amazon.smithy.model.shapes.ShapeId;
 import software.amazon.smithy.model.traits.StringTrait;
 
+/**
+ * Use the {@code @iamAction} trait's {@code name} property instead.
+ *
+ * @deprecated As of release 1.44.0, replaced by {@link IamActionTrait#resolveActionName}.
+ */
+@Deprecated
 public final class ActionNameTrait extends StringTrait {
 
     public static final ShapeId ID = ShapeId.from("aws.iam#actionName");
diff --git a/smithy-aws-iam-traits/src/main/java/software/amazon/smithy/aws/iam/traits/ActionPermissionDescriptionTrait.java b/smithy-aws-iam-traits/src/main/java/software/amazon/smithy/aws/iam/traits/ActionPermissionDescriptionTrait.java
index c818afdb1..331357e53 100644
--- a/smithy-aws-iam-traits/src/main/java/software/amazon/smithy/aws/iam/traits/ActionPermissionDescriptionTrait.java
+++ b/smithy-aws-iam-traits/src/main/java/software/amazon/smithy/aws/iam/traits/ActionPermissionDescriptionTrait.java
@@ -20,8 +20,11 @@ import software.amazon.smithy.model.shapes.ShapeId;
 import software.amazon.smithy.model.traits.StringTrait;
 
 /**
- * Defines the description of what providing access to an operation entails.
+ * Use the {@code @iamAction} trait's {@code documentation} property instead.
+ *
+ * @deprecated As of release 1.44.0, replaced by {@link IamActionTrait#resolveActionDocumentation}.
  */
+@Deprecated
 public final class ActionPermissionDescriptionTrait extends StringTrait {
     public static final ShapeId ID = ShapeId.from("aws.iam#actionPermissionDescription");
 
diff --git a/smithy-aws-iam-traits/src/main/java/software/amazon/smithy/aws/iam/traits/IamActionTrait.java b/smithy-aws-iam-traits/src/main/java/software/amazon/smithy/aws/iam/traits/IamActionTrait.java
index 67377e186..63a81ed1f 100644
--- a/smithy-aws-iam-traits/src/main/java/software/amazon/smithy/aws/iam/traits/IamActionTrait.java
+++ b/smithy-aws-iam-traits/src/main/java/software/amazon/smithy/aws/iam/traits/IamActionTrait.java
@@ -19,11 +19,15 @@ import java.util.List;
 import java.util.Optional;
 import software.amazon.smithy.model.node.Node;
 import software.amazon.smithy.model.node.NodeMapper;
+import software.amazon.smithy.model.shapes.OperationShape;
 import software.amazon.smithy.model.shapes.ShapeId;
 import software.amazon.smithy.model.traits.AbstractTrait;
 import software.amazon.smithy.model.traits.AbstractTraitBuilder;
+import software.amazon.smithy.model.traits.DocumentationTrait;
 import software.amazon.smithy.model.traits.Trait;
 import software.amazon.smithy.utils.BuilderRef;
+import software.amazon.smithy.utils.FunctionalUtils;
+import software.amazon.smithy.utils.ListUtils;
 import software.amazon.smithy.utils.SmithyBuilder;
 import software.amazon.smithy.utils.ToSmithyBuilder;
 
@@ -60,6 +64,27 @@ public final class IamActionTrait extends AbstractTrait
         return Optional.ofNullable(name);
     }
 
+    /**
+     * Resolves the IAM action name for the given operation. Uses the following
+     * resolution order:
+     *
+     * <ol>
+     *     <li>Value of the {@code @iamAction} trait's {@code name} property</li>
+     *     <li>Value of the {@code @actionName} trait</li>
+     *     <li>The operation's name</li>
+     * </ol>
+     *
+     * @param operation the operation to resolve a name for.
+     * @return The resolved action name.
+     */
+    @SuppressWarnings("deprecation")
+    public static String resolveActionName(OperationShape operation) {
+        return operation.getTrait(IamActionTrait.class).flatMap(IamActionTrait::getName)
+                .orElseGet(() -> operation.getTrait(ActionNameTrait.class)
+                        .map(ActionNameTrait::getValue)
+                        .orElseGet(() -> operation.getId().getName()));
+    }
+
     /**
      * Gets the description of what granting the user permission to
      * invoke an operation would entail.
@@ -70,6 +95,29 @@ public final class IamActionTrait extends AbstractTrait
         return Optional.ofNullable(documentation);
     }
 
+    /**
+     * Resolves the IAM action documentation for the given operation. Uses the following
+     * resolution order:
+     *
+     * <ol>
+     *     <li>Value of the {@code @iamAction} trait's {@code documentation} property</li>
+     *     <li>Value of the {@code @actionPermissionDescription} trait</li>
+     *     <li>Value of the {@code @documentation} trait</li>
+     * </ol>
+     *
+     * @param operation the operation to resolve documentation for.
+     * @return The resolved action documentation.
+     */
+    @SuppressWarnings("deprecation")
+    public static String resolveActionDocumentation(OperationShape operation) {
+        return operation.getTrait(IamActionTrait.class).flatMap(IamActionTrait::getDocumentation)
+                .orElseGet(() -> operation.getTrait(ActionPermissionDescriptionTrait.class)
+                        .map(ActionPermissionDescriptionTrait::getValue)
+                        .orElseGet(() -> operation.getTrait(DocumentationTrait.class)
+                                .map(DocumentationTrait::getValue)
+                                .orElse(null)));
+    }
+
     /**
      * Gets the relative URL path for the action within a set of
      * IAM-related documentation.
@@ -90,6 +138,28 @@ public final class IamActionTrait extends AbstractTrait
         return requiredActions;
     }
 
+    /**
+     * Resolves the IAM action required actions for the given operation. Uses the following
+     * resolution order:
+     *
+     * <ol>
+     *     <li>Value of the {@code @iamAction} trait's {@code requiredActions} property</li>
+     *     <li>Value of the {@code @requiredActions} trait</li>
+     *     <li>An empty list.</li>
+     * </ol>
+     *
+     * @param operation the operation to resolve required actions for.
+     * @return The resolved required actions.
+     */
+    @SuppressWarnings("deprecation")
+    public static List<String> resolveRequiredActions(OperationShape operation) {
+        return operation.getTrait(IamActionTrait.class).map(IamActionTrait::getRequiredActions)
+                .filter(FunctionalUtils.not(List::isEmpty))
+                .orElseGet(() -> operation.getTrait(RequiredActionsTrait.class)
+                        .map(RequiredActionsTrait::getValues)
+                        .orElse(ListUtils.of()));
+    }
+
     /**
      * Gets the resources an IAM action can be authorized against.
      *
diff --git a/smithy-aws-iam-traits/src/main/java/software/amazon/smithy/aws/iam/traits/IamActionValidator.java b/smithy-aws-iam-traits/src/main/java/software/amazon/smithy/aws/iam/traits/IamActionValidator.java
index b04a2a268..9c5de2802 100644
--- a/smithy-aws-iam-traits/src/main/java/software/amazon/smithy/aws/iam/traits/IamActionValidator.java
+++ b/smithy-aws-iam-traits/src/main/java/software/amazon/smithy/aws/iam/traits/IamActionValidator.java
@@ -32,6 +32,7 @@ public final class IamActionValidator extends AbstractValidator {
         return events;
     }
 
+    @SuppressWarnings("deprecation")
     private List<ValidationEvent> validateDuplicateTraits(OperationShape operation, IamActionTrait trait) {
         List<ValidationEvent> events = new ArrayList<>();
         if (operation.hasTrait(ActionNameTrait.ID) && trait.getName().isPresent()) {
diff --git a/smithy-aws-iam-traits/src/main/java/software/amazon/smithy/aws/iam/traits/IamResourceTrait.java b/smithy-aws-iam-traits/src/main/java/software/amazon/smithy/aws/iam/traits/IamResourceTrait.java
index 1787b320d..74e68fab6 100644
--- a/smithy-aws-iam-traits/src/main/java/software/amazon/smithy/aws/iam/traits/IamResourceTrait.java
+++ b/smithy-aws-iam-traits/src/main/java/software/amazon/smithy/aws/iam/traits/IamResourceTrait.java
@@ -18,6 +18,7 @@ package software.amazon.smithy.aws.iam.traits;
 import java.util.Optional;
 import software.amazon.smithy.model.node.Node;
 import software.amazon.smithy.model.node.NodeMapper;
+import software.amazon.smithy.model.shapes.ResourceShape;
 import software.amazon.smithy.model.shapes.ShapeId;
 import software.amazon.smithy.model.traits.AbstractTrait;
 import software.amazon.smithy.model.traits.AbstractTraitBuilder;
@@ -52,6 +53,26 @@ public final class IamResourceTrait extends AbstractTrait
         return Optional.ofNullable(name);
     }
 
+
+
+    /**
+     * Resolves the IAM resource name for the given resource. Uses the following
+     * resolution order:
+     *
+     * <ol>
+     *     <li>Value of the {@code @iamResource} trait's {@code name} property</li>
+     *     <li>The resource's name</li>
+     * </ol>
+     *
+     * @param resource the resource to resolve a name for.
+     * @return The resolved resource name.
+     */
+    public static String resolveResourceName(ResourceShape resource) {
+        return resource.getTrait(IamResourceTrait.class)
+                .flatMap(IamResourceTrait::getName)
+                .orElse(resource.getId().getName());
+    }
+
     /**
      * Get the relative URL path that defines more information about the resource
      * within a set of IAM-related documentation.
diff --git a/smithy-aws-iam-traits/src/main/java/software/amazon/smithy/aws/iam/traits/RequiredActionsTrait.java b/smithy-aws-iam-traits/src/main/java/software/amazon/smithy/aws/iam/traits/RequiredActionsTrait.java
index 4a434b1b7..f008b5eae 100644
--- a/smithy-aws-iam-traits/src/main/java/software/amazon/smithy/aws/iam/traits/RequiredActionsTrait.java
+++ b/smithy-aws-iam-traits/src/main/java/software/amazon/smithy/aws/iam/traits/RequiredActionsTrait.java
@@ -22,6 +22,12 @@ import software.amazon.smithy.model.shapes.ShapeId;
 import software.amazon.smithy.model.traits.StringListTrait;
 import software.amazon.smithy.utils.ToSmithyBuilder;
 
+/**
+ * Use the {@code @iamAction} trait's {@code requiredActions} property instead.
+ *
+ * @deprecated As of release 1.44.0, replaced by {@link IamActionTrait#resolveRequiredActions}.
+ */
+@Deprecated
 public final class RequiredActionsTrait extends StringListTrait implements ToSmithyBuilder<RequiredActionsTrait> {
     public static final ShapeId ID = ShapeId.from("aws.iam#requiredActions");
 
diff --git a/smithy-build/src/main/java/software/amazon/smithy/build/transforms/FlattenNamespaces.java b/smithy-build/src/main/java/software/amazon/smithy/build/transforms/FlattenNamespaces.java
index a8c0a5e76..6c4637795 100644
--- a/smithy-build/src/main/java/software/amazon/smithy/build/transforms/FlattenNamespaces.java
+++ b/smithy-build/src/main/java/software/amazon/smithy/build/transforms/FlattenNamespaces.java
@@ -28,8 +28,8 @@ import software.amazon.smithy.model.neighbor.Walker;
 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.transform.ModelTransformer;
 import software.amazon.smithy.utils.FunctionalUtils;
+import software.amazon.smithy.utils.ListUtils;
 import software.amazon.smithy.utils.Pair;
 
 /**
@@ -120,9 +120,23 @@ public final class FlattenNamespaces extends ConfigurableProjectionTransformer<F
             throw new SmithyBuildException(
                     "'namespace' and 'service' properties must be set on flattenNamespace transformer.");
         }
+
         Model model = context.getModel();
+        if (!model.getShape(config.getService()).isPresent()) {
+            throw new SmithyBuildException("Configured service, " + config.getService()
+                    + ", not found in model when performing flattenNamespaces transform.");
+        }
+
         Map<ShapeId, ShapeId> shapesToRename = getRenamedShapes(config, model);
-        return ModelTransformer.create().renameShapes(model, shapesToRename);
+        model = context.getTransformer().renameShapes(model, shapesToRename);
+
+        // Remove service renames since they've now been applied, so consumers don't fail service shape validation
+        ShapeId updatedServiceId = shapesToRename.get(config.getService());
+        ServiceShape updatedService = model.expectShape(updatedServiceId, ServiceShape.class)
+                .toBuilder()
+                .clearRename()
+                .build();
+        return context.getTransformer().replaceShapes(model, ListUtils.of(updatedService));
     }
 
     @Override
@@ -131,11 +145,6 @@ public final class FlattenNamespaces extends ConfigurableProjectionTransformer<F
     }
 
     private Map<ShapeId, ShapeId> getRenamedShapes(Config config, Model model) {
-        if (!model.getShape(config.getService()).isPresent()) {
-            throw new SmithyBuildException("Configured service, " + config.getService()
-                    + ", not found in model when performing flattenNamespaces transform.");
-        }
-
         Map<ShapeId, ShapeId> shapesToRename = getRenamedShapesConnectedToService(config, model);
         Set<ShapeId> taggedShapesToInclude = getTaggedShapesToInclude(config.getIncludeTagged(), model);
 
diff --git a/smithy-diff/src/main/java/software/amazon/smithy/diff/evaluators/ModifiedTrait.java b/smithy-diff/src/main/java/software/amazon/smithy/diff/evaluators/ModifiedTrait.java
index fc51e84a0..ec41e2667 100644
--- a/smithy-diff/src/main/java/software/amazon/smithy/diff/evaluators/ModifiedTrait.java
+++ b/smithy-diff/src/main/java/software/amazon/smithy/diff/evaluators/ModifiedTrait.java
@@ -42,6 +42,7 @@ import software.amazon.smithy.model.traits.synthetic.OriginalShapeIdTrait;
 import software.amazon.smithy.model.traits.synthetic.SyntheticEnumTrait;
 import software.amazon.smithy.model.validation.Severity;
 import software.amazon.smithy.model.validation.ValidationEvent;
+import software.amazon.smithy.model.validation.ValidationUtils;
 import software.amazon.smithy.utils.ListUtils;
 import software.amazon.smithy.utils.SetUtils;
 import software.amazon.smithy.utils.StringUtils;
@@ -227,7 +228,7 @@ public final class ModifiedTrait extends AbstractDiffEvaluator {
                 }
 
                 String message;
-                String pretty = Node.prettyPrintJson(right.toNode());
+                String pretty = ValidationUtils.tickedPrettyPrintedNode(right);
                 if (path.isEmpty()) {
                     message = String.format("Added trait `%s` with value %s", trait, pretty);
                 } else {
@@ -260,7 +261,7 @@ public final class ModifiedTrait extends AbstractDiffEvaluator {
                     return Collections.emptyList();
                 }
 
-                String pretty = Node.prettyPrintJson(left.toNode());
+                String pretty = ValidationUtils.tickedPrettyPrintedNode(left);
                 String message;
                 if (path.isEmpty()) {
                     message = String.format("Removed trait `%s`. Previous trait value: %s", trait, pretty);
@@ -294,8 +295,8 @@ public final class ModifiedTrait extends AbstractDiffEvaluator {
                     return Collections.emptyList();
                 }
 
-                String leftPretty = Node.prettyPrintJson(left.toNode());
-                String rightPretty = Node.prettyPrintJson(right.toNode());
+                String leftPretty = ValidationUtils.tickedPrettyPrintedNode(left);
+                String rightPretty = ValidationUtils.tickedPrettyPrintedNode(right);
                 String message;
                 if (path.isEmpty()) {
                     message = String.format("Changed trait `%s` from %s to %s", trait, leftPretty, rightPretty);
diff --git a/smithy-diff/src/main/java/software/amazon/smithy/diff/evaluators/RemovedShape.java b/smithy-diff/src/main/java/software/amazon/smithy/diff/evaluators/RemovedShape.java
index c0abb8a9d..8b4913433 100644
--- a/smithy-diff/src/main/java/software/amazon/smithy/diff/evaluators/RemovedShape.java
+++ b/smithy-diff/src/main/java/software/amazon/smithy/diff/evaluators/RemovedShape.java
@@ -19,11 +19,15 @@ import java.util.List;
 import java.util.stream.Collectors;
 import software.amazon.smithy.diff.Differences;
 import software.amazon.smithy.model.shapes.Shape;
+import software.amazon.smithy.model.shapes.ShapeType;
+import software.amazon.smithy.model.traits.EnumTrait;
 import software.amazon.smithy.model.traits.PrivateTrait;
+import software.amazon.smithy.model.validation.Severity;
 import software.amazon.smithy.model.validation.ValidationEvent;
 
 /**
- * Creates an ERROR event when a non-private shape is removed.
+ * Creates an ERROR event when a non-private non-scalar shape is removed.
+ * Creates a WARNING event when a non-private scalar shape is removed.
  */
 public final class RemovedShape extends AbstractDiffEvaluator {
     @Override
@@ -31,7 +35,15 @@ public final class RemovedShape extends AbstractDiffEvaluator {
         return differences.removedShapes()
                 .filter(shape -> !shape.hasTrait(PrivateTrait.class))
                 .filter(shape -> !isMemberOfRemovedShape(shape, differences))
-                .map(shape -> error(shape, String.format("Removed %s `%s`", shape.getType(), shape.getId())))
+                .map(shape -> isInconsequentialType(shape)
+                        ? ValidationEvent.builder()
+                            .severity(Severity.WARNING)
+                            .message(String.format("Removed %s `%s`", shape.getType(), shape.getId()))
+                            .shapeId(shape.getId())
+                            .id(getEventId() + ".ScalarShape")
+                            .sourceLocation(shape.getSourceLocation())
+                            .build()
+                        : error(shape, String.format("Removed %s `%s`", shape.getType(), shape.getId())))
                 .collect(Collectors.toList());
     }
 
@@ -40,4 +52,20 @@ public final class RemovedShape extends AbstractDiffEvaluator {
                 .filter(member -> !differences.getNewModel().getShapeIds().contains(member.getContainer()))
                 .isPresent();
     }
+
+    private boolean isInconsequentialType(Shape shape) {
+        ShapeType shapeType = shape.getType();
+        return shapeType == ShapeType.BIG_DECIMAL
+                || shapeType == ShapeType.BIG_INTEGER
+                || shapeType == ShapeType.BLOB
+                || shapeType == ShapeType.BOOLEAN
+                || shapeType == ShapeType.BYTE
+                || shapeType == ShapeType.DOUBLE
+                || shapeType == ShapeType.FLOAT
+                || shapeType == ShapeType.SHORT
+                || shapeType == ShapeType.TIMESTAMP
+                || shapeType == ShapeType.LONG
+                || ((shapeType == ShapeType.STRING) && !shape.hasTrait(EnumTrait.class))
+                || (shapeType == ShapeType.INTEGER);
+    }
 }
diff --git a/smithy-diff/src/main/java/software/amazon/smithy/diff/evaluators/TraitBreakingChange.java b/smithy-diff/src/main/java/software/amazon/smithy/diff/evaluators/TraitBreakingChange.java
index aacf2296c..a082f0247 100644
--- a/smithy-diff/src/main/java/software/amazon/smithy/diff/evaluators/TraitBreakingChange.java
+++ b/smithy-diff/src/main/java/software/amazon/smithy/diff/evaluators/TraitBreakingChange.java
@@ -30,6 +30,7 @@ import software.amazon.smithy.model.shapes.Shape;
 import software.amazon.smithy.model.traits.Trait;
 import software.amazon.smithy.model.traits.TraitDefinition;
 import software.amazon.smithy.model.validation.ValidationEvent;
+import software.amazon.smithy.model.validation.ValidationUtils;
 import software.amazon.smithy.utils.StringUtils;
 
 /**
@@ -214,15 +215,15 @@ public final class TraitBreakingChange extends AbstractDiffEvaluator {
         }
 
         private String createBreakingMessage(TraitDefinition.ChangeType type, String path, Node left, Node right) {
-            String leftPretty = Node.prettyPrintJson(left.toNode());
-            String rightPretty = Node.prettyPrintJson(right.toNode());
+            String leftPretty = ValidationUtils.tickedPrettyPrintedNode(left);
+            String rightPretty = ValidationUtils.tickedPrettyPrintedNode(right);
 
             switch (type) {
                 case ADD:
                     if (!path.isEmpty()) {
                         return String.format("Added trait contents to `%s` at path `%s` with value %s",
                                              trait.getId(), path, rightPretty);
-                    } else if (rightPretty.equals("{}")) {
+                    } else if (Node.objectNode().equals(right)) {
                         return String.format("Added trait `%s`", trait.getId());
                     } else {
                         return String.format("Added trait `%s` with value %s", trait.getId(), rightPretty);
@@ -231,7 +232,7 @@ public final class TraitBreakingChange extends AbstractDiffEvaluator {
                     if (!path.isEmpty()) {
                         return String.format("Removed trait contents from `%s` at path `%s`. Removed value: %s",
                                              trait.getId(), path, leftPretty);
-                    } else if (leftPretty.equals("{}")) {
+                    } else if (Node.objectNode().equals(left)) {
                         return String.format("Removed trait `%s`", trait.getId());
                     } else {
                         return String.format("Removed trait `%s`. Previous trait value: %s",
diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/loader/ModelAssembler.java b/smithy-model/src/main/java/software/amazon/smithy/model/loader/ModelAssembler.java
index bff196c1d..93ce65a61 100644
--- a/smithy-model/src/main/java/software/amazon/smithy/model/loader/ModelAssembler.java
+++ b/smithy-model/src/main/java/software/amazon/smithy/model/loader/ModelAssembler.java
@@ -574,10 +574,11 @@ public final class ModelAssembler {
 
         try {
             List<ValidationEvent> mergedEvents = ModelValidator.builder()
-                    .validators(validators)
+                    .addValidators(validators)
                     .validatorFactory(validatorFactory, decorator)
                     .eventListener(validationEventListener)
                     .includeEvents(events)
+                    .legacyValidationMode((boolean) properties.getOrDefault("LEGACY_VALIDATION_MODE", false))
                     .build()
                     .validate(transformed);
             return new ValidatedResult<>(transformed, mergedEvents);
diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/loader/ModelValidator.java b/smithy-model/src/main/java/software/amazon/smithy/model/loader/ModelValidator.java
index 85edc2c20..9cc9d26be 100644
--- a/smithy-model/src/main/java/software/amazon/smithy/model/loader/ModelValidator.java
+++ b/smithy-model/src/main/java/software/amazon/smithy/model/loader/ModelValidator.java
@@ -19,24 +19,26 @@ import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
+import java.util.Map;
 import java.util.Objects;
-import java.util.Set;
 import java.util.function.Consumer;
 import java.util.stream.Collectors;
+import java.util.stream.Stream;
 import software.amazon.smithy.model.Model;
 import software.amazon.smithy.model.SourceLocation;
 import software.amazon.smithy.model.validation.Severity;
 import software.amazon.smithy.model.validation.ValidatedResult;
 import software.amazon.smithy.model.validation.ValidationEvent;
 import software.amazon.smithy.model.validation.ValidationEventDecorator;
+import software.amazon.smithy.model.validation.ValidationUtils;
 import software.amazon.smithy.model.validation.Validator;
 import software.amazon.smithy.model.validation.ValidatorFactory;
 import software.amazon.smithy.model.validation.suppressions.ModelBasedEventDecorator;
-import software.amazon.smithy.model.validation.suppressions.SeverityOverride;
 import software.amazon.smithy.model.validation.validators.ResourceCycleValidator;
 import software.amazon.smithy.model.validation.validators.TargetValidator;
+import software.amazon.smithy.utils.BuilderRef;
 import software.amazon.smithy.utils.ListUtils;
-import software.amazon.smithy.utils.SetUtils;
+import software.amazon.smithy.utils.MapUtils;
 import software.amazon.smithy.utils.SmithyBuilder;
 
 /**
@@ -68,24 +70,27 @@ final class ModelValidator implements Validator {
     }
 
     /** If these validators fail, then many others will too. Validate these first. */
-    private static final Set<Class<? extends Validator>> CORE_VALIDATORS = SetUtils.of(
-            TargetValidator.class,
-            ResourceCycleValidator.class
+    private static final Map<Class<?>, Validator> CORRECTNESS_VALIDATORS = MapUtils.of(
+            TargetValidator.class, new TargetValidator(),
+            ResourceCycleValidator.class, new ResourceCycleValidator()
     );
 
     private final ValidatorFactory validatorFactory;
-    private final List<ValidationEvent> events = new ArrayList<>();
-    private final List<Validator> validators = new ArrayList<>();
-    private final List<SeverityOverride> severityOverrides = new ArrayList<>();
+    private final List<ValidationEvent> events;
+    private final List<Validator> validators;
+    private final List<Validator> criticalValidators;
     private final ValidationEventDecorator validationEventDecorator;
     private final Consumer<ValidationEvent> eventListener;
+    private final boolean legacyValidationMode;
 
     ModelValidator(Builder builder) {
         this.validatorFactory = builder.validatorFactory;
         this.eventListener = builder.eventListener;
         this.validationEventDecorator = builder.validationEventDecorator;
-        this.events.addAll(builder.includeEvents);
-        this.validators.addAll(builder.validators);
+        this.events = builder.includeEvents.copy();
+        this.validators = builder.validators.copy();
+        this.criticalValidators = builder.criticalValidators.copy();
+        this.legacyValidationMode = builder.legacyValidationMode;
     }
 
     @Override
@@ -103,34 +108,43 @@ final class ModelValidator implements Validator {
 
     static final class Builder implements SmithyBuilder<ModelValidator> {
 
-        private final List<Validator> validators = new ArrayList<>();
-        private final List<ValidationEvent> includeEvents = new ArrayList<>();
+        private final BuilderRef<List<Validator>> validators = BuilderRef.forList();
+        private final BuilderRef<List<Validator>> criticalValidators = BuilderRef.forList();
+        private final BuilderRef<List<ValidationEvent>> includeEvents = BuilderRef.forList();
         private ValidatorFactory validatorFactory = LazyValidatorFactoryHolder.INSTANCE;
         private Consumer<ValidationEvent> eventListener = event -> { };
         private ValidationEventDecorator validationEventDecorator;
+        private boolean legacyValidationMode = false;
 
         private Builder() {}
 
         /**
-         * Sets the custom {@link Validator}s to use when running the ModelValidator.
+         * Adds an array of {@link Validator}s to use when running the ModelValidator.
          *
-         * @param validators Validators to set.
-         * @return Returns the ModelValidator.
+         * @param validators Validators to add.
+         * @return Returns the builder.
          */
-        public Builder validators(Collection<? extends Validator> validators) {
-            this.validators.clear();
-            validators.forEach(this::addValidator);
+        public Builder addValidators(Collection<? extends Validator> validators) {
+            for (Validator validator : validators) {
+                addValidator(validator);
+            }
             return this;
         }
 
         /**
-         * Adds a custom {@link Validator}.
+         * Adds a {@link Validator}.
          *
          * @param validator Validator to add.
          * @return Returns the builder.
          */
         public Builder addValidator(Validator validator) {
-            validators.add(Objects.requireNonNull(validator));
+            if (!CORRECTNESS_VALIDATORS.containsKey(validator.getClass())) {
+                if (ValidationUtils.isCriticalValidator(validator.getClass())) {
+                    criticalValidators.get().add(validator);
+                } else {
+                    validators.get().add(validator);
+                }
+            }
             return this;
         }
 
@@ -165,21 +179,31 @@ final class ModelValidator implements Validator {
          * Includes a set of events that were already encountered in the result.
          *
          * <p>Suppressions and severity overrides will be applied to the given {@code events}. However, the validator
-         * assumes that the event has already been decroated and the event listener has already seen the event.
+         * assumes that the event has already been decorated and the event listener has already seen the event.
          *
          * @param events Events to include.
          * @return Returns the builder.
          */
         public Builder includeEvents(List<ValidationEvent> events) {
-            this.includeEvents.clear();
-            this.includeEvents.addAll(events);
+            this.includeEvents.get().addAll(events);
+            return this;
+        }
+
+        /**
+         * Enables legacy validation mode that does not fail if critical Validators emit an ERROR.
+         *
+         * @param legacyValidationMode Set to true to enable legacy validation mode.
+         * @return Returns the builder.
+         */
+        public Builder legacyValidationMode(boolean legacyValidationMode) {
+            this.legacyValidationMode = legacyValidationMode;
             return this;
         }
 
         @Override
         public ModelValidator build() {
-            validators.addAll(validatorFactory.loadBuiltinValidators());
-            validators.removeIf(v -> CORE_VALIDATORS.contains(v.getClass()));
+            // Adding built-in validators is deferred to allow for a custom factory to be set on the builder.
+            addValidators(validatorFactory.loadBuiltinValidators());
             return new ModelValidator(this);
         }
     }
@@ -187,22 +211,23 @@ final class ModelValidator implements Validator {
     private static final class LoadedModelValidator {
 
         private final Model model;
-        private final List<SeverityOverride> severityOverrides;
         private final List<Validator> validators;
+        private final List<Validator> criticalValidators;
         private final List<ValidationEvent> events = new ArrayList<>();
         private final ValidationEventDecorator validationEventDecorator;
         private final Consumer<ValidationEvent> eventListener;
+        private final boolean legacyValidationMode;
 
         private LoadedModelValidator(Model model, ModelValidator validator) {
             this.model = model;
             this.eventListener = validator.eventListener;
-            this.severityOverrides = new ArrayList<>(validator.severityOverrides);
             this.validators = new ArrayList<>(validator.validators);
+            this.criticalValidators = Collections.unmodifiableList(validator.criticalValidators);
+            this.legacyValidationMode = validator.legacyValidationMode;
 
             // Suppressing and elevating events is handled by composing a given decorator with a
             // ModelBasedEventDecorator.
             ModelBasedEventDecorator modelBasedEventDecorator = new ModelBasedEventDecorator();
-            modelBasedEventDecorator.severityOverrides(validator.severityOverrides);
             ValidatedResult<ValidationEventDecorator> result = modelBasedEventDecorator.createDecorator(model);
             this.validationEventDecorator = result.getResult()
                     .map(decorator -> ValidationEventDecorator.compose(
@@ -265,35 +290,42 @@ final class ModelValidator implements Validator {
         }
 
         private void pushEvent(ValidationEvent event) {
+            events.add(updateAndEmitEvent(event));
+        }
+
+        private ValidationEvent updateAndEmitEvent(ValidationEvent event) {
             if (validationEventDecorator.canDecorate(event)) {
                 event = validationEventDecorator.decorate(event);
             }
-            events.add(event);
             eventListener.accept(event);
+            return event;
         }
 
         private List<ValidationEvent> validate() {
-            // Perform critical validation before other more granular semantic validators.
-            pushEvents(new TargetValidator().validate(model));
-            pushEvents(new ResourceCycleValidator().validate(model));
-
-            // Fail early if errors were detected since further validation will just obscure the root cause.
+            // Perform critical correctness validation before other critical validators.
+            events.addAll(streamEvents(CORRECTNESS_VALIDATORS.values().stream()));
             if (LoaderUtils.containsErrorEvents(events)) {
                 return events;
             }
 
-            List<ValidationEvent> result = validators.parallelStream()
+            // Same thing, but for other critical validators.
+            events.addAll(streamEvents(criticalValidators.parallelStream()));
+
+            // Only fail early here if legacy validation mode is enabled.
+            if (!legacyValidationMode && LoaderUtils.containsErrorEvents(events)) {
+                return events;
+            }
+
+            events.addAll(streamEvents(validators.parallelStream()));
+            return events;
+        }
+
+        private List<ValidationEvent> streamEvents(Stream<Validator> validators) {
+            return validators
                     .flatMap(validator -> validator.validate(model).stream())
                     .filter(this::filterPrelude)
-                    .map(e -> validationEventDecorator.canDecorate(e) ? validationEventDecorator.decorate(e) : e)
-                    // Emit events as they occur during validation.
-                    .peek(eventListener)
+                    .map(this::updateAndEmitEvent)
                     .collect(Collectors.toList());
-
-            // Add in events encountered while building up validators and suppressions.
-            result.addAll(events);
-
-            return result;
         }
 
         private boolean filterPrelude(ValidationEvent event) {
diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/loader/Version.java b/smithy-model/src/main/java/software/amazon/smithy/model/loader/Version.java
index a9de96dcb..9f5367902 100644
--- a/smithy-model/src/main/java/software/amazon/smithy/model/loader/Version.java
+++ b/smithy-model/src/main/java/software/amazon/smithy/model/loader/Version.java
@@ -171,7 +171,7 @@ enum Version {
         }
 
         @Override
-        @SuppressWarnings("deprecated")
+        @SuppressWarnings("deprecation")
         ValidationEvent validateVersionedTrait(ShapeId target, ShapeId traitId, Node value) {
             if (traitId.equals(BoxTrait.ID)) {
                 return ValidationEvent.builder()
diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/neighbor/IdRefShapeRelationships.java b/smithy-model/src/main/java/software/amazon/smithy/model/neighbor/IdRefShapeRelationships.java
new file mode 100644
index 000000000..7466afb61
--- /dev/null
+++ b/smithy-model/src/main/java/software/amazon/smithy/model/neighbor/IdRefShapeRelationships.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package software.amazon.smithy.model.neighbor;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import software.amazon.smithy.model.Model;
+import software.amazon.smithy.model.node.Node;
+import software.amazon.smithy.model.node.StringNode;
+import software.amazon.smithy.model.selector.PathFinder;
+import software.amazon.smithy.model.selector.Selector;
+import software.amazon.smithy.model.shapes.MemberShape;
+import software.amazon.smithy.model.shapes.Shape;
+import software.amazon.smithy.model.shapes.ShapeId;
+import software.amazon.smithy.model.traits.IdRefTrait;
+import software.amazon.smithy.model.traits.Trait;
+import software.amazon.smithy.model.traits.TraitDefinition;
+
+/**
+ * Finds all {@link RelationshipType#ID_REF} relationships in a model.
+ *
+ * <p>This works by finding all paths from {@link TraitDefinition} shapes
+ * to shapes with {@link IdRefTrait}, then using those paths to search
+ * the node values of each application of the trait to extract the {@link ShapeId}
+ * value. Because we don't have a fixed set of traits known to potentially have
+ * idRef members, this has to be done dynamically.
+ */
+final class IdRefShapeRelationships {
+    private static final Selector WITH_ID_REF = Selector.parse("[trait|idRef]");
+
+    private final Model model;
+    private final Map<ShapeId, Set<Relationship>> relationships = new HashMap<>();
+
+    IdRefShapeRelationships(Model model) {
+        this.model = model;
+    }
+
+    Map<ShapeId, Set<Relationship>> getRelationships() {
+        PathFinder finder = PathFinder.create(model);
+        for (Shape traitDef : model.getShapesWithTrait(TraitDefinition.class)) {
+            if (traitDef.hasTrait(IdRefTrait.class)) {
+                // PathFinder doesn't handle the case where the trait def has the idRef
+                NodeQuery query = new NodeQuery().self();
+                addRelationships(traitDef, query);
+                continue;
+            }
+            List<PathFinder.Path> paths = finder.search(traitDef, WITH_ID_REF);
+            if (!paths.isEmpty()) {
+                for (PathFinder.Path path : paths) {
+                    NodeQuery query = buildNodeQuery(path);
+                    addRelationships(traitDef, query);
+                }
+            }
+        }
+        return relationships;
+    }
+
+    private void addRelationships(Shape traitDef, NodeQuery query) {
+        model.getShapesWithTrait(traitDef.getId()).forEach(shape -> {
+            Trait trait = shape.findTrait(traitDef.getId()).get(); // We already know the shape has the trait.
+            Node node = trait.toNode();
+            // Invalid shape ids are handled by the idRef trait validator, so ignore them here.
+            query.execute(node).forEach(n -> n.asStringNode()
+                    .flatMap(StringNode::asShapeId)
+                    .flatMap(model::getShape)
+                    .map(referenced -> Relationship.create(shape, RelationshipType.ID_REF, referenced))
+                    .ifPresent(rel -> relationships
+                            .computeIfAbsent(rel.getShape().getId(), id -> new HashSet<>()).add(rel)));
+        });
+    }
+
+    private static NodeQuery buildNodeQuery(PathFinder.Path path) {
+        NodeQuery query = new NodeQuery();
+        // The path goes from trait definition -> shape with idRef
+        for (Relationship relationship : path) {
+            if (!relationship.getNeighborShape().isPresent()) {
+                break;
+            }
+            switch (relationship.getRelationshipType()) {
+                case MAP_KEY:
+                    query.anyMemberName();
+                    break;
+                case MAP_VALUE:
+                    query.anyMember();
+                    break;
+                case LIST_MEMBER:
+                case SET_MEMBER:
+                    query.anyElement();
+                    break;
+                case UNION_MEMBER:
+                case STRUCTURE_MEMBER:
+                    MemberShape member = (MemberShape) relationship.getNeighborShape().get();
+                    query.member(member.getMemberName());
+                    break;
+                default:
+                    // Other relationship types don't produce meaningful edges to search the node.
+                    break;
+            }
+        }
+        return query;
+    }
+}
diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/neighbor/NeighborProvider.java b/smithy-model/src/main/java/software/amazon/smithy/model/neighbor/NeighborProvider.java
index 76978d85c..00f8dedf3 100644
--- a/smithy-model/src/main/java/software/amazon/smithy/model/neighbor/NeighborProvider.java
+++ b/smithy-model/src/main/java/software/amazon/smithy/model/neighbor/NeighborProvider.java
@@ -26,6 +26,7 @@ import java.util.concurrent.ConcurrentHashMap;
 import software.amazon.smithy.model.Model;
 import software.amazon.smithy.model.shapes.Shape;
 import software.amazon.smithy.model.shapes.ShapeId;
+import software.amazon.smithy.model.traits.IdRefTrait;
 import software.amazon.smithy.utils.ListUtils;
 
 /**
@@ -91,6 +92,29 @@ public interface NeighborProvider {
         };
     }
 
+    /**
+     * Creates a NeighborProvider that includes {@link RelationshipType#ID_REF}
+     * relationships.
+     *
+     * @param model Model to use to look up shapes referenced by {@link IdRefTrait}.
+     * @param neighborProvider Provider to wrap.
+     * @return Returns the created neighbor provider.
+     */
+    static NeighborProvider withIdRefRelationships(Model model, NeighborProvider neighborProvider) {
+        Map<ShapeId, Set<Relationship>> idRefRelationships = new IdRefShapeRelationships(model).getRelationships();
+        return shape -> {
+            List<Relationship> relationships = neighborProvider.getNeighbors(shape);
+
+            if (!idRefRelationships.containsKey(shape.getId())) {
+                return relationships;
+            }
+
+            relationships = new ArrayList<>(relationships);
+            relationships.addAll(idRefRelationships.get(shape.getId()));
+            return relationships;
+        };
+    }
+
     /**
      * Creates a NeighborProvider that precomputes the neighbors of a model.
      *
diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/neighbor/NodeQuery.java b/smithy-model/src/main/java/software/amazon/smithy/model/neighbor/NodeQuery.java
new file mode 100644
index 000000000..c01738d18
--- /dev/null
+++ b/smithy-model/src/main/java/software/amazon/smithy/model/neighbor/NodeQuery.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package software.amazon.smithy.model.neighbor;
+
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Queue;
+import software.amazon.smithy.model.node.Node;
+
+/**
+ * Searches {@link Node}s to find matching children. Each search
+ * condition is executed on the result of the previous search,
+ * and the results are aggregated.
+ */
+final class NodeQuery {
+    private static final Query SELF = (node, result) -> result.add(node);
+
+    private static final Query ANY_MEMBER = (node, result) -> {
+        if (node == null || !node.isObjectNode()) {
+            return;
+        }
+        result.addAll(node.expectObjectNode().getMembers().values());
+    };
+
+    private static final Query ANY_ELEMENT = (node, result) -> {
+        if (node == null || !node.isArrayNode()) {
+            return;
+        }
+        result.addAll(node.expectArrayNode().getElements());
+    };
+
+    private static final Query ANY_MEMBER_NAME = (node, result) -> {
+        if (node == null || !node.isObjectNode()) {
+            return;
+        }
+        result.addAll(node.expectObjectNode().getMembers().keySet());
+    };
+
+    private final List<Query> queries = new ArrayList<>();
+
+    NodeQuery() {
+    }
+
+    NodeQuery self() {
+        queries.add(SELF);
+        return this;
+    }
+
+    NodeQuery member(String name) {
+        queries.add((node, result) -> {
+            if (node == null || !node.isObjectNode()) {
+                return;
+            }
+            node.expectObjectNode().getMember(name).ifPresent(result::add);
+        });
+        return this;
+    }
+
+    NodeQuery anyMember() {
+        queries.add(ANY_MEMBER);
+        return this;
+    }
+
+    NodeQuery anyElement() {
+        queries.add(ANY_ELEMENT);
+        return this;
+    }
+
+    NodeQuery anyMemberName() {
+        queries.add(ANY_MEMBER_NAME);
+        return this;
+    }
+
+    Collection<Node> execute(Node node) {
+        Queue<Node> previousResult = new ArrayDeque<>();
+
+        if (queries.isEmpty()) {
+            return previousResult;
+        }
+
+        previousResult.add(node);
+        for (Query query : queries) {
+            // Each time a query runs, it adds to the queue, but we only want it to
+            // run on the nodes added by the previous query.
+            for (int i = previousResult.size(); i > 0; i--) {
+                query.run(previousResult.poll(), previousResult);
+            }
+        }
+
+        return previousResult;
+    }
+
+    @FunctionalInterface
+    interface Query {
+        void run(Node node, Queue<Node> result);
+    }
+}
diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/neighbor/RelationshipType.java b/smithy-model/src/main/java/software/amazon/smithy/model/neighbor/RelationshipType.java
index 97aaceb5b..633154676 100644
--- a/smithy-model/src/main/java/software/amazon/smithy/model/neighbor/RelationshipType.java
+++ b/smithy-model/src/main/java/software/amazon/smithy/model/neighbor/RelationshipType.java
@@ -26,8 +26,10 @@ import software.amazon.smithy.model.shapes.MemberShape;
 import software.amazon.smithy.model.shapes.OperationShape;
 import software.amazon.smithy.model.shapes.ResourceShape;
 import software.amazon.smithy.model.shapes.SetShape;
+import software.amazon.smithy.model.shapes.ShapeId;
 import software.amazon.smithy.model.shapes.StructureShape;
 import software.amazon.smithy.model.shapes.UnionShape;
+import software.amazon.smithy.model.traits.IdRefTrait;
 import software.amazon.smithy.model.traits.TraitDefinition;
 import software.amazon.smithy.utils.SmithyInternalApi;
 
@@ -214,7 +216,37 @@ public enum RelationshipType {
      * Relationship that exists between a structure or union and a mixin applied
      * to the shape.
      */
-    MIXIN("mixin", RelationshipDirection.DIRECTED);
+    MIXIN("mixin", RelationshipDirection.DIRECTED),
+
+    /**
+     * Relationships that exist between a shape and another shape referenced by an
+     * {@link IdRefTrait}.
+     *
+     * <p>This relationship is formed by applying a trait with a value containing a
+     * reference to another {@link ShapeId}. For
+     * example:
+     * <pre>
+     * {@code
+     * @trait
+     * structure myRef {
+     *     @idRef
+     *     shape: String
+     * }
+     *
+     * // @myRef trait applied, and the value references the shape `Referenced`
+     * @myRef(shape: Referenced)
+     * structure WithMyRef {}
+     *
+     * string Referenced
+     * }
+     * </pre>
+     *
+     * <p>This kind of relationship is not returned by default from a
+     * {@link NeighborProvider}. You must explicitly wrap a {@link NeighborProvider}
+     * with {@link NeighborProvider#withIdRefRelationships(Model, NeighborProvider)}
+     * in order to yield idRef relationships.
+     */
+    ID_REF(null, RelationshipDirection.DIRECTED);
 
     private String selectorLabel;
     private RelationshipDirection direction;
diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/neighbor/UnreferencedShapes.java b/smithy-model/src/main/java/software/amazon/smithy/model/neighbor/UnreferencedShapes.java
index 2de3ce2b5..e412f885a 100644
--- a/smithy-model/src/main/java/software/amazon/smithy/model/neighbor/UnreferencedShapes.java
+++ b/smithy-model/src/main/java/software/amazon/smithy/model/neighbor/UnreferencedShapes.java
@@ -27,7 +27,9 @@ import software.amazon.smithy.utils.FunctionalUtils;
 
 /**
  * Finds shapes that are not connected to a service shape, are not trait
- * definitions, and are not referenced by trait definitions.
+ * definitions, are not referenced by trait definitions, and are not
+ * referenced in trait values through
+ * {@link software.amazon.smithy.model.traits.IdRefTrait}.
  *
  * <p>Prelude shapes are never considered unreferenced.
  */
@@ -53,7 +55,9 @@ public final class UnreferencedShapes {
      * @return Returns the unreferenced shapes.
      */
     public Set<Shape> compute(Model model) {
-        Walker shapeWalker = new Walker(NeighborProviderIndex.of(model).getProvider());
+        NeighborProvider baseProvider = NeighborProviderIndex.of(model).getProvider();
+        NeighborProvider providerWithIdRefRelationships = NeighborProvider.withIdRefRelationships(model, baseProvider);
+        Walker shapeWalker = new Walker(providerWithIdRefRelationships);
 
         // Find all shapes connected to any service shape.
         Set<Shape> connected = new HashSet<>();
diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/node/StringNode.java b/smithy-model/src/main/java/software/amazon/smithy/model/node/StringNode.java
index c77751087..81b001dcc 100644
--- a/smithy-model/src/main/java/software/amazon/smithy/model/node/StringNode.java
+++ b/smithy-model/src/main/java/software/amazon/smithy/model/node/StringNode.java
@@ -168,6 +168,19 @@ public final class StringNode extends Node implements Comparable<StringNode> {
         }
     }
 
+    /**
+     * Gets the value of the string as a ShapeId if it is a valid Shape ID.
+     *
+     * @return Returns the optional Shape ID.
+     */
+    public Optional<ShapeId> asShapeId() {
+        try {
+            return Optional.of(ShapeId.from(getValue()));
+        } catch (ShapeIdSyntaxException e) {
+            return Optional.empty();
+        }
+    }
+
     @Override
     public boolean equals(Object other) {
         return other instanceof StringNode && value.equals(((StringNode) other).getValue());
diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/node/internal/PrettyPrintWriter.java b/smithy-model/src/main/java/software/amazon/smithy/model/node/internal/PrettyPrintWriter.java
index bd04c6551..7847e2726 100644
--- a/smithy-model/src/main/java/software/amazon/smithy/model/node/internal/PrettyPrintWriter.java
+++ b/smithy-model/src/main/java/software/amazon/smithy/model/node/internal/PrettyPrintWriter.java
@@ -95,7 +95,7 @@ final class PrettyPrintWriter extends JsonWriter {
         if (indentString == null) {
             return false;
         }
-        writer.write('\n');
+        writer.write(System.lineSeparator());
         for (int i = 0; i < indent; i++) {
             writer.write(indentString);
         }
diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/shapes/EnumShape.java b/smithy-model/src/main/java/software/amazon/smithy/model/shapes/EnumShape.java
index 39eeb3c02..c38d6212a 100644
--- a/smithy-model/src/main/java/software/amazon/smithy/model/shapes/EnumShape.java
+++ b/smithy-model/src/main/java/software/amazon/smithy/model/shapes/EnumShape.java
@@ -18,6 +18,7 @@ package software.amazon.smithy.model.shapes;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.LinkedHashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.Optional;
 import java.util.function.Consumer;
@@ -31,6 +32,7 @@ import software.amazon.smithy.model.traits.EnumDefinition;
 import software.amazon.smithy.model.traits.EnumTrait;
 import software.amazon.smithy.model.traits.EnumValueTrait;
 import software.amazon.smithy.model.traits.InternalTrait;
+import software.amazon.smithy.model.traits.StringListTrait;
 import software.amazon.smithy.model.traits.TagsTrait;
 import software.amazon.smithy.model.traits.Trait;
 import software.amazon.smithy.model.traits.UnitTypeTrait;
@@ -286,8 +288,16 @@ public final class EnumShape extends StringShape {
                 .orElseThrow(() -> new IllegalStateException("Enum definitions can only be made for string enums."));
         builder.value(traitValue);
         member.getTrait(DocumentationTrait.class).ifPresent(docTrait -> builder.documentation(docTrait.getValue()));
-        member.getTrait(TagsTrait.class).ifPresent(tagsTrait -> builder.tags(tagsTrait.getValues()));
         member.getTrait(DeprecatedTrait.class).ifPresent(deprecatedTrait -> builder.deprecated(true));
+
+        List<String> tags = member.getTrait(TagsTrait.class)
+                .map(StringListTrait::getValues)
+                .orElse(Collections.emptyList());
+
+        builder.tags(tags);
+        if (member.hasTrait(InternalTrait.class) && !tags.contains("internal")) {
+            builder.addTag("internal");
+        }
         return builder.build();
     }
 
diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/validation/ValidationUtils.java b/smithy-model/src/main/java/software/amazon/smithy/model/validation/ValidationUtils.java
index 7027aee32..459de686d 100644
--- a/smithy-model/src/main/java/software/amazon/smithy/model/validation/ValidationUtils.java
+++ b/smithy-model/src/main/java/software/amazon/smithy/model/validation/ValidationUtils.java
@@ -26,21 +26,97 @@ import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
+import java.util.Set;
 import java.util.StringJoiner;
 import java.util.regex.Pattern;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
+import software.amazon.smithy.model.node.Node;
 import software.amazon.smithy.model.shapes.ShapeId;
 import software.amazon.smithy.model.shapes.ToShapeId;
+import software.amazon.smithy.model.validation.validators.DefaultTraitValidator;
+import software.amazon.smithy.model.validation.validators.EnumShapeValidator;
+import software.amazon.smithy.model.validation.validators.ExclusiveStructureMemberTraitValidator;
+import software.amazon.smithy.model.validation.validators.ResourceCycleValidator;
+import software.amazon.smithy.model.validation.validators.ShapeIdConflictValidator;
+import software.amazon.smithy.model.validation.validators.SingleOperationBindingValidator;
+import software.amazon.smithy.model.validation.validators.SingleResourceBindingValidator;
+import software.amazon.smithy.model.validation.validators.TargetValidator;
+import software.amazon.smithy.model.validation.validators.TraitConflictValidator;
+import software.amazon.smithy.model.validation.validators.TraitTargetValidator;
+import software.amazon.smithy.model.validation.validators.TraitValueValidator;
+import software.amazon.smithy.utils.SetUtils;
+import software.amazon.smithy.utils.SmithyInternalApi;
 
 /**
  * Utility methods used when validating.
  */
 public final class ValidationUtils {
+
+    // Critical validators must emit validation events with names that match their class name (Name in NameValidator).
+    private static final Set<Class<? extends Validator>> CRITICAL_VALIDATORS = SetUtils.of(
+            // These validators should run before even any other validators. These validators are considered
+            // correctness validators that ensure relationships are valid and to shapes that can be found and that
+            // the model has no cycles.
+            TargetValidator.class,
+            ResourceCycleValidator.class,
+
+            // This validator is considered critical because it ensures an enum shape itself is valid.
+            EnumShapeValidator.class,
+
+            // Ensure that traits are valid, according to the model, before other validators are run.
+            ExclusiveStructureMemberTraitValidator.class,
+            TraitTargetValidator.class,
+            TraitValueValidator.class,
+            TraitConflictValidator.class,
+
+            // Defaults need to be valid.
+            DefaultTraitValidator.class,
+
+            // Ensure the semantic model adheres to the requirements of shape IDs and service shape uniqueness.
+            ShapeIdConflictValidator.class,
+            SingleOperationBindingValidator.class,
+            SingleResourceBindingValidator.class
+    );
+
     private static final Pattern CAMEL_WORD_SPLITTER = Pattern.compile("(?<!(^|[A-Z]))(?=[A-Z])|(?<!^)(?=[A-Z][a-z])");
 
     private ValidationUtils() {}
 
+    /**
+     * Checks if the given validator class is a critical validator that should be applied before other downstream
+     * validators are applied.
+     *
+     * @param validator Validator class to check.
+     * @return Returns true if the given validator is considered a critical validator.
+     */
+    @SmithyInternalApi
+    public static boolean isCriticalValidator(Class<? extends Validator> validator) {
+        return CRITICAL_VALIDATORS.contains(validator);
+    }
+
+    /**
+     * Checks if the given validation event was emitted by a critical validator.
+     *
+     * @param eventId Event ID to check if it's a critical validation event.
+     * @return Returns true if the given event was emitted from a critical validator.
+     */
+    @SmithyInternalApi
+    public static boolean isCriticalEvent(String eventId) {
+        int slice = eventId.indexOf(".");
+        if (slice > 0) {
+            eventId = eventId.substring(0, slice);
+        }
+
+        for (Class<?> c : CRITICAL_VALIDATORS) {
+            if (c.getSimpleName().replace("Validator", "").equals(eventId)) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
     /**
      * Splits a camelCase word into a list of words.
      *
@@ -89,6 +165,24 @@ public final class ValidationUtils {
         return values.map(Object::toString).sorted().collect(Collectors.joining("`, `", "`", "`"));
     }
 
+    /**
+     * Writes the contents of a Node to a pretty-printed JSON string surrounded in backticks.
+     *
+     * @param node Node to write.
+     * @return Returns a string suitable for Markdown rendering.
+     */
+    public static String tickedPrettyPrintedNode(Node node) {
+        String prettyPrinted = Node.prettyPrintJson(node);
+        if (prettyPrinted.contains(System.lineSeparator()) || prettyPrinted.contains("\n")) {
+            return String.format("%n```%n%s%n```%n", prettyPrinted);
+        } else if (prettyPrinted.startsWith("\"") && prettyPrinted.endsWith("\"")) {
+            // for pure strings, replace the quotes with backticks
+            return "`" + prettyPrinted.substring(1, prettyPrinted.length() - 1) + "`";
+        } else {
+            return "`" + prettyPrinted + "`";
+        }
+    }
+
     @Deprecated
     public static <T extends ToShapeId> Map<String, List<ShapeId>> findDuplicateShapeNames(Collection<T> shapes) {
         return shapes.stream()
diff --git a/smithy-syntax/src/main/java/software/amazon/smithy/syntax/BracketFormatter.java b/smithy-syntax/src/main/java/software/amazon/smithy/syntax/BracketFormatter.java
index dfb992fdd..68c494ff8 100644
--- a/smithy-syntax/src/main/java/software/amazon/smithy/syntax/BracketFormatter.java
+++ b/smithy-syntax/src/main/java/software/amazon/smithy/syntax/BracketFormatter.java
@@ -32,6 +32,7 @@ final class BracketFormatter {
     private Doc close = Formatter.RBRACE;
     private Collection<Doc> children;
     private boolean forceLineBreaks;
+    private boolean forceInline = false;
 
     static Function<TreeCursor, Stream<Doc>> extractor(
             Function<TreeCursor, Doc> visitor,
@@ -91,8 +92,8 @@ final class BracketFormatter {
         return false;
     }
 
-    BracketFormatter forceLineBreaks(boolean forceLineBreaks) {
-        this.forceLineBreaks = forceLineBreaks;
+    BracketFormatter forceLineBreaks() {
+        forceLineBreaks = true;
         return this;
     }
 
@@ -103,15 +104,39 @@ final class BracketFormatter {
         return this;
     }
 
+    // Don't force line breaks and don't indent inside brackets
+    BracketFormatter forceInline() {
+        forceInline = true;
+        forceLineBreaks = false;
+        return this;
+    }
+
     Doc write() {
         SmithyBuilder.requiredState("open", open);
         SmithyBuilder.requiredState("close", close);
         SmithyBuilder.requiredState("children", children);
         if (forceLineBreaks) {
             return FormatVisitor.renderBlock(open, close, Doc.intersperse(Doc.line(), children));
+        }
+
+        int indent = forceInline ? 0 : 4;
+        Doc lineBreakDoc = getBracketLineBreakDoc();
+        return Doc.intersperse(Formatter.LINE_OR_COMMA, children).bracket(indent, lineBreakDoc, open, close);
+    }
+
+    private Doc getBracketLineBreakDoc() {
+        Doc lineDoc;
+        if (!children.isEmpty() && open == Formatter.LBRACE) {
+            // Make flattened objects have space padding inside the braces
+            lineDoc = Doc.lineOrSpace();
+        } else if (children.size() > 1) {
+            // Multiple children may be separated on to different lines
+            lineDoc = Doc.lineOrEmpty();
         } else {
-            return Doc.intersperse(Formatter.LINE_OR_COMMA, children).bracket(4, Doc.lineOrEmpty(), open, close);
+            // Only one child or less doesn't need line separation
+            lineDoc = Doc.empty();
         }
+        return lineDoc;
     }
 
     private static final class Extractor implements Function<TreeCursor, Stream<Doc>> {
diff --git a/smithy-syntax/src/main/java/software/amazon/smithy/syntax/FormatVisitor.java b/smithy-syntax/src/main/java/software/amazon/smithy/syntax/FormatVisitor.java
index 59d2995ff..2df51058e 100644
--- a/smithy-syntax/src/main/java/software/amazon/smithy/syntax/FormatVisitor.java
+++ b/smithy-syntax/src/main/java/software/amazon/smithy/syntax/FormatVisitor.java
@@ -271,8 +271,7 @@ final class FormatVisitor {
                                         .close(Formatter.RBRACKET)
                                         .extractChildren(cursor, BracketFormatter.extractByType(TreeType.SHAPE_ID,
                                                                                                 this::visit))
-                                        .detectHardLines(cursor)
-                                        .forceLineBreaks(true) // always put each error on separate lines.
+                                        .forceLineBreaks() // always put each error on separate lines.
                                         .write());
             }
 
@@ -327,30 +326,46 @@ final class FormatVisitor {
                             }))
                             .detectHardLines(cursor)
                             .write();
-                } else {
-                    if (cursor.getFirstChild(TreeType.TRAIT_NODE) != null) {
-                        // Check the inner trait node for hard line breaks rather than the wrapper.
-                        TreeCursor traitNode = cursor
-                                .getFirstChild(TreeType.TRAIT_NODE)
-                                .getFirstChild(TreeType.NODE_VALUE)
-                                .getFirstChild(); // The actual node value.
-                        return new BracketFormatter()
-                                .open(Formatter.LPAREN)
-                                .close(Formatter.RPAREN)
-                                .extractChildren(cursor, BracketFormatter.extractor(this::visit, child -> {
-                                    if (child.getTree().getType() == TreeType.TRAIT_NODE) {
-                                        // Split WS and NODE_VALUE so that they appear on different lines.
-                                        return child.getChildrenByType(TreeType.NODE_VALUE, TreeType.WS).stream();
-                                    } else {
-                                        return Stream.empty();
-                                    }
-                                }))
-                                .detectHardLines(traitNode)
-                                .write();
+                } else if (cursor.getFirstChild(TreeType.TRAIT_NODE) != null) {
+                    TreeCursor traitNode = cursor.getFirstChild(TreeType.TRAIT_NODE);
+                    // Check the inner trait node for hard line breaks rather than the wrapper.
+                    TreeCursor actualTraitNodeValue = traitNode
+                            .getFirstChild(TreeType.NODE_VALUE)
+                            .getFirstChild(); // The actual node value.
+
+                    BracketFormatter formatter = new BracketFormatter()
+                            .open(Formatter.LPAREN)
+                            .close(Formatter.RPAREN)
+                            .extractChildren(cursor, BracketFormatter.extractor(this::visit, child -> {
+                                if (child.getTree().getType() == TreeType.TRAIT_NODE) {
+                                    // Split WS and NODE_VALUE so that they appear on different lines.
+                                    return child.getChildrenByType(TreeType.NODE_VALUE, TreeType.WS).stream();
+                                } else {
+                                    return Stream.empty();
+                                }
+                            }));
+
+                    // TraitBody may have leading comments and TraitNode may have trailing comments
+                    // which both require line break
+                    TreeCursor bodyWs = cursor.getFirstChild(TreeType.WS);
+                    TreeCursor traitNodeWs = traitNode.getFirstChild(TreeType.WS);
+                    if ((bodyWs != null && !bodyWs.findChildrenByType(TreeType.COMMENT).isEmpty())
+                            || (traitNodeWs != null && !traitNodeWs.findChildrenByType(TreeType.COMMENT).isEmpty())) {
+                        // Need to line break if there's a leading comment in the trait body no matter what
+                        formatter.forceLineBreaks();
+                    } else if (actualTraitNodeValue.getTree().getType() == TreeType.NODE_ARRAY
+                            || actualTraitNodeValue.getTree().getType() == TreeType.NODE_OBJECT) {
+                        // Just inline arrays and objects in trait body
+                        formatter.forceInline();
                     } else {
-                        // If the trait node is empty, remove the empty parentheses.
-                        return Doc.text("");
+                        // Any other values can use normal detection
+                        formatter.detectHardLines(cursor);
                     }
+
+                    return formatter.write();
+                } else {
+                    // If the trait node is empty, remove the empty parentheses.
+                    return Doc.text("");
                 }
             }
 
@@ -400,11 +415,17 @@ final class FormatVisitor {
             }
 
             case NODE_OBJECT: {
-                return new BracketFormatter()
+                BracketFormatter formatter = new BracketFormatter()
                         .extractChildren(cursor, BracketFormatter.extractByType(TreeType.NODE_OBJECT_KVP,
-                                                                                this::visit))
-                        .detectHardLines(cursor)
-                        .write();
+                                                                                this::visit));
+                if (cursor.getParent().getParent().getTree().getType() == TreeType.NODE_ARRAY)  {
+                    // Always break objects inside arrays if not empty
+                    formatter.forceLineBreaksIfNotEmpty();
+                } else {
+                    formatter.detectHardLines(cursor);
+                }
+                return formatter.write();
+
             }
 
             case NODE_OBJECT_KVP: {
diff --git a/smithy-syntax/src/main/java/software/amazon/smithy/syntax/SortUseStatements.java b/smithy-syntax/src/main/java/software/amazon/smithy/syntax/SortUseStatements.java
index b9e29bc06..ac7ea465c 100644
--- a/smithy-syntax/src/main/java/software/amazon/smithy/syntax/SortUseStatements.java
+++ b/smithy-syntax/src/main/java/software/amazon/smithy/syntax/SortUseStatements.java
@@ -172,22 +172,16 @@ final class SortUseStatements implements Function<TokenTree, TokenTree> {
 
             if (lineComment != null) {
                 br.appendChild(sp);
-                // Modify the position of the comment so that the formatter knows it's a trailing line comment.
-                CapturedToken updatedLineComment = lineComment.getTree()
-                        .tokens()
-                        .findFirst()
-                        .get()
-                        .toBuilder()
-                        // Set the line and column to zero so the formatter knows it's on the same line as the
-                        // statement, making it an end-of-line comment.
-                        .startLine(0)
-                        .endLine(0)
-                        .build();
-                TokenTree commentTree = TokenTree.of(TreeType.COMMENT);
-                commentTree.appendChild(TokenTree.of(updatedLineComment));
-                br.appendChild(commentTree);
+                br.appendChild(lineComment.getTree());
             } else {
-                br.appendChild(TokenTree.of(CapturedToken.builder().token(IdlToken.NEWLINE).lexeme("\n").build()));
+                br.appendChild(TokenTree.of(CapturedToken.builder()
+                        .token(IdlToken.NEWLINE)
+                        .lexeme("\n")
+                        // Set the start line to be the start line of the use statement
+                        // so the formatter  knows it's a line comment.
+                        .startLine(id.getStartLine())
+                        .endLine(id.getStartLine() + 1)
+                        .build()));
             }
 
             TokenTree ws = TokenTree.of(TreeType.WS);
diff --git a/smithy-syntax/src/main/java/software/amazon/smithy/syntax/TokenTreeLeaf.java b/smithy-syntax/src/main/java/software/amazon/smithy/syntax/TokenTreeLeaf.java
index 562e05f94..68a66c0e4 100644
--- a/smithy-syntax/src/main/java/software/amazon/smithy/syntax/TokenTreeLeaf.java
+++ b/smithy-syntax/src/main/java/software/amazon/smithy/syntax/TokenTreeLeaf.java
@@ -79,7 +79,9 @@ final class TokenTreeLeaf implements TokenTree {
         if (token.getErrorMessage() != null) {
             return token.getIdlToken() + "(" + token.getErrorMessage() + ')';
         } else {
-            return token.getIdlToken().getDebug(token.getLexeme());
+            return token.getIdlToken().getDebug(token.getLexeme())
+                    + " (" + getStartLine() + ", " + getStartColumn() + ")"
+                    + " - (" + getEndLine() + ", " + getEndColumn() + ")";
         }
     }
 
diff --git a/smithy-syntax/src/main/java/software/amazon/smithy/syntax/TokenTreeNode.java b/smithy-syntax/src/main/java/software/amazon/smithy/syntax/TokenTreeNode.java
index 3b1b2466c..ba13b52f1 100644
--- a/smithy-syntax/src/main/java/software/amazon/smithy/syntax/TokenTreeNode.java
+++ b/smithy-syntax/src/main/java/software/amazon/smithy/syntax/TokenTreeNode.java
@@ -109,22 +109,50 @@ class TokenTreeNode implements TokenTree {
 
     @Override
     public final int getStartLine() {
-        return getChildren().isEmpty() ? 0 : getChildren().get(0).getStartLine();
+        // Start line of 0 indicates an empty child, so ignore it.
+        for (TokenTree child : getChildren()) {
+            int startLine = child.getStartLine();
+            if (startLine > 0) {
+                return startLine;
+            }
+        }
+        return 0;
     }
 
     @Override
     public final int getStartColumn() {
-        return getChildren().isEmpty() ? 0 : getChildren().get(0).getStartColumn();
+        // Start column of 0 indicates an empty child, so ignore it.
+        for (TokenTree child : getChildren()) {
+            int startColumn = child.getStartColumn();
+            if (startColumn > 0) {
+                return startColumn;
+            }
+        }
+        return 0;
     }
 
     @Override
     public final int getEndLine() {
-        return children.isEmpty() ? getStartLine() : children.get(children.size() - 1).getEndLine();
+        // End line of 0 indicates an empty child, so ignore it.
+        for (int i = children.size() - 1; i >= 0; i--) {
+            int endLine = children.get(i).getEndLine();
+            if (endLine > 0) {
+                return endLine;
+            }
+        }
+        return 0;
     }
 
     @Override
     public final int getEndColumn() {
-        return children.isEmpty() ? getStartColumn() : children.get(children.size() - 1).getEndColumn();
+        // End column of 0 indicates an empty child, so ignore it.
+        for (int i = children.size() - 1; i >= 0; i--) {
+            int endColumn = children.get(i).getEndColumn();
+            if (endColumn > 0) {
+                return endColumn;
+            }
+        }
+        return 0;
     }
 
     @Override


Test patch:
diff --git a/smithy-aws-apigateway-traits/src/test/resources/software/amazon/smithy/aws/apigateway/traits/errorfiles/invalid-authorizer-target.errors b/smithy-aws-apigateway-traits/src/test/resources/software/amazon/smithy/aws/apigateway/traits/errorfiles/invalid-authorizer-target.errors
new file mode 100644
index 000000000..b39f0ed06
--- /dev/null
+++ b/smithy-aws-apigateway-traits/src/test/resources/software/amazon/smithy/aws/apigateway/traits/errorfiles/invalid-authorizer-target.errors
@@ -0,0 +1,1 @@
+[ERROR] ns.foo#SomeService: Each `scheme` of the `aws.apigateway#authorizers` trait must target one of the auth schemes applied to the service (i.e., [`aws.auth#sigv4`]). The following mappings of authorizer names to schemes are invalid: ns.foo#invalidAuth -> ns.foo#invalidAuth | AuthorizersTrait
diff --git a/smithy-aws-apigateway-traits/src/test/resources/software/amazon/smithy/aws/apigateway/traits/errorfiles/invalid-authorizer-target.json b/smithy-aws-apigateway-traits/src/test/resources/software/amazon/smithy/aws/apigateway/traits/errorfiles/invalid-authorizer-target.json
new file mode 100644
index 000000000..acb6008db
--- /dev/null
+++ b/smithy-aws-apigateway-traits/src/test/resources/software/amazon/smithy/aws/apigateway/traits/errorfiles/invalid-authorizer-target.json
@@ -0,0 +1,26 @@
+{
+    "smithy": "2.0",
+    "shapes": {
+        "ns.foo#SomeService": {
+            "type": "service",
+            "version": "2018-03-17",
+            "traits": {
+                "aws.auth#sigv4": {
+                    "name": "someservice"
+                },
+                "aws.apigateway#authorizers": {
+                    "ns.foo#invalidAuth": {
+                        "scheme": "ns.foo#invalidAuth"
+                    }
+                }
+            }
+        },
+        "ns.foo#invalidAuth": {
+            "type": "structure",
+            "traits": {
+                "smithy.api#trait": {},
+                "smithy.api#authDefinition": {}
+            }
+        }
+    }
+}
diff --git a/smithy-aws-apigateway-traits/src/test/resources/software/amazon/smithy/aws/apigateway/traits/errorfiles/invalid-authorizers.errors b/smithy-aws-apigateway-traits/src/test/resources/software/amazon/smithy/aws/apigateway/traits/errorfiles/invalid-authorizers.errors
index 05579a2d5..09b98aed9 100644
--- a/smithy-aws-apigateway-traits/src/test/resources/software/amazon/smithy/aws/apigateway/traits/errorfiles/invalid-authorizers.errors
+++ b/smithy-aws-apigateway-traits/src/test/resources/software/amazon/smithy/aws/apigateway/traits/errorfiles/invalid-authorizers.errors
@@ -1,2 +1,1 @@
-[ERROR] ns.foo#SomeService: Each `scheme` of the `aws.apigateway#authorizers` trait must target one of the auth schemes applied to the service (i.e., [`aws.auth#sigv4`]). The following mappings of authorizer names to schemes are invalid: another -> smithy.api#httpBasicAuth, invalid -> ns.foo#invalid | AuthorizersTrait
-[ERROR] ns.foo#SomeService: Error validating trait `aws.apigateway#authorizers`.invalid.scheme: The scheme of an authorizer definition must reference an auth trait | TraitValue
+[ERROR] ns.foo#SomeService: Each `scheme` of the `aws.apigateway#authorizers` trait must target one of the auth schemes applied to the service (i.e., [`aws.auth#sigv4`]). The following mappings of authorizer names to schemes are invalid: another -> smithy.api#httpBasicAuth | AuthorizersTrait
diff --git a/smithy-aws-apigateway-traits/src/test/resources/software/amazon/smithy/aws/apigateway/traits/errorfiles/invalid-authorizers.json b/smithy-aws-apigateway-traits/src/test/resources/software/amazon/smithy/aws/apigateway/traits/errorfiles/invalid-authorizers.json
index e3debf2e2..844630ffa 100644
--- a/smithy-aws-apigateway-traits/src/test/resources/software/amazon/smithy/aws/apigateway/traits/errorfiles/invalid-authorizers.json
+++ b/smithy-aws-apigateway-traits/src/test/resources/software/amazon/smithy/aws/apigateway/traits/errorfiles/invalid-authorizers.json
@@ -9,9 +9,6 @@
                     "name": "someservice"
                 },
                 "aws.apigateway#authorizers": {
-                    "invalid": {
-                        "scheme": "ns.foo#invalid"
-                    },
                     "another": {
                         "scheme": "smithy.api#httpBasicAuth",
                         "type": "token",
diff --git a/smithy-aws-cloudformation-traits/src/test/resources/software/amazon/smithy/aws/cloudformation/traits/errorfiles/additionalschemas-conflict.errors b/smithy-aws-cloudformation-traits/src/test/resources/software/amazon/smithy/aws/cloudformation/traits/errorfiles/additionalschemas-conflict.errors
index 9e324e1b4..471020f98 100644
--- a/smithy-aws-cloudformation-traits/src/test/resources/software/amazon/smithy/aws/cloudformation/traits/errorfiles/additionalschemas-conflict.errors
+++ b/smithy-aws-cloudformation-traits/src/test/resources/software/amazon/smithy/aws/cloudformation/traits/errorfiles/additionalschemas-conflict.errors
@@ -1,3 +1,2 @@
 [ERROR] smithy.example#AdditionalSchemasConflictResource: The `bar` property of the generated `AdditionalSchemasConflictResource` CloudFormation resource targets multiple shapes: [smithy.api#Boolean, smithy.api#String]. Reusing member names that target different shapes can cause confusion for users of the API. This target discrepancy must either be resolved in the model or one of the members must be excluded from the conversion. | CfnResourceProperty
-[NOTE] smithy.example#AdditionalSchemasConflictProperties: The structure shape is not connected to from any service shape. | UnreferencedShape
 [WARNING] smithy.example#AdditionalSchemasConflictResource: This shape applies a trait that is unstable: aws.cloudformation#cfnResource | UnstableTrait
diff --git a/smithy-aws-cloudformation-traits/src/test/resources/software/amazon/smithy/aws/cloudformation/traits/errorfiles/deconflict-by-excluding.errors b/smithy-aws-cloudformation-traits/src/test/resources/software/amazon/smithy/aws/cloudformation/traits/errorfiles/deconflict-by-excluding.errors
index da36a2fc3..4bda42c13 100644
--- a/smithy-aws-cloudformation-traits/src/test/resources/software/amazon/smithy/aws/cloudformation/traits/errorfiles/deconflict-by-excluding.errors
+++ b/smithy-aws-cloudformation-traits/src/test/resources/software/amazon/smithy/aws/cloudformation/traits/errorfiles/deconflict-by-excluding.errors
@@ -1,3 +1,2 @@
-[NOTE] smithy.example#AdditionalSchemasDeconflictedProperties: The structure shape is not connected to from any service shape. | UnreferencedShape
 [WARNING] smithy.example#CreateAdditionalSchemasDeconflictedResourceRequest$bar: This shape applies a trait that is unstable: aws.cloudformation#cfnExcludeProperty | UnstableTrait
 [WARNING] smithy.example#AdditionalSchemasDeconflictedResource: This shape applies a trait that is unstable: aws.cloudformation#cfnResource | UnstableTrait
diff --git a/smithy-aws-cloudformation-traits/src/test/resources/software/amazon/smithy/aws/cloudformation/traits/errorfiles/invalid-additional-schemas-shape.errors b/smithy-aws-cloudformation-traits/src/test/resources/software/amazon/smithy/aws/cloudformation/traits/errorfiles/invalid-additional-schemas-shape.errors
index 0d7f38476..3fe72f1e1 100644
--- a/smithy-aws-cloudformation-traits/src/test/resources/software/amazon/smithy/aws/cloudformation/traits/errorfiles/invalid-additional-schemas-shape.errors
+++ b/smithy-aws-cloudformation-traits/src/test/resources/software/amazon/smithy/aws/cloudformation/traits/errorfiles/invalid-additional-schemas-shape.errors
@@ -1,3 +1,2 @@
 [ERROR] smithy.example#InvalidAdditionalSchemasShapeResource: Error validating trait `aws.cloudformation#cfnResource`.additionalSchemas.0: Shape ID `smithy.example#ListShape` does not match selector `structure` | TraitValue
-[NOTE] smithy.example#ListShape: The list shape is not connected to from any service shape. | UnreferencedShape
 [WARNING] smithy.example#InvalidAdditionalSchemasShapeResource: This shape applies a trait that is unstable: aws.cloudformation#cfnResource | UnstableTrait
diff --git a/smithy-aws-cloudformation/src/test/java/software/amazon/smithy/aws/cloudformation/schema/fromsmithy/mappers/HandlerPermissionMapperTest.java b/smithy-aws-cloudformation/src/test/java/software/amazon/smithy/aws/cloudformation/schema/fromsmithy/mappers/HandlerPermissionMapperTest.java
index 410b00ec2..1bbe03ca9 100644
--- a/smithy-aws-cloudformation/src/test/java/software/amazon/smithy/aws/cloudformation/schema/fromsmithy/mappers/HandlerPermissionMapperTest.java
+++ b/smithy-aws-cloudformation/src/test/java/software/amazon/smithy/aws/cloudformation/schema/fromsmithy/mappers/HandlerPermissionMapperTest.java
@@ -58,7 +58,7 @@ public final class HandlerPermissionMapperTest {
                 containsInAnyOrder("testservice:CreateFooOperation", "otherservice:DescribeDependencyComponent"));
         assertThat(handlersDefined.get("read").expectObjectNode()
                            .expectArrayMember("permissions").getElementsAs(StringNode::getValue),
-                contains("testservice:GetFooOperation"));
+                containsInAnyOrder("testservice:GetFooOperation", "otherservice:DescribeThing"));
         assertThat(handlersDefined.get("update").expectObjectNode()
                            .expectArrayMember("permissions").getElementsAs(StringNode::getValue),
                 contains("testservice:UpdateFooOperation"));
diff --git a/smithy-aws-cloudformation/src/test/resources/software/amazon/smithy/aws/cloudformation/schema/fromsmithy/mappers/simple.smithy b/smithy-aws-cloudformation/src/test/resources/software/amazon/smithy/aws/cloudformation/schema/fromsmithy/mappers/simple.smithy
index 115fd0c2e..edcf04fec 100644
--- a/smithy-aws-cloudformation/src/test/resources/software/amazon/smithy/aws/cloudformation/schema/fromsmithy/mappers/simple.smithy
+++ b/smithy-aws-cloudformation/src/test/resources/software/amazon/smithy/aws/cloudformation/schema/fromsmithy/mappers/simple.smithy
@@ -3,6 +3,7 @@ $version: "2.0"
 namespace smithy.example
 
 use aws.cloudformation#cfnResource
+use aws.iam#iamAction
 
 service TestService {
     version: "2020-07-02",
@@ -56,6 +57,7 @@ structure CreateFooResponse {
 }
 
 @readonly
+@iamAction(requiredActions: ["otherservice:DescribeThing"])
 operation GetFooOperation {
     input: GetFooRequest,
     output: GetFooResponse,
diff --git a/smithy-aws-endpoints/src/test/java/software/amazon/smithy/rulesengine/aws/language/functions/AwsPartitionFunctionTest.java b/smithy-aws-endpoints/src/test/java/software/amazon/smithy/rulesengine/aws/language/functions/AwsPartitionFunctionTest.java
deleted file mode 100644
index 089164f93..000000000
--- a/smithy-aws-endpoints/src/test/java/software/amazon/smithy/rulesengine/aws/language/functions/AwsPartitionFunctionTest.java
+++ /dev/null
@@ -1,43 +0,0 @@
-package software.amazon.smithy.rulesengine.aws.language.functions;
-
-import static org.hamcrest.CoreMatchers.equalTo;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.not;
-
-import org.junit.jupiter.api.Test;
-import software.amazon.smithy.rulesengine.language.evaluation.RuleEvaluator;
-import software.amazon.smithy.rulesengine.language.evaluation.value.RecordValue;
-import software.amazon.smithy.rulesengine.language.syntax.expressions.Expression;
-
-public class AwsPartitionFunctionTest {
-    @Test
-    public void eval() {
-        RecordValue result = evalWithRegion("us-west-2");
-
-        assertThat(result.get(AwsPartition.DNS_SUFFIX).expectStringValue().getValue(), not(equalTo("")));
-        assertThat(result.get(AwsPartition.DUAL_STACK_DNS_SUFFIX).expectStringValue().getValue(), not(equalTo("")));
-        assertThat(result.get(AwsPartition.SUPPORTS_FIPS).expectBooleanValue().getValue(), equalTo(true));
-        assertThat(result.get(AwsPartition.SUPPORTS_DUAL_STACK).expectBooleanValue().getValue(), equalTo(true));
-        assertThat(result.get(AwsPartition.IMPLICIT_GLOBAL_REGION).expectStringValue().getValue(), not(equalTo("")));
-    }
-
-    @Test
-    public void eval_enumeratedRegion_inferredIsFalse() {
-        RecordValue result = evalWithRegion("us-west-1");
-
-        assertThat(result.get(AwsPartition.INFERRED).expectBooleanValue().getValue(), equalTo(false));
-    }
-
-    @Test
-    public void eval_regionNotEnumerated_inferredIsTrue() {
-        RecordValue result = evalWithRegion("us-west-3");
-
-        assertThat(result.get(AwsPartition.INFERRED).expectBooleanValue().getValue(), equalTo(true));
-    }
-
-
-    private RecordValue evalWithRegion(String region) {
-        AwsPartition fn = AwsPartition.ofExpressions(Expression.of(region));
-        return fn.accept(new RuleEvaluator()).expectRecordValue();
-    }
-}
diff --git a/smithy-aws-endpoints/src/test/java/software/amazon/smithy/rulesengine/aws/language/functions/AwsPartitionTest.java b/smithy-aws-endpoints/src/test/java/software/amazon/smithy/rulesengine/aws/language/functions/AwsPartitionTest.java
new file mode 100644
index 000000000..73a57e37e
--- /dev/null
+++ b/smithy-aws-endpoints/src/test/java/software/amazon/smithy/rulesengine/aws/language/functions/AwsPartitionTest.java
@@ -0,0 +1,75 @@
+package software.amazon.smithy.rulesengine.aws.language.functions;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.not;
+
+import org.junit.jupiter.api.Test;
+import software.amazon.smithy.model.node.Node;
+import software.amazon.smithy.rulesengine.aws.language.functions.partition.Partition;
+import software.amazon.smithy.rulesengine.aws.language.functions.partition.PartitionOutputs;
+import software.amazon.smithy.rulesengine.aws.language.functions.partition.Partitions;
+import software.amazon.smithy.rulesengine.language.evaluation.RuleEvaluator;
+import software.amazon.smithy.rulesengine.language.evaluation.value.RecordValue;
+import software.amazon.smithy.rulesengine.language.syntax.expressions.Expression;
+
+public class AwsPartitionTest {
+    @Test
+    public void eval() {
+        RecordValue result = evalWithRegion("us-west-2");
+
+        assertThat(result.get(AwsPartition.DNS_SUFFIX).expectStringValue().getValue(), not(equalTo("")));
+        assertThat(result.get(AwsPartition.DUAL_STACK_DNS_SUFFIX).expectStringValue().getValue(), not(equalTo("")));
+        assertThat(result.get(AwsPartition.SUPPORTS_FIPS).expectBooleanValue().getValue(), equalTo(true));
+        assertThat(result.get(AwsPartition.SUPPORTS_DUAL_STACK).expectBooleanValue().getValue(), equalTo(true));
+        assertThat(result.get(AwsPartition.IMPLICIT_GLOBAL_REGION).expectStringValue().getValue(), not(equalTo("")));
+    }
+
+    @Test
+    public void enumeratedRegionIsNotInferred() {
+        RecordValue result = evalWithRegion("us-west-1");
+
+        assertThat(result.get(AwsPartition.INFERRED).expectBooleanValue().getValue(), equalTo(false));
+    }
+
+    @Test
+    public void regionNotEnumeratedIsInferred() {
+        RecordValue result = evalWithRegion("us-west-3");
+
+        assertThat(result.get(AwsPartition.INFERRED).expectBooleanValue().getValue(), equalTo(true));
+    }
+
+    @Test
+    public void overridesPartitions() {
+        RecordValue result = evalWithRegion("us-west-1");
+        assertThat(result.get(AwsPartition.INFERRED).expectBooleanValue().getValue(), equalTo(false));
+
+        // Remove the enumerated regions, so the same region is now inferred.
+        AwsPartition.overridePartitions(Partitions.builder()
+                .addPartition(Partition.builder()
+                        .id("aws")
+                        .regionRegex("^(us|eu|ap|sa|ca|me|af)-\\w+-\\d+$")
+                        .outputs(PartitionOutputs.builder()
+                                .name("aws")
+                                .dnsSuffix("amazonaws.com")
+                                .dualStackDnsSuffix("api.aws")
+                                .supportsFips(true)
+                                .supportsDualStack(true)
+                                .implicitGlobalRegion("us-east-1")
+                                .build())
+                        .build())
+                .build());
+
+        result = evalWithRegion("us-west-1");
+        assertThat(result.get(AwsPartition.INFERRED).expectBooleanValue().getValue(), equalTo(true));
+
+        // Set the partitions back to what they were.
+        AwsPartition.overridePartitions(Partitions.fromNode(
+                        Node.parse(Partitions.class.getResourceAsStream("partitions.json"))));
+    }
+
+    private RecordValue evalWithRegion(String region) {
+        AwsPartition fn = AwsPartition.ofExpressions(Expression.of(region));
+        return fn.accept(new RuleEvaluator()).expectRecordValue();
+    }
+}
diff --git a/smithy-aws-endpoints/src/test/java/software/amazon/smithy/rulesengine/aws/traits/EndpointModifierIndexTest.java b/smithy-aws-endpoints/src/test/java/software/amazon/smithy/rulesengine/aws/traits/EndpointModifierIndexTest.java
index fa11edd67..f4da256f4 100644
--- a/smithy-aws-endpoints/src/test/java/software/amazon/smithy/rulesengine/aws/traits/EndpointModifierIndexTest.java
+++ b/smithy-aws-endpoints/src/test/java/software/amazon/smithy/rulesengine/aws/traits/EndpointModifierIndexTest.java
@@ -46,6 +46,27 @@ class EndpointModifierIndexTest {
             index.getEndpointModifierTraits(service4).get(RuleBasedEndpointsTrait.ID));
     }
 
+
+    @Test
+    public void indexSkipsLoadingTraitsWhenDefinitionIsMissing() {
+        Model model = Model.assembler()
+                .discoverModels(getClass().getClassLoader())
+                .addImport(getClass().getResource("endpointModifierIndex.smithy"))
+                .assemble()
+                .unwrap();
+
+        // Remove trait shape definition from model to verify index can skip it
+        model = model.toBuilder().removeShape(StandardRegionalEndpointsTrait.ID).build();
+
+        EndpointModifierIndex index = new EndpointModifierIndex(model);
+
+        ShapeId service1 = getServiceShapeId(model, "ns.foo#Service1");
+        ShapeId service3 = getServiceShapeId(model, "ns.foo#Service3");
+
+        assertEquals(index.getEndpointModifierTraits(service1).size(), 0);
+        assertEquals(index.getEndpointModifierTraits(service3).size(), 1);
+    }
+
     private ShapeId getServiceShapeId(Model model, String service) {
         return model
             .expectShape(ShapeId.from(service), ServiceShape.class).toShapeId();
diff --git a/smithy-aws-iam-traits/src/test/java/software/amazon/smithy/aws/iam/traits/ActionNameTraitTest.java b/smithy-aws-iam-traits/src/test/java/software/amazon/smithy/aws/iam/traits/ActionNameTraitTest.java
index 0b39e3b1c..274ad449b 100644
--- a/smithy-aws-iam-traits/src/test/java/software/amazon/smithy/aws/iam/traits/ActionNameTraitTest.java
+++ b/smithy-aws-iam-traits/src/test/java/software/amazon/smithy/aws/iam/traits/ActionNameTraitTest.java
@@ -5,16 +5,17 @@
 
 package software.amazon.smithy.aws.iam.traits;
 
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.equalTo;
+
 import org.junit.jupiter.api.Test;
 import software.amazon.smithy.model.Model;
 import software.amazon.smithy.model.shapes.Shape;
 import software.amazon.smithy.model.shapes.ShapeId;
 
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.equalTo;
-
 public class ActionNameTraitTest {
     @Test
+    @SuppressWarnings("deprecation")
     public void loadsFromModel() {
         Model result = Model.assembler()
                 .discoverModels(getClass().getClassLoader())
diff --git a/smithy-aws-iam-traits/src/test/java/software/amazon/smithy/aws/iam/traits/ActionPermissionDescriptionTraitTest.java b/smithy-aws-iam-traits/src/test/java/software/amazon/smithy/aws/iam/traits/ActionPermissionDescriptionTraitTest.java
index 426e9cada..5ab161cec 100644
--- a/smithy-aws-iam-traits/src/test/java/software/amazon/smithy/aws/iam/traits/ActionPermissionDescriptionTraitTest.java
+++ b/smithy-aws-iam-traits/src/test/java/software/amazon/smithy/aws/iam/traits/ActionPermissionDescriptionTraitTest.java
@@ -28,6 +28,7 @@ import software.amazon.smithy.model.traits.TraitFactory;
 
 public class ActionPermissionDescriptionTraitTest {
     @Test
+    @SuppressWarnings("deprecation")
     public void createsTrait() {
         Node node = Node.from("Foo baz bar");
         TraitFactory provider = TraitFactory.createServiceFactory();
diff --git a/smithy-aws-iam-traits/src/test/java/software/amazon/smithy/aws/iam/traits/IamActionTraitTest.java b/smithy-aws-iam-traits/src/test/java/software/amazon/smithy/aws/iam/traits/IamActionTraitTest.java
index e0cad6b3e..d4f40f661 100644
--- a/smithy-aws-iam-traits/src/test/java/software/amazon/smithy/aws/iam/traits/IamActionTraitTest.java
+++ b/smithy-aws-iam-traits/src/test/java/software/amazon/smithy/aws/iam/traits/IamActionTraitTest.java
@@ -18,15 +18,21 @@ package software.amazon.smithy.aws.iam.traits;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.contains;
 import static org.hamcrest.Matchers.containsInAnyOrder;
+import static org.hamcrest.Matchers.empty;
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
 import org.junit.jupiter.api.Test;
 import software.amazon.smithy.model.Model;
+import software.amazon.smithy.model.shapes.OperationShape;
 import software.amazon.smithy.model.shapes.Shape;
 import software.amazon.smithy.model.shapes.ShapeId;
+import software.amazon.smithy.model.traits.DocumentationTrait;
 
 public class IamActionTraitTest {
+    private static final ShapeId ID = ShapeId.from("smithy.example#Foo");
+
     @Test
     public void loadsFromModel() {
         Model result = Model.assembler()
@@ -35,7 +41,7 @@ public class IamActionTraitTest {
                 .assemble()
                 .unwrap();
 
-        Shape fooOperation = result.expectShape(ShapeId.from("smithy.example#Foo"));
+        Shape fooOperation = result.expectShape(ID);
 
         assertTrue(fooOperation.hasTrait(IamActionTrait.class));
         IamActionTrait trait = fooOperation.expectTrait(IamActionTrait.class);
@@ -54,4 +60,99 @@ public class IamActionTraitTest {
         assertTrue(actionResources.getOptional().containsKey("baz"));
         assertTrue(actionResources.getOptional().get("baz").getConditionKeys().isEmpty());
     }
+
+    @Test
+    @SuppressWarnings("deprecation")
+    public void prefersIamActionTraitName() {
+        OperationShape op = OperationShape.builder().id(ID)
+                .addTrait(IamActionTrait.builder().name("ThisOne").build())
+                .addTrait(new ActionNameTrait("Unused"))
+                .build();
+
+        assertEquals("ThisOne", IamActionTrait.resolveActionName(op));
+    }
+
+    @Test
+    @SuppressWarnings("deprecation")
+    public void usesDeprecatedActionNameTrait() {
+        OperationShape op = OperationShape.builder().id(ID)
+                .addTrait(new ActionNameTrait("ThisOne"))
+                .build();
+
+        assertEquals("ThisOne", IamActionTrait.resolveActionName(op));
+    }
+
+    @Test
+    public void defaultsToOperationName() {
+        OperationShape op = OperationShape.builder().id(ID).build();
+
+        assertEquals("Foo", IamActionTrait.resolveActionName(op));
+    }
+
+    @Test
+    @SuppressWarnings("deprecation")
+    public void prefersIamActionTraitDocumentation() {
+        OperationShape op = OperationShape.builder().id(ID)
+                .addTrait(IamActionTrait.builder().documentation("ThisOne").build())
+                .addTrait(new ActionPermissionDescriptionTrait("Unused"))
+                .addTrait(new DocumentationTrait("Unused"))
+                .build();
+
+        assertEquals("ThisOne", IamActionTrait.resolveActionDocumentation(op));
+    }
+
+    @Test
+    @SuppressWarnings("deprecation")
+    public void usesDeprecatedActionPermissionDescriptionTrait() {
+        OperationShape op = OperationShape.builder().id(ID)
+                .addTrait(new ActionPermissionDescriptionTrait("ThisOne"))
+                .addTrait(new DocumentationTrait("Unused"))
+                .build();
+
+        assertEquals("ThisOne", IamActionTrait.resolveActionDocumentation(op));
+    }
+
+    @Test
+    public void usesDocumentationTrait() {
+        OperationShape op = OperationShape.builder().id(ID)
+                .addTrait(new DocumentationTrait("ThisOne"))
+                .build();
+
+        assertEquals("ThisOne", IamActionTrait.resolveActionDocumentation(op));
+    }
+
+    @Test
+    public void defaultsToNoDocumentation() {
+        OperationShape op = OperationShape.builder().id(ID).build();
+
+        assertNull(IamActionTrait.resolveActionDocumentation(op));
+    }
+
+    @Test
+    @SuppressWarnings("deprecation")
+    public void prefersIamActionTraitRequiredActions() {
+        OperationShape op = OperationShape.builder().id(ID)
+                .addTrait(IamActionTrait.builder().addRequiredAction("ThisOne").build())
+                .addTrait(new ActionNameTrait("Unused"))
+                .build();
+
+        assertThat(IamActionTrait.resolveRequiredActions(op), contains("ThisOne"));
+    }
+
+    @Test
+    @SuppressWarnings("deprecation")
+    public void usesDeprecatedRequiredActionsTrait() {
+        OperationShape op = OperationShape.builder().id(ID)
+                .addTrait(RequiredActionsTrait.builder().addValue("ThisOne").build())
+                .build();
+
+        assertThat(IamActionTrait.resolveRequiredActions(op), contains("ThisOne"));
+    }
+
+    @Test
+    public void defaultsToEmptyRequiredActions() {
+        OperationShape op = OperationShape.builder().id(ID).build();
+
+        assertThat(IamActionTrait.resolveRequiredActions(op), empty());
+    }
 }
diff --git a/smithy-aws-iam-traits/src/test/java/software/amazon/smithy/aws/iam/traits/IamResourceTraitTest.java b/smithy-aws-iam-traits/src/test/java/software/amazon/smithy/aws/iam/traits/IamResourceTraitTest.java
index dc03eb967..eceacac1a 100644
--- a/smithy-aws-iam-traits/src/test/java/software/amazon/smithy/aws/iam/traits/IamResourceTraitTest.java
+++ b/smithy-aws-iam-traits/src/test/java/software/amazon/smithy/aws/iam/traits/IamResourceTraitTest.java
@@ -21,10 +21,13 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
 
 import org.junit.jupiter.api.Test;
 import software.amazon.smithy.model.Model;
+import software.amazon.smithy.model.shapes.ResourceShape;
 import software.amazon.smithy.model.shapes.Shape;
 import software.amazon.smithy.model.shapes.ShapeId;
 
 public class IamResourceTraitTest {
+    private static final ShapeId ID = ShapeId.from("smithy.example#SuperResource");
+
     @Test
     public void loadsFromModel() {
         Model result = Model.assembler()
@@ -33,11 +36,27 @@ public class IamResourceTraitTest {
                 .assemble()
                 .unwrap();
 
-        Shape superResource = result.expectShape(ShapeId.from("smithy.example#SuperResource"));
+        Shape superResource = result.expectShape(ID);
 
         assertTrue(superResource.hasTrait(IamResourceTrait.class));
         assertEquals(superResource.expectTrait(IamResourceTrait.class).getName().get(), "super");
         assertEquals(superResource.expectTrait(IamResourceTrait.class).getRelativeDocumentation().get(), "API-Super.html");
         assertFalse(superResource.expectTrait(IamResourceTrait.class).isDisableConditionKeyInheritance());
     }
+
+    @Test
+    public void prefersIamResourceTraitName() {
+        ResourceShape resource = ResourceShape.builder().id(ID)
+                .addTrait(IamResourceTrait.builder().name("ThisOne").build())
+                .build();
+
+        assertEquals("ThisOne", IamResourceTrait.resolveResourceName(resource));
+    }
+
+    @Test
+    public void defaultsToResourceName() {
+        ResourceShape resource = ResourceShape.builder().id(ID).build();
+
+        assertEquals("SuperResource", IamResourceTrait.resolveResourceName(resource));
+    }
 }
diff --git a/smithy-aws-iam-traits/src/test/java/software/amazon/smithy/aws/iam/traits/RequiredActionsTraitTest.java b/smithy-aws-iam-traits/src/test/java/software/amazon/smithy/aws/iam/traits/RequiredActionsTraitTest.java
index aba961b71..d4b2569d2 100644
--- a/smithy-aws-iam-traits/src/test/java/software/amazon/smithy/aws/iam/traits/RequiredActionsTraitTest.java
+++ b/smithy-aws-iam-traits/src/test/java/software/amazon/smithy/aws/iam/traits/RequiredActionsTraitTest.java
@@ -26,6 +26,7 @@ import software.amazon.smithy.model.shapes.ShapeId;
 
 public class RequiredActionsTraitTest {
     @Test
+    @SuppressWarnings("deprecation")
     public void loadsFromModel() {
         Model result = Model.assembler()
                 .discoverModels(getClass().getClassLoader())
diff --git a/smithy-aws-protocol-tests/model/awsJson1_1/main.smithy b/smithy-aws-protocol-tests/model/awsJson1_1/main.smithy
index 19e660cc3..380b2ab8a 100644
--- a/smithy-aws-protocol-tests/model/awsJson1_1/main.smithy
+++ b/smithy-aws-protocol-tests/model/awsJson1_1/main.smithy
@@ -14,7 +14,7 @@ use smithy.test#httpResponseTests
     cloudFormationName: "JsonProtocol",
     cloudTrailEventSource: "jsonprotocol.amazonaws.com",
 )
-@sigv4(name: "foo")
+@sigv4(name: "jsonprotocol")
 @awsJson1_1
 @title("Sample Json 1.1 Protocol Service")
 service JsonProtocol {
diff --git a/smithy-build/src/test/java/software/amazon/smithy/build/transforms/FlattenNamespacesTest.java b/smithy-build/src/test/java/software/amazon/smithy/build/transforms/FlattenNamespacesTest.java
index 681b83fda..4f530e210 100644
--- a/smithy-build/src/test/java/software/amazon/smithy/build/transforms/FlattenNamespacesTest.java
+++ b/smithy-build/src/test/java/software/amazon/smithy/build/transforms/FlattenNamespacesTest.java
@@ -16,11 +16,13 @@
 package software.amazon.smithy.build.transforms;
 
 import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.anEmptyMap;
 import static org.hamcrest.Matchers.containsInAnyOrder;
 import static org.hamcrest.Matchers.not;
 
 import java.nio.file.Paths;
 import java.util.List;
+import java.util.Optional;
 import java.util.stream.Collectors;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
@@ -31,7 +33,11 @@ import software.amazon.smithy.model.loader.Prelude;
 import software.amazon.smithy.model.node.ExpectationNotMetException;
 import software.amazon.smithy.model.node.Node;
 import software.amazon.smithy.model.node.ObjectNode;
+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.ValidatedResult;
+import software.amazon.smithy.model.validation.ValidationEvent;
 import software.amazon.smithy.utils.FunctionalUtils;
 
 public class FlattenNamespacesTest {
@@ -143,6 +149,52 @@ public class FlattenNamespacesTest {
                 "ns.bar#Widget", "foo.example#Widget")));
     }
 
+    @Test
+    public void removesServiceRenames() {
+        Model model = Model.assembler()
+                .addImport(getClass().getResource("flatten-namespaces-with-renames.json"))
+                .assemble()
+                .unwrap();
+        TransformContext context = TransformContext.builder()
+                .model(model)
+                .settings(Node.objectNode()
+                        .withMember("namespace", "ns.qux")
+                        .withMember("service", "ns.foo#MyService"))
+                .build();
+
+        Model result = new FlattenNamespaces().transform(context);
+        ShapeId transformedServiceId = ShapeId.from("ns.qux#MyService");
+
+        assertThat(result.getShape(transformedServiceId), not(Optional.empty()));
+        assertThat(result.expectShape(transformedServiceId, ServiceShape.class).getRename(), anEmptyMap());
+    }
+
+    @Test
+    public void serviceShapeIsValidAfterTransform() {
+        Model model = Model.assembler()
+                .addImport(getClass().getResource("flatten-namespaces-with-renames.json"))
+                .assemble()
+                .unwrap();
+        TransformContext context = TransformContext.builder()
+                .model(model)
+                .settings(Node.objectNode()
+                        .withMember("namespace", "ns.qux")
+                        .withMember("service", "ns.foo#MyService"))
+                .build();
+
+        Model result = new FlattenNamespaces().transform(context);
+        ValidatedResult<Model> validatedResult = Model.assembler()
+                .addModel(result)
+                .assemble();
+        List<ShapeId> validationEventShapeIds = validatedResult.getValidationEvents().stream()
+                .map(ValidationEvent::getShapeId)
+                .filter(Optional::isPresent)
+                .map(Optional::get)
+                .collect(Collectors.toList());
+
+        assertThat(validationEventShapeIds, not(containsInAnyOrder(ShapeId.from("ns.qux#MyService"))));
+    }
+
     @Test
     public void throwsWhenServiceIsNotConfigured() {
         Model model = Model.assembler()
diff --git a/smithy-cli/src/it/java/software/amazon/smithy/cli/FormatCommandTest.java b/smithy-cli/src/it/java/software/amazon/smithy/cli/FormatCommandTest.java
index efc888aac..da96fdd5a 100644
--- a/smithy-cli/src/it/java/software/amazon/smithy/cli/FormatCommandTest.java
+++ b/smithy-cli/src/it/java/software/amazon/smithy/cli/FormatCommandTest.java
@@ -32,14 +32,14 @@ public class FormatCommandTest {
         IntegUtils.run("bad-formatting", ListUtils.of("format", "model/other.smithy"), result -> {
             assertThat(result.getExitCode(), equalTo(0));
 
-            String model = result.getFile("model/other.smithy").replace("\r\n", "\n");
-            assertThat(model, equalTo("$version: \"2.0\"\n"
-                                      + "\n"
-                                      + "namespace smithy.example\n"
-                                      + "\n"
-                                      + "string MyString\n"
-                                      + "\n"
-                                      + "string MyString2\n"));
+            String model = result.getFile("model/other.smithy");
+            assertThat(model, equalTo(String.format("$version: \"2.0\"%n"
+                                      + "%n"
+                                      + "namespace smithy.example%n"
+                                      + "%n"
+                                      + "string MyString%n"
+                                      + "%n"
+                                      + "string MyString2%n")));
         });
     }
 
@@ -48,24 +48,24 @@ public class FormatCommandTest {
         IntegUtils.run("bad-formatting", ListUtils.of("format", "model"), result -> {
             assertThat(result.getExitCode(), equalTo(0));
 
-            String main = result.getFile("model/main.smithy").replace("\r\n", "\n");
-            assertThat(main, equalTo("$version: \"2.0\"\n"
-                                     + "\n"
-                                     + "metadata this_is_a_long_string = {\n"
-                                     + "    this_is_a_long_string1: \"a\"\n"
-                                     + "    this_is_a_long_string2: \"b\"\n"
-                                     + "    this_is_a_long_string3: \"c\"\n"
-                                     + "    this_is_a_long_string4: \"d\"\n"
-                                     + "}\n"));
+            String main = result.getFile("model/main.smithy");
+            assertThat(main, equalTo(String.format("$version: \"2.0\"%n"
+                                     + "%n"
+                                     + "metadata this_is_a_long_string = {%n"
+                                     + "    this_is_a_long_string1: \"a\"%n"
+                                     + "    this_is_a_long_string2: \"b\"%n"
+                                     + "    this_is_a_long_string3: \"c\"%n"
+                                     + "    this_is_a_long_string4: \"d\"%n"
+                                     + "}%n")));
 
-            String other = result.getFile("model/other.smithy").replace("\r\n", "\n");
-            assertThat(other, equalTo("$version: \"2.0\"\n"
-                                      + "\n"
-                                      + "namespace smithy.example\n"
-                                      + "\n"
-                                      + "string MyString\n"
-                                      + "\n"
-                                      + "string MyString2\n"));
+            String other = result.getFile("model/other.smithy");
+            assertThat(other, equalTo(String.format("$version: \"2.0\"%n"
+                                      + "%n"
+                                      + "namespace smithy.example%n"
+                                      + "%n"
+                                      + "string MyString%n"
+                                      + "%n"
+                                      + "string MyString2%n")));
         });
     }
 }
diff --git a/smithy-cli/src/test/java/software/amazon/smithy/cli/commands/ValidateCommandTest.java b/smithy-cli/src/test/java/software/amazon/smithy/cli/commands/ValidateCommandTest.java
index efb9551a8..b3f2fcf32 100644
--- a/smithy-cli/src/test/java/software/amazon/smithy/cli/commands/ValidateCommandTest.java
+++ b/smithy-cli/src/test/java/software/amazon/smithy/cli/commands/ValidateCommandTest.java
@@ -87,7 +87,7 @@ public class ValidateCommandTest {
         assertThat(result, containsString("EmitNotes"));
         assertThat(result, containsString("EmitWarnings"));
         assertThat(result, containsString("EmitDangers"));
-        assertThat(result, containsString("TraitTarget"));
+        assertThat(result, containsString("HttpLabelTrait"));
     }
 
     @Test
@@ -99,7 +99,7 @@ public class ValidateCommandTest {
         assertThat(result, containsString("─ EmitNotes"));
         assertThat(result, containsString("─ EmitWarnings"));
         assertThat(result, containsString("─ EmitDangers"));
-        assertThat(result, containsString("─ TraitTarget"));
+        assertThat(result, containsString("─ HttpLabelTrait"));
     }
 
     @Test
@@ -111,7 +111,7 @@ public class ValidateCommandTest {
         assertThat(result, not(containsString("EmitNotes")));
         assertThat(result, containsString("EmitWarnings"));
         assertThat(result, containsString("EmitDangers"));
-        assertThat(result, containsString("TraitTarget"));
+        assertThat(result, containsString("HttpLabelTrait"));
     }
 
     @Test
@@ -123,7 +123,7 @@ public class ValidateCommandTest {
         assertThat(result, not(containsString("EmitNotes")));
         assertThat(result, not(containsString("EmitWarnings")));
         assertThat(result, containsString("EmitDangers"));
-        assertThat(result, containsString("TraitTarget"));
+        assertThat(result, containsString("HttpLabelTrait"));
     }
 
     @Test
@@ -135,7 +135,7 @@ public class ValidateCommandTest {
         assertThat(result, not(containsString("EmitNotes")));
         assertThat(result, not(containsString("EmitWarnings")));
         assertThat(result, not(containsString("EmitDangers")));
-        assertThat(result, containsString("TraitTarget"));
+        assertThat(result, containsString("HttpLabelTrait"));
     }
 
     private CliUtils.Result runValidationEventsTest(Severity severity) throws Exception {
diff --git a/smithy-cli/src/test/resources/software/amazon/smithy/cli/commands/validation-events.smithy b/smithy-cli/src/test/resources/software/amazon/smithy/cli/commands/validation-events.smithy
index 8060a6231..b159b690f 100644
--- a/smithy-cli/src/test/resources/software/amazon/smithy/cli/commands/validation-events.smithy
+++ b/smithy-cli/src/test/resources/software/amazon/smithy/cli/commands/validation-events.smithy
@@ -46,5 +46,7 @@ string Warning
 
 string Danger
 
-@required // this trait is invalid and causes an error.
-string Error
+// The uri will trigger an ERROR.
+@http(method: "GET", uri: "/hi/{missingLabel}")
+@readonly
+operation Error {}
diff --git a/smithy-diff/src/test/java/software/amazon/smithy/diff/ModelDiffTest.java b/smithy-diff/src/test/java/software/amazon/smithy/diff/ModelDiffTest.java
index cbc22a416..307dacbe9 100644
--- a/smithy-diff/src/test/java/software/amazon/smithy/diff/ModelDiffTest.java
+++ b/smithy-diff/src/test/java/software/amazon/smithy/diff/ModelDiffTest.java
@@ -11,6 +11,7 @@ import org.junit.jupiter.api.Test;
 import software.amazon.smithy.model.Model;
 import software.amazon.smithy.model.node.Node;
 import software.amazon.smithy.model.shapes.StringShape;
+import software.amazon.smithy.model.shapes.StructureShape;
 import software.amazon.smithy.model.traits.SensitiveTrait;
 import software.amazon.smithy.model.validation.Severity;
 import software.amazon.smithy.model.validation.ValidatedResult;
@@ -77,7 +78,7 @@ public class ModelDiffTest {
     @Test
     public void findsBreakingChanges() {
         Model oldModel = Model.builder()
-                .addShape(StringShape.builder().id("smithy.example#Str").build())
+                .addShape(StructureShape.builder().id("smithy.example#Str").build())
                 .build();
         Model newModel = Model.builder().build();
         ModelDiff.Result result = ModelDiff.builder().oldModel(oldModel).newModel(newModel).compare();
diff --git a/smithy-diff/src/test/java/software/amazon/smithy/diff/evaluators/ChangedPaginatedTraitTest.java b/smithy-diff/src/test/java/software/amazon/smithy/diff/evaluators/ChangedPaginatedTraitTest.java
index de67316c2..71b0f5bb0 100644
--- a/smithy-diff/src/test/java/software/amazon/smithy/diff/evaluators/ChangedPaginatedTraitTest.java
+++ b/smithy-diff/src/test/java/software/amazon/smithy/diff/evaluators/ChangedPaginatedTraitTest.java
@@ -74,7 +74,7 @@ public class ChangedPaginatedTraitTest {
 
         assertThat(changedTraitEvents.size(), equalTo(1));
         assertThat(changedTraitEvents.get(0).getMessage(),
-                   containsString("Removed trait contents from `smithy.api#paginated` at path `/items`. Removed value: \"things\""));
+                   containsString("Removed trait contents from `smithy.api#paginated` at path `/items`. Removed value: `things`"));
     }
 
     @Test
@@ -91,7 +91,7 @@ public class ChangedPaginatedTraitTest {
 
         assertThat(changedTraitEvents.size(), equalTo(1));
         assertThat(changedTraitEvents.get(0).getMessage(),
-                   containsString("Added trait contents to `smithy.api#paginated` at path `/items` with value \"things\""));
+                   containsString("Added trait contents to `smithy.api#paginated` at path `/items` with value `things`"));
     }
 
     @Test
@@ -108,7 +108,7 @@ public class ChangedPaginatedTraitTest {
 
         assertThat(changedTraitEvents.size(), equalTo(1));
         assertThat(changedTraitEvents.get(0).getMessage(),
-                   containsString("Changed trait contents of `smithy.api#paginated` at path `/items` from \"things\" to \"otherThings\""));
+                   containsString("Changed trait contents of `smithy.api#paginated` at path `/items` from `things` to `otherThings`"));
     }
 
     @Test
@@ -125,7 +125,7 @@ public class ChangedPaginatedTraitTest {
 
         assertThat(changedTraitEvents.size(), equalTo(1));
         assertThat(changedTraitEvents.get(0).getMessage(),
-                   containsString("Removed trait contents from `smithy.api#paginated` at path `/pageSize`. Removed value: \"maxResults\""));
+                   containsString("Removed trait contents from `smithy.api#paginated` at path `/pageSize`. Removed value: `maxResults`"));
     }
 
     @Test
@@ -157,7 +157,7 @@ public class ChangedPaginatedTraitTest {
 
         assertThat(changedTraitEvents.size(), equalTo(1));
         assertThat(changedTraitEvents.get(0).getMessage(),
-                   containsString("Changed trait contents of `smithy.api#paginated` at path `/pageSize` from \"maxResults\" to \"otherMaxResults\""));
+                   containsString("Changed trait contents of `smithy.api#paginated` at path `/pageSize` from `maxResults` to `otherMaxResults`"));
     }
 
     @Test
@@ -173,7 +173,7 @@ public class ChangedPaginatedTraitTest {
 
         assertThat(changedTraitEvents.size(), equalTo(1));
         assertThat(changedTraitEvents.get(0).getMessage(),
-                   containsString("Changed trait contents of `smithy.api#paginated` at path `/inputToken` from \"token\" to \"otherToken\""));
+                   containsString("Changed trait contents of `smithy.api#paginated` at path `/inputToken` from `token` to `otherToken`"));
     }
 
     @Test
@@ -189,6 +189,6 @@ public class ChangedPaginatedTraitTest {
 
         assertThat(changedTraitEvents.size(), equalTo(1));
         assertThat(changedTraitEvents.get(0).getMessage(),
-                   containsString("Changed trait contents of `smithy.api#paginated` at path `/outputToken` from \"token\" to \"otherToken\""));
+                   containsString("Changed trait contents of `smithy.api#paginated` at path `/outputToken` from `token` to `otherToken`"));
     }
 }
diff --git a/smithy-diff/src/test/java/software/amazon/smithy/diff/evaluators/ModifiedTraitTest.java b/smithy-diff/src/test/java/software/amazon/smithy/diff/evaluators/ModifiedTraitTest.java
index e27be2901..5e0aa9f6c 100644
--- a/smithy-diff/src/test/java/software/amazon/smithy/diff/evaluators/ModifiedTraitTest.java
+++ b/smithy-diff/src/test/java/software/amazon/smithy/diff/evaluators/ModifiedTraitTest.java
@@ -160,9 +160,9 @@ public class ModifiedTraitTest {
                 equalTo(2L));
 
         assertThat(messages, containsInAnyOrder(
-                "Changed trait `smithy.example#b` from \"hello\" to \"hello!\"",
-                "Removed trait `smithy.example#a`. Previous trait value: {}",
-                "Added trait `smithy.example#c` with value \"foo\""
+                "Changed trait `smithy.example#b` from `hello` to `hello!`",
+                "Removed trait `smithy.example#a`. Previous trait value: `{}`",
+                "Added trait `smithy.example#c` with value `foo`"
         ));
     }
 
@@ -186,8 +186,8 @@ public class ModifiedTraitTest {
                 equalTo(2L));
 
         assertThat(messages, containsInAnyOrder(
-                "Added trait contents to `smithy.example#aTrait` at path `/bar` with value \"no\"",
-                "Changed trait contents of `smithy.example#aTrait` at path `/baz/foo` from \"bye\" to \"adios\""
+                "Added trait contents to `smithy.example#aTrait` at path `/bar` with value `no`",
+                "Changed trait contents of `smithy.example#aTrait` at path `/baz/foo` from `bye` to `adios`"
         ));
     }
 
@@ -210,11 +210,17 @@ public class ModifiedTraitTest {
         assertThat(events.stream().filter(e -> !e.getMessage().contains("Removed"))
                 .filter(e -> e.getSourceLocation().getFilename().endsWith("b.smithy")).count(), equalTo(2L));
         assertThat(messages, containsInAnyOrder(
-                "Changed trait contents of `smithy.example#aTrait` at path `/foo/1` from \"b\" to \"B\"",
-                "Added trait contents to `smithy.example#aTrait` at path `/foo/3` with value \"4\"",
-                "Removed trait contents from `smithy.example#aTrait` at path `/foo/2`. Removed value: \"3\"",
-                "Removed trait contents from `smithy.example#aTrait` at path `/foo`. Removed value: "
-                + "[\n    \"1\",\n    \"2\",\n    \"3\"\n]"
+                "Changed trait contents of `smithy.example#aTrait` at path `/foo/1` from `b` to `B`",
+                "Added trait contents to `smithy.example#aTrait` at path `/foo/3` with value `4`",
+                "Removed trait contents from `smithy.example#aTrait` at path `/foo/2`. Removed value: `3`",
+                String.format("Removed trait contents from `smithy.example#aTrait` at path `/foo`. Removed value: %n"
+                        + "```%n"
+                        + "[%n"
+                        + "    \"1\",%n"
+                        + "    \"2\",%n"
+                        + "    \"3\"%n"
+                        + "]%n"
+                        + "```%n")
         ));
     }
 
@@ -237,10 +243,18 @@ public class ModifiedTraitTest {
         assertThat(events.stream().filter(e -> !e.getMessage().contains("Removed"))
                 .filter(e -> e.getSourceLocation().getFilename().endsWith("b.smithy")).count(), equalTo(2L));
         assertThat(messages, containsInAnyOrder(
-                "Changed trait contents of `smithy.example#aTrait` at path `/foo/1` from \"b\" to \"B\"",
-                "Added trait contents to `smithy.example#aTrait` at path `/foo/3` with value \"4\"",
-                "Removed trait contents from `smithy.example#aTrait` at path `/foo/2`. Removed value: \"3\"",
-                "Removed trait contents from `smithy.example#aTrait` at path `/foo`. Removed value: [\n    \"1\",\n    \"2\",\n    \"3\"\n]"
+                "Changed trait contents of `smithy.example#aTrait` at path `/foo/1` from `b` to `B`",
+                "Added trait contents to `smithy.example#aTrait` at path `/foo/3` with value `4`",
+                "Removed trait contents from `smithy.example#aTrait` at path `/foo/2`. Removed value: `3`",
+                String.format("Removed trait contents from `smithy.example#aTrait` at path `/foo`. "
+                        + "Removed value: %n"
+                        + "```%n"
+                        + "[%n"
+                        + "    \"1\",%n"
+                        + "    \"2\",%n"
+                        + "    \"3\"%n"
+                        + "]%n"
+                        + "```%n")
         ));
     }
 
@@ -263,10 +277,18 @@ public class ModifiedTraitTest {
         assertThat(events.stream().filter(e -> !e.getMessage().contains("Removed"))
                 .filter(e -> e.getSourceLocation().getFilename().endsWith("b.smithy")).count(), equalTo(2L));
         assertThat(messages, containsInAnyOrder(
-                "Changed trait contents of `smithy.example#aTrait` at path `/foo/bam` from \"b\" to \"B\"",
-                "Removed trait contents from `smithy.example#aTrait` at path `/foo`. Removed value: {\n    \"baz\": \"1\",\n    \"bam\": \"2\",\n    \"boo\": \"3\"\n}",
-                "Added trait contents to `smithy.example#aTrait` at path `/foo/qux` with value \"4\"",
-                "Removed trait contents from `smithy.example#aTrait` at path `/foo/boo`. Removed value: \"3\""
+                "Changed trait contents of `smithy.example#aTrait` at path `/foo/bam` from `b` to `B`",
+                String.format("Removed trait contents from `smithy.example#aTrait` at path `/foo`. "
+                        + "Removed value: %n"
+                        + "```%n"
+                        + "{%n"
+                        + "    \"baz\": \"1\",%n"
+                        + "    \"bam\": \"2\",%n"
+                        + "    \"boo\": \"3\"%n"
+                        + "}%n"
+                        + "```%n"),
+                "Added trait contents to `smithy.example#aTrait` at path `/foo/qux` with value `4`",
+                "Removed trait contents from `smithy.example#aTrait` at path `/foo/boo`. Removed value: `3`"
         ));
     }
 
@@ -287,8 +309,8 @@ public class ModifiedTraitTest {
         assertThat(events.stream().filter(e -> e.getSourceLocation().getFilename().endsWith("b.smithy")).count(),
                 equalTo(2L));
         assertThat(messages, containsInAnyOrder(
-                "Changed trait contents of `smithy.example#aTrait` at path `/baz/foo` from \"a\" to \"b\"",
-                "Changed trait contents of `smithy.example#aTrait` at path `/baz/baz` from \"a\" to \"b\""
+                "Changed trait contents of `smithy.example#aTrait` at path `/baz/foo` from `a` to `b`",
+                "Changed trait contents of `smithy.example#aTrait` at path `/baz/baz` from `a` to `b`"
         ));
     }
 
@@ -311,18 +333,18 @@ public class ModifiedTraitTest {
         assertThat(events.stream().filter(e -> e.getMessage().contains("Changed") || e.getMessage().contains("Added"))
                 .filter(e -> e.getSourceLocation().getFilename().endsWith("b.smithy")).count(), equalTo(9L));
         assertThat(messages, containsInAnyOrder(
-                "Added trait contents to `smithy.example#aTrait` at path `/a` with value \"a\"",
-                "Removed trait contents from `smithy.example#aTrait` at path `/b`. Removed value: \"a\"",
-                "Changed trait contents of `smithy.example#aTrait` at path `/c` from \"a\" to \"c\"",
-                "Changed trait contents of `smithy.example#aTrait` at path `/d` from \"a\" to \"d\"",
-                "Added trait contents to `smithy.example#aTrait` at path `/e` with value \"a\"",
-                "Removed trait contents from `smithy.example#aTrait` at path `/f`. Removed value: \"a\"",
-                "Changed trait contents of `smithy.example#aTrait` at path `/g` from \"a\" to \"h\"",
-                "Changed trait contents of `smithy.example#aTrait` at path `/h` from \"a\" to \"h\"",
-                "Added trait contents to `smithy.example#aTrait` at path `/i` with value \"a\"",
-                "Removed trait contents from `smithy.example#aTrait` at path `/j`. Removed value: \"a\"",
-                "Changed trait contents of `smithy.example#aTrait` at path `/k` from \"a\" to \"k\"",
-                "Changed trait contents of `smithy.example#aTrait` at path `/l` from \"a\" to \"l\""
+                "Added trait contents to `smithy.example#aTrait` at path `/a` with value `a`",
+                "Removed trait contents from `smithy.example#aTrait` at path `/b`. Removed value: `a`",
+                "Changed trait contents of `smithy.example#aTrait` at path `/c` from `a` to `c`",
+                "Changed trait contents of `smithy.example#aTrait` at path `/d` from `a` to `d`",
+                "Added trait contents to `smithy.example#aTrait` at path `/e` with value `a`",
+                "Removed trait contents from `smithy.example#aTrait` at path `/f`. Removed value: `a`",
+                "Changed trait contents of `smithy.example#aTrait` at path `/g` from `a` to `h`",
+                "Changed trait contents of `smithy.example#aTrait` at path `/h` from `a` to `h`",
+                "Added trait contents to `smithy.example#aTrait` at path `/i` with value `a`",
+                "Removed trait contents from `smithy.example#aTrait` at path `/j`. Removed value: `a`",
+                "Changed trait contents of `smithy.example#aTrait` at path `/k` from `a` to `k`",
+                "Changed trait contents of `smithy.example#aTrait` at path `/l` from `a` to `l`"
         ));
     }
 
diff --git a/smithy-diff/src/test/java/software/amazon/smithy/diff/evaluators/RemovedShapeTest.java b/smithy-diff/src/test/java/software/amazon/smithy/diff/evaluators/RemovedShapeTest.java
index 7cae1ff63..04b187e6c 100644
--- a/smithy-diff/src/test/java/software/amazon/smithy/diff/evaluators/RemovedShapeTest.java
+++ b/smithy-diff/src/test/java/software/amazon/smithy/diff/evaluators/RemovedShapeTest.java
@@ -23,10 +23,26 @@ import java.util.List;
 import org.junit.jupiter.api.Test;
 import software.amazon.smithy.diff.ModelDiff;
 import software.amazon.smithy.model.Model;
+import software.amazon.smithy.model.shapes.BigDecimalShape;
+import software.amazon.smithy.model.shapes.BigIntegerShape;
+import software.amazon.smithy.model.shapes.BlobShape;
+import software.amazon.smithy.model.shapes.BooleanShape;
+import software.amazon.smithy.model.shapes.ByteShape;
+import software.amazon.smithy.model.shapes.DoubleShape;
+import software.amazon.smithy.model.shapes.EnumShape;
+import software.amazon.smithy.model.shapes.FloatShape;
+import software.amazon.smithy.model.shapes.IntEnumShape;
+import software.amazon.smithy.model.shapes.IntegerShape;
 import software.amazon.smithy.model.shapes.ListShape;
+import software.amazon.smithy.model.shapes.LongShape;
 import software.amazon.smithy.model.shapes.MemberShape;
 import software.amazon.smithy.model.shapes.Shape;
+import software.amazon.smithy.model.shapes.ShortShape;
 import software.amazon.smithy.model.shapes.StringShape;
+import software.amazon.smithy.model.shapes.StructureShape;
+import software.amazon.smithy.model.shapes.TimestampShape;
+import software.amazon.smithy.model.traits.EnumDefinition;
+import software.amazon.smithy.model.traits.EnumTrait;
 import software.amazon.smithy.model.traits.PrivateTrait;
 import software.amazon.smithy.model.validation.Severity;
 import software.amazon.smithy.model.validation.ValidationEvent;
@@ -34,8 +50,8 @@ import software.amazon.smithy.model.validation.ValidationEvent;
 public class RemovedShapeTest {
     @Test
     public void detectsShapeRemoval() {
-        Shape shapeA1 = StringShape.builder().id("foo.baz#Baz").build();
-        Shape shapeB1 = StringShape.builder().id("foo.baz#Bam").build();
+        Shape shapeA1 = StructureShape.builder().id("foo.baz#Baz").build();
+        Shape shapeB1 = StructureShape.builder().id("foo.baz#Bam").build();
         Model modelA = Model.assembler().addShapes(shapeA1, shapeB1).assemble().unwrap();
         Model modelB = Model.assembler().addShapes(shapeB1).assemble().unwrap();
         List<ValidationEvent> events = ModelDiff.compare(modelA, modelB);
@@ -45,6 +61,61 @@ public class RemovedShapeTest {
         assertThat(TestHelper.findEvents(events, Severity.ERROR).size(), equalTo(1));
     }
 
+    @Test
+    public void emitsWarningsForScalarShapes() {
+        Shape[] scalarShapes = new Shape[] {
+                IntegerShape.builder().id("foo.baz#BazOne").build(),
+                BigDecimalShape.builder().id("foo.baz#BazTwo").build(),
+                BigIntegerShape.builder().id("foo.baz#BazThree").build(),
+                BlobShape.builder().id("foo.baz#BazFour").build(),
+                BooleanShape.builder().id("foo.baz#BazFive").build(),
+                ByteShape.builder().id("foo.baz#BazSix").build(),
+                DoubleShape.builder().id("foo.baz#BazSeven").build(),
+                FloatShape.builder().id("foo.baz#BazEight").build(),
+                ShortShape.builder().id("foo.baz#BazNine").build(),
+                TimestampShape.builder().id("foo.baz#BazTen").build(),
+                LongShape.builder().id("foo.baz#BazEleven").build(),
+                StringShape.builder().id("foo.baz#BazTwelve").build()
+        };
+        Model modelA = Model.assembler().addShapes(scalarShapes).assemble().unwrap();
+        Model modelB = Model.assembler().assemble().unwrap();
+        List<ValidationEvent> events = ModelDiff.compare(modelA, modelB);
+
+        assertThat(TestHelper.findEvents(events, "RemovedShape.ScalarShape").size(), equalTo(12));
+        assertThat("Scalar removals should be WARNING severity",
+                events.stream().allMatch(event -> Severity.WARNING.equals(event.getSeverity())));
+    }
+
+    @Test
+    public void emitsErrorForEnumString() {
+        Shape shapeA1 = StringShape.builder()
+                .id("foo.baz#Baz")
+                .addTrait(EnumTrait.builder().addEnum(EnumDefinition.builder().value("val").build()).build())
+                .build();
+        Model modelA = Model.assembler().addShapes(shapeA1).assemble().unwrap();
+        Model modelB = Model.assembler().addShapes().assemble().unwrap();
+        List<ValidationEvent> events = ModelDiff.compare(modelA, modelB);
+
+        assertThat(TestHelper.findEvents(events, "RemovedShape").size(), equalTo(1));
+        assertThat(TestHelper.findEvents(events, shapeA1.getId()).size(), equalTo(1));
+        assertThat(TestHelper.findEvents(events, Severity.ERROR).size(), equalTo(1));
+    }
+
+    @Test
+    public void emitsErrorForIntEnum() {
+        Shape shapeA1 = IntEnumShape.builder()
+                .id("foo.baz#Baz")
+                .addMember("FOO", 1)
+                .build();
+        Model modelA = Model.assembler().addShapes(shapeA1).assemble().unwrap();
+        Model modelB = Model.assembler().addShapes().assemble().unwrap();
+        List<ValidationEvent> events = ModelDiff.compare(modelA, modelB);
+
+        assertThat(TestHelper.findEvents(events, "RemovedShape").size(), equalTo(1));
+        assertThat(TestHelper.findEvents(events, shapeA1.getId()).size(), equalTo(1));
+        assertThat(TestHelper.findEvents(events, Severity.ERROR).size(), equalTo(1));
+    }
+
     @Test
     public void doesNotEmitForPrivateShapes() {
         Shape shape = StringShape.builder().id("foo.baz#Baz").addTrait(new PrivateTrait()).build();
diff --git a/smithy-diff/src/test/java/software/amazon/smithy/diff/evaluators/TraitBreakingChangeTest.java b/smithy-diff/src/test/java/software/amazon/smithy/diff/evaluators/TraitBreakingChangeTest.java
index 8bcb204bd..4e4659ae8 100644
--- a/smithy-diff/src/test/java/software/amazon/smithy/diff/evaluators/TraitBreakingChangeTest.java
+++ b/smithy-diff/src/test/java/software/amazon/smithy/diff/evaluators/TraitBreakingChangeTest.java
@@ -131,7 +131,7 @@ public class TraitBreakingChangeTest {
                 assertThat(events.get(0).getId(), equalTo("TraitBreakingChange.Update.smithy.example#exampleTrait"));
                 assertThat(events.get(0).getMessage(),
                            equalTo("Changed trait contents of `smithy.example#exampleTrait` at path `/1` "
-                                   + "from \"b\" to \"B\""));
+                                   + "from `b` to `B`"));
             }
         );
     }
@@ -147,7 +147,7 @@ public class TraitBreakingChangeTest {
                     assertThat(events.get(0).getId(), equalTo("TraitBreakingChange.Remove.smithy.example#exampleTrait"));
                     assertThat(events.get(0).getMessage(),
                                equalTo("Removed trait contents from `smithy.example#exampleTrait` at path `/b`. "
-                                       + "Removed value: \"B\""));
+                                       + "Removed value: `B`"));
                 }
         );
     }
@@ -166,7 +166,7 @@ public class TraitBreakingChangeTest {
                 assertThat(events.get(0).getId(), equalTo("TraitBreakingChange.Update.smithy.example#exampleTrait"));
                 assertThat(events.get(0).getMessage(),
                            equalTo("Changed trait contents of `smithy.example#exampleTrait` at path `/b` "
-                                   + "from \"B\" to \"_B_\""));
+                                   + "from `B` to `_B_`"));
             }
         );
     }
@@ -184,7 +184,7 @@ public class TraitBreakingChangeTest {
                 assertThat(events.get(0).getId(), equalTo("TraitBreakingChange.Remove.smithy.example#exampleTrait"));
                 assertThat(events.get(0).getMessage(),
                            equalTo("Removed trait contents from `smithy.example#exampleTrait` at path "
-                                   + "`/foo/bar`. Removed value: \"hi\""));
+                                   + "`/foo/bar`. Removed value: `hi`"));
             }
         );
     }
@@ -202,7 +202,7 @@ public class TraitBreakingChangeTest {
                 assertThat(events.get(0).getId(), equalTo("TraitBreakingChange.Update.smithy.example#exampleTrait"));
                 assertThat(events.get(0).getMessage(),
                            equalTo("Changed trait contents of `smithy.example#exampleTrait` at path "
-                                   + "`/foo` from \"hi\" to \"bye\""));
+                                   + "`/foo` from `hi` to `bye`"));
             }
         );
     }
diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/validation/testrunner/SmithyTestSuite.java b/smithy-model/src/main/java/software/amazon/smithy/model/validation/testrunner/SmithyTestSuite.java
index 943c4d4e1..70a8e4716 100644
--- a/smithy-model/src/main/java/software/amazon/smithy/model/validation/testrunner/SmithyTestSuite.java
+++ b/smithy-model/src/main/java/software/amazon/smithy/model/validation/testrunner/SmithyTestSuite.java
@@ -25,21 +25,30 @@ import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
+import java.util.Set;
+import java.util.TreeSet;
 import java.util.concurrent.Callable;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.ForkJoinPool;
 import java.util.concurrent.Future;
 import java.util.function.Supplier;
+import java.util.logging.Logger;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 import software.amazon.smithy.model.Model;
 import software.amazon.smithy.model.loader.ModelAssembler;
+import software.amazon.smithy.model.validation.Severity;
+import software.amazon.smithy.model.validation.ValidationEvent;
+import software.amazon.smithy.model.validation.ValidationUtils;
+import software.amazon.smithy.model.validation.Validator;
 
 /**
  * Runs test cases against a directory of models and error files.
  */
 public final class SmithyTestSuite {
+
+    private static final Logger LOGGER = Logger.getLogger(SmithyTestSuite.class.getName());
     private static final String DEFAULT_TEST_CASE_LOCATION = "errorfiles";
 
     private final List<SmithyTestCase> cases = new ArrayList<>();
@@ -247,13 +256,60 @@ public final class SmithyTestSuite {
     }
 
     private Callable<SmithyTestCase.Result> createTestCaseCallable(SmithyTestCase testCase) {
+        boolean useLegacyValidationMode = isLegacyValidationRequired(testCase);
         return () -> {
             ModelAssembler assembler = modelAssemblerFactory.get();
             assembler.addImport(testCase.getModelLocation());
+            if (useLegacyValidationMode) {
+                assembler.putProperty("LEGACY_VALIDATION_MODE", true);
+            }
             return testCase.createResult(assembler.assemble());
         };
     }
 
+    // We introduced the concept of "critical" validation events after many errorfiles were created that relied
+    // on all validators being run, including validators now considered critical that prevent further validation.
+    // If we didn't account for that here, the addition of "critical" validators would have been a breaking change.
+    // To make it backward compatible, we preemptively detect if the errorfiles contains both critical and
+    // non-critical validation event assertions, and if so, we run validation using an internal-only validation
+    // mode that doesn't fail after critical validators report errors.
+    private boolean isLegacyValidationRequired(SmithyTestCase testCase) {
+        Set<String> criticalEvents = new TreeSet<>();
+        Set<String> nonCriticalEvents = new TreeSet<>();
+
+        for (ValidationEvent event : testCase.getExpectedEvents()) {
+            if (isCriticalValidationEvent(event)) {
+                criticalEvents.add(event.getId());
+            } else {
+                nonCriticalEvents.add(event.getId());
+            }
+        }
+
+        if (!criticalEvents.isEmpty() && !nonCriticalEvents.isEmpty()) {
+            LOGGER.warning(String.format("Test suite `%s` relies on the emission of non-critical validation events "
+                                         + "after critical validation events were emitted. This test case should be "
+                                         + "refactored so that critical validation events are tested using separate "
+                                         + "test cases from non-critical events. Critical events: %s. Non-critical "
+                                         + "events: %s",
+                                         testCase.getModelLocation(),
+                                         criticalEvents,
+                                         nonCriticalEvents));
+            return true;
+        }
+
+        return false;
+    }
+
+    private static boolean isCriticalValidationEvent(ValidationEvent event) {
+        if (ValidationUtils.isCriticalEvent(event.getId())) {
+            return true;
+        }
+
+        // In addition to the method to the check based on Validator classes, MODEL validation event IDs marked as
+        // ERROR that go along with non-critical events requires legacy validation.
+        return event.getId().equals(Validator.MODEL_ERROR) && event.getSeverity() == Severity.ERROR;
+    }
+
     /**
      * Executes the test runner.
      *
diff --git a/smithy-model/src/test/java/software/amazon/smithy/model/neighbor/NeighborProviderTest.java b/smithy-model/src/test/java/software/amazon/smithy/model/neighbor/NeighborProviderTest.java
index eedc0d27d..15162e5a2 100644
--- a/smithy-model/src/test/java/software/amazon/smithy/model/neighbor/NeighborProviderTest.java
+++ b/smithy-model/src/test/java/software/amazon/smithy/model/neighbor/NeighborProviderTest.java
@@ -1,13 +1,18 @@
 package software.amazon.smithy.model.neighbor;
 
 import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.containsInAnyOrder;
 import static org.hamcrest.Matchers.empty;
 import static org.hamcrest.Matchers.equalTo;
 import static org.hamcrest.Matchers.hasSize;
 
 import java.util.List;
+import java.util.stream.Collectors;
 import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
 import software.amazon.smithy.model.Model;
+import software.amazon.smithy.model.shapes.Shape;
 import software.amazon.smithy.model.shapes.ShapeId;
 import software.amazon.smithy.model.shapes.StringShape;
 import software.amazon.smithy.model.traits.SensitiveTrait;
@@ -42,4 +47,87 @@ public class NeighborProviderTest {
 
         assertThat(relationships, empty());
     }
+
+    @ParameterizedTest
+    @CsvSource({
+            "One,Ref1",
+            "Two,Ref2",
+            "Three,Ref3",
+            "Four,Ref4",
+            "Five,Ref5",
+            "Six,Ref6",
+            "Seven,Ref7",
+            "Eight,Ref8",
+            "Nine,Ref9",
+            "Ten,Ref10",
+            "Eleven,Ref11",
+            "Twelve,Ref12",
+            "Thirteen,Ref13",
+            "Fourteen,Ref14"
+    })
+    public void canGetIdRefRelationships(String shapeName, String referencedShapeName) {
+        Model model = Model.assembler()
+                .addImport(getClass().getResource("idref-neighbors.smithy"))
+                .assemble()
+                .unwrap();
+        NeighborProvider provider = NeighborProvider.of(model);
+        provider = NeighborProvider.withIdRefRelationships(model, provider);
+
+        Shape shape = model.expectShape(ShapeId.fromParts("com.foo", shapeName));
+        Shape ref = model.expectShape(ShapeId.fromParts("com.foo", referencedShapeName));
+        List<Relationship> relationships = provider.getNeighbors(shape).stream()
+                .filter(relationship -> relationship.getRelationshipType().equals(RelationshipType.ID_REF))
+                .collect(Collectors.toList());
+
+        assertThat(relationships, containsInAnyOrder(
+                equalTo(Relationship.create(shape, RelationshipType.ID_REF, ref))));
+    }
+
+    @Test
+    public void canGetIdRefRelationshipsThroughTraitDefs() {
+        Model model = Model.assembler()
+                .addImport(getClass().getResource("idref-neighbors-in-trait-def.smithy"))
+                .assemble()
+                .unwrap();
+        NeighborProvider provider = NeighborProvider.of(model);
+        provider = NeighborProvider.withIdRefRelationships(model, provider);
+
+        Shape shape = model.expectShape(ShapeId.from("com.foo#WithRefStructTrait"));
+        Shape ref = model.expectShape(ShapeId.from("com.foo#OtherReferenced"));
+        List<Relationship> relationships = provider.getNeighbors(shape).stream()
+                .filter(relationship -> relationship.getRelationshipType().equals(RelationshipType.ID_REF))
+                .collect(Collectors.toList());
+        Shape shape1 = model.expectShape(ShapeId.from("com.foo#refStruct$other"));
+        Shape ref1 = model.expectShape(ShapeId.from("com.foo#ReferencedInTraitDef"));
+        List<Relationship> relationships1 = provider.getNeighbors(shape1).stream()
+                .filter(relationship -> relationship.getRelationshipType().equals(RelationshipType.ID_REF))
+                .collect(Collectors.toList());
+
+        assertThat(relationships, containsInAnyOrder(Relationship.create(shape, RelationshipType.ID_REF, ref)));
+        assertThat(relationships1, containsInAnyOrder(Relationship.create(shape1, RelationshipType.ID_REF, ref1)));
+    }
+
+    @Test
+    public void canGetIdRefRelationshipsThroughMultipleLevelsOfIdRef() {
+        Model model = Model.assembler()
+                .addImport(getClass().getResource("idref-neighbors-multiple-levels.smithy"))
+                .assemble()
+                .unwrap();
+        NeighborProvider provider = NeighborProvider.of(model);
+        provider = NeighborProvider.withIdRefRelationships(model, provider);
+
+        Shape shape = model.expectShape(ShapeId.from("com.foo#WithIdRef"));
+        Shape ref = model.expectShape(ShapeId.from("com.foo#Referenced"));
+        List<Relationship> relationships = provider.getNeighbors(shape).stream()
+                .filter(relationship -> relationship.getRelationshipType().equals(RelationshipType.ID_REF))
+                .collect(Collectors.toList());
+        Shape shape1 = model.expectShape(ShapeId.from("com.foo#ConnectedThroughReferenced"));
+        Shape ref1 = model.expectShape(ShapeId.from("com.foo#AnotherReferenced"));
+        List<Relationship> relationships1 = provider.getNeighbors(shape1).stream()
+                .filter(relationship -> relationship.getRelationshipType().equals(RelationshipType.ID_REF))
+                .collect(Collectors.toList());
+
+        assertThat(relationships, containsInAnyOrder(Relationship.create(shape, RelationshipType.ID_REF, ref)));
+        assertThat(relationships1, containsInAnyOrder(Relationship.create(shape1, RelationshipType.ID_REF, ref1)));
+    }
 }
diff --git a/smithy-model/src/test/java/software/amazon/smithy/model/neighbor/NodeQueryTest.java b/smithy-model/src/test/java/software/amazon/smithy/model/neighbor/NodeQueryTest.java
new file mode 100644
index 000000000..51ab63925
--- /dev/null
+++ b/smithy-model/src/test/java/software/amazon/smithy/model/neighbor/NodeQueryTest.java
@@ -0,0 +1,183 @@
+package software.amazon.smithy.model.neighbor;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.containsInAnyOrder;
+import static org.hamcrest.Matchers.hasSize;
+
+import java.util.Collection;
+import org.junit.jupiter.api.Test;
+import software.amazon.smithy.model.node.Node;
+import software.amazon.smithy.model.node.StringNode;
+
+public class NodeQueryTest {
+    @Test
+    public void noQueriesGivesNoResults() {
+        Node node = Node.from("{}");
+
+        Collection<Node> result = new NodeQuery().execute(node);
+
+        assertThat(result, hasSize(0));
+    }
+
+    @Test
+    public void self() {
+        Node node = Node.from("{}");
+
+        Collection<Node> result = new NodeQuery().self().execute(node);
+
+        assertThat(result, containsInAnyOrder(node));
+    }
+
+    @Test
+    public void selfCanBeAppliedMultipleTimes() {
+        Node node = Node.from("{}");
+
+        Collection<Node> result = new NodeQuery().self().self().self().execute(node);
+
+        assertThat(result, containsInAnyOrder(node));
+    }
+
+    @Test
+    public void member() {
+        Node member = StringNode.from("bar");
+        Node node = Node.objectNode().withMember("foo", member);
+
+        Collection<Node> result = new NodeQuery().member("foo").execute(node);
+
+        assertThat(result, containsInAnyOrder(member));
+    }
+
+    @Test
+    public void anyMember() {
+        Node member1 = StringNode.from("member-one");
+        Node member2 = StringNode.from("member-two");
+        Node node = Node.objectNode().withMember("one", member1).withMember("two", member2);
+
+        Collection<Node> result = new NodeQuery().anyMember().execute(node);
+
+        assertThat(result, containsInAnyOrder(member1, member2));
+    }
+
+    @Test
+    public void anyElement() {
+        Node element1 = StringNode.from("element-one");
+        Node element2 = StringNode.from("element-two");
+        Node node = Node.arrayNode(element1, element2);
+
+        Collection<Node> result = new NodeQuery().anyElement().execute(node);
+
+        assertThat(result, containsInAnyOrder(element1, element2));
+    }
+
+    @Test
+    public void anyMemberName() {
+        StringNode key1 = StringNode.from("one");
+        StringNode key2 = StringNode.from("two");
+        Node member1 = StringNode.from("member-one");
+        Node member2 = StringNode.from("member-two");
+        Node node = Node.objectNode().withMember(key1, member1).withMember(key2, member2);
+
+        Collection<Node> result = new NodeQuery().anyMemberName().execute(node);
+
+        assertThat(result, containsInAnyOrder(key1, key2));
+    }
+
+    @Test
+    public void memberGivesNoResultsOnNonObjectNode() {
+        Node node = Node.from("[{\"foo\": 0}]");
+
+        Collection<Node> result = new NodeQuery().member("foo").execute(node);
+
+        assertThat(result, hasSize(0));
+    }
+
+    @Test
+    public void memberGivesNoResultsIfMemberNameNotFound() {
+        Node node = Node.from("{\"a\": 0, \"b\": 0}");
+
+        Collection<Node> result = new NodeQuery().member("foo").execute(node);
+
+        assertThat(result, hasSize(0));
+    }
+
+    @Test
+    public void anyMemberGivesNoResultsOnNonObjectNode() {
+        Node node = Node.from("[{\"foo\": 0}]");
+
+        Collection<Node> result = new NodeQuery().anyMember().execute(node);
+
+        assertThat(result, hasSize(0));
+    }
+
+    @Test
+    public void anyMemberGivesNoResultsOnEmptyObjectNode() {
+        Node node = Node.from("{}");
+
+        Collection<Node> result = new NodeQuery().anyMember().execute(node);
+
+        assertThat(result, hasSize(0));
+    }
+
+    @Test
+    public void anyElementGivesNoResultsOnNonArrayNode() {
+        Node node = Node.from("{\"foo\": [0]}");
+
+        Collection<Node> result = new NodeQuery().anyElement().execute(node);
+
+        assertThat(result, hasSize(0));
+    }
+
+    @Test
+    public void anyElementGivesNoResultsOnEmptyArrayNode() {
+        Node node = Node.from("[]");
+
+        Collection<Node> result = new NodeQuery().anyElement().execute(node);
+
+        assertThat(result, hasSize(0));
+    }
+
+    @Test
+    public void anyMemberNameGivesNoResultsOnNonObjectNode() {
+        Node node = Node.from("1");
+
+        Collection<Node> result = new NodeQuery().anyMemberName().execute(node);
+
+        assertThat(result, hasSize(0));
+    }
+
+    @Test
+    public void anyMemberNameGivesNoResultsOnEmptyObject() {
+        Node node = Node.from("{}");
+
+        Collection<Node> result = new NodeQuery().anyMemberName().execute(node);
+
+        assertThat(result, hasSize(0));
+    }
+
+    @Test
+    public void eachQueryExecuteOnResultOfPreviousQuery() {
+        Node element1 = Node.from(0);
+        Node element2 = Node.from("{}");
+        Node element3 = Node.from("element3");
+        Node obj = Node.objectNode().withMember("foo", Node.objectNode()
+                .withMember("arr1", Node.arrayNode(element1))
+                .withMember("arr2", Node.arrayNode(element2))
+                .withMember("arr3", Node.arrayNode(element3)));
+        Node node = Node.arrayNode(obj, obj);
+
+        Collection<Node> result = new NodeQuery()
+                .anyElement()
+                .member("foo")
+                .anyMember()
+                .anyElement()
+                .execute(node);
+
+        assertThat(result, containsInAnyOrder(
+                element1,
+                element2,
+                element3,
+                element1,
+                element2,
+                element3));
+    }
+}
diff --git a/smithy-model/src/test/java/software/amazon/smithy/model/neighbor/UnreferencedShapesTest.java b/smithy-model/src/test/java/software/amazon/smithy/model/neighbor/UnreferencedShapesTest.java
index 41e4d7589..7164cedd6 100644
--- a/smithy-model/src/test/java/software/amazon/smithy/model/neighbor/UnreferencedShapesTest.java
+++ b/smithy-model/src/test/java/software/amazon/smithy/model/neighbor/UnreferencedShapesTest.java
@@ -80,4 +80,32 @@ public class UnreferencedShapesTest {
                 .assemble()
                 .unwrap();
     }
+
+    @Test
+    public void checksShapeReferencesThroughIdRef() {
+        Model m = Model.assembler()
+                .addImport(getClass().getResource("idref-neighbors.smithy"))
+                .assemble()
+                .unwrap();
+
+        Set<Shape> shapes = new UnreferencedShapes().compute(m);
+
+        assertThat(shapes, empty());
+    }
+
+    @Test
+    public void doesNotCheckShapeReferencesThroughIdRefOnUnconnectedShapes() {
+        Model m = Model.assembler()
+                .addImport(getClass().getResource("idref-neighbors-unconnected.smithy"))
+                .assemble()
+                .unwrap();
+
+        Set<ShapeId> ids = new UnreferencedShapes().compute(m).stream().map(Shape::getId).collect(Collectors.toSet());
+
+        assertThat(ids, containsInAnyOrder(
+                ShapeId.from("com.foo#WithTrait"),
+                ShapeId.from("com.foo#Referenced"),
+                ShapeId.from("com.foo#Unconnected")
+        ));
+    }
 }
diff --git a/smithy-model/src/test/java/software/amazon/smithy/model/node/DefaultNodeWriterTest.java b/smithy-model/src/test/java/software/amazon/smithy/model/node/DefaultNodeWriterTest.java
index d03d12971..bafd4e053 100644
--- a/smithy-model/src/test/java/software/amazon/smithy/model/node/DefaultNodeWriterTest.java
+++ b/smithy-model/src/test/java/software/amazon/smithy/model/node/DefaultNodeWriterTest.java
@@ -42,21 +42,21 @@ public class DefaultNodeWriterTest {
                 {new NullNode(SourceLocation.none()), "null"},
                 {new ArrayNode(
                         Arrays.asList(Node.from(1), Node.from(2)),
-                        SourceLocation.none()), String.format("[\n    1,\n    2\n]")},
+                        SourceLocation.none()), String.format("[%n    1,%n    2%n]")},
                 {Node.objectNode()
                          .withMember("foo", Node.from("foo"))
                          .withMember("baz", Node.from(1))
                          .withMember("bar", Node.objectNode()
                              .withMember("qux", Node.arrayNode()
                                      .withValue(Node.from("ipsum")))),
-                 String.format("{\n"
-                               + "    \"foo\": \"foo\",\n"
-                               + "    \"baz\": 1,\n"
-                               + "    \"bar\": {\n"
-                               + "        \"qux\": [\n"
-                               + "            \"ipsum\"\n"
-                               + "        ]\n"
-                               + "    }\n"
+                 String.format("{%n"
+                               + "    \"foo\": \"foo\",%n"
+                               + "    \"baz\": 1,%n"
+                               + "    \"bar\": {%n"
+                               + "        \"qux\": [%n"
+                               + "            \"ipsum\"%n"
+                               + "        ]%n"
+                               + "    }%n"
                                + "}")
                 },
                 {Node.objectNode()
@@ -66,17 +66,17 @@ public class DefaultNodeWriterTest {
                          .withMember("bam", Node.arrayNode()
                                  .withValue(Node.objectNode()
                                          .withMember("abc", Node.from(123)))),
-                        String.format("{\n"
-                                      + "    \"foo\": {\n"
-                                      + "        \"bar\": [\n"
-                                      + "            \"baz\"\n"
-                                      + "        ]\n"
-                                      + "    },\n"
-                                      + "    \"bam\": [\n"
-                                      + "        {\n"
-                                      + "            \"abc\": 123\n"
-                                      + "        }\n"
-                                      + "    ]\n"
+                        String.format("{%n"
+                                      + "    \"foo\": {%n"
+                                      + "        \"bar\": [%n"
+                                      + "            \"baz\"%n"
+                                      + "        ]%n"
+                                      + "    },%n"
+                                      + "    \"bam\": [%n"
+                                      + "        {%n"
+                                      + "            \"abc\": 123%n"
+                                      + "        }%n"
+                                      + "    ]%n"
                                       + "}")
                 }
         });
diff --git a/smithy-model/src/test/java/software/amazon/smithy/model/node/NodeSerializerTest.java b/smithy-model/src/test/java/software/amazon/smithy/model/node/NodeSerializerTest.java
index db7d284bb..eb205331d 100644
--- a/smithy-model/src/test/java/software/amazon/smithy/model/node/NodeSerializerTest.java
+++ b/smithy-model/src/test/java/software/amazon/smithy/model/node/NodeSerializerTest.java
@@ -11,18 +11,18 @@ public class NodeSerializerTest {
         Node node = Node.parse("{\"foo\": \"bar\", \"baz\": true, \"bam\": false, \"boo\": 10}");
 
         assertThat(Node.printJson(node), equalTo("{\"foo\":\"bar\",\"baz\":true,\"bam\":false,\"boo\":10}"));
-        assertThat(Node.prettyPrintJson(node), equalTo("{\n"
-                                                       + "    \"foo\": \"bar\",\n"
-                                                       + "    \"baz\": true,\n"
-                                                       + "    \"bam\": false,\n"
-                                                       + "    \"boo\": 10\n"
-                                                       + "}"));
-        assertThat(Node.prettyPrintJson(node, "\t"), equalTo("{\n"
-                                                             + "\t\"foo\": \"bar\",\n"
-                                                             + "\t\"baz\": true,\n"
-                                                             + "\t\"bam\": false,\n"
-                                                             + "\t\"boo\": 10\n"
-                                                             + "}"));
+        assertThat(Node.prettyPrintJson(node), equalTo(String.format("{%n"
+                                                       + "    \"foo\": \"bar\",%n"
+                                                       + "    \"baz\": true,%n"
+                                                       + "    \"bam\": false,%n"
+                                                       + "    \"boo\": 10%n"
+                                                       + "}")));
+        assertThat(Node.prettyPrintJson(node, "\t"), equalTo(String.format("{%n"
+                                                             + "\t\"foo\": \"bar\",%n"
+                                                             + "\t\"baz\": true,%n"
+                                                             + "\t\"bam\": false,%n"
+                                                             + "\t\"boo\": 10%n"
+                                                             + "}")));
     }
 
     @Test
@@ -45,24 +45,24 @@ public class NodeSerializerTest {
         assertThat(Node.printJson(node), equalTo(
                 "[{\"foo\":\"\uD83D\uDCA9\",\"baz\":true,\"bam\":false,"
                 + "\"boo\":10},10,true,false,{},[],\"\",\" \",null,-1,-1.0]"));
-        assertThat(Node.prettyPrintJson(node), equalTo(
-                "[\n"
-                + "    {\n"
-                + "        \"foo\": \"\uD83D\uDCA9\",\n"
-                + "        \"baz\": true,\n"
-                + "        \"bam\": false,\n"
-                + "        \"boo\": 10\n"
-                + "    },\n"
-                + "    10,\n"
-                + "    true,\n"
-                + "    false,\n"
-                + "    {},\n" // optimized empty object
-                + "    [],\n" // optimized empty array
-                + "    \"\",\n"
-                + "    \" \",\n"
-                + "    null,\n"
-                + "    -1,\n"
-                + "    -1.0\n"
-                + "]"));
+        assertThat(Node.prettyPrintJson(node), equalTo(String.format(
+                "[%n"
+                + "    {%n"
+                + "        \"foo\": \"\uD83D\uDCA9\",%n"
+                + "        \"baz\": true,%n"
+                + "        \"bam\": false,%n"
+                + "        \"boo\": 10%n"
+                + "    },%n"
+                + "    10,%n"
+                + "    true,%n"
+                + "    false,%n"
+                + "    {},%n" // optimized empty object
+                + "    [],%n" // optimized empty array
+                + "    \"\",%n"
+                + "    \" \",%n"
+                + "    null,%n"
+                + "    -1,%n"
+                + "    -1.0%n"
+                + "]")));
     }
 }
diff --git a/smithy-model/src/test/java/software/amazon/smithy/model/node/NodeTest.java b/smithy-model/src/test/java/software/amazon/smithy/model/node/NodeTest.java
index 91c646e5e..d0a124289 100644
--- a/smithy-model/src/test/java/software/amazon/smithy/model/node/NodeTest.java
+++ b/smithy-model/src/test/java/software/amazon/smithy/model/node/NodeTest.java
@@ -178,7 +178,7 @@ public class NodeTest {
     @Test
     public void prettyPrintsJson() {
         assertThat(Node.prettyPrintJson(Node.parse("{\"foo\": true}")),
-                   equalTo(String.format("{\n    \"foo\": true\n}")));
+                   equalTo(String.format("{%n    \"foo\": true%n}")));
     }
 
     @Test
diff --git a/smithy-model/src/test/java/software/amazon/smithy/model/shapes/EnumShapeTest.java b/smithy-model/src/test/java/software/amazon/smithy/model/shapes/EnumShapeTest.java
index 8a9e951c9..1c6d83b4d 100644
--- a/smithy-model/src/test/java/software/amazon/smithy/model/shapes/EnumShapeTest.java
+++ b/smithy-model/src/test/java/software/amazon/smithy/model/shapes/EnumShapeTest.java
@@ -499,4 +499,62 @@ public class EnumShapeTest {
         assertFalse(EnumShape.canConvertToEnum(string, false));
         assertFalse(EnumShape.canConvertToEnum(string, true));
     }
+
+    @Test
+    public void syntheticEnumSupportsDocumentation() {
+        EnumShape shape = EnumShape.builder()
+                .id("com.example#DocumentedEnum")
+                .addMember("documented", "foo", builder -> {
+                    builder.addTrait(new DocumentationTrait("bar"));
+                })
+                .build();
+
+        SyntheticEnumTrait trait = shape.expectTrait(SyntheticEnumTrait.class);
+        assertEquals("bar", trait.getValues().get(0).getDocumentation().get());
+    }
+
+    @Test
+    public void syntheticEnumSupportsDeprecated() {
+        EnumShape shape = EnumShape.builder()
+                .id("com.example#DeprecatedEnum")
+                .addMember("deprecated", "foo", builder -> {
+                    builder.addTrait(DeprecatedTrait.builder().build());
+                })
+                .build();
+
+        SyntheticEnumTrait trait = shape.expectTrait(SyntheticEnumTrait.class);
+        assertTrue(trait.getValues().get(0).isDeprecated());
+    }
+
+    @Test
+    public void syntheticEnumSupportsTags() {
+        EnumShape shape = EnumShape.builder()
+                .id("com.example#TaggedEnum")
+                .addMember("tagged", "foo", builder -> {
+                    builder.addTrait(TagsTrait.builder().addValue("bar").build());
+                })
+                .build();
+
+        SyntheticEnumTrait trait = shape.expectTrait(SyntheticEnumTrait.class);
+        assertEquals(ListUtils.of("bar"), trait.getValues().get(0).getTags());
+    }
+
+    @Test
+    public void syntheticEnumSupportsInternal() {
+        EnumShape shape = EnumShape.builder()
+                .id("com.example#InternalEnum")
+                .addMember("withoutTag", "foo", builder -> {
+                    builder.addTrait(new InternalTrait());
+                })
+                .addMember("withTag", "bar", builder -> {
+                    builder.addTrait(new InternalTrait());
+                    builder.addTrait(TagsTrait.builder().addValue("internal").build());
+                })
+                .build();
+
+        SyntheticEnumTrait trait = shape.expectTrait(SyntheticEnumTrait.class);
+        for (EnumDefinition definition : trait.getValues()) {
+            assertEquals(ListUtils.of("internal"), definition.getTags());
+        }
+    }
 }
diff --git a/smithy-model/src/test/java/software/amazon/smithy/model/validation/ContextualValidationEventFormatterTest.java b/smithy-model/src/test/java/software/amazon/smithy/model/validation/ContextualValidationEventFormatterTest.java
index b8e1ec644..5b69b8df8 100644
--- a/smithy-model/src/test/java/software/amazon/smithy/model/validation/ContextualValidationEventFormatterTest.java
+++ b/smithy-model/src/test/java/software/amazon/smithy/model/validation/ContextualValidationEventFormatterTest.java
@@ -31,17 +31,15 @@ public class ContextualValidationEventFormatterTest {
                 .build();
 
         String format = new ContextualValidationEventFormatter().format(event);
-        // Normalize line endings for Windows.
-        format = format.replace("\r\n", "\n");
 
         assertThat(format, startsWith("ERROR: example.smithy#Foo (foo)"));
-        assertThat(format, containsString("\n     @ "));
-        assertThat(format, endsWith(
-                "\n     |"
-                + "\n   3 | structure Foo {"
-                + "\n     | ^"
-                + "\n     = This is the message"
-                + "\n"));
+        assertThat(format, containsString(String.format("%n     @ ")));
+        assertThat(format, endsWith(String.format(
+                "%n     |"
+                + "%n   3 | structure Foo {"
+                + "%n     | ^"
+                + "%n     = This is the message"
+                + "%n")));
     }
 
     @Test
@@ -54,12 +52,11 @@ public class ContextualValidationEventFormatterTest {
                 .build();
 
         String format = new ContextualValidationEventFormatter().format(event);
-        // Normalize line endings for Windows.
-        format = format.replace("\r\n", "\n");
 
-        assertThat(format, equalTo(
+        assertThat(format, equalTo(String.format(
                 "ERROR: - (foo)"
-                + "\n     = This is the message"
-                + "\n"));
+                + "%n     = This is the message"
+                + "%n")
+        ));
     }
 }
diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/neighbor/idref-neighbors-in-trait-def.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/neighbor/idref-neighbors-in-trait-def.smithy
new file mode 100644
index 000000000..59c230304
--- /dev/null
+++ b/smithy-model/src/test/resources/software/amazon/smithy/model/neighbor/idref-neighbors-in-trait-def.smithy
@@ -0,0 +1,34 @@
+$version: "2.0"
+
+namespace com.foo
+
+service FooService {
+    version: "2024-01-22"
+    operations: [GetFoo]
+}
+
+operation GetFoo {
+    input := {
+        withRefStructTrait: WithRefStructTrait
+    }
+}
+
+@trait
+@idRef(failWhenMissing: true)
+string ref
+
+@trait
+structure refStruct {
+    @ref(ReferencedInTraitDef)
+    other: String
+
+    @idRef(failWhenMissing: true)
+    ref: String
+}
+
+string ReferencedInTraitDef
+
+@refStruct(other: "foo", ref: OtherReferenced)
+structure WithRefStructTrait {}
+
+string OtherReferenced
diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/neighbor/idref-neighbors-multiple-levels.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/neighbor/idref-neighbors-multiple-levels.smithy
new file mode 100644
index 000000000..211cf736d
--- /dev/null
+++ b/smithy-model/src/test/resources/software/amazon/smithy/model/neighbor/idref-neighbors-multiple-levels.smithy
@@ -0,0 +1,32 @@
+$version: "2.0"
+
+namespace com.foo
+
+service FooService {
+    version: "2024-01-22"
+    operations: [GetFoo]
+}
+
+operation GetFoo {
+    input := {
+        withIdRef: WithIdRef
+    }
+}
+
+@trait
+@idRef(failWhenMissing: true)
+string ref
+
+@ref(Referenced)
+structure WithIdRef {}
+
+structure Referenced {
+    connectedThroughReferenced: ConnectedThroughReferenced
+}
+
+// Only connected through `Referenced`, which itself is only
+// connected via idRef.
+@ref(AnotherReferenced)
+structure ConnectedThroughReferenced {}
+
+string AnotherReferenced
diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/neighbor/idref-neighbors-unconnected.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/neighbor/idref-neighbors-unconnected.smithy
new file mode 100644
index 000000000..1d1e24434
--- /dev/null
+++ b/smithy-model/src/test/resources/software/amazon/smithy/model/neighbor/idref-neighbors-unconnected.smithy
@@ -0,0 +1,32 @@
+$version: "2.0"
+
+namespace com.foo
+
+service FooService {
+    version: "2024-01-22"
+    operations: [GetFoo]
+}
+
+operation GetFoo {
+    input := {
+        withReferencedByUnconnected: WithReferencedByUnconnected
+    }
+}
+
+@trait
+@idRef(failWhenMissing: true)
+string ref
+
+@ref(Referenced)
+structure WithTrait {}
+
+structure Referenced {}
+
+@ref(ReferencedByUnconnected)
+structure WithReferencedByUnconnected {}
+
+string ReferencedByUnconnected
+
+structure Unconnected {
+    ref: ReferencedByUnconnected
+}
diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/neighbor/idref-neighbors.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/neighbor/idref-neighbors.smithy
new file mode 100644
index 000000000..ab45d60fd
--- /dev/null
+++ b/smithy-model/src/test/resources/software/amazon/smithy/model/neighbor/idref-neighbors.smithy
@@ -0,0 +1,236 @@
+$version: "2.0"
+
+namespace com.foo
+
+service FooService {
+    version: "2024-01-18"
+    operations: [GetFoo]
+}
+
+operation GetFoo {
+    input := {
+        one: One
+        two: Two
+        three: Three
+        four: Four
+        five: Five
+        six: Six
+        seven: Seven
+        eight: Eight
+        nine: Nine
+        ten: Ten
+        eleven: Eleven
+        twelve: Twelve
+        thirteen: Thirteen
+        fourteen: Fourteen
+        fifteen: Fifteen
+    }
+}
+
+// --
+@trait
+structure withIdRefOnMember {
+    @idRef(failWhenMissing: true)
+    ref: String
+}
+
+@withIdRefOnMember(ref: Ref1)
+structure One {}
+
+structure Ref1 {}
+
+// --
+@trait
+structure withIdRefOnMemberTarget {
+    ref: OnTarget
+}
+
+@idRef(failWhenMissing: true)
+string OnTarget
+
+@withIdRefOnMemberTarget(ref: Ref2)
+structure Two {}
+
+structure Ref2 {}
+
+// --
+@trait
+structure withIdRefOnNestedStructureMember {
+    struct: Nested
+}
+
+structure Nested {
+    @idRef(failWhenMissing: true)
+    member: String
+}
+
+@withIdRefOnNestedStructureMember(
+    struct: {
+        member: Ref3
+    }
+)
+structure Three {}
+
+structure Ref3 {}
+
+
+// --
+@trait
+list withIdRefOnListMemberTarget {
+    member: OnListMemberTarget
+}
+
+@idRef(failWhenMissing: true)
+string OnListMemberTarget
+
+@withIdRefOnListMemberTarget([
+    Ref4
+])
+structure Four {}
+
+structure Ref4 {}
+
+// --
+@trait
+@idRef(failWhenMissing: true)
+string withIdRefOnSelf
+
+@withIdRefOnSelf(Ref5)
+structure Five {}
+
+structure Ref5 {}
+
+// --
+@withIdRefOnSelf(Ref6)
+structure Six {}
+
+structure Ref6 {
+    ref: RefByRef6
+}
+
+structure RefByRef6 {}
+
+// --
+@trait
+list withIdRefOnNestedStruct {
+    member: Nested
+}
+
+@withIdRefOnNestedStruct([
+    {
+        member: Ref7
+    }
+])
+structure Seven {}
+
+structure Ref7 {}
+
+// --
+@trait
+structure withIdRefThroughMixin with [ThroughMixin] {}
+
+@mixin
+structure ThroughMixin {
+    @idRef(failWhenMissing: true)
+    ref: String
+}
+
+@withIdRefThroughMixin(ref: Ref8)
+structure Eight {}
+
+structure Ref8 {}
+
+// --
+@trait
+map withIdRefOnMapValue {
+    key: String
+    value: OnMap
+}
+
+@idRef(failWhenMissing: true)
+string OnMap
+
+@withIdRefOnMapValue({
+    foo: Ref9
+})
+structure Nine {}
+
+structure Ref9 {}
+
+// --
+@trait
+map withIdRefOnNestedMapValue {
+    key: String
+    value: Nested
+}
+
+@withIdRefOnNestedMapValue({
+    foo: {
+        member: Ref10
+    }
+})
+structure Ten {}
+
+structure Ref10 {}
+
+// --
+@trait
+map withIdRefOnMapKey {
+    key: OnMap
+    value: String
+}
+
+@withIdRefOnMapKey({
+    "com.foo#Ref11": "foo"
+})
+structure Eleven {}
+
+structure Ref11 {}
+
+// --
+@trait
+@idRef(failWhenMissing: true)
+string ref
+
+@ref(Ref12)
+structure Twelve {}
+
+structure Ref12 {
+    connectedToRef13: ConnectedToRef13
+}
+
+structure ConnectedToRef13 {
+    ref13: Ref13
+}
+
+@ref(Ref13)
+structure Thirteen {}
+
+structure Ref13 {
+    connectedToRef14: ConnectedToRef14
+}
+
+structure ConnectedToRef14 {
+    ref14: Ref14
+}
+
+@ref(Ref14)
+structure Fourteen {}
+
+string Ref14
+
+// --
+@trait
+structure withIdRefOnEnum {
+    refEnum: RefEnum
+}
+
+@idRef(failWhenMissing: true)
+enum RefEnum {
+    REF15 = "com.foo#Ref15"
+}
+
+@withIdRefOnEnum(refEnum: "com.foo#Ref15")
+structure Fifteen {}
+
+structure Ref15 {}
diff --git a/smithy-openapi/src/test/java/software/amazon/smithy/openapi/fromsmithy/protocols/AwsRestJson1ProtocolTest.java b/smithy-openapi/src/test/java/software/amazon/smithy/openapi/fromsmithy/protocols/AwsRestJson1ProtocolTest.java
index 4b588f984..35209a359 100644
--- a/smithy-openapi/src/test/java/software/amazon/smithy/openapi/fromsmithy/protocols/AwsRestJson1ProtocolTest.java
+++ b/smithy-openapi/src/test/java/software/amazon/smithy/openapi/fromsmithy/protocols/AwsRestJson1ProtocolTest.java
@@ -215,6 +215,7 @@ public class AwsRestJson1ProtocolTest {
 
     @ParameterizedTest
     @MethodSource("protocolHeaderCases")
+    @SuppressWarnings("unchecked")
     public void assertProtocolHeaders(
             String operationId,
             Set<String> expectedRequestHeaders,
diff --git a/smithy-syntax/src/test/java/software/amazon/smithy/syntax/ParseAndFormatTest.java b/smithy-syntax/src/test/java/software/amazon/smithy/syntax/ParseAndFormatTest.java
index e97c6dc60..3eb26da41 100644
--- a/smithy-syntax/src/test/java/software/amazon/smithy/syntax/ParseAndFormatTest.java
+++ b/smithy-syntax/src/test/java/software/amazon/smithy/syntax/ParseAndFormatTest.java
@@ -14,6 +14,7 @@ import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.MethodSource;
 import software.amazon.smithy.model.Model;
 import software.amazon.smithy.model.loader.IdlTokenizer;
+import software.amazon.smithy.model.loader.ModelAssembler;
 import software.amazon.smithy.utils.IoUtils;
 
 // A parameterized test that finds models in corpus, parses them, skipping files that end with ".formatted.smithy".
@@ -32,9 +33,19 @@ public class ParseAndFormatTest {
         }
 
         // Ensure that the tests can be parsed by smithy-model too.
-        Model.assembler().addImport(filename).disableValidation().assemble().unwrap();
+        Model.assembler()
+                .addImport(filename)
+                .putProperty(ModelAssembler.ALLOW_UNKNOWN_TRAITS, true)
+                .disableValidation()
+                .assemble()
+                .unwrap();
         if (!formattedFile.equals(filename)) {
-            Model.assembler().addImport(formattedFile).disableValidation().assemble().unwrap();
+            Model.assembler()
+                    .addImport(formattedFile)
+                    .putProperty(ModelAssembler.ALLOW_UNKNOWN_TRAITS, true)
+                    .disableValidation()
+                    .assemble()
+                    .unwrap();
         }
 
         String model = IoUtils.readUtf8File(filename);
diff --git a/smithy-syntax/src/test/java/software/amazon/smithy/syntax/TokenTreeTest.java b/smithy-syntax/src/test/java/software/amazon/smithy/syntax/TokenTreeTest.java
index d16ebed91..e8832fda9 100644
--- a/smithy-syntax/src/test/java/software/amazon/smithy/syntax/TokenTreeTest.java
+++ b/smithy-syntax/src/test/java/software/amazon/smithy/syntax/TokenTreeTest.java
@@ -85,4 +85,54 @@ public class TokenTreeTest {
         assertThat(tree.replaceChild(child1, child2), is(false));
         assertThat(tree.zipper().getFirstChild(TreeType.COMMA).getTree(), is(child2));
     }
+
+    @Test
+    public void emptyTreeLocationsDontBubbleUpToParent() {
+        // If a tree's first/last child is empty, meaning it has a location of
+        // (0, 0) - (0, 0), that should not be considered the starting/ending
+        // location of the tree.
+        IdlTokenizer shapeStatementTokenizer = IdlTokenizer.create("structure Foo {}");
+        TokenTree shapeStatement = TokenTree.of(shapeStatementTokenizer, TreeType.SHAPE_STATEMENT);
+        assertThat(shapeStatement.getStartLine(), equalTo(1));
+        assertThat(shapeStatement.getStartColumn(), equalTo(1));
+        assertThat(shapeStatement.getEndLine(), equalTo(1));
+        assertThat(shapeStatement.getEndColumn(), equalTo(17));
+
+        // There are no traits, so this will be an empty tree with location
+        // (0, 0) - (0, 0).
+        TokenTree traitStatements = shapeStatement.getChildren().get(0);
+        assertThat(traitStatements.getStartLine(), equalTo(0));
+        assertThat(traitStatements.getStartColumn(), equalTo(0));
+        assertThat(traitStatements.getEndLine(), equalTo(0));
+        assertThat(traitStatements.getEndColumn(), equalTo(0));
+
+        // The SHAPE_STATEMENT location should come from the SHAPE child.
+        TokenTree shape = shapeStatement.getChildren().get(1);
+        assertThat(shape.getStartLine(), equalTo(shapeStatement.getStartLine()));
+        assertThat(shape.getStartColumn(), equalTo(shapeStatement.getStartColumn()));
+        assertThat(shape.getEndLine(), equalTo(shapeStatement.getEndLine()));
+        assertThat(shape.getEndColumn(), equalTo(shapeStatement.getEndColumn()));
+
+        IdlTokenizer shapeSectionTokenizer = IdlTokenizer.create("namespace com.foo\n");
+        TokenTree shapeSection = TokenTree.of(shapeSectionTokenizer, TreeType.SHAPE_SECTION);
+        assertThat(shapeSection.getStartLine(), equalTo(1));
+        assertThat(shapeSection.getStartColumn(), equalTo(1));
+        assertThat(shapeSection.getEndLine(), equalTo(2));
+        assertThat(shapeSection.getEndColumn(), equalTo(1));
+
+        // There are shape statements, so this will be an empty tree with location
+        // (0, 0) - (0, 0).
+        TokenTree shapeStatements = shapeSection.getChildren().get(2);
+        assertThat(shapeStatements.getStartLine(), equalTo(0));
+        assertThat(shapeStatements.getStartColumn(), equalTo(0));
+        assertThat(shapeStatements.getEndLine(), equalTo(0));
+        assertThat(shapeStatements.getEndColumn(), equalTo(0));
+
+        // The SHAPE_SECTION location should come from the NAMESPACE_STATEMENT child.
+        TokenTree namespaceStatement = shapeSection.getChildren().get(0);
+        assertThat(namespaceStatement.getStartLine(), equalTo(shapeSection.getStartLine()));
+        assertThat(namespaceStatement.getStartColumn(), equalTo(shapeSection.getStartColumn()));
+        assertThat(namespaceStatement.getEndLine(), equalTo(shapeSection.getEndLine()));
+        assertThat(namespaceStatement.getEndColumn(), equalTo(shapeSection.getEndColumn()));
+    }
 }
diff --git a/smithy-syntax/src/test/resources/software/amazon/smithy/syntax/formatter/array-of-objects.formatted.smithy b/smithy-syntax/src/test/resources/software/amazon/smithy/syntax/formatter/array-of-objects.formatted.smithy
new file mode 100644
index 000000000..2a32fbdb5
--- /dev/null
+++ b/smithy-syntax/src/test/resources/software/amazon/smithy/syntax/formatter/array-of-objects.formatted.smithy
@@ -0,0 +1,15 @@
+$version: "2.0"
+
+namespace smithy.example
+
+@examples([
+    {
+        title: "Foo"
+        documentation: "Foo"
+    }
+    {
+        title: "Bar"
+        documentation: "Bar"
+    }
+])
+operation Foo {}
diff --git a/smithy-syntax/src/test/resources/software/amazon/smithy/syntax/formatter/array-of-objects.smithy b/smithy-syntax/src/test/resources/software/amazon/smithy/syntax/formatter/array-of-objects.smithy
new file mode 100644
index 000000000..768b31d0b
--- /dev/null
+++ b/smithy-syntax/src/test/resources/software/amazon/smithy/syntax/formatter/array-of-objects.smithy
@@ -0,0 +1,17 @@
+$version: "2.0"
+
+namespace smithy.example
+
+@examples(
+    [
+        {
+            title: "Foo"
+            documentation: "Foo"
+        }
+        {
+            title: "Bar"
+            documentation: "Bar"
+        }
+    ]
+)
+operation Foo {}
diff --git a/smithy-syntax/src/test/resources/software/amazon/smithy/syntax/formatter/br-comments-with-multiple-use.formatted.smithy b/smithy-syntax/src/test/resources/software/amazon/smithy/syntax/formatter/br-comments-with-multiple-use.formatted.smithy
new file mode 100644
index 000000000..e9c3d2ee2
--- /dev/null
+++ b/smithy-syntax/src/test/resources/software/amazon/smithy/syntax/formatter/br-comments-with-multiple-use.formatted.smithy
@@ -0,0 +1,12 @@
+$version: "2.0"
+
+namespace smithy.example
+
+use smithy.api#length // def
+use smithy.api#sensitive // abc
+
+// Comment
+/// A
+@sensitive
+@length(min: 1)
+string A
diff --git a/smithy-syntax/src/test/resources/software/amazon/smithy/syntax/formatter/br-comments-with-multiple-use.smithy b/smithy-syntax/src/test/resources/software/amazon/smithy/syntax/formatter/br-comments-with-multiple-use.smithy
new file mode 100644
index 000000000..1f3f37a88
--- /dev/null
+++ b/smithy-syntax/src/test/resources/software/amazon/smithy/syntax/formatter/br-comments-with-multiple-use.smithy
@@ -0,0 +1,12 @@
+$version: "2.0"
+
+namespace smithy.example
+
+use smithy.api#sensitive // abc
+use smithy.api#length // def
+
+// Comment
+/// A
+@sensitive
+@length(min: 1)
+string A
diff --git a/smithy-syntax/src/test/resources/software/amazon/smithy/syntax/formatter/brackets.formatted.smithy b/smithy-syntax/src/test/resources/software/amazon/smithy/syntax/formatter/brackets.formatted.smithy
new file mode 100644
index 000000000..4f79946e1
--- /dev/null
+++ b/smithy-syntax/src/test/resources/software/amazon/smithy/syntax/formatter/brackets.formatted.smithy
@@ -0,0 +1,192 @@
+$version: "2.0"
+
+// Single line
+metadata a = { a: "b" }
+
+// Multiline
+metadata b = {
+    a: "foobarbazbux"
+    b: "foobarbazbux"
+    c: "foobarbazbux"
+    d: "foobarbazbux"
+    e: "foobarbazbux"
+    f: "foobarbazbux"
+}
+
+// Leading and trailing WS
+metadata c = {
+    // Foo
+    a: "b"
+}
+
+metadata d = {
+    a: "b"
+    // Foo
+}
+
+// Parent is array
+metadata e = [
+    {
+        foo: "bar"
+    }
+]
+
+// Line breaking values
+metadata f = [
+    """
+    foo
+    """
+]
+
+metadata g = [
+    "abc
+def"
+]
+
+metadata h = {
+    a: [
+        {
+            foo: "bar"
+        }
+    ]
+}
+
+namespace smithy.example
+
+// TraitStructure
+// Single line
+@externalDocumentation(a: "http://foo.com", c: "http://foo.com")
+structure A {}
+
+// Multi-line
+@externalDocumentation(
+    a: "http://foo.com"
+    b: "http://foo.com"
+    c: "http://foo.com"
+    d: "http://foo.com"
+    e: "http://foo.com"
+    f: "http://foo.com"
+    g: "http://foo.com"
+    h: "http://foo.com"
+    i: "http://foo.com"
+    j: "http://foo.com"
+)
+structure B {}
+
+// Leading WS
+@externalDocumentation(
+    // Foo
+    a: "http://foo.com"
+)
+structure C {}
+
+// Trailing WS
+@externalDocumentation(
+    a: "http://foo.com"
+    // Foo
+)
+structure D {}
+
+// Object values
+@foo(
+    a: { bar: "baz" }
+)
+structure E {}
+
+// Array values
+@foo(
+    a: ["bar", "baz"]
+)
+structure F {}
+
+// TraitNode
+// Single line
+@foo(["foo", "bar"])
+structure G {}
+
+@foo({ foo: "bar", baz: "foo" })
+structure H {}
+
+// Multiline
+@foo([
+    "foobarbaz"
+    "foobarbaz"
+    "foobarbaz"
+    "foobarbaz"
+    "foobarbaz"
+    "foobarbaz"
+    "foobarbaz"
+    "foobarbaz"
+    "foobarbaz"
+])
+structure I {}
+
+@foo({
+    a: "foobarbaz"
+    b: "foobarbaz"
+    c: "foobarbaz"
+    d: "foobarbaz"
+    e: "foobarbaz"
+    f: "foobarbaz"
+    g: "foobarbaz"
+    h: "foobarbaz"
+    i: "foobarbaz"
+})
+structure J {}
+
+// Leading WS
+@foo(
+    // Foo
+    ["a"]
+)
+structure K {}
+
+// Trailing WS
+@foo(
+    ["a"]
+    // Foo
+)
+structure L {}
+
+// Line breaking NodeValues
+@foo(
+    """
+    foo
+    """
+)
+structure M {}
+
+@foo(
+    "a
+bc"
+)
+structure N {}
+
+// Nested comments
+@foo([
+    "abc"
+    // Foo
+    "def"
+    // Bar
+])
+structure O {}
+
+// Nested objects
+@foo([
+    {}
+])
+structure P {}
+
+@foo([
+    {
+        foo: "bar"
+    }
+])
+structure Q {}
+
+@foo([
+    [
+        []
+    ]
+])
+structure R {}
diff --git a/smithy-syntax/src/test/resources/software/amazon/smithy/syntax/formatter/brackets.smithy b/smithy-syntax/src/test/resources/software/amazon/smithy/syntax/formatter/brackets.smithy
new file mode 100644
index 000000000..ec25f1878
--- /dev/null
+++ b/smithy-syntax/src/test/resources/software/amazon/smithy/syntax/formatter/brackets.smithy
@@ -0,0 +1,184 @@
+$version: "2.0"
+
+// Single line
+metadata a = { a: "b" }
+
+// Multiline
+metadata b = {
+    a: "foobarbazbux"
+    b: "foobarbazbux"
+    c: "foobarbazbux"
+    d: "foobarbazbux"
+    e: "foobarbazbux"
+    f: "foobarbazbux"
+}
+
+// Leading and trailing WS
+metadata c = {
+    // Foo
+    a: "b"
+}
+
+metadata d = {
+    a: "b" // Foo
+}
+
+// Parent is array
+metadata e = [
+    {
+        foo: "bar"
+    }
+]
+
+// Line breaking values
+metadata f = [
+    """
+    foo
+    """
+]
+
+metadata g = [
+    "abc
+def"
+]
+
+metadata h = {
+    a: [
+        {
+            foo: "bar"
+        }
+    ]
+
+}
+
+namespace smithy.example
+
+// TraitStructure
+// Single line
+@externalDocumentation(a: "http://foo.com", c: "http://foo.com")
+structure A {}
+
+// Multi-line
+@externalDocumentation(
+    a: "http://foo.com"
+    b: "http://foo.com"
+    c: "http://foo.com"
+    d: "http://foo.com"
+    e: "http://foo.com"
+    f: "http://foo.com"
+    g: "http://foo.com"
+    h: "http://foo.com"
+    i: "http://foo.com"
+    j: "http://foo.com"
+)
+structure B {}
+
+// Leading WS
+@externalDocumentation(
+    // Foo
+    a: "http://foo.com"
+)
+structure C {}
+
+// Trailing WS
+@externalDocumentation(
+    a: "http://foo.com" // Foo
+)
+structure D {}
+
+// Object values
+@foo(
+    a: { bar: "baz" }
+)
+structure E {}
+
+// Array values
+@foo(
+    a: ["bar", "baz"]
+)
+structure F {}
+
+// TraitNode
+// Single line
+@foo(["foo", "bar"])
+structure G {}
+
+@foo({ foo: "bar", baz: "foo" })
+structure H {}
+
+// Multiline
+@foo([
+    "foobarbaz"
+    "foobarbaz"
+    "foobarbaz"
+    "foobarbaz"
+    "foobarbaz"
+    "foobarbaz"
+    "foobarbaz"
+    "foobarbaz"
+    "foobarbaz"
+])
+structure I {}
+
+@foo({
+    a: "foobarbaz"
+    b: "foobarbaz"
+    c: "foobarbaz"
+    d: "foobarbaz"
+    e: "foobarbaz"
+    f: "foobarbaz"
+    g: "foobarbaz"
+    h: "foobarbaz"
+    i: "foobarbaz"
+})
+structure J {}
+
+// Leading WS
+@foo(
+    // Foo
+    [
+        "a"
+    ]
+)
+structure K {}
+
+// Trailing WS
+@foo(
+    [
+        "a"
+    ] // Foo
+)
+structure L {}
+
+// Line breaking NodeValues
+@foo(
+    """
+    foo
+    """
+)
+structure M {}
+
+@foo(
+    "a
+bc"
+)
+structure N {}
+
+// Nested comments
+@foo([
+    "abc" // Foo
+    "def" // Bar
+])
+structure O {}
+
+// Nested objects
+@foo([{}])
+structure P {}
+
+@foo([{
+    foo: "bar"
+      }])
+structure Q {}
+
+@foo([[[]]])
+structure R {}
diff --git a/smithy-syntax/src/test/resources/software/amazon/smithy/syntax/formatter/entity-shapes-line-break.formatted.smithy b/smithy-syntax/src/test/resources/software/amazon/smithy/syntax/formatter/entity-shapes-line-break.formatted.smithy
index f4aac3766..76a25c8f7 100644
--- a/smithy-syntax/src/test/resources/software/amazon/smithy/syntax/formatter/entity-shapes-line-break.formatted.smithy
+++ b/smithy-syntax/src/test/resources/software/amazon/smithy/syntax/formatter/entity-shapes-line-break.formatted.smithy
@@ -30,7 +30,7 @@ operation GetTime2 {
 
 @http(method: "X", uri: "/foo", code: 200)
 resource Sprocket2 {
-    identifiers: {username: String, id: String, otherId: String}
+    identifiers: { username: String, id: String, otherId: String }
 }
 
 @error("client")
diff --git a/smithy-syntax/src/test/resources/software/amazon/smithy/syntax/formatter/entity-shapes.smithy b/smithy-syntax/src/test/resources/software/amazon/smithy/syntax/formatter/entity-shapes.smithy
index 585e574ef..ce49d2b3a 100644
--- a/smithy-syntax/src/test/resources/software/amazon/smithy/syntax/formatter/entity-shapes.smithy
+++ b/smithy-syntax/src/test/resources/software/amazon/smithy/syntax/formatter/entity-shapes.smithy
@@ -33,12 +33,12 @@ operation GetTime2 {
 }
 
 resource Sprocket1 {
-    identifiers: {username: String}
+    identifiers: { username: String }
 }
 
 @http(method: "X", uri: "/foo", code: 200)
 resource Sprocket2 {
-    identifiers: {username: String, id: String, otherId: String}
+    identifiers: { username: String, id: String, otherId: String }
     collectionOperations: [
         SomeOperation
     ]
@@ -54,7 +54,7 @@ structure SomeOperationFoo {}
 
 @http(method: "X", uri: "/foo3", code: 200)
 resource Sprocket3 {
-    identifiers: {username: String, id: String, otherId: String}
+    identifiers: { username: String, id: String, otherId: String }
     // It's empty, so on a single line.
     collectionOperations: []
 }
diff --git a/smithy-syntax/src/test/resources/software/amazon/smithy/syntax/formatter/nested-arrays-and-objects.formatted.smithy b/smithy-syntax/src/test/resources/software/amazon/smithy/syntax/formatter/nested-arrays-and-objects.formatted.smithy
new file mode 100644
index 000000000..5c55a0567
--- /dev/null
+++ b/smithy-syntax/src/test/resources/software/amazon/smithy/syntax/formatter/nested-arrays-and-objects.formatted.smithy
@@ -0,0 +1,24 @@
+$version: "2.0"
+
+metadata foo = {
+    bar: ["baz", "foo", "bar", "baz"]
+    baz: {
+        foo: ["bar"]
+    }
+}
+
+metadata bar = [
+    {
+        foo: [
+            {
+                bar: ["baz", "foo"]
+                baz: ["foo", "bar"]
+            }
+            {
+                foo: "bar"
+            }
+        ]
+    }
+]
+
+namespace smithy.example
diff --git a/smithy-syntax/src/test/resources/software/amazon/smithy/syntax/formatter/nested-arrays-and-objects.smithy b/smithy-syntax/src/test/resources/software/amazon/smithy/syntax/formatter/nested-arrays-and-objects.smithy
new file mode 100644
index 000000000..405c2933f
--- /dev/null
+++ b/smithy-syntax/src/test/resources/software/amazon/smithy/syntax/formatter/nested-arrays-and-objects.smithy
@@ -0,0 +1,7 @@
+$version: "2.0"
+
+metadata foo = {bar: ["baz", "foo", "bar", "baz"], baz: {foo: ["bar"]}}
+
+metadata bar = [{foo: [{bar: ["baz", "foo"], baz: ["foo", "bar"]}, {foo: "bar"}]}]
+
+namespace smithy.example
diff --git a/smithy-syntax/src/test/resources/software/amazon/smithy/syntax/formatter/section-statement-blank-lines.formatted.smithy b/smithy-syntax/src/test/resources/software/amazon/smithy/syntax/formatter/section-statement-blank-lines.formatted.smithy
index ff3b73ab8..a5bf0f95a 100644
--- a/smithy-syntax/src/test/resources/software/amazon/smithy/syntax/formatter/section-statement-blank-lines.formatted.smithy
+++ b/smithy-syntax/src/test/resources/software/amazon/smithy/syntax/formatter/section-statement-blank-lines.formatted.smithy
@@ -2,7 +2,7 @@
 // newline.
 $version: "2.0"
 $foo: 100
-$baz: {abc: 123}
+$baz: { abc: 123 }
 
 $bar: {
     aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1"
diff --git a/smithy-syntax/src/test/resources/software/amazon/smithy/syntax/formatter/single-line-object.formatted.smithy b/smithy-syntax/src/test/resources/software/amazon/smithy/syntax/formatter/single-line-object.formatted.smithy
new file mode 100644
index 000000000..01e6f7860
--- /dev/null
+++ b/smithy-syntax/src/test/resources/software/amazon/smithy/syntax/formatter/single-line-object.formatted.smithy
@@ -0,0 +1,9 @@
+$version: "2.0"
+
+metadata foo = [
+    {
+        bar: { baz: "2" }
+    }
+]
+
+namespace smithy.example
diff --git a/smithy-syntax/src/test/resources/software/amazon/smithy/syntax/formatter/single-line-object.smithy b/smithy-syntax/src/test/resources/software/amazon/smithy/syntax/formatter/single-line-object.smithy
new file mode 100644
index 000000000..7bd08106e
--- /dev/null
+++ b/smithy-syntax/src/test/resources/software/amazon/smithy/syntax/formatter/single-line-object.smithy
@@ -0,0 +1,9 @@
+$version: "2.0"
+
+metadata foo = [
+    {
+        bar: {baz: "2"}
+    }
+]
+
+namespace smithy.example
diff --git a/smithy-syntax/src/test/resources/software/amazon/smithy/syntax/formatter/trait-textblock.formatted.smithy b/smithy-syntax/src/test/resources/software/amazon/smithy/syntax/formatter/trait-textblock.formatted.smithy
index 8fa2be8e6..899acc3ee 100644
--- a/smithy-syntax/src/test/resources/software/amazon/smithy/syntax/formatter/trait-textblock.formatted.smithy
+++ b/smithy-syntax/src/test/resources/software/amazon/smithy/syntax/formatter/trait-textblock.formatted.smithy
@@ -24,3 +24,11 @@ string Bar
     """
 )
 string Baz
+
+@documentation(
+    """
+    This is the documentation for Bux.
+    Lorem ipsum dolor.
+    """
+)
+string Bux
diff --git a/smithy-syntax/src/test/resources/software/amazon/smithy/syntax/formatter/trait-textblock.smithy b/smithy-syntax/src/test/resources/software/amazon/smithy/syntax/formatter/trait-textblock.smithy
index 99efba0d8..834b3579b 100644
--- a/smithy-syntax/src/test/resources/software/amazon/smithy/syntax/formatter/trait-textblock.smithy
+++ b/smithy-syntax/src/test/resources/software/amazon/smithy/syntax/formatter/trait-textblock.smithy
@@ -24,3 +24,9 @@ Lorem ipsum dolor.
 """
 )
 string Baz
+
+@documentation("""
+    This is the documentation for Bux.
+    Lorem ipsum dolor.
+""")
+string Bux
diff --git a/smithy-syntax/src/test/resources/software/amazon/smithy/syntax/formatter/traits-lots-of-tags.formatted.smithy b/smithy-syntax/src/test/resources/software/amazon/smithy/syntax/formatter/traits-lots-of-tags.formatted.smithy
new file mode 100644
index 000000000..29b9d4ded
--- /dev/null
+++ b/smithy-syntax/src/test/resources/software/amazon/smithy/syntax/formatter/traits-lots-of-tags.formatted.smithy
@@ -0,0 +1,20 @@
+$version: "2.0"
+
+namespace smithy.example
+
+@tags([
+    "abcdefghijklmnopqrstuvwxyz"
+    "abcdefghijklmnopqrstuvwxyz"
+    "abcdefghijklmnopqrstuvwxyz"
+    "abcdefghijklmnopqrstuvwxyz"
+    "abcdefghijklmnopqrstuvwxyz"
+    "abcdefghijklmnopqrstuvwxyz"
+    "abcdefghijklmnopqrstuvwxyz"
+    "abcdefghijklmnopqrstuvwxyz"
+    "abcdefghijklmnopqrstuvwxyz"
+    "abcdefghijklmnopqrstuvwxyz"
+    "abcdefghijklmnopqrstuvwxyz"
+    "abcdefghijklmnopqrstuvwxyz"
+    "abcdefghijklmnopqrstuvwxyz"
+])
+string Foo