Repository: smithy-lang/smithy
Pull request #2284 (2024-05-09 22:42:07)

Update post-release.yml
#### Background
- When starting the workflow, it uses the ref by default, so when a release triggers it, it would use ref/tags/x.y.z, which won't exist in the downstream-repo (homebrew-tap)

#### Testing
- Tested in personal fork

---
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/.github/workflows/post-release.yml b/.github/workflows/post-release.yml
index 3d3d7333d..aebc963cd 100644
--- a/.github/workflows/post-release.yml
+++ b/.github/workflows/post-release.yml
@@ -24,6 +24,7 @@ jobs:
       with:
         workflow: autoupdate
         repo: smithy-lang/homebrew-tap
+        ref: 'main'
         inputs: '{ "version": "${{ env.version }}" }'
         # Required when using the `repo` option
-        token: "${{ secrets.PR_TOKEN }}"
\ No newline at end of file
+        token: "${{ secrets.PR_TOKEN }}"
diff --git a/docs/source-1.0/spec/aws/aws-json.rst.template b/docs/source-1.0/spec/aws/aws-json.rst.template
index ed8744312..641970472 100644
--- a/docs/source-1.0/spec/aws/aws-json.rst.template
+++ b/docs/source-1.0/spec/aws/aws-json.rst.template
@@ -50,7 +50,7 @@ The |quoted shape name| protocol uses the following headers:
     * - ``Content-Length``
       - true
       - The standard ``Content-Length`` header defined by
-        `RFC 7230 Section 3.3.2`_.
+        :rfc:`9110#section-8.6`.
     * - ``X-Amz-Target``
       - true for requests
       - The value of this header is the :token:`shape name <smithy:Identifier>` of the
@@ -207,5 +207,3 @@ These compliance tests define a model that is used to define test cases and
 the expected serialized HTTP requests and responses for each case.
 
 *TODO: Add event stream handling specifications.*
-
-.. _`RFC 7230 Section 3.3.2`: https://tools.ietf.org/html/rfc7230#section-3.3.2
diff --git a/docs/source-1.0/spec/aws/aws-query-serialization.rst.template b/docs/source-1.0/spec/aws/aws-query-serialization.rst.template
index 4b11f1fbb..fac8711b5 100644
--- a/docs/source-1.0/spec/aws/aws-query-serialization.rst.template
+++ b/docs/source-1.0/spec/aws/aws-query-serialization.rst.template
@@ -97,5 +97,3 @@ to the input's key.
     * - ``union``
       - A union is serialized identically to a ``structure`` shape, but only a
         single member can be set to a non-null value.
-
-.. _`RFC 7230 Section 3.3.2`: https://tools.ietf.org/html/rfc7230#section-3.3.2
diff --git a/docs/source-1.0/spec/core/http-traits.rst b/docs/source-1.0/spec/core/http-traits.rst
index 61d2368e9..2ae3190bf 100644
--- a/docs/source-1.0/spec/core/http-traits.rst
+++ b/docs/source-1.0/spec/core/http-traits.rst
@@ -14,8 +14,8 @@ and error structures are considered when serializing HTTP messages.
 
 .. important::
 
-    Violating `HTTP specifications`_ or relying on poorly-supported HTTP
-    functionality when defining HTTP bindings will limit interoperability
+    Violating :rfc:`HTTP specifications <9110>` or relying on poorly-supported
+    HTTP functionality when defining HTTP bindings will limit interoperability
     and likely lead to undefined behavior across Smithy implementations. For
     example, avoid defining GET/DELETE requests with payloads, defining
     response payloads for operations with a 204/205 status, etc.
@@ -106,9 +106,8 @@ method
 The ``method`` property defines the HTTP method of the operation (e.g., "GET",
 "PUT", "POST", "DELETE", "PATCH", etc). Smithy will use this value literally
 and will perform no validation on the method. The ``method`` value SHOULD
-match the ``operation`` production rule of :rfc:`7230#appendix-B`. This
-property does not influence the safety or idempotency characteristics of an
-operation.
+match one of the definitions found in :rfc:`9110#section-9.3`. This property
+does not influence the safety or idempotency characteristics of an operation.
 
 
 .. _http-uri:
@@ -117,7 +116,7 @@ uri
 ---
 
 The ``uri`` property defines the *request-target* of the operation in
-*origin-form* as defined in :rfc:`7230#section-5.3.1`. The URI is a simple
+*origin-form* as defined in :rfc:`9112#section-3.2.1`. The URI is a simple
 pattern that Smithy uses to match HTTP requests to operations and to bind
 components of the request URI to fields in the operations's input structure.
 :dfn:`Patterns` consist of literal characters that MUST be matched in the
@@ -525,9 +524,8 @@ Trait selector
     ``structure`` member that targets a list of these types.
 Value type
     ``string`` value defining a valid HTTP header field name according to
-    :rfc:`section 3.2 of RFC7230 <7230#section-3.2>`. The value MUST NOT be
-    empty and MUST be case-insensitively unique across all other members of
-    the structure.
+    :rfc:`9110#section-5.1`. The value MUST NOT be empty and MUST be
+    case-insensitively unique across all other members of the structure.
 Conflicts with
    :ref:`httpLabel-trait`,
    :ref:`httpQuery-trait`,
@@ -546,7 +544,7 @@ Conflicts with
 * ``string`` values with a :ref:`mediaType-trait` are always base64 encoded.
 * ``timestamp`` values are serialized using the ``http-date``
   format by default, as defined in the ``IMF-fixdate`` production of
-  :rfc:`7231#section-7.1.1.1`. The :ref:`timestampFormat-trait` MAY be used
+  :rfc:`9110#section-5.6.7`. The :ref:`timestampFormat-trait` MAY be used
   to use a custom serialization format.
 
 .. rubric:: Do not put too much data in HTTP headers
@@ -954,10 +952,10 @@ the body of the response.
 
 .. rubric:: Do not put too much data in the query string
 
-While there is no limit placed on the length of an `HTTP request line`_,
-many HTTP client and server implementations enforce limits in practice.
-Carefully consider the maximum allowed length of each member that is bound to
-an HTTP query string or path.
+While there is no limit placed on the length of an
+:rfc:`HTTP request line <9112#section-3>`, many HTTP client and server
+implementations enforce limits in practice. Carefully consider the maximum
+allowed length of each member that is bound to an HTTP query string or path.
 
 
 .. smithy-trait:: smithy.api#httpQueryParams
@@ -1335,5 +1333,3 @@ marked with the ``httpPayload`` trait:
 
 
 .. _percent-encoded: https://tools.ietf.org/html/rfc3986#section-2.1
-.. _HTTP request line: https://tools.ietf.org/html/rfc7230.html#section-3.1.1
-.. _HTTP specifications: https://datatracker.ietf.org/doc/html/rfc7230
diff --git a/docs/source-1.0/spec/core/model.rst b/docs/source-1.0/spec/core/model.rst
index bd5c4e6ae..78f1659dc 100644
--- a/docs/source-1.0/spec/core/model.rst
+++ b/docs/source-1.0/spec/core/model.rst
@@ -2020,7 +2020,7 @@ The following example defines the ``PutForecast`` operation.
 .. rubric:: Put semantics
 
 The semantics of a ``put`` lifecycle operation are similar to the semantics
-of a HTTP PUT method as described in :rfc:`section 4.3.4 of [RFC7231] <7231#section-4.3.4>`:
+of a HTTP PUT method as described in :rfc:`section 4.3.4 of [RFC9110] <9110#section-9.3.4>`:
 
   The PUT method requests that the state of the target resource be
   created or replaced ...
diff --git a/docs/source-1.0/spec/core/protocol-traits.rst b/docs/source-1.0/spec/core/protocol-traits.rst
index e420498b0..61c1af42b 100644
--- a/docs/source-1.0/spec/core/protocol-traits.rst
+++ b/docs/source-1.0/spec/core/protocol-traits.rst
@@ -309,7 +309,7 @@ Smithy defines the following built-in timestamp formats:
         values that contain offsets gracefully by normalizing them to UTC.
     * - http-date
       - An HTTP date as defined by the ``IMF-fixdate`` production in
-        :rfc:`7231#section-7.1.1.1` (for example,
+        :rfc:`9110#section-5.6.7` (for example,
         ``Tue, 29 Apr 2014 18:30:38 GMT``). A deserializer that encounters an
         ``http-date`` timestamp with fractional precision SHOULD fail to
         deserialize the value (for example, an HTTP server SHOULD return a 400
diff --git a/docs/source-2.0/additional-specs/protocols/smithy-rpc-v2.rst b/docs/source-2.0/additional-specs/protocols/smithy-rpc-v2.rst
index 40479383a..f4abe4669 100644
--- a/docs/source-2.0/additional-specs/protocols/smithy-rpc-v2.rst
+++ b/docs/source-2.0/additional-specs/protocols/smithy-rpc-v2.rst
@@ -214,10 +214,10 @@ Buffered RPC requests for the ``rpcv2Cbor`` protocol SHOULD include a
 ``Content-Length`` header. Event streaming requests MUST NOT specify a content
 length (instead using ``Transfer-Encoding: chunked`` on HTTP/1.1).
 
-Event streaming requests for the ``rpcv2Cbor`` protocol MUST include an
-``Accept`` header set to the value ``application/vnd.amazon.eventstream``.
-Other forms of content streaming MAY be added in the future, utilizing
-different values for ``Accept``.
+Requests with event stream responses for the ``rpcv2Cbor`` protocol MUST
+include an ``Accept`` header set to the value
+``application/vnd.amazon.eventstream``. Other forms of content streaming MAY be
+added in the future, utilizing different values for ``Accept``.
 
 In summary, the ``rpcv2Cbor`` protocol defines behavior for the following
 headers for requests:
@@ -238,11 +238,12 @@ headers for requests:
         is ``application/vnd.amazon.eventstream``.
     * - ``Content-Length``
       - Conditional
-      - The standard ``Content-Length`` header defined by :rfc:`7230#section-3.3.2`.
+      - The standard ``Content-Length`` header defined by :rfc:`9110#section-8.6`.
         For event streaming requests, this MUST NOT be set.
     * - ``Accept``
       - Conditional
-      - For event streaming requests, to the value ``application/vnd.amazon.eventstream``.
+      - For requests with event streaming responses, this is
+        ``application/vnd.amazon.eventstream``.
 
 
 ~~~~~~~~~
@@ -303,7 +304,7 @@ headers for responses:
         is ``application/vnd.amazon.eventstream``.
     * - ``Content-Length``
       - Conditional
-      - The standard ``Content-Length`` header defined by :rfc:`7230#section-3.3.2`.
+      - The standard ``Content-Length`` header defined by :rfc:`9110#section-8.6`.
         For event streaming requests, this SHOULD NOT be set.
 
 
diff --git a/docs/source-2.0/aws/aws-iam.rst b/docs/source-2.0/aws/aws-iam.rst
index 91377a1fe..ad0c2a8cb 100644
--- a/docs/source-2.0/aws/aws-iam.rst
+++ b/docs/source-2.0/aws/aws-iam.rst
@@ -432,6 +432,11 @@ Each condition key structure supports the following members:
       - ``string``
       - A relative URL path that defines more information about the condition key
         within a set of IAM-related documentation.
+    * - required
+      - ``boolean``
+      - Defines whether a service resolved condition key is required. Not 
+        applicable to request resolved condition keys, as the native 
+        :ref:`required-trait` trait MUST be used on the member directly.
 
 .. code-block:: smithy
 
@@ -448,7 +453,14 @@ Each condition key structure supports the following members:
             type: "String"
             documentation: "The Bar string"
             externalDocumentation: "http://example.com"
-        })
+        },
+        "myservice:Baz": {
+            type: "String"
+            documentation: "The Baz string"
+            externalDocumentation: "http://baz.com"
+            required: true
+        }
+    )
     service MyService {
         version: "2017-02-11"
         resources: [MyResource]
@@ -545,6 +557,10 @@ The following example defines two service-specific condition keys:
 
     namespace smithy.example
 
+    use aws.api#service
+    use aws.iam#defineConditionKeys
+    use aws.iam#serviceResolvedConditionKeys
+
     @defineConditionKeys(
         "myservice:ActionContextKey1": { type: "String" },
         "myservice:ActionContextKey2": { type: "String" }
@@ -583,6 +599,11 @@ explicitly binds ``ActionContextKey1`` to the field ``key``.
 
     namespace smithy.example
 
+    use aws.api#service
+    use aws.iam#conditionKeys
+    use aws.iam#defineConditionKeys
+    use aws.iam#conditionKeyValue
+
     @defineConditionKeys(
         "myservice:ActionContextKey1": { type: "String" }
     )
@@ -595,7 +616,7 @@ explicitly binds ``ActionContextKey1`` to the field ``key``.
     @conditionKeys(["myservice:ActionContextKey1"])
     operation OperationA {
         input := {
-            @conditionKeyValue("ActionContextKey1")
+            @conditionKeyValue("myservice:ActionContextKey1")
             key: String
         }
         output := {
diff --git a/docs/source-2.0/aws/protocols/aws-json.rst.template b/docs/source-2.0/aws/protocols/aws-json.rst.template
index e77914073..0e28b73a1 100644
--- a/docs/source-2.0/aws/protocols/aws-json.rst.template
+++ b/docs/source-2.0/aws/protocols/aws-json.rst.template
@@ -50,7 +50,7 @@ The |quoted shape name| protocol uses the following headers:
     * - ``Content-Length``
       - true
       - The standard ``Content-Length`` header defined by
-        :rfc:`7230#section-3.3.2`.
+        :rfc:`9110#section-8.6`.
     * - ``X-Amz-Target``
       - true for requests
       - The value of this header is the :token:`shape name <smithy:Identifier>` of the
diff --git a/docs/source-2.0/guides/model-translations/converting-to-openapi.rst b/docs/source-2.0/guides/model-translations/converting-to-openapi.rst
index 91746b394..bbeb9a93c 100644
--- a/docs/source-2.0/guides/model-translations/converting-to-openapi.rst
+++ b/docs/source-2.0/guides/model-translations/converting-to-openapi.rst
@@ -1550,6 +1550,29 @@ additionalAllowedCorsHeaders (``[string]``)
             }
         }
 
+
+.. _generate-openapi-apigateway-setting-syncCorsPreflightIntegration:
+
+syncCorsPreflightIntegration (``boolean``)
+    Set to true to sync CORS preflight integration request templates with all possible content-types
+    from other methods within the same path resource.
+
+    .. code-block:: json
+        :caption: smithy-build.json
+
+        {
+            "version": "1.0",
+            "plugins": {
+                "openapi": {
+                    "service": "example.weather#Weather",
+                    "syncCorsPreflightIntegration": true
+                }
+            }
+        }
+
+    With this enabled, the `integration's passthroughBehavior`_ for CORS preflight integration
+    will be set to ``never``.
+
 Binary types
 ============
 
@@ -1648,6 +1671,10 @@ additions during the OpenAPI conversion:
 * Adds static CORS response headers to API Gateway "gateway" responses.  These are added only when
   no gateway responses are defined in the OpenAPI model.
 
+.. note::
+    If :ref:`syncCorsPreflightIntegration <generate-openapi-apigateway-setting-syncCorsPreflightIntegration>` is
+    set to ``true``, the CORS preflight `integration's passthroughBehavior`_ will be set to ``never`` and the integration's
+    request templates will be synced with all possible content-types from other methods within the same path resource.
 
 .. _authorizers:
 
@@ -2037,4 +2064,5 @@ The conversion process is highly extensible through
 .. _Lambda authorizers: https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-swagger-extensions-authorizer.html
 .. _API Gateway's API key usage plans: https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-api-usage-plans.html
 .. _OpenAPI specification extension: https://spec.openapis.org/oas/v3.1.0#specification-extensions
+.. _integration's passthroughBehavior: https://docs.aws.amazon.com/apigateway/latest/developerguide/integration-passthrough-behaviors.html
 .. _gradle installed: https://gradle.org/install/
diff --git a/docs/source-2.0/spec/behavior-traits.rst b/docs/source-2.0/spec/behavior-traits.rst
index 7ada59fea..5fdfdb834 100644
--- a/docs/source-2.0/spec/behavior-traits.rst
+++ b/docs/source-2.0/spec/behavior-traits.rst
@@ -6,7 +6,7 @@ Idempotency
 ===========
 
 Operations marked with the :ref:`readonly-trait` or :ref:`idempotent-trait`
-are considered idempotent as defined in :rfc:`7231#section-4.2.2`. Operations
+are considered idempotent as defined in :rfc:`9110#section-9.2.2`. Operations
 that contain a top-level input member marked with the :ref:`idempotencytoken-trait`
 that are provided a token for the member are also considered idempotent. All
 other operations SHOULD be considered unsafe to retry unless the response to
diff --git a/docs/source-2.0/spec/http-bindings.rst b/docs/source-2.0/spec/http-bindings.rst
index fa00f8de0..b177a5454 100644
--- a/docs/source-2.0/spec/http-bindings.rst
+++ b/docs/source-2.0/spec/http-bindings.rst
@@ -14,7 +14,7 @@ and error structures are considered when serializing HTTP messages.
 
 .. important::
 
-    Violating :rfc:`HTTP specifications <7230>` or relying on poorly-supported
+    Violating :rfc:`HTTP specifications <9110>` or relying on poorly-supported
     HTTP functionality when defining HTTP bindings will limit interoperability
     and likely lead to undefined behavior across Smithy implementations. For
     example, avoid defining GET/DELETE requests with payloads, defining
@@ -107,9 +107,8 @@ method
 The ``method`` property defines the HTTP method of the operation (e.g., "GET",
 "PUT", "POST", "DELETE", "PATCH", etc). Smithy will use this value literally
 and will perform no validation on the method. The ``method`` value SHOULD
-match the ``operation`` production rule of :rfc:`7230#appendix-B`. This
-property does not influence the safety or idempotency characteristics of an
-operation.
+match one of the definitions found in :rfc:`9110#section-9.3`. This property
+does not influence the safety or idempotency characteristics of an operation.
 
 
 .. _http-uri:
@@ -118,7 +117,7 @@ uri
 ---
 
 The ``uri`` property defines the *request-target* of the operation in
-*origin-form* as defined in :rfc:`7230#section-5.3.1`. The URI is a simple
+*origin-form* as defined in :rfc:`9112#section-3.2.1`. The URI is a simple
 pattern that Smithy uses to match HTTP requests to operations and to bind
 components of the request URI to fields in the operations's input structure.
 :dfn:`Patterns` consist of literal characters that MUST be matched in the
@@ -634,9 +633,8 @@ Trait selector
     ``structure`` member that targets a list of these types.
 Value type
     ``string`` value defining a valid HTTP header field name according to
-    :rfc:`section 3.2 of RFC7230 <7230#section-3.2>`. The value MUST NOT be
-    empty and MUST be case-insensitively unique across all other members of
-    the structure.
+    :rfc:`9110#section-5.1`. The value MUST NOT be empty and MUST be
+    case-insensitively unique across all other members of the structure.
 Conflicts with
    :ref:`httpLabel-trait`,
    :ref:`httpQuery-trait`,
@@ -656,7 +654,7 @@ Conflicts with
 * ``string`` values with a :ref:`mediaType-trait` are always base64 encoded.
 * ``timestamp`` values are serialized using the ``http-date``
   format by default, as defined in the ``IMF-fixdate`` production of
-  :rfc:`7231#section-7.1.1.1`. The :ref:`timestampFormat-trait` MAY be used
+  :rfc:`9110#section-5.6.7`. The :ref:`timestampFormat-trait` MAY be used
   to use a custom serialization format.
 
 .. note::
@@ -1089,7 +1087,7 @@ is simply ignored.
 .. note::
 
     While there is no limit placed on the length of an
-    :rfc:`HTTP request line <7230#section-3.1.1>`, many HTTP client and server
+    :rfc:`HTTP request line <9112#section-3>`, many HTTP client and server
     implementations enforce limits in practice. Carefully consider the maximum
     allowed length of each member that is bound to an HTTP query string or
     path.
diff --git a/docs/source-2.0/spec/protocol-traits.rst b/docs/source-2.0/spec/protocol-traits.rst
index 9e9341043..18f9518ff 100644
--- a/docs/source-2.0/spec/protocol-traits.rst
+++ b/docs/source-2.0/spec/protocol-traits.rst
@@ -228,7 +228,7 @@ Smithy defines the following built-in timestamp formats:
         values that contain offsets gracefully by normalizing them to UTC.
     * - http-date
       - An HTTP date as defined by the ``IMF-fixdate`` production in
-        :rfc:`7231#section-7.1.1.1` (for example,
+        :rfc:`9110#section-5.6.7` (for example,
         ``Tue, 29 Apr 2014 18:30:38 GMT``). A deserializer that encounters an
         ``http-date`` timestamp with fractional precision SHOULD fail to
         deserialize the value (for example, an HTTP server SHOULD return a 400
diff --git a/docs/source-2.0/spec/service-types.rst b/docs/source-2.0/spec/service-types.rst
index 5ce6ac3d2..c5dd2bcda 100644
--- a/docs/source-2.0/spec/service-types.rst
+++ b/docs/source-2.0/spec/service-types.rst
@@ -862,7 +862,7 @@ Put semantics
 ^^^^^^^^^^^^^
 
 The semantics of a ``put`` lifecycle operation are similar to the semantics
-of a HTTP PUT method as described in :rfc:`section 4.3.4 of [RFC7231] <7231#section-4.3.4>`:
+of a HTTP PUT method as described in :rfc:`section 4.3.4 of [RFC9110] <9110#section-9.3.4>`:
 
   The PUT method requests that the state of the target resource be
   created or replaced ...
diff --git a/smithy-aws-apigateway-openapi/src/main/java/software/amazon/smithy/aws/apigateway/openapi/AddCorsPreflightIntegration.java b/smithy-aws-apigateway-openapi/src/main/java/software/amazon/smithy/aws/apigateway/openapi/AddCorsPreflightIntegration.java
index ad2eab0f1..c80458cc4 100644
--- a/smithy-aws-apigateway-openapi/src/main/java/software/amazon/smithy/aws/apigateway/openapi/AddCorsPreflightIntegration.java
+++ b/smithy-aws-apigateway-openapi/src/main/java/software/amazon/smithy/aws/apigateway/openapi/AddCorsPreflightIntegration.java
@@ -31,6 +31,7 @@ import software.amazon.smithy.aws.apigateway.traits.MockIntegrationTrait;
 import software.amazon.smithy.jsonschema.Schema;
 import software.amazon.smithy.model.Model;
 import software.amazon.smithy.model.knowledge.TopDownIndex;
+import software.amazon.smithy.model.node.Node;
 import software.amazon.smithy.model.node.ObjectNode;
 import software.amazon.smithy.model.shapes.OperationShape;
 import software.amazon.smithy.model.traits.CorsTrait;
@@ -45,6 +46,7 @@ import software.amazon.smithy.openapi.model.Ref;
 import software.amazon.smithy.openapi.model.ResponseObject;
 import software.amazon.smithy.utils.CaseUtils;
 import software.amazon.smithy.utils.ListUtils;
+import software.amazon.smithy.utils.SetUtils;
 
 /**
  * Adds CORS-preflight OPTIONS requests using mock API Gateway integrations.
@@ -70,6 +72,7 @@ final class AddCorsPreflightIntegration implements ApiGatewayMapper {
     private static final Logger LOGGER = Logger.getLogger(AddCorsPreflightIntegration.class.getName());
     private static final String API_GATEWAY_DEFAULT_ACCEPT_VALUE = "application/json";
     private static final String INTEGRATION_EXTENSION = "x-amazon-apigateway-integration";
+    private static final String REQUEST_TEMPLATES_KEY = "requestTemplates";
     private static final String PREFLIGHT_SUCCESS = "{\"statusCode\":200}";
 
     @Override
@@ -95,7 +98,7 @@ final class AddCorsPreflightIntegration implements ApiGatewayMapper {
         LOGGER.fine(() -> "Adding CORS-preflight OPTIONS request and API Gateway integration for " + path);
         Map<CorsHeader, String> headers = deduceCorsHeaders(context, path, pathItem, corsTrait);
         return pathItem.toBuilder()
-                .options(createPreflightOperation(path, pathItem, headers))
+                .options(createPreflightOperation(context, path, pathItem, headers))
                 .build();
     }
 
@@ -170,7 +173,7 @@ final class AddCorsPreflightIntegration implements ApiGatewayMapper {
     }
 
     private static OperationObject createPreflightOperation(
-            String path, PathItem pathItem, Map<CorsHeader, String> headers) {
+            Context<? extends Trait> context, String path, PathItem pathItem, Map<CorsHeader, String> headers) {
         return OperationObject.builder()
                 .tags(ListUtils.of("CORS"))
                 .security(Collections.emptyList())
@@ -178,7 +181,7 @@ final class AddCorsPreflightIntegration implements ApiGatewayMapper {
                 .operationId(createOperationId(path))
                 .putResponse("200", createPreflightResponse(headers))
                 .parameters(findPathParameters(pathItem))
-                .putExtension(INTEGRATION_EXTENSION, createPreflightIntegration(headers, pathItem))
+                .putExtension(INTEGRATION_EXTENSION, createPreflightIntegration(context, headers, pathItem))
                 .build();
     }
 
@@ -214,7 +217,8 @@ final class AddCorsPreflightIntegration implements ApiGatewayMapper {
         return builder.build();
     }
 
-    private static ObjectNode createPreflightIntegration(Map<CorsHeader, String> headers, PathItem pathItem) {
+    private static ObjectNode createPreflightIntegration(
+            Context<? extends Trait> context, Map<CorsHeader, String> headers, PathItem pathItem) {
         IntegrationResponse.Builder responseBuilder = IntegrationResponse.builder().statusCode("200");
 
         // Add each CORS header to the mock integration response.
@@ -222,13 +226,34 @@ final class AddCorsPreflightIntegration implements ApiGatewayMapper {
             responseBuilder.putResponseParameter("method.response.header." + e.getKey(), "'" + e.getValue() + "'");
         }
 
+        boolean isPreflightSynced = Boolean.TRUE.equals(context.getConfig().getSyncCorsPreflightIntegration());
         MockIntegrationTrait.Builder integration = MockIntegrationTrait.builder()
                 // See https://forums.aws.amazon.com/thread.jspa?threadID=256140
                 .contentHandling("CONVERT_TO_TEXT")
-                .passThroughBehavior("when_no_match")
+                // Passthrough behavior "never" will fail the request with unsupported content type more appropriately.
+                // https://docs.aws.amazon.com/apigateway/latest/developerguide/integration-passthrough-behaviors.html
+                .passThroughBehavior(isPreflightSynced ? "never" : "when_no_match")
                 .putResponse("default", responseBuilder.build())
                 .putRequestTemplate(API_GATEWAY_DEFAULT_ACCEPT_VALUE, PREFLIGHT_SUCCESS);
 
+        if (isPreflightSynced) {
+            // Adds request template for every unique Content-Type supported by all path operations.
+            // This ensures that for Content-Type(s) other than 'application/json', the entire request payload
+            // is not sent to APIGW mock integration as stipulated by 'when_no_match' passthroughBehavior.
+            // APIGW throws an error if the mock integration request does not follow a set contract,
+            // example {"statusCode":200}.
+            for (OperationObject operation : pathItem.getOperations().values()) {
+                ObjectNode extensionNode = operation.getExtension(INTEGRATION_EXTENSION)
+                        .flatMap(Node::asObjectNode)
+                        .orElse(Node.objectNode());
+                Set<String> mimeTypes = extensionNode.getObjectMember(REQUEST_TEMPLATES_KEY)
+                        .map(ObjectNode::getStringMap)
+                        .map(Map::keySet)
+                        .orElse(SetUtils.of());
+                mimeTypes.forEach(mimeType -> integration.putRequestTemplate(mimeType, PREFLIGHT_SUCCESS));
+            }
+        }
+
         // Add a request template for every mime-type of every response.
         for (OperationObject operation : pathItem.getOperations().values()) {
             for (ResponseObject response : operation.getResponses().values()) {
diff --git a/smithy-aws-apigateway-openapi/src/main/java/software/amazon/smithy/aws/apigateway/openapi/AddDefaultConfigSettings.java b/smithy-aws-apigateway-openapi/src/main/java/software/amazon/smithy/aws/apigateway/openapi/AddDefaultConfigSettings.java
index de5bf6117..06917e347 100644
--- a/smithy-aws-apigateway-openapi/src/main/java/software/amazon/smithy/aws/apigateway/openapi/AddDefaultConfigSettings.java
+++ b/smithy-aws-apigateway-openapi/src/main/java/software/amazon/smithy/aws/apigateway/openapi/AddDefaultConfigSettings.java
@@ -43,7 +43,7 @@ final class AddDefaultConfigSettings implements ApiGatewayMapper {
     public void updateDefaultSettings(Model model, OpenApiConfig openApiConfig) {
         ApiGatewayConfig config = openApiConfig.getExtensions(ApiGatewayConfig.class);
         if (config.getApiGatewayDefaults() == null) {
-            LOGGER.warning(String.format("`apiGatewayDefaults` configuration not set for opeanapi plugin. Assuming %s.",
+            LOGGER.warning(String.format("`apiGatewayDefaults` configuration not set for openapi plugin. Assuming %s.",
                     DEFAULT_VERSION));
             config.setApiGatewayDefaults(DEFAULT_VERSION);
         }
diff --git a/smithy-aws-iam-traits/src/main/java/software/amazon/smithy/aws/iam/traits/ConditionKeyDefinition.java b/smithy-aws-iam-traits/src/main/java/software/amazon/smithy/aws/iam/traits/ConditionKeyDefinition.java
index da0b38d61..aa112491a 100644
--- a/smithy-aws-iam-traits/src/main/java/software/amazon/smithy/aws/iam/traits/ConditionKeyDefinition.java
+++ b/smithy-aws-iam-traits/src/main/java/software/amazon/smithy/aws/iam/traits/ConditionKeyDefinition.java
@@ -17,6 +17,7 @@ package software.amazon.smithy.aws.iam.traits;
 
 import java.util.Objects;
 import java.util.Optional;
+import software.amazon.smithy.model.node.BooleanNode;
 import software.amazon.smithy.model.node.Node;
 import software.amazon.smithy.model.node.ObjectNode;
 import software.amazon.smithy.model.node.StringNode;
@@ -29,17 +30,20 @@ public final class ConditionKeyDefinition implements ToNode, ToSmithyBuilder<Con
     private static final String DOCUMENTATION = "documentation";
     private static final String EXTERNAL_DOCUMENTATION = "externalDocumentation";
     private static final String RELATIVE_DOCUMENTATION = "relativeDocumentation";
+    private static final String REQUIRED = "required";
 
     private final String type;
     private final String documentation;
     private final String externalDocumentation;
     private final String relativeDocumentation;
+    private final boolean required;
 
     private ConditionKeyDefinition(Builder builder) {
         type = SmithyBuilder.requiredState(TYPE, builder.type);
         documentation = builder.documentation;
         externalDocumentation = builder.externalDocumentation;
         relativeDocumentation = builder.relativeDocumentation;
+        required = builder.required;
     }
 
     public static Builder builder() {
@@ -56,6 +60,8 @@ public final class ConditionKeyDefinition implements ToNode, ToSmithyBuilder<Con
                 .ifPresent(builder::externalDocumentation);
         objectNode.getStringMember(RELATIVE_DOCUMENTATION).map(StringNode::getValue)
                 .ifPresent(builder::relativeDocumentation);
+        objectNode.getBooleanMember(REQUIRED).map(BooleanNode::getValue)
+                .ifPresent(builder::required);
 
         return builder.build();
     }
@@ -91,13 +97,24 @@ public final class ConditionKeyDefinition implements ToNode, ToSmithyBuilder<Con
         return Optional.ofNullable(relativeDocumentation);
     }
 
+    /**
+     * Whether a service resolved condition key is required. Not applicable to request resolved condition keys,
+     * as the native @required trait must be used.
+     *
+     * @return If the service resolved condition key is required.
+     **/
+    public boolean isRequired() {
+        return required;
+    }
+
     @Override
     public SmithyBuilder<ConditionKeyDefinition> toBuilder() {
         return builder()
                 .documentation(documentation)
                 .externalDocumentation(externalDocumentation)
                 .relativeDocumentation(relativeDocumentation)
-                .type(type);
+                .type(type)
+                .required(required);
     }
 
     @Override
@@ -107,6 +124,7 @@ public final class ConditionKeyDefinition implements ToNode, ToSmithyBuilder<Con
                 .withOptionalMember(DOCUMENTATION, getDocumentation().map(Node::from))
                 .withOptionalMember(EXTERNAL_DOCUMENTATION, getExternalDocumentation().map(Node::from))
                 .withOptionalMember(RELATIVE_DOCUMENTATION, getRelativeDocumentation().map(Node::from))
+                .withMember(REQUIRED, isRequired())
                 .build();
     }
 
@@ -122,7 +140,8 @@ public final class ConditionKeyDefinition implements ToNode, ToSmithyBuilder<Con
         return Objects.equals(type, that.type)
                && Objects.equals(documentation, that.documentation)
                && Objects.equals(externalDocumentation, that.externalDocumentation)
-               && Objects.equals(relativeDocumentation, that.relativeDocumentation);
+               && Objects.equals(relativeDocumentation, that.relativeDocumentation)
+               && Objects.equals(required, that.required);
     }
 
     @Override
@@ -135,6 +154,7 @@ public final class ConditionKeyDefinition implements ToNode, ToSmithyBuilder<Con
         private String documentation;
         private String externalDocumentation;
         private String relativeDocumentation;
+        private boolean required;
 
         @Override
         public ConditionKeyDefinition build() {
@@ -160,5 +180,10 @@ public final class ConditionKeyDefinition implements ToNode, ToSmithyBuilder<Con
             this.relativeDocumentation = relativeDocumentation;
             return this;
         }
+
+        public Builder required(boolean required) {
+            this.required = required;
+            return this;
+        }
     }
 }
diff --git a/smithy-aws-iam-traits/src/main/java/software/amazon/smithy/aws/iam/traits/DefineConditionKeysTraitValidator.java b/smithy-aws-iam-traits/src/main/java/software/amazon/smithy/aws/iam/traits/DefineConditionKeysTraitValidator.java
new file mode 100644
index 000000000..5d5baa25a
--- /dev/null
+++ b/smithy-aws-iam-traits/src/main/java/software/amazon/smithy/aws/iam/traits/DefineConditionKeysTraitValidator.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package software.amazon.smithy.aws.iam.traits;
+
+import static java.lang.String.format;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import software.amazon.smithy.model.FromSourceLocation;
+import software.amazon.smithy.model.Model;
+import software.amazon.smithy.model.shapes.ServiceShape;
+import software.amazon.smithy.model.traits.StringListTrait;
+import software.amazon.smithy.model.traits.Trait;
+import software.amazon.smithy.model.validation.AbstractValidator;
+import software.amazon.smithy.model.validation.ValidationEvent;
+import software.amazon.smithy.utils.ListUtils;
+
+/**
+ * Checks properties of the defineConditionKeys IAM trait
+ * 1. @required only for service resolved condition keys. ERROR if used for request resolved condition keys.
+ */
+public final class DefineConditionKeysTraitValidator extends AbstractValidator {
+    @Override
+    public List<ValidationEvent> validate(Model model) {
+        List<ValidationEvent> events = new ArrayList<>();
+        ConditionKeysIndex conditionKeysIndex = ConditionKeysIndex.of(model);
+        for (ServiceShape serviceShape : model.getServiceShapes()) {
+            for (Map.Entry<String, ConditionKeyDefinition> conditionKeyEntry
+                    : conditionKeysIndex.getDefinedConditionKeys(serviceShape).entrySet()
+            ) {
+                // Get all the service resolved condition keys.
+                List<String> serviceResolvedKeys = serviceShape.getTrait(ServiceResolvedConditionKeysTrait.class)
+                        .map(StringListTrait::getValues)
+                        .orElse(ListUtils.of());
+                // We want to emit an event for definitions that state being required...
+                if (conditionKeyEntry.getValue().isRequired()
+                        // and are not configured to be service resolved.
+                        && !serviceResolvedKeys.contains(conditionKeyEntry.getKey())
+                ) {
+                    FromSourceLocation sourceLocation = serviceShape.getTrait(DefineConditionKeysTrait.class)
+                            .map(Trait::getSourceLocation)
+                            .orElse(serviceShape.getSourceLocation());
+                    events.add(error(serviceShape, sourceLocation, format("The `%s` condition key is defined as "
+                            + "required but is resolved from the request. This property is only valid for condition "
+                            + "keys resolved by the service, it MUST also be specified in the "
+                            + "`@serviceResolvedConditionKeys` trait or use the `@required` trait instead.",
+                            conditionKeyEntry.getKey())));
+                }
+            }
+        }
+        return events;
+    }
+}
diff --git a/smithy-aws-iam-traits/src/main/java/software/amazon/smithy/aws/iam/traits/IamResourceTraitConflictingNameValidator.java b/smithy-aws-iam-traits/src/main/java/software/amazon/smithy/aws/iam/traits/IamResourceTraitConflictingNameValidator.java
new file mode 100644
index 000000000..9ea4fa3d9
--- /dev/null
+++ b/smithy-aws-iam-traits/src/main/java/software/amazon/smithy/aws/iam/traits/IamResourceTraitConflictingNameValidator.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package software.amazon.smithy.aws.iam.traits;
+
+import static software.amazon.smithy.model.validation.ValidationUtils.tickedList;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+import software.amazon.smithy.model.Model;
+import software.amazon.smithy.model.knowledge.TopDownIndex;
+import software.amazon.smithy.model.shapes.ResourceShape;
+import software.amazon.smithy.model.shapes.ServiceShape;
+import software.amazon.smithy.model.shapes.ShapeId;
+import software.amazon.smithy.model.validation.AbstractValidator;
+import software.amazon.smithy.model.validation.ValidationEvent;
+import software.amazon.smithy.utils.SmithyInternalApi;
+
+/**
+ * Ensures that there is no resource name conflict in a service closure
+ * in IAM space after processing {@link IamResourceTrait}.
+ */
+@SmithyInternalApi
+public class IamResourceTraitConflictingNameValidator extends AbstractValidator {
+    @Override
+    public List<ValidationEvent> validate(Model model) {
+        TopDownIndex topDownIndex = TopDownIndex.of(model);
+        return model.shapes(ServiceShape.class)
+                .flatMap(shape -> validateService(topDownIndex, shape).stream())
+                .collect(Collectors.toList());
+    }
+
+    private List<ValidationEvent> validateService(TopDownIndex topDownIndex, ServiceShape service) {
+        List<ValidationEvent> events = new ArrayList<>();
+        Map<String, List<ShapeId>> resourceMap = new HashMap<>();
+        for (ResourceShape resource : topDownIndex.getContainedResources(service)) {
+            String resourceName = IamResourceTrait.resolveResourceName(resource);
+            resourceMap.computeIfAbsent(resourceName, k -> new ArrayList<>()).add(resource.getId());
+        }
+        resourceMap.entrySet().stream()
+                .filter(entry -> entry.getValue().size() > 1)
+                .map(entry -> error(service, String.format(
+                        "Multiple IAM resources defined with the same IAM resource name is not allowed in a service "
+                                + "closure, but found multiple resources named `%s` in the service `%s`: [%s]",
+                        entry.getKey(), service.getId(), tickedList(entry.getValue()))))
+                .forEach(events::add);
+        return events;
+    }
+}
diff --git a/smithy-aws-iam-traits/src/main/resources/META-INF/services/software.amazon.smithy.model.validation.Validator b/smithy-aws-iam-traits/src/main/resources/META-INF/services/software.amazon.smithy.model.validation.Validator
index 856e5dbe6..702fcbb3f 100644
--- a/smithy-aws-iam-traits/src/main/resources/META-INF/services/software.amazon.smithy.model.validation.Validator
+++ b/smithy-aws-iam-traits/src/main/resources/META-INF/services/software.amazon.smithy.model.validation.Validator
@@ -1,3 +1,5 @@
 software.amazon.smithy.aws.iam.traits.ConditionKeysValidator
 software.amazon.smithy.aws.iam.traits.IamActionValidator
 software.amazon.smithy.aws.iam.traits.IamResourceTraitValidator
+software.amazon.smithy.aws.iam.traits.DefineConditionKeysTraitValidator
+software.amazon.smithy.aws.iam.traits.IamResourceTraitConflictingNameValidator
diff --git a/smithy-aws-iam-traits/src/main/resources/META-INF/smithy/aws.iam.smithy b/smithy-aws-iam-traits/src/main/resources/META-INF/smithy/aws.iam.smithy
index 157028602..144ad78e4 100644
--- a/smithy-aws-iam-traits/src/main/resources/META-INF/smithy/aws.iam.smithy
+++ b/smithy-aws-iam-traits/src/main/resources/META-INF/smithy/aws.iam.smithy
@@ -119,6 +119,10 @@ structure ConditionKeyDefinition {
     /// A relative URL path that defines more information about the condition key
     /// within a set of IAM-related documentation.
     relativeDocumentation: String
+
+    /// Whether a service resolved condition key is required.
+    /// Request resolved condition keys MUST use the @required trait.
+    required: Boolean
 }
 
 /// Contains information about a resource an IAM action can be authorized against.
diff --git a/smithy-diff/src/main/java/software/amazon/smithy/diff/evaluators/ChangedEnumTrait.java b/smithy-diff/src/main/java/software/amazon/smithy/diff/evaluators/ChangedEnumTrait.java
index 72cba13cd..75a494e20 100644
--- a/smithy-diff/src/main/java/software/amazon/smithy/diff/evaluators/ChangedEnumTrait.java
+++ b/smithy-diff/src/main/java/software/amazon/smithy/diff/evaluators/ChangedEnumTrait.java
@@ -43,6 +43,7 @@ public final class ChangedEnumTrait extends AbstractDiffEvaluator {
     private static final String ORDER_CHANGED = ".OrderChanged.";
     private static final String NAME_CHANGED = ".NameChanged.";
     private static final String REMOVED = ".Removed.";
+    private static final String APPENDED = ".Appended.";
 
     @Override
     public List<ValidationEvent> evaluate(Differences differences) {
@@ -135,8 +136,14 @@ public final class ChangedEnumTrait extends AbstractDiffEvaluator {
                     if (newTrait.isSynthetic()) {
                         appendedSource = change.getNewShape().getSourceLocation();
                     }
-                    events.add(note(change.getNewShape(), appendedSource, String.format(
-                            "Enum value `%s` was appended", definition.getValue())));
+                    events.add(
+                            ValidationEvent.builder()
+                                    .severity(Severity.NOTE)
+                                    .message(String.format("Enum value `%s` was appended", definition.getValue()))
+                                    .shape(change.getNewShape())
+                                    .sourceLocation(appendedSource)
+                                    .id(getEventId() + APPENDED + newPosition)
+                                    .build());
                 }
             }
 
diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/pattern/UriPattern.java b/smithy-model/src/main/java/software/amazon/smithy/model/pattern/UriPattern.java
index e9847ed2e..7cc90857c 100644
--- a/smithy-model/src/main/java/software/amazon/smithy/model/pattern/UriPattern.java
+++ b/smithy-model/src/main/java/software/amazon/smithy/model/pattern/UriPattern.java
@@ -55,12 +55,12 @@ public final class UriPattern extends SmithyPattern {
      * Parse a URI pattern string into a UriPattern.
      *
      * <p>The provided value must match the origin-form request-target
-     * grammar production in RFC 7230, section 5.3.1.
+     * grammar production in RFC 9112, section 3.2.1.
      *
      * @param uri URI pattern to parse.
      * @return Returns the parsed URI pattern.
      * @throws InvalidUriPatternException for invalid URI patterns.
-     * @see <a href="https://tools.ietf.org/html/rfc7230#section-5.3.1">RFC 7230 Section 5.3.1</a>
+     * @see <a href="https://tools.ietf.org/html/rfc9112#section-3.2.1">RFC 9112 Section 3.2.1</a>
      */
     public static UriPattern parse(String uri) {
         if (uri.endsWith("?")) {
diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/validation/node/TimestampFormatPlugin.java b/smithy-model/src/main/java/software/amazon/smithy/model/validation/node/TimestampFormatPlugin.java
index 8518356cd..24919cf17 100644
--- a/smithy-model/src/main/java/software/amazon/smithy/model/validation/node/TimestampFormatPlugin.java
+++ b/smithy-model/src/main/java/software/amazon/smithy/model/validation/node/TimestampFormatPlugin.java
@@ -126,7 +126,7 @@ final class TimestampFormatPlugin implements NodeValidatorPlugin {
     private String createInvalidHttpDateMessage(String dateValue) {
         return String.format(
                 "Invalid value provided for %s formatted timestamp. Expected a string value that "
-                + "matches the IMF-fixdate production of RFC 7231 section-7.1.1.1. Found: %s",
+                + "matches the IMF-fixdate production of RFC 9110 section-5.6.7. Found: %s",
                 TimestampFormatTrait.HTTP_DATE, dateValue);
     }
 
diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/validation/validators/HttpHeaderTraitValidator.java b/smithy-model/src/main/java/software/amazon/smithy/model/validation/validators/HttpHeaderTraitValidator.java
index a438298b1..45dbff2f2 100644
--- a/smithy-model/src/main/java/software/amazon/smithy/model/validation/validators/HttpHeaderTraitValidator.java
+++ b/smithy-model/src/main/java/software/amazon/smithy/model/validation/validators/HttpHeaderTraitValidator.java
@@ -39,7 +39,7 @@ import software.amazon.smithy.utils.SetUtils;
  */
 public final class HttpHeaderTraitValidator extends AbstractValidator {
 
-    /** Gather the allowed characters for HTTP headers (tchar from RFC 7230). **/
+    /** Gather the allowed characters for HTTP headers (tchar from RFC 9110 section 5.6.2). **/
     private static final Set<Character> TCHAR = SetUtils.of(
             // "!" / "#" / "$" / "%" / "&" / "'" / "*" / "+" / "-" / "." / "^" / "_" / "`" / "|" / "~"
             '!', '#', '$', '%', '&', '\'', '*', '+', '-', '.', '^', '_', '`', '|', '~',
@@ -101,7 +101,7 @@ public final class HttpHeaderTraitValidator extends AbstractValidator {
         for (int i = 0; i < header.length(); i++) {
             if (!TCHAR.contains(header.charAt(i))) {
                 return Optional.of(danger(member, trait, String.format(
-                        "`%s` is not a valid HTTP header field name according to section 3.2 of RFC 7230", header)));
+                        "`%s` is not a valid HTTP header field name according to section 5.6.2 of RFC 9110", header)));
             }
         }
 
diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/validation/validators/HttpMethodSemanticsValidator.java b/smithy-model/src/main/java/software/amazon/smithy/model/validation/validators/HttpMethodSemanticsValidator.java
index 69f38b383..42f660a06 100644
--- a/smithy-model/src/main/java/software/amazon/smithy/model/validation/validators/HttpMethodSemanticsValidator.java
+++ b/smithy-model/src/main/java/software/amazon/smithy/model/validation/validators/HttpMethodSemanticsValidator.java
@@ -35,7 +35,7 @@ import software.amazon.smithy.utils.MapUtils;
 
 /**
  * Validates that `http` traits applied to operation shapes use the most
- * semantically appropriate HTTP method according to RFC 7231.
+ * semantically appropriate HTTP method according to RFC 9110.
  */
 public final class HttpMethodSemanticsValidator extends AbstractValidator {
     /**
diff --git a/smithy-model/src/main/resources/software/amazon/smithy/model/loader/prelude.smithy b/smithy-model/src/main/resources/software/amazon/smithy/model/loader/prelude.smithy
index abce5303e..b8a92aff7 100644
--- a/smithy-model/src/main/resources/software/amazon/smithy/model/loader/prelude.smithy
+++ b/smithy-model/src/main/resources/software/amazon/smithy/model/loader/prelude.smithy
@@ -1123,7 +1123,7 @@ enum timestampFormat {
     EPOCH_SECONDS = "epoch-seconds"
 
     /// An HTTP date as defined by the IMF-fixdate production in
-    /// RFC 7231#section-7.1.1.1 (for example, Tue, 29 Apr 2014 18:30:38 GMT).
+    /// RFC 9110#section-5.6.7 (for example, Tue, 29 Apr 2014 18:30:38 GMT).
     HTTP_DATE = "http-date"
 }
 
diff --git a/smithy-openapi/src/main/java/software/amazon/smithy/openapi/OpenApiConfig.java b/smithy-openapi/src/main/java/software/amazon/smithy/openapi/OpenApiConfig.java
index cf0db1894..90b83e14d 100644
--- a/smithy-openapi/src/main/java/software/amazon/smithy/openapi/OpenApiConfig.java
+++ b/smithy-openapi/src/main/java/software/amazon/smithy/openapi/OpenApiConfig.java
@@ -101,6 +101,7 @@ public class OpenApiConfig extends JsonSchemaConfig {
     private List<String> externalDocs = ListUtils.of(
             "Homepage", "API Reference", "User Guide", "Developer Guide", "Reference", "Guide");
     private boolean disableIntegerFormat = false;
+    private boolean syncCorsPreflightIntegration = false;
     private ErrorStatusConflictHandlingStrategy onErrorStatusConflict;
     private OpenApiVersion version = OpenApiVersion.VERSION_3_0_2;
 
@@ -355,6 +356,21 @@ public class OpenApiConfig extends JsonSchemaConfig {
     }
 
 
+    public boolean getSyncCorsPreflightIntegration() {
+        return this.syncCorsPreflightIntegration;
+    }
+
+    /**
+     * Set true to sync CORS preflight integration request templates with the other
+     * methods of the same path resource and set passthroughBehavior to "never".
+     *
+     * @param syncCorsPreflightIntegration True to match CORS preflight integration.
+     */
+    public void setSyncCorsPreflightIntegration(boolean syncCorsPreflightIntegration) {
+        this.syncCorsPreflightIntegration = syncCorsPreflightIntegration;
+    }
+
+
     public ErrorStatusConflictHandlingStrategy getOnErrorStatusConflict() {
         return onErrorStatusConflict;
     }
diff --git a/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/analysis/OperationContextParamsChecker.java b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/analysis/OperationContextParamsChecker.java
new file mode 100644
index 000000000..9581d4c6f
--- /dev/null
+++ b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/analysis/OperationContextParamsChecker.java
@@ -0,0 +1,296 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package software.amazon.smithy.rulesengine.analysis;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.function.Supplier;
+import software.amazon.smithy.jmespath.JmespathExpression;
+import software.amazon.smithy.jmespath.LinterResult;
+import software.amazon.smithy.jmespath.RuntimeType;
+import software.amazon.smithy.jmespath.ast.LiteralExpression;
+import software.amazon.smithy.model.Model;
+import software.amazon.smithy.model.knowledge.OperationIndex;
+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.DocumentShape;
+import software.amazon.smithy.model.shapes.DoubleShape;
+import software.amazon.smithy.model.shapes.FloatShape;
+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.MapShape;
+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.ServiceShape;
+import software.amazon.smithy.model.shapes.Shape;
+import software.amazon.smithy.model.shapes.ShapeVisitor;
+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.shapes.UnionShape;
+import software.amazon.smithy.model.traits.LengthTrait;
+import software.amazon.smithy.model.traits.RangeTrait;
+import software.amazon.smithy.rulesengine.language.syntax.parameters.ParameterType;
+import software.amazon.smithy.rulesengine.traits.OperationContextParamDefinition;
+
+public final class OperationContextParamsChecker {
+
+    private OperationContextParamsChecker() {
+
+    }
+
+    public static LinterResult lint(
+            OperationContextParamDefinition paramDefinition, OperationShape operationShape, Model model) {
+        JmespathExpression path = JmespathExpression.parse(paramDefinition.getPath());
+        StructureShape input = OperationIndex.of(model).expectInputShape(operationShape);
+        return path.lint(createCurrentNodeFromShape(input, model));
+    }
+
+    public static Optional<ParameterType> inferParameterType(
+            OperationContextParamDefinition paramDefinition, OperationShape operationShape, Model model) {
+        RuntimeType runtimeType = lint(paramDefinition, operationShape, model).getReturnType();
+        switch (runtimeType) {
+            case BOOLEAN:
+                return Optional.of(ParameterType.BOOLEAN);
+            case STRING:
+                return Optional.of(ParameterType.STRING);
+            case ARRAY:
+                return Optional.of(ParameterType.STRING_ARRAY);
+            default:
+                return Optional.empty();
+        }
+    }
+
+    private static LiteralExpression createCurrentNodeFromShape(Shape shape, Model model) {
+        return shape == null
+                ? LiteralExpression.ANY
+                : new LiteralExpression(shape.accept(new ModelRuntimeTypeGenerator(model)));
+    }
+
+
+    /**
+     * This class is duplicated from
+     * smithy-waiters/src/main/java/software/amazon/smithy/waiters/ModelRuntimeTypeGenerator.java
+     *
+     * It is duplicated here to avoid taking a dependency on smithy-waiters.
+     */
+    private static final class ModelRuntimeTypeGenerator implements ShapeVisitor<Object> {
+
+        private final Model model;
+        private Set<MemberShape> visited = new HashSet<>();
+
+        ModelRuntimeTypeGenerator(Model model) {
+            this.model = model;
+        }
+
+        @Override
+        public Object blobShape(BlobShape shape) {
+            return "blob";
+        }
+
+        @Override
+        public Object booleanShape(BooleanShape shape) {
+            return true;
+        }
+
+        @Override
+        public Object byteShape(ByteShape shape) {
+            return computeRange(shape);
+        }
+
+        @Override
+        public Object shortShape(ShortShape shape) {
+            return computeRange(shape);
+        }
+
+        @Override
+        public Object integerShape(IntegerShape shape) {
+            return computeRange(shape);
+        }
+
+        @Override
+        public Object longShape(LongShape shape) {
+            return computeRange(shape);
+        }
+
+        @Override
+        public Object floatShape(FloatShape shape) {
+            return computeRange(shape);
+        }
+
+        @Override
+        public Object doubleShape(DoubleShape shape) {
+            return computeRange(shape);
+        }
+
+        @Override
+        public Object bigIntegerShape(BigIntegerShape shape) {
+            return computeRange(shape);
+        }
+
+        @Override
+        public Object bigDecimalShape(BigDecimalShape shape) {
+            return computeRange(shape);
+        }
+
+        @Override
+        public Object documentShape(DocumentShape shape) {
+            return LiteralExpression.ANY;
+        }
+
+        @Override
+        public Object stringShape(StringShape shape) {
+            // Create a random string that does not exceed or go under the length trait.
+            int chars = computeLength(shape);
+
+            // Fill a string with "a"'s up to chars.
+            return new String(new char[chars]).replace("\0", "a");
+        }
+
+        @Override
+        public Object listShape(ListShape shape) {
+            return withCopiedVisitors(() -> {
+                int size = computeLength(shape);
+                List<Object> result = new ArrayList<>(size);
+                Object memberValue = shape.getMember().accept(this);
+                if (memberValue != null) {
+                    for (int i = 0; i < size; i++) {
+                        result.add(memberValue);
+                    }
+                }
+                return result;
+            });
+        }
+
+        // Visits members and mutates a copy of the current set of visited
+        // shapes rather than a shared set. This allows a shape to be used
+        // multiple times in the closure of a single shape without causing the
+        // reuse of the shape to always be assumed to be a recursive type.
+        private Object withCopiedVisitors(Supplier<Object> supplier) {
+            // Account for recursive shapes at the current
+            Set<MemberShape> visitedCopy = new HashSet<>(visited);
+            Object result = supplier.get();
+            visited = visitedCopy;
+            return result;
+        }
+
+        @Override
+        public Object mapShape(MapShape shape) {
+            return withCopiedVisitors(() -> {
+                int size = computeLength(shape);
+                Map<String, Object> result = new HashMap<>();
+                String key = (String) shape.getKey().accept(this);
+                Object memberValue = shape.getValue().accept(this);
+                for (int i = 0; i < size; i++) {
+                    result.put(key + i, memberValue);
+                }
+                return result;
+            });
+        }
+
+        @Override
+        public Object structureShape(StructureShape shape) {
+            return structureOrUnion(shape);
+        }
+
+        @Override
+        public Object unionShape(UnionShape shape) {
+            return structureOrUnion(shape);
+        }
+
+        private Object structureOrUnion(Shape shape) {
+            return withCopiedVisitors(() -> {
+                Map<String, Object> result = new LinkedHashMap<>();
+                for (MemberShape member : shape.members()) {
+                    Object memberValue = member.accept(this);
+                    result.put(member.getMemberName(), memberValue);
+                }
+                return result;
+            });
+        }
+
+        @Override
+        public Object memberShape(MemberShape shape) {
+            // Account for recursive shapes.
+            // A false return value means it was in the set.
+            if (!visited.add(shape)) {
+                return LiteralExpression.ANY;
+            }
+
+            return model.getShape(shape.getTarget())
+                    .map(target -> target.accept(this))
+                    // Rather than fail on broken models during waiter validation,
+                    // return an ANY to get *some* validation.
+                    .orElse(LiteralExpression.ANY);
+        }
+
+        @Override
+        public Object timestampShape(TimestampShape shape) {
+            return LiteralExpression.NUMBER;
+        }
+
+        @Override
+        public Object operationShape(OperationShape shape) {
+            throw new UnsupportedOperationException(shape.toString());
+        }
+
+        @Override
+        public Object resourceShape(ResourceShape shape) {
+            throw new UnsupportedOperationException(shape.toString());
+        }
+
+        @Override
+        public Object serviceShape(ServiceShape shape) {
+            throw new UnsupportedOperationException(shape.toString());
+        }
+
+        private int computeLength(Shape shape) {
+            // Create a random string that does not exceed or go under the length trait.
+            int chars = 2;
+
+            if (shape.hasTrait(LengthTrait.class)) {
+                LengthTrait trait = shape.expectTrait(LengthTrait.class);
+                if (trait.getMin().isPresent()) {
+                    chars = Math.max(chars, trait.getMin().get().intValue());
+                }
+                if (trait.getMax().isPresent()) {
+                    chars = Math.min(chars, trait.getMax().get().intValue());
+                }
+            }
+
+            return chars;
+        }
+
+        private double computeRange(Shape shape) {
+            // Create a random string that does not exceed or go under the range trait.
+            double i = 8;
+
+            if (shape.hasTrait(RangeTrait.class)) {
+                RangeTrait trait = shape.expectTrait(RangeTrait.class);
+                if (trait.getMin().isPresent()) {
+                    i = Math.max(i, trait.getMin().get().doubleValue());
+                }
+                if (trait.getMax().isPresent()) {
+                    i = Math.min(i, trait.getMax().get().doubleValue());
+                }
+            }
+
+            return i;
+        }
+    }
+}
diff --git a/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/traits/OperationContextParamsTraitValidator.java b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/traits/OperationContextParamsTraitValidator.java
index c652fc1c1..e2666f1c3 100644
--- a/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/traits/OperationContextParamsTraitValidator.java
+++ b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/traits/OperationContextParamsTraitValidator.java
@@ -7,13 +7,8 @@ package software.amazon.smithy.rulesengine.traits;
 
 import java.util.ArrayList;
 import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.Set;
-import java.util.function.Supplier;
 import java.util.stream.Collectors;
 import software.amazon.smithy.jmespath.ExpressionVisitor;
 import software.amazon.smithy.jmespath.JmespathException;
@@ -38,34 +33,10 @@ import software.amazon.smithy.jmespath.ast.ProjectionExpression;
 import software.amazon.smithy.jmespath.ast.SliceExpression;
 import software.amazon.smithy.jmespath.ast.Subexpression;
 import software.amazon.smithy.model.Model;
-import software.amazon.smithy.model.knowledge.OperationIndex;
-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.DocumentShape;
-import software.amazon.smithy.model.shapes.DoubleShape;
-import software.amazon.smithy.model.shapes.FloatShape;
-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.MapShape;
-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.ServiceShape;
-import software.amazon.smithy.model.shapes.Shape;
-import software.amazon.smithy.model.shapes.ShapeVisitor;
-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.shapes.UnionShape;
-import software.amazon.smithy.model.traits.LengthTrait;
-import software.amazon.smithy.model.traits.RangeTrait;
 import software.amazon.smithy.model.validation.AbstractValidator;
 import software.amazon.smithy.model.validation.ValidationEvent;
+import software.amazon.smithy.rulesengine.analysis.OperationContextParamsChecker;
 import software.amazon.smithy.utils.ListUtils;
 import software.amazon.smithy.utils.SmithyUnstableApi;
 
@@ -86,8 +57,8 @@ public final class OperationContextParamsTraitValidator extends AbstractValidato
 
                 try {
                     JmespathExpression path = JmespathExpression.parse(entry.getValue().getPath());
-                    StructureShape input = OperationIndex.of(model).expectInputShape(operationShape);
-                    LinterResult linterResult = path.lint(createCurrentNodeFromShape(input, model));
+                    LinterResult linterResult = OperationContextParamsChecker.lint(
+                            entry.getValue(), operationShape, model);
 
                     if (!linterResult.getProblems().isEmpty()) {
                         events.add(error(operationShape,
@@ -134,12 +105,6 @@ public final class OperationContextParamsTraitValidator extends AbstractValidato
         return events;
     }
 
-    private LiteralExpression createCurrentNodeFromShape(Shape shape, Model model) {
-        return shape == null
-                ? LiteralExpression.ANY
-                : new LiteralExpression(shape.accept(new ModelRuntimeTypeGenerator(model)));
-    }
-
     private static final class UnsupportedJmesPathVisitor implements ExpressionVisitor<List<String>> {
 
         @Override
@@ -245,215 +210,4 @@ public final class OperationContextParamsTraitValidator extends AbstractValidato
             return Collections.unmodifiableList(unsupported);
         }
     }
-
-    /**
-     * This class is duplicated from
-     * smithy-waiters/src/main/java/software/amazon/smithy/waiters/ModelRuntimeTypeGenerator.java
-     *
-     * It is duplicated here to avoid taking a dependency on smithy-waiters.
-     */
-    private static final class ModelRuntimeTypeGenerator implements ShapeVisitor<Object> {
-
-        private final Model model;
-        private Set<MemberShape> visited = new HashSet<>();
-
-        ModelRuntimeTypeGenerator(Model model) {
-            this.model = model;
-        }
-
-        @Override
-        public Object blobShape(BlobShape shape) {
-            return "blob";
-        }
-
-        @Override
-        public Object booleanShape(BooleanShape shape) {
-            return true;
-        }
-
-        @Override
-        public Object byteShape(ByteShape shape) {
-            return computeRange(shape);
-        }
-
-        @Override
-        public Object shortShape(ShortShape shape) {
-            return computeRange(shape);
-        }
-
-        @Override
-        public Object integerShape(IntegerShape shape) {
-            return computeRange(shape);
-        }
-
-        @Override
-        public Object longShape(LongShape shape) {
-            return computeRange(shape);
-        }
-
-        @Override
-        public Object floatShape(FloatShape shape) {
-            return computeRange(shape);
-        }
-
-        @Override
-        public Object doubleShape(DoubleShape shape) {
-            return computeRange(shape);
-        }
-
-        @Override
-        public Object bigIntegerShape(BigIntegerShape shape) {
-            return computeRange(shape);
-        }
-
-        @Override
-        public Object bigDecimalShape(BigDecimalShape shape) {
-            return computeRange(shape);
-        }
-
-        @Override
-        public Object documentShape(DocumentShape shape) {
-            return LiteralExpression.ANY;
-        }
-
-        @Override
-        public Object stringShape(StringShape shape) {
-            // Create a random string that does not exceed or go under the length trait.
-            int chars = computeLength(shape);
-
-            // Fill a string with "a"'s up to chars.
-            return new String(new char[chars]).replace("\0", "a");
-        }
-
-        @Override
-        public Object listShape(ListShape shape) {
-            return withCopiedVisitors(() -> {
-                int size = computeLength(shape);
-                List<Object> result = new ArrayList<>(size);
-                Object memberValue = shape.getMember().accept(this);
-                if (memberValue != null) {
-                    for (int i = 0; i < size; i++) {
-                        result.add(memberValue);
-                    }
-                }
-                return result;
-            });
-        }
-
-        // Visits members and mutates a copy of the current set of visited
-        // shapes rather than a shared set. This allows a shape to be used
-        // multiple times in the closure of a single shape without causing the
-        // reuse of the shape to always be assumed to be a recursive type.
-        private Object withCopiedVisitors(Supplier<Object> supplier) {
-            // Account for recursive shapes at the current
-            Set<MemberShape> visitedCopy = new HashSet<>(visited);
-            Object result = supplier.get();
-            visited = visitedCopy;
-            return result;
-        }
-
-        @Override
-        public Object mapShape(MapShape shape) {
-            return withCopiedVisitors(() -> {
-                int size = computeLength(shape);
-                Map<String, Object> result = new HashMap<>();
-                String key = (String) shape.getKey().accept(this);
-                Object memberValue = shape.getValue().accept(this);
-                for (int i = 0; i < size; i++) {
-                    result.put(key + i, memberValue);
-                }
-                return result;
-            });
-        }
-
-        @Override
-        public Object structureShape(StructureShape shape) {
-            return structureOrUnion(shape);
-        }
-
-        @Override
-        public Object unionShape(UnionShape shape) {
-            return structureOrUnion(shape);
-        }
-
-        private Object structureOrUnion(Shape shape) {
-            return withCopiedVisitors(() -> {
-                Map<String, Object> result = new LinkedHashMap<>();
-                for (MemberShape member : shape.members()) {
-                    Object memberValue = member.accept(this);
-                    result.put(member.getMemberName(), memberValue);
-                }
-                return result;
-            });
-        }
-
-        @Override
-        public Object memberShape(MemberShape shape) {
-            // Account for recursive shapes.
-            // A false return value means it was in the set.
-            if (!visited.add(shape)) {
-                return LiteralExpression.ANY;
-            }
-
-            return model.getShape(shape.getTarget())
-                    .map(target -> target.accept(this))
-                    // Rather than fail on broken models during waiter validation,
-                    // return an ANY to get *some* validation.
-                    .orElse(LiteralExpression.ANY);
-        }
-
-        @Override
-        public Object timestampShape(TimestampShape shape) {
-            return LiteralExpression.NUMBER;
-        }
-
-        @Override
-        public Object operationShape(OperationShape shape) {
-            throw new UnsupportedOperationException(shape.toString());
-        }
-
-        @Override
-        public Object resourceShape(ResourceShape shape) {
-            throw new UnsupportedOperationException(shape.toString());
-        }
-
-        @Override
-        public Object serviceShape(ServiceShape shape) {
-            throw new UnsupportedOperationException(shape.toString());
-        }
-
-        private int computeLength(Shape shape) {
-            // Create a random string that does not exceed or go under the length trait.
-            int chars = 2;
-
-            if (shape.hasTrait(LengthTrait.class)) {
-                LengthTrait trait = shape.expectTrait(LengthTrait.class);
-                if (trait.getMin().isPresent()) {
-                    chars = Math.max(chars, trait.getMin().get().intValue());
-                }
-                if (trait.getMax().isPresent()) {
-                    chars = Math.min(chars, trait.getMax().get().intValue());
-                }
-            }
-
-            return chars;
-        }
-
-        private double computeRange(Shape shape) {
-            // Create a random string that does not exceed or go under the range trait.
-            double i = 8;
-
-            if (shape.hasTrait(RangeTrait.class)) {
-                RangeTrait trait = shape.expectTrait(RangeTrait.class);
-                if (trait.getMin().isPresent()) {
-                    i = Math.max(i, trait.getMin().get().doubleValue());
-                }
-                if (trait.getMax().isPresent()) {
-                    i = Math.min(i, trait.getMax().get().doubleValue());
-                }
-            }
-
-            return i;
-        }
-    }
 }
diff --git a/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/validators/RuleSetParameterValidator.java b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/validators/RuleSetParameterValidator.java
index a08d3bc0b..27f6d9f45 100644
--- a/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/validators/RuleSetParameterValidator.java
+++ b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/validators/RuleSetParameterValidator.java
@@ -10,6 +10,7 @@ import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 import java.util.Set;
 import software.amazon.smithy.model.FromSourceLocation;
 import software.amazon.smithy.model.Model;
@@ -22,6 +23,7 @@ import software.amazon.smithy.model.shapes.Shape;
 import software.amazon.smithy.model.shapes.StructureShape;
 import software.amazon.smithy.model.validation.AbstractValidator;
 import software.amazon.smithy.model.validation.ValidationEvent;
+import software.amazon.smithy.rulesengine.analysis.OperationContextParamsChecker;
 import software.amazon.smithy.rulesengine.language.EndpointRuleSet;
 import software.amazon.smithy.rulesengine.language.evaluation.value.Value;
 import software.amazon.smithy.rulesengine.language.syntax.parameters.Parameter;
@@ -34,6 +36,7 @@ import software.amazon.smithy.rulesengine.traits.EndpointRuleSetTrait;
 import software.amazon.smithy.rulesengine.traits.EndpointTestCase;
 import software.amazon.smithy.rulesengine.traits.EndpointTestOperationInput;
 import software.amazon.smithy.rulesengine.traits.EndpointTestsTrait;
+import software.amazon.smithy.rulesengine.traits.OperationContextParamsTrait;
 import software.amazon.smithy.rulesengine.traits.StaticContextParamDefinition;
 import software.amazon.smithy.rulesengine.traits.StaticContextParamsTrait;
 import software.amazon.smithy.utils.ListUtils;
@@ -80,7 +83,7 @@ public final class RuleSetParameterValidator extends AbstractValidator {
 
         if (serviceShape.hasTrait(ClientContextParamsTrait.class)) {
             ClientContextParamsTrait trait = serviceShape.expectTrait(ClientContextParamsTrait.class);
-            for (Map.Entry<String, ClientContextParamDefinition> entry : trait.getParameters().entrySet())  {
+            for (Map.Entry<String, ClientContextParamDefinition> entry : trait.getParameters().entrySet()) {
                 endpointParams.put(entry.getKey(), Parameter.builder()
                         .name(entry.getKey())
                         .type(ParameterType.fromShapeType(entry.getValue().getType()))
@@ -108,6 +111,26 @@ public final class RuleSetParameterValidator extends AbstractValidator {
                 }
             }
 
+            if (operationShape.hasTrait(OperationContextParamsTrait.ID)) {
+                OperationContextParamsTrait trait = operationShape.expectTrait(OperationContextParamsTrait.class);
+                trait.getParameters().forEach((name, p) -> {
+                    Optional<ParameterType> maybeType = OperationContextParamsChecker
+                            .inferParameterType(p, operationShape, model);
+                    maybeType.ifPresent(parameterType -> {
+                        if (endpointParams.containsKey(name) && endpointParams.get(name).getType() != parameterType) {
+                            errors.add(parameterError(operationShape, trait,
+                                    "OperationContextParams.InconsistentType",
+                                    String.format("Inconsistent type for `%s` parameter", name)));
+                        } else {
+                            endpointParams.put(name, Parameter.builder()
+                                    .name(name)
+                                    .type(parameterType)
+                                    .build());
+                        }
+                    });
+                });
+            }
+
             StructureShape input = model.expectShape(operationShape.getInputShape(), StructureShape.class);
             for (MemberShape memberShape : input.members()) {
                 if (memberShape.hasTrait(ContextParamTrait.class)) {
@@ -200,7 +223,7 @@ public final class RuleSetParameterValidator extends AbstractValidator {
             // All test parameter types from corresponding ruleset parameters must match in all test cases.
             if (!testSuiteHasParam) {
                 errors.add(danger(serviceShape, parameter,
-                                String.format("Parameter `%s` is never used in an `EndpointTests` test case", name))
+                        String.format("Parameter `%s` is never used in an `EndpointTests` test case", name))
                         .toBuilder()
                         .id(getName() + ".TestCase.Unused").build());
             } else {
@@ -208,7 +231,7 @@ public final class RuleSetParameterValidator extends AbstractValidator {
                     if (testParam.getType() != parameter.getType()) {
                         errors.add(parameterError(serviceShape, testParam, "TestCase.TypeMismatch",
                                 String.format("Type mismatch for parameter `%s`, `%s` expected",
-                                testParam.getName().toString(), parameter.getType())));
+                                        testParam.getName().toString(), parameter.getType())));
                     }
                 }
             }
@@ -302,11 +325,11 @@ public final class RuleSetParameterValidator extends AbstractValidator {
     private Parameter buildParameter(String name, Node node) {
         Value value = Value.fromNode(node);
         return Parameter.builder()
-                       .sourceLocation(value)
-                       .name(name)
-                       .value(value)
-                       .type(ParameterType.fromType(value.getType()))
-                       .build();
+                .sourceLocation(value)
+                .name(name)
+                .value(value)
+                .type(ParameterType.fromType(value.getType()))
+                .build();
     }
 
     private List<Parameter> merge(List<Parameter> previousList, List<Parameter> newList) {
diff --git a/smithy-utils/src/main/java/software/amazon/smithy/utils/MediaType.java b/smithy-utils/src/main/java/software/amazon/smithy/utils/MediaType.java
index 96492e025..66ff16794 100644
--- a/smithy-utils/src/main/java/software/amazon/smithy/utils/MediaType.java
+++ b/smithy-utils/src/main/java/software/amazon/smithy/utils/MediaType.java
@@ -26,7 +26,7 @@ import java.util.Set;
 
 /**
  * Implements a simple media type parser based on the Content-Type grammar defined in
- * <a href="https://tools.ietf.org/html/rfc7231#section-3.1.1.1">RFC 7231</a>.
+ * <a href="https://tools.ietf.org/html/rfc9110#section-8.3">RFC 9110</a>.
  *
  * <p>The type, subtype, and parameter names are all canonicalized to
  * lowercase strings.
@@ -154,7 +154,7 @@ public final class MediaType {
     }
 
     private static final class Parser extends SimpleParser {
-        // See https://tools.ietf.org/html/rfc7230#section-3.2.6
+        // See https://tools.ietf.org/html/rfc9110#section-5.6.2
         // token          = 1*tchar
         // tchar          = "!" / "#" / "$" / "%" / "&" / "'" / "*"
         //                / "+" / "-" / "." / "^" / "_" / "`" / "|" / "~"
@@ -198,7 +198,7 @@ public final class MediaType {
         }
 
         private void parse() {
-            // From: https://tools.ietf.org/html/rfc7231#section-3.1.1.1
+            // From: https://tools.ietf.org/html/rfc9110#section-8.3.1
             // The type, subtype, and parameter name tokens are case-insensitive.
             //     media-type = type "/" subtype *( OWS ";" OWS parameter )
             //     type       = token.
@@ -240,7 +240,7 @@ public final class MediaType {
             return sliceFrom(start);
         }
 
-        // See https://tools.ietf.org/html/rfc7230#section-3.2.6
+        // See https://tools.ietf.org/html/rfc9110#section-5.6.4
         // quoted-string  = DQUOTE *( qdtext / quoted-pair ) DQUOTE
         // qdtext         = HTAB / SP /%x21 / %x23-5B / %x5D-7E / obs-text
         // obs-text       = %x80-FF


Test patch:
diff --git a/smithy-aws-apigateway-openapi/src/test/java/software/amazon/smithy/aws/apigateway/openapi/CorsTest.java b/smithy-aws-apigateway-openapi/src/test/java/software/amazon/smithy/aws/apigateway/openapi/CorsTest.java
index dd4caa027..1cd507ecd 100644
--- a/smithy-aws-apigateway-openapi/src/test/java/software/amazon/smithy/aws/apigateway/openapi/CorsTest.java
+++ b/smithy-aws-apigateway-openapi/src/test/java/software/amazon/smithy/aws/apigateway/openapi/CorsTest.java
@@ -112,4 +112,37 @@ public class CorsTest {
 
         Node.assertEquals(result, expectedNode);
     }
+
+    @Test
+    public void withPreflightIntegrationSync() {
+        Model model = Model.assembler(getClass().getClassLoader())
+                .discoverModels(getClass().getClassLoader())
+                .addImport(getClass().getResource("cors-with-multi-request-templates.json"))
+                .assemble()
+                .unwrap();
+        OpenApiConfig config = new OpenApiConfig();
+        config.setService(ShapeId.from("example.smithy#MyService"));
+        config.setSyncCorsPreflightIntegration(true);
+        ObjectNode result = OpenApiConverter.create().config(config).convertToNode(model);
+        Node expectedNode = Node.parse(IoUtils.toUtf8String(
+                getClass().getResourceAsStream("cors-with-preflight-sync.openapi.json")));
+
+        Node.assertEquals(result, expectedNode);
+    }
+
+    @Test
+    public void withoutPreflightIntegrationSync() {
+        Model model = Model.assembler(getClass().getClassLoader())
+                .discoverModels(getClass().getClassLoader())
+                .addImport(getClass().getResource("cors-with-multi-request-templates.json"))
+                .assemble()
+                .unwrap();
+        OpenApiConfig config = new OpenApiConfig();
+        config.setService(ShapeId.from("example.smithy#MyService"));
+        ObjectNode result = OpenApiConverter.create().config(config).convertToNode(model);
+        Node expectedNode = Node.parse(IoUtils.toUtf8String(
+                getClass().getResourceAsStream("cors-without-preflight-sync.openapi.json")));
+
+        Node.assertEquals(result, expectedNode);
+    }
 }
diff --git a/smithy-aws-apigateway-openapi/src/test/resources/software/amazon/smithy/aws/apigateway/openapi/cors-with-multi-request-templates.json b/smithy-aws-apigateway-openapi/src/test/resources/software/amazon/smithy/aws/apigateway/openapi/cors-with-multi-request-templates.json
new file mode 100644
index 000000000..714d3df7d
--- /dev/null
+++ b/smithy-aws-apigateway-openapi/src/test/resources/software/amazon/smithy/aws/apigateway/openapi/cors-with-multi-request-templates.json
@@ -0,0 +1,102 @@
+{
+    "smithy": "2.0",
+    "shapes": {
+        "example.smithy#MyService": {
+            "type": "service",
+            "version": "2006-03-01",
+            "operations": [
+                {
+                    "target": "example.smithy#MockPut"
+                },
+                {
+                    "target": "example.smithy#MockGet"
+                }
+            ],
+            "traits": {
+                "aws.protocols#restJson1": {},
+                "aws.auth#sigv4": {
+                    "name": "myservice"
+                },
+                "smithy.api#cors": {
+                    "origin": "https://www.example.com",
+                    "maxAge": 86400,
+                    "additionalAllowedHeaders": [
+                        "X-Service-Input-Metadata"
+                    ],
+                    "additionalExposedHeaders": [
+                        "X-Service-Output-Metadata"
+                    ]
+                }
+            }
+        },
+        "example.smithy#MockGet": {
+            "type": "operation",
+            "output": {
+                "target": "example.smithy#MockOutput"
+            },
+            "traits": {
+                "aws.apigateway#mockIntegration": {
+                    "passThroughBehavior": "never",
+                    "requestTemplates": {
+                        "application/json": "{\"statusCode\": 200}",
+                        "application/x-www-form-urlencoded": "{\"statusCode\": 200}"
+                    },
+                    "responses": {
+                        "default": {
+                            "statusCode": "200",
+                            "responseTemplates": {
+                                "application/json": "{\"extendedRequestId\": \"$context.extendedRequestId\"}"
+                            }
+                        }
+                    }
+                },
+                "smithy.api#http": {
+                    "code": 200,
+                    "method": "GET",
+                    "uri": "/mock"
+                },
+                "smithy.api#readonly": {}
+            }
+        },
+        "example.smithy#MockPut": {
+            "type": "operation",
+            "output": {
+                "target": "example.smithy#MockOutput"
+            },
+            "traits": {
+                "aws.apigateway#mockIntegration": {
+                    "passThroughBehavior": "never",
+                    "requestTemplates": {
+                        "text/plain": "{\"statusCode\": 200}",
+                        "application/xml": "{\"statusCode\": 200}"
+                    },
+                    "responses": {
+                        "default": {
+                            "statusCode": "200",
+                            "responseTemplates": {
+                                "application/json": "{\"extendedRequestId\": \"$context.extendedRequestId\"}"
+                            }
+                        }
+                    }
+                },
+                "smithy.api#http": {
+                    "code": 201,
+                    "method": "PUT",
+                    "uri": "/mock"
+                },
+                "smithy.api#idempotent": {}
+            }
+        },
+        "example.smithy#MockOutput": {
+            "type": "structure",
+            "members": {
+                "extendedRequestId": {
+                    "target": "smithy.api#String",
+                    "traits": {
+                        "smithy.api#required": {}
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/smithy-aws-apigateway-openapi/src/test/resources/software/amazon/smithy/aws/apigateway/openapi/cors-with-preflight-sync.openapi.json b/smithy-aws-apigateway-openapi/src/test/resources/software/amazon/smithy/aws/apigateway/openapi/cors-with-preflight-sync.openapi.json
new file mode 100644
index 000000000..5f3b8154b
--- /dev/null
+++ b/smithy-aws-apigateway-openapi/src/test/resources/software/amazon/smithy/aws/apigateway/openapi/cors-with-preflight-sync.openapi.json
@@ -0,0 +1,222 @@
+{
+  "openapi": "3.0.2",
+  "info": {
+    "title": "MyService",
+    "version": "2006-03-01"
+  },
+  "paths": {
+    "/mock": {
+      "get": {
+        "operationId": "MockGet",
+        "responses": {
+          "200": {
+            "description": "MockGet 200 response",
+            "headers": {
+              "Access-Control-Allow-Origin": {
+                "schema": {
+                  "type": "string"
+                }
+              },
+              "Access-Control-Expose-Headers": {
+                "schema": {
+                  "type": "string"
+                }
+              }
+            },
+            "content": {
+              "application/json": {
+                "schema": {
+                  "$ref": "#/components/schemas/MockGetResponseContent"
+                }
+              }
+            }
+          }
+        },
+        "x-amazon-apigateway-integration": {
+          "requestTemplates": {
+            "application/json": "{\"statusCode\": 200}",
+            "application/x-www-form-urlencoded": "{\"statusCode\": 200}"
+          },
+          "responses": {
+            "default": {
+              "statusCode": "200",
+              "responseTemplates": {
+                "application/json": "{\"extendedRequestId\": \"$context.extendedRequestId\"}"
+              },
+              "responseParameters": {
+                "method.response.header.Access-Control-Allow-Origin": "'https://www.example.com'",
+                "method.response.header.Access-Control-Expose-Headers": "'Content-Length,Content-Type,X-Amzn-Errortype,X-Amzn-Requestid,X-Service-Output-Metadata'"
+              }
+            }
+          },
+          "type": "mock",
+          "passthroughBehavior": "never"
+        }
+      },
+      "options": {
+        "description": "Handles CORS-preflight requests",
+        "operationId": "CorsMock",
+        "responses": {
+          "200": {
+            "description": "Canned response for CORS-preflight requests",
+            "headers": {
+              "Access-Control-Allow-Headers": {
+                "schema": {
+                  "type": "string"
+                }
+              },
+              "Access-Control-Allow-Methods": {
+                "schema": {
+                  "type": "string"
+                }
+              },
+              "Access-Control-Allow-Origin": {
+                "schema": {
+                  "type": "string"
+                }
+              },
+              "Access-Control-Max-Age": {
+                "schema": {
+                  "type": "string"
+                }
+              }
+            }
+          }
+        },
+        "security": [],
+        "tags": [
+          "CORS"
+        ],
+        "x-amazon-apigateway-integration": {
+          "contentHandling": "CONVERT_TO_TEXT",
+          "requestTemplates": {
+            "application/xml": "{\"statusCode\":200}",
+            "application/x-www-form-urlencoded": "{\"statusCode\":200}",
+            "application/json": "{\"statusCode\":200}",
+            "text/plain": "{\"statusCode\":200}"
+          },
+          "responses": {
+            "default": {
+              "responseParameters": {
+                "method.response.header.Access-Control-Max-Age": "'86400'",
+                "method.response.header.Access-Control-Allow-Headers": "'Amz-Sdk-Invocation-Id,Amz-Sdk-Request,Authorization,Date,Host,X-Amz-Content-Sha256,X-Amz-Date,X-Amz-Security-Token,X-Amz-Target,X-Amz-User-Agent,X-Amzn-Trace-Id,X-Service-Input-Metadata'",
+                "method.response.header.Access-Control-Allow-Origin": "'https://www.example.com'",
+                "method.response.header.Access-Control-Allow-Methods": "'GET,PUT'"
+              },
+              "statusCode": "200"
+            }
+          },
+          "type": "mock",
+          "passthroughBehavior": "never"
+        }
+      },
+      "put": {
+        "operationId": "MockPut",
+        "responses": {
+          "201": {
+            "description": "MockPut 201 response",
+            "headers": {
+              "Access-Control-Allow-Origin": {
+                "schema": {
+                  "type": "string"
+                }
+              },
+              "Access-Control-Expose-Headers": {
+                "schema": {
+                  "type": "string"
+                }
+              }
+            },
+            "content": {
+              "application/json": {
+                "schema": {
+                  "$ref": "#/components/schemas/MockPutResponseContent"
+                }
+              }
+            }
+          }
+        },
+        "x-amazon-apigateway-integration": {
+          "requestTemplates": {
+            "text/plain": "{\"statusCode\": 200}",
+            "application/xml": "{\"statusCode\": 200}"
+          },
+          "responses": {
+            "default": {
+              "statusCode": "200",
+              "responseTemplates": {
+                "application/json": "{\"extendedRequestId\": \"$context.extendedRequestId\"}"
+              },
+              "responseParameters": {
+                "method.response.header.Access-Control-Allow-Origin": "'https://www.example.com'",
+                "method.response.header.Access-Control-Expose-Headers": "'Content-Length,Content-Type,X-Amzn-Errortype,X-Amzn-Requestid,X-Service-Output-Metadata'"
+              }
+            }
+          },
+          "type": "mock",
+          "passthroughBehavior": "never"
+        }
+      }
+    }
+  },
+  "components": {
+    "schemas": {
+      "MockGetResponseContent": {
+        "type": "object",
+        "properties": {
+          "extendedRequestId": {
+            "type": "string"
+          }
+        },
+        "required": [
+          "extendedRequestId"
+        ]
+      },
+      "MockPutResponseContent": {
+        "type": "object",
+        "properties": {
+          "extendedRequestId": {
+            "type": "string"
+          }
+        },
+        "required": [
+          "extendedRequestId"
+        ]
+      }
+    },
+    "securitySchemes": {
+      "aws.auth.sigv4": {
+        "type": "apiKey",
+        "description": "AWS Signature Version 4 authentication",
+        "name": "Authorization",
+        "in": "header",
+        "x-amazon-apigateway-authtype": "awsSigv4"
+      }
+    }
+  },
+  "security": [
+    {
+      "aws.auth.sigv4": [ ]
+    }
+  ],
+  "x-amazon-apigateway-gateway-responses": {
+    "DEFAULT_4XX": {
+      "responseTemplates": {
+        "application/json": "{\"message\":$context.error.messageString}"
+      },
+      "responseParameters": {
+        "gatewayresponse.header.Access-Control-Allow-Origin": "'https://www.example.com'",
+        "gatewayresponse.header.Access-Control-Expose-Headers": "'X-Service-Output-Metadata'"
+      }
+    },
+    "DEFAULT_5XX": {
+      "responseTemplates": {
+        "application/json": "{\"message\":$context.error.messageString}"
+      },
+      "responseParameters": {
+        "gatewayresponse.header.Access-Control-Allow-Origin": "'https://www.example.com'",
+        "gatewayresponse.header.Access-Control-Expose-Headers": "'X-Service-Output-Metadata'"
+      }
+    }
+  }
+}
diff --git a/smithy-aws-apigateway-openapi/src/test/resources/software/amazon/smithy/aws/apigateway/openapi/cors-without-preflight-sync.openapi.json b/smithy-aws-apigateway-openapi/src/test/resources/software/amazon/smithy/aws/apigateway/openapi/cors-without-preflight-sync.openapi.json
new file mode 100644
index 000000000..2ce6e10fa
--- /dev/null
+++ b/smithy-aws-apigateway-openapi/src/test/resources/software/amazon/smithy/aws/apigateway/openapi/cors-without-preflight-sync.openapi.json
@@ -0,0 +1,219 @@
+{
+  "openapi": "3.0.2",
+  "info": {
+    "title": "MyService",
+    "version": "2006-03-01"
+  },
+  "paths": {
+    "/mock": {
+      "get": {
+        "operationId": "MockGet",
+        "responses": {
+          "200": {
+            "description": "MockGet 200 response",
+            "headers": {
+              "Access-Control-Allow-Origin": {
+                "schema": {
+                  "type": "string"
+                }
+              },
+              "Access-Control-Expose-Headers": {
+                "schema": {
+                  "type": "string"
+                }
+              }
+            },
+            "content": {
+              "application/json": {
+                "schema": {
+                  "$ref": "#/components/schemas/MockGetResponseContent"
+                }
+              }
+            }
+          }
+        },
+        "x-amazon-apigateway-integration": {
+          "requestTemplates": {
+            "application/json": "{\"statusCode\": 200}",
+            "application/x-www-form-urlencoded": "{\"statusCode\": 200}"
+          },
+          "responses": {
+            "default": {
+              "statusCode": "200",
+              "responseTemplates": {
+                "application/json": "{\"extendedRequestId\": \"$context.extendedRequestId\"}"
+              },
+              "responseParameters": {
+                "method.response.header.Access-Control-Allow-Origin": "'https://www.example.com'",
+                "method.response.header.Access-Control-Expose-Headers": "'Content-Length,Content-Type,X-Amzn-Errortype,X-Amzn-Requestid,X-Service-Output-Metadata'"
+              }
+            }
+          },
+          "type": "mock",
+          "passthroughBehavior": "never"
+        }
+      },
+      "options": {
+        "description": "Handles CORS-preflight requests",
+        "operationId": "CorsMock",
+        "responses": {
+          "200": {
+            "description": "Canned response for CORS-preflight requests",
+            "headers": {
+              "Access-Control-Allow-Headers": {
+                "schema": {
+                  "type": "string"
+                }
+              },
+              "Access-Control-Allow-Methods": {
+                "schema": {
+                  "type": "string"
+                }
+              },
+              "Access-Control-Allow-Origin": {
+                "schema": {
+                  "type": "string"
+                }
+              },
+              "Access-Control-Max-Age": {
+                "schema": {
+                  "type": "string"
+                }
+              }
+            }
+          }
+        },
+        "security": [],
+        "tags": [
+          "CORS"
+        ],
+        "x-amazon-apigateway-integration": {
+          "contentHandling": "CONVERT_TO_TEXT",
+          "requestTemplates": {
+            "application/json": "{\"statusCode\":200}"
+          },
+          "responses": {
+            "default": {
+              "responseParameters": {
+                "method.response.header.Access-Control-Max-Age": "'86400'",
+                "method.response.header.Access-Control-Allow-Headers": "'Amz-Sdk-Invocation-Id,Amz-Sdk-Request,Authorization,Date,Host,X-Amz-Content-Sha256,X-Amz-Date,X-Amz-Security-Token,X-Amz-Target,X-Amz-User-Agent,X-Amzn-Trace-Id,X-Service-Input-Metadata'",
+                "method.response.header.Access-Control-Allow-Origin": "'https://www.example.com'",
+                "method.response.header.Access-Control-Allow-Methods": "'GET,PUT'"
+              },
+              "statusCode": "200"
+            }
+          },
+          "type": "mock",
+          "passthroughBehavior": "when_no_match"
+        }
+      },
+      "put": {
+        "operationId": "MockPut",
+        "responses": {
+          "201": {
+            "description": "MockPut 201 response",
+            "headers": {
+              "Access-Control-Allow-Origin": {
+                "schema": {
+                  "type": "string"
+                }
+              },
+              "Access-Control-Expose-Headers": {
+                "schema": {
+                  "type": "string"
+                }
+              }
+            },
+            "content": {
+              "application/json": {
+                "schema": {
+                  "$ref": "#/components/schemas/MockPutResponseContent"
+                }
+              }
+            }
+          }
+        },
+        "x-amazon-apigateway-integration": {
+          "requestTemplates": {
+            "text/plain": "{\"statusCode\": 200}",
+            "application/xml": "{\"statusCode\": 200}"
+          },
+          "responses": {
+            "default": {
+              "statusCode": "200",
+              "responseTemplates": {
+                "application/json": "{\"extendedRequestId\": \"$context.extendedRequestId\"}"
+              },
+              "responseParameters": {
+                "method.response.header.Access-Control-Allow-Origin": "'https://www.example.com'",
+                "method.response.header.Access-Control-Expose-Headers": "'Content-Length,Content-Type,X-Amzn-Errortype,X-Amzn-Requestid,X-Service-Output-Metadata'"
+              }
+            }
+          },
+          "type": "mock",
+          "passthroughBehavior": "never"
+        }
+      }
+    }
+  },
+  "components": {
+    "schemas": {
+      "MockGetResponseContent": {
+        "type": "object",
+        "properties": {
+          "extendedRequestId": {
+            "type": "string"
+          }
+        },
+        "required": [
+          "extendedRequestId"
+        ]
+      },
+      "MockPutResponseContent": {
+        "type": "object",
+        "properties": {
+          "extendedRequestId": {
+            "type": "string"
+          }
+        },
+        "required": [
+          "extendedRequestId"
+        ]
+      }
+    },
+    "securitySchemes": {
+      "aws.auth.sigv4": {
+        "type": "apiKey",
+        "description": "AWS Signature Version 4 authentication",
+        "name": "Authorization",
+        "in": "header",
+        "x-amazon-apigateway-authtype": "awsSigv4"
+      }
+    }
+  },
+  "security": [
+    {
+      "aws.auth.sigv4": [ ]
+    }
+  ],
+  "x-amazon-apigateway-gateway-responses": {
+    "DEFAULT_4XX": {
+      "responseTemplates": {
+        "application/json": "{\"message\":$context.error.messageString}"
+      },
+      "responseParameters": {
+        "gatewayresponse.header.Access-Control-Allow-Origin": "'https://www.example.com'",
+        "gatewayresponse.header.Access-Control-Expose-Headers": "'X-Service-Output-Metadata'"
+      }
+    },
+    "DEFAULT_5XX": {
+      "responseTemplates": {
+        "application/json": "{\"message\":$context.error.messageString}"
+      },
+      "responseParameters": {
+        "gatewayresponse.header.Access-Control-Allow-Origin": "'https://www.example.com'",
+        "gatewayresponse.header.Access-Control-Expose-Headers": "'X-Service-Output-Metadata'"
+      }
+    }
+  }
+}
diff --git a/smithy-aws-iam-traits/src/test/java/software/amazon/smithy/aws/iam/traits/DefineConditionKeysTraitTest.java b/smithy-aws-iam-traits/src/test/java/software/amazon/smithy/aws/iam/traits/DefineConditionKeysTraitTest.java
new file mode 100644
index 000000000..5ab1dab9c
--- /dev/null
+++ b/smithy-aws-iam-traits/src/test/java/software/amazon/smithy/aws/iam/traits/DefineConditionKeysTraitTest.java
@@ -0,0 +1,38 @@
+package software.amazon.smithy.aws.iam.traits;
+
+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 java.util.Collections;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.contains;
+import static org.hamcrest.Matchers.equalTo;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.equalTo;
+
+
+public class DefineConditionKeysTraitTest {
+    @Test
+    public void loadsFromModel() {
+        Model result = Model.assembler()
+                .discoverModels(getClass().getClassLoader())
+                .addImport(getClass().getResource("define-condition-keys.smithy"))
+                .assemble()
+                .unwrap();
+
+        Shape shape = result.expectShape(ShapeId.from("smithy.example#MyService"));
+        DefineConditionKeysTrait trait = shape.expectTrait(DefineConditionKeysTrait.class);
+        assertEquals(3,trait.getConditionKeys().size());
+        assertFalse(trait.getConditionKey("myservice:Bar").get().isRequired());
+        assertFalse(trait.getConditionKey("myservice:Foo").get().isRequired());
+        assertTrue(trait.getConditionKey("myservice:Baz").get().isRequired());
+    }
+}
diff --git a/smithy-aws-iam-traits/src/test/resources/software/amazon/smithy/aws/iam/traits/define-condition-keys.smithy b/smithy-aws-iam-traits/src/test/resources/software/amazon/smithy/aws/iam/traits/define-condition-keys.smithy
new file mode 100644
index 000000000..e26626550
--- /dev/null
+++ b/smithy-aws-iam-traits/src/test/resources/software/amazon/smithy/aws/iam/traits/define-condition-keys.smithy
@@ -0,0 +1,32 @@
+$version: "2"
+
+namespace smithy.example
+
+use aws.api#service
+use aws.iam#defineConditionKeys
+use aws.iam#serviceResolvedConditionKeys
+
+@service(sdkId: "My Value", arnNamespace: "myservice")
+@defineConditionKeys(
+    "myservice:Bar": {
+        type: "String"
+        documentation: "The Bar string"
+        externalDocumentation: "http://example.com"
+    },
+    "myservice:Baz": {
+        type: "String"
+        documentation: "The Baz string"
+        externalDocumentation: "http://baz.com"
+        required: true
+    }
+    "myservice:Foo": {
+        type: "String"
+        documentation: "The Foo string"
+        externalDocumentation: "http://foo.com"
+        required: false
+    }
+)
+@serviceResolvedConditionKeys(["myservice:Baz"])
+service MyService {
+    version: "2017-02-11"
+}
diff --git a/smithy-aws-iam-traits/src/test/resources/software/amazon/smithy/aws/iam/traits/errorfiles/define-conditionkeys-norequired.errors b/smithy-aws-iam-traits/src/test/resources/software/amazon/smithy/aws/iam/traits/errorfiles/define-conditionkeys-norequired.errors
new file mode 100644
index 000000000..e69de29bb
diff --git a/smithy-aws-iam-traits/src/test/resources/software/amazon/smithy/aws/iam/traits/errorfiles/define-conditionkeys-norequired.smithy b/smithy-aws-iam-traits/src/test/resources/software/amazon/smithy/aws/iam/traits/errorfiles/define-conditionkeys-norequired.smithy
new file mode 100644
index 000000000..44d0c8497
--- /dev/null
+++ b/smithy-aws-iam-traits/src/test/resources/software/amazon/smithy/aws/iam/traits/errorfiles/define-conditionkeys-norequired.smithy
@@ -0,0 +1,18 @@
+$version: "2"
+
+namespace smithy.example
+
+use aws.api#service
+use aws.iam#defineConditionKeys
+
+@service(sdkId: "My Value", arnNamespace: "myservice")
+@defineConditionKeys(
+    "myservice:Bar": {
+        type: "String"
+        documentation: "The Bar string"
+        externalDocumentation: "http://example.com"
+    })
+service MyService {
+    version: "2017-02-11"
+}
+
diff --git a/smithy-aws-iam-traits/src/test/resources/software/amazon/smithy/aws/iam/traits/errorfiles/define-conditionkeys-requestresolved-required-invalid.errors b/smithy-aws-iam-traits/src/test/resources/software/amazon/smithy/aws/iam/traits/errorfiles/define-conditionkeys-requestresolved-required-invalid.errors
new file mode 100644
index 000000000..0efc3e1c2
--- /dev/null
+++ b/smithy-aws-iam-traits/src/test/resources/software/amazon/smithy/aws/iam/traits/errorfiles/define-conditionkeys-requestresolved-required-invalid.errors
@@ -0,0 +1,1 @@
+[ERROR] smithy.example#MyService: The `myservice:Bar` condition key is defined as required but is resolved from the request. This property is only valid for condition keys resolved by the service, it MUST also be specified in the `@serviceResolvedConditionKeys` trait or use the `@required` trait instead. | DefineConditionKeysTrait
diff --git a/smithy-aws-iam-traits/src/test/resources/software/amazon/smithy/aws/iam/traits/errorfiles/define-conditionkeys-requestresolved-required-invalid.smithy b/smithy-aws-iam-traits/src/test/resources/software/amazon/smithy/aws/iam/traits/errorfiles/define-conditionkeys-requestresolved-required-invalid.smithy
new file mode 100644
index 000000000..004c04a16
--- /dev/null
+++ b/smithy-aws-iam-traits/src/test/resources/software/amazon/smithy/aws/iam/traits/errorfiles/define-conditionkeys-requestresolved-required-invalid.smithy
@@ -0,0 +1,19 @@
+$version: "2"
+
+namespace smithy.example
+
+use aws.api#service
+use aws.iam#defineConditionKeys
+
+@service(sdkId: "My Value", arnNamespace: "myservice")
+@defineConditionKeys(
+    "myservice:Bar": {
+        type: "String"
+        documentation: "The Bar string"
+        externalDocumentation: "http://example.com"
+        required: true
+    })
+service MyService {
+    version: "2017-02-11"
+}
+
diff --git a/smithy-aws-iam-traits/src/test/resources/software/amazon/smithy/aws/iam/traits/errorfiles/define-conditionkeys-required.errors b/smithy-aws-iam-traits/src/test/resources/software/amazon/smithy/aws/iam/traits/errorfiles/define-conditionkeys-required.errors
new file mode 100644
index 000000000..e69de29bb
diff --git a/smithy-aws-iam-traits/src/test/resources/software/amazon/smithy/aws/iam/traits/errorfiles/define-conditionkeys-required.smithy b/smithy-aws-iam-traits/src/test/resources/software/amazon/smithy/aws/iam/traits/errorfiles/define-conditionkeys-required.smithy
new file mode 100644
index 000000000..a7ba99d00
--- /dev/null
+++ b/smithy-aws-iam-traits/src/test/resources/software/amazon/smithy/aws/iam/traits/errorfiles/define-conditionkeys-required.smithy
@@ -0,0 +1,21 @@
+$version: "2"
+
+namespace smithy.example
+
+use aws.api#service
+use aws.iam#defineConditionKeys
+use aws.iam#serviceResolvedConditionKeys
+
+@service(sdkId: "My Value", arnNamespace: "myservice")
+@defineConditionKeys(
+    "myservice:Bar": {
+        type: "String"
+        documentation: "The Bar string"
+        externalDocumentation: "http://example.com"
+        required: true
+    })
+@serviceResolvedConditionKeys(["myservice:Bar"])
+service MyService {
+    version: "2017-02-11"
+}
+
diff --git a/smithy-aws-iam-traits/src/test/resources/software/amazon/smithy/aws/iam/traits/errorfiles/iam-resources/duplicate-iam-resources-name.errors b/smithy-aws-iam-traits/src/test/resources/software/amazon/smithy/aws/iam/traits/errorfiles/iam-resources/duplicate-iam-resources-name.errors
new file mode 100644
index 000000000..976e0c872
--- /dev/null
+++ b/smithy-aws-iam-traits/src/test/resources/software/amazon/smithy/aws/iam/traits/errorfiles/iam-resources/duplicate-iam-resources-name.errors
@@ -0,0 +1,1 @@
+[ERROR] smithy.example#MyService: Multiple IAM resources defined with the same IAM resource name is not allowed in a service closure, but found multiple resources named `Beer` in the service `smithy.example#MyService`: [`smithy.example#BadIamResourceName1`, `smithy.example#BadIamResourceName2`, `smithy.example#Beer`] | IamResourceTraitConflictingName
diff --git a/smithy-aws-iam-traits/src/test/resources/software/amazon/smithy/aws/iam/traits/errorfiles/iam-resources/duplicate-iam-resources-name.smithy b/smithy-aws-iam-traits/src/test/resources/software/amazon/smithy/aws/iam/traits/errorfiles/iam-resources/duplicate-iam-resources-name.smithy
new file mode 100644
index 000000000..9833e42db
--- /dev/null
+++ b/smithy-aws-iam-traits/src/test/resources/software/amazon/smithy/aws/iam/traits/errorfiles/iam-resources/duplicate-iam-resources-name.smithy
@@ -0,0 +1,39 @@
+$version: "2"
+namespace smithy.example
+
+use aws.api#arn
+
+@aws.api#service(sdkId: "My")
+@aws.iam#defineConditionKeys("foo:baz": {type: "String", documentation: "Foo baz"})
+service MyService {
+    version: "2019-02-20",
+    resources: [
+        BadIamResourceName1,
+        BadIamResourceName2,
+        Beer,
+        ShouldNotThrowAnErrorFirst,
+        ShouldNotThrowAnErrorSecond
+    ]
+}
+
+resource Beer {}
+
+@aws.iam#iamResource(name: "Beer")
+resource BadIamResourceName1 {}
+
+@aws.iam#iamResource(name: "Beer")
+resource BadIamResourceName2 {}
+
+@aws.iam#iamResource(name: "shouldNotThrowErrorSecond")
+resource ShouldNotThrowAnErrorFirst {
+    identifiers: {
+        beerId: String
+    }
+}
+
+@aws.iam#iamResource(name: "shouldNotThrowErrorFirst")
+resource ShouldNotThrowAnErrorSecond {
+    identifiers: {
+        arn: String
+    }
+}
diff --git a/smithy-aws-protocol-tests/model/awsJson1_0/content-type.smithy b/smithy-aws-protocol-tests/model/awsJson1_0/content-type.smithy
new file mode 100644
index 000000000..db83af8a0
--- /dev/null
+++ b/smithy-aws-protocol-tests/model/awsJson1_0/content-type.smithy
@@ -0,0 +1,44 @@
+// This file defines test cases that test Content-Type headers.
+
+$version: "2.0"
+
+namespace aws.protocoltests.json10
+
+use aws.protocols#awsJson1_0
+use smithy.test#httpRequestTests
+use smithy.test#httpResponseTests
+
+/// The example tests how servers must support requests
+/// containing a `Content-Type` header with parameters.
+operation ContentTypeParameters {
+    input: ContentTypeParametersInput,
+    output: ContentTypeParametersOutput
+}
+
+apply ContentTypeParameters @httpRequestTests([
+    {
+        id: "AwsJson10MustSupportParametersInContentType",
+        documentation: "A server should ignore parameters added to the content type",
+        protocol: awsJson1_0,
+        method: "POST",
+        headers: {
+            "Content-Type": "application/x-amz-json-1.0; charset=utf-8",
+            "X-Amz-Target": "JsonRpc10.ContentTypeParameters",
+        },
+        uri: "/",
+        body: "{\"value\":5}",
+        bodyMediaType: "application/json",
+        params: {
+            value: 5,
+        },
+        appliesTo: "server"
+    }
+])
+
+@input
+structure ContentTypeParametersInput {
+    value: Integer,
+}
+
+@output
+structure ContentTypeParametersOutput {}
diff --git a/smithy-aws-protocol-tests/model/awsJson1_0/main.smithy b/smithy-aws-protocol-tests/model/awsJson1_0/main.smithy
index 091a5c93b..6204e414e 100644
--- a/smithy-aws-protocol-tests/model/awsJson1_0/main.smithy
+++ b/smithy-aws-protocol-tests/model/awsJson1_0/main.smithy
@@ -35,6 +35,9 @@ service JsonRpc10 {
         // requestCompression trait tests
         PutWithContentEncoding,
 
+        // Content-Type header tests
+        ContentTypeParameters,
+
         OperationWithDefaults,
         OperationWithRequiredMembers,
         OperationWithNestedStructure
diff --git a/smithy-aws-protocol-tests/model/awsJson1_1/content-type.smithy b/smithy-aws-protocol-tests/model/awsJson1_1/content-type.smithy
new file mode 100644
index 000000000..4b1159e0c
--- /dev/null
+++ b/smithy-aws-protocol-tests/model/awsJson1_1/content-type.smithy
@@ -0,0 +1,44 @@
+// This file defines test cases that test Content-Type headers.
+
+$version: "2.0"
+
+namespace aws.protocoltests.json
+
+use aws.protocols#awsJson1_1
+use smithy.test#httpRequestTests
+use smithy.test#httpResponseTests
+
+/// The example tests how servers must support requests
+/// containing a `Content-Type` header with parameters.
+operation ContentTypeParameters {
+    input: ContentTypeParametersInput,
+    output: ContentTypeParametersOutput
+}
+
+apply ContentTypeParameters @httpRequestTests([
+    {
+        id: "AwsJson11MustSupportParametersInContentType",
+        documentation: "A server should ignore parameters added to the content type",
+        protocol: awsJson1_1,
+        method: "POST",
+        headers: {
+            "Content-Type": "application/x-amz-json-1.1; charset=utf-8",
+            "X-Amz-Target": "JsonProtocol.ContentTypeParameters",
+        },
+        uri: "/",
+        body: "{\"value\":5}",
+        bodyMediaType: "application/json",
+        params: {
+            value: 5,
+        },
+        appliesTo: "server"
+    }
+])
+
+@input
+structure ContentTypeParametersInput {
+    value: Integer,
+}
+
+@output
+structure ContentTypeParametersOutput {}
diff --git a/smithy-aws-protocol-tests/model/awsJson1_1/main.smithy b/smithy-aws-protocol-tests/model/awsJson1_1/main.smithy
index 9503e7df7..fcd1c29c9 100644
--- a/smithy-aws-protocol-tests/model/awsJson1_1/main.smithy
+++ b/smithy-aws-protocol-tests/model/awsJson1_1/main.smithy
@@ -44,6 +44,9 @@ service JsonProtocol {
 
         // requestCompression trait tests
         PutWithContentEncoding
+
+        // Content-Type header tests
+        ContentTypeParameters,
     ],
 }
 
diff --git a/smithy-aws-protocol-tests/model/restJson1/content-type.smithy b/smithy-aws-protocol-tests/model/restJson1/content-type.smithy
new file mode 100644
index 000000000..f003441f0
--- /dev/null
+++ b/smithy-aws-protocol-tests/model/restJson1/content-type.smithy
@@ -0,0 +1,41 @@
+// This file defines test cases that test Content-Type headers.
+
+$version: "2.0"
+
+namespace aws.protocoltests.restjson
+
+use aws.protocols#restJson1
+use aws.protocoltests.shared#DateTime
+use smithy.test#httpRequestTests
+
+/// The example tests how servers must support requests
+/// containing a `Content-Type` header with parameters.
+@http(uri: "/ContentTypeParameters", method: "POST")
+operation ContentTypeParameters {
+    input: ContentTypeParametersInput,
+    output: ContentTypeParametersOutput,
+}
+
+apply ContentTypeParameters @httpRequestTests([
+    {
+        id: "RestJsonMustSupportParametersInContentType",
+        documentation: "A server should ignore parameters added to the content type",
+        uri: "/ContentTypeParameters",
+        method: "POST",
+        protocol: "aws.protocols#restJson1",
+        body: "{\"value\":5}",
+        headers: { "Content-Type": "application/json; charset=utf-8" },
+        params: {
+            value: 5,
+        },
+        appliesTo: "server",
+    },
+])
+
+@input
+structure ContentTypeParametersInput {
+    value: Integer,
+}
+
+@output
+structure ContentTypeParametersOutput {}
diff --git a/smithy-aws-protocol-tests/model/restJson1/defaults.smithy b/smithy-aws-protocol-tests/model/restJson1/defaults.smithy
new file mode 100644
index 000000000..f14363293
--- /dev/null
+++ b/smithy-aws-protocol-tests/model/restJson1/defaults.smithy
@@ -0,0 +1,460 @@
+$version: "2.0"
+
+namespace aws.protocoltests.restjson
+
+use aws.protocols#restJson1
+use smithy.test#httpRequestTests
+use smithy.test#httpResponseTests
+
+apply OperationWithDefaults @httpRequestTests([
+    {
+        id: "RestJsonClientPopulatesDefaultValuesInInput"
+        documentation: "Client populates default values in input."
+        appliesTo: "client"
+        tags: ["defaults"]
+        protocol: restJson1
+        method: "POST"
+        bodyMediaType: "application/json"
+        uri: "/OperationWithDefaults"
+        headers: {"Content-Type": "application/json"}
+        body: """
+            {
+                "defaults": {
+                    "defaultString": "hi",
+                    "defaultBoolean": true,
+                    "defaultList": [],
+                    "defaultDocumentMap": {},
+                    "defaultDocumentString": "hi",
+                    "defaultDocumentBoolean": true,
+                    "defaultDocumentList": [],
+                    "defaultTimestamp": 0,
+                    "defaultBlob": "YWJj",
+                    "defaultByte": 1,
+                    "defaultShort": 1,
+                    "defaultInteger": 10,
+                    "defaultLong": 100,
+                    "defaultFloat": 1.0,
+                    "defaultDouble": 1.0,
+                    "defaultMap": {},
+                    "defaultEnum": "FOO",
+                    "defaultIntEnum": 1,
+                    "emptyString": "",
+                    "falseBoolean": false,
+                    "emptyBlob": "",
+                    "zeroByte": 0,
+                    "zeroShort": 0,
+                    "zeroInteger": 0,
+                    "zeroLong": 0,
+                    "zeroFloat": 0.0,
+                    "zeroDouble": 0.0
+                }
+            }"""
+        params: {
+            defaults: {}
+        }
+    }
+    {
+        id: "RestJsonClientSkipsTopLevelDefaultValuesInInput"
+        documentation: "Client skips top level default values in input."
+        appliesTo: "client"
+        tags: ["defaults"]
+        protocol: restJson1
+        method: "POST"
+        bodyMediaType: "application/json"
+        uri: "/OperationWithDefaults"
+        headers: {"Content-Type": "application/json"}
+        body: """
+            {
+            }"""
+        params: {
+        }
+    }
+    {
+        id: "RestJsonClientUsesExplicitlyProvidedMemberValuesOverDefaults"
+        documentation: "Client uses explicitly provided member values over defaults"
+        appliesTo: "client"
+        tags: ["defaults"]
+        protocol: restJson1
+        method: "POST"
+        bodyMediaType: "application/json"
+        uri: "/OperationWithDefaults"
+        headers: {"Content-Type": "application/json"}
+        params: {
+            defaults: {
+                defaultString: "bye"
+                defaultBoolean: true
+                defaultList: ["a"]
+                defaultDocumentMap: {name: "Jack"}
+                defaultDocumentString: "bye"
+                defaultDocumentBoolean: true
+                defaultDocumentList: ["b"]
+                defaultNullDocument: "notNull"
+                defaultTimestamp: 1
+                defaultBlob: "hi"
+                defaultByte: 2
+                defaultShort: 2
+                defaultInteger: 20
+                defaultLong: 200
+                defaultFloat: 2.0
+                defaultDouble: 2.0
+                defaultMap: {name: "Jack"}
+                defaultEnum: "BAR"
+                defaultIntEnum: 2
+                emptyString: "foo"
+                falseBoolean: true
+                emptyBlob: "hi"
+                zeroByte: 1
+                zeroShort: 1
+                zeroInteger: 1
+                zeroLong: 1
+                zeroFloat: 1.0
+                zeroDouble: 1.0
+            }
+        }
+        body: """
+            {
+                "defaults": {
+                    "defaultString": "bye",
+                    "defaultBoolean": true,
+                    "defaultList": ["a"],
+                    "defaultDocumentMap": {"name": "Jack"},
+                    "defaultDocumentString": "bye",
+                    "defaultDocumentBoolean": true,
+                    "defaultDocumentList": ["b"],
+                    "defaultNullDocument": "notNull",
+                    "defaultTimestamp": 1,
+                    "defaultBlob": "aGk=",
+                    "defaultByte": 2,
+                    "defaultShort": 2,
+                    "defaultInteger": 20,
+                    "defaultLong": 200,
+                    "defaultFloat": 2.0,
+                    "defaultDouble": 2.0,
+                    "defaultMap": {"name": "Jack"},
+                    "defaultEnum": "BAR",
+                    "defaultIntEnum": 2,
+                    "emptyString": "foo",
+                    "falseBoolean": true,
+                    "emptyBlob": "aGk=",
+                    "zeroByte": 1,
+                    "zeroShort": 1,
+                    "zeroInteger": 1,
+                    "zeroLong": 1,
+                    "zeroFloat": 1.0,
+                    "zeroDouble": 1.0
+                }
+            }"""
+    }
+    {
+        id: "RestJsonServerPopulatesDefaultsWhenMissingInRequestBody"
+        documentation: "Server populates default values when missing in request body."
+        appliesTo: "server"
+        tags: ["defaults"]
+        protocol: restJson1
+        method: "POST"
+        bodyMediaType: "application/json"
+        uri: "/OperationWithDefaults"
+        headers: {"Content-Type": "application/json"}
+        body: """
+            {
+            "defaults": {}
+            }"""
+        params: {
+            defaults: {
+                defaultString: "hi"
+                defaultBoolean: true
+                defaultList: []
+                defaultDocumentMap: {}
+                defaultDocumentString: "hi"
+                defaultDocumentBoolean: true
+                defaultDocumentList: []
+                defaultTimestamp: 0
+                defaultBlob: "abc"
+                defaultByte: 1
+                defaultShort: 1
+                defaultInteger: 10
+                defaultLong: 100
+                defaultFloat: 1.0
+                defaultDouble: 1.0
+                defaultMap: {}
+                defaultEnum: "FOO"
+                defaultIntEnum: 1
+                emptyString: ""
+                falseBoolean: false
+                emptyBlob: ""
+                zeroByte: 0
+                zeroShort: 0
+                zeroInteger: 0
+                zeroLong: 0
+                zeroFloat: 0.0
+                zeroDouble: 0.0
+            },
+            topLevelDefault: "hi"
+            otherTopLevelDefault: 0
+        }
+    }
+    {
+        id: "RestJsonClientUsesExplicitlyProvidedValuesInTopLevel"
+        documentation: "Any time a value is provided for a member in the top level of input, it is used, regardless of if its the default."
+        appliesTo: "client"
+        tags: ["defaults"]
+        protocol: restJson1
+        method: "POST"
+        bodyMediaType: "application/json"
+        uri: "/OperationWithDefaults"
+        headers: {"Content-Type": "application/json"}
+        body: """
+            {
+                "topLevelDefault": "hi",
+                "otherTopLevelDefault": 0
+            }"""
+        params: {
+            topLevelDefault: "hi"
+            otherTopLevelDefault: 0
+        }
+    }
+    {
+        id: "RestJsonClientIgnoresNonTopLevelDefaultsOnMembersWithClientOptional"
+        documentation: "Typically, non top-level members would have defaults filled in, but if they have the clientOptional trait, the defaults should be ignored."
+        appliesTo: "client"
+        tags: ["defaults"]
+        protocol: restJson1
+        method: "POST"
+        bodyMediaType: "application/json"
+        uri: "/OperationWithDefaults"
+        headers: {"Content-Type": "application/json"}
+        body: """
+            {
+                "clientOptionalDefaults": {}
+            }"""
+        params: {
+            clientOptionalDefaults: {}
+        }
+    }
+])
+
+apply OperationWithDefaults @httpResponseTests([
+    {
+        id: "RestJsonClientPopulatesDefaultsValuesWhenMissingInResponse"
+        documentation: "Client populates default values when missing in response."
+        appliesTo: "client"
+        tags: ["defaults"]
+        protocol: restJson1
+        code: 200
+        bodyMediaType: "application/json"
+        headers: {"Content-Type": "application/json"}
+        body: "{}"
+        params: {
+            defaultString: "hi"
+            defaultBoolean: true
+            defaultList: []
+            defaultDocumentMap: {}
+            defaultDocumentString: "hi"
+            defaultDocumentBoolean: true
+            defaultDocumentList: []
+            defaultTimestamp: 0
+            defaultBlob: "abc"
+            defaultByte: 1
+            defaultShort: 1
+            defaultInteger: 10
+            defaultLong: 100
+            defaultFloat: 1.0
+            defaultDouble: 1.0
+            defaultMap: {}
+            defaultEnum: "FOO"
+            defaultIntEnum: 1
+            emptyString: ""
+            falseBoolean: false
+            emptyBlob: ""
+            zeroByte: 0
+            zeroShort: 0
+            zeroInteger: 0
+            zeroLong: 0
+            zeroFloat: 0.0
+            zeroDouble: 0.0
+        }
+    }
+    {
+        id: "RestJsonClientIgnoresDefaultValuesIfMemberValuesArePresentInResponse"
+        documentation: "Client ignores default values if member values are present in the response."
+        appliesTo: "client"
+        tags: ["defaults"]
+        protocol: restJson1
+        code: 200
+        bodyMediaType: "application/json"
+        headers: {"Content-Type": "application/json"}
+        body: """
+            {
+                "defaultString": "bye",
+                "defaultBoolean": false,
+                "defaultList": ["a"],
+                "defaultDocumentMap": {"name": "Jack"},
+                "defaultDocumentString": "bye",
+                "defaultDocumentBoolean": false,
+                "defaultDocumentList": ["b"],
+                "defaultNullDocument": "notNull",
+                "defaultTimestamp": 2,
+                "defaultBlob": "aGk=",
+                "defaultByte": 2,
+                "defaultShort": 2,
+                "defaultInteger": 20,
+                "defaultLong": 200,
+                "defaultFloat": 2.0,
+                "defaultDouble": 2.0,
+                "defaultMap": {"name": "Jack"},
+                "defaultEnum": "BAR",
+                "defaultIntEnum": 2,
+                "emptyString": "foo",
+                "falseBoolean": true,
+                "emptyBlob": "aGk=",
+                "zeroByte": 1,
+                "zeroShort": 1,
+                "zeroInteger": 1,
+                "zeroLong": 1,
+                "zeroFloat": 1.0,
+                "zeroDouble": 1.0
+            }"""
+        params: {
+            defaultString: "bye"
+            defaultBoolean: false
+            defaultList: ["a"]
+            defaultDocumentMap: {name: "Jack"}
+            defaultDocumentString: "bye"
+            defaultDocumentBoolean: false
+            defaultDocumentList: ["b"]
+            defaultNullDocument: "notNull"
+            defaultTimestamp: 1
+            defaultBlob: "hi"
+            defaultByte: 2
+            defaultShort: 2
+            defaultInteger: 20
+            defaultLong: 200
+            defaultFloat: 2.0
+            defaultDouble: 2.0
+            defaultMap: {name: "Jack"}
+            defaultEnum: "BAR"
+            defaultIntEnum: 2
+            emptyString: "foo"
+            falseBoolean: true
+            emptyBlob: "hi"
+            zeroByte: 1
+            zeroShort: 1
+            zeroInteger: 1
+            zeroLong: 1
+            zeroFloat: 1.0
+            zeroDouble: 1.0
+        }
+    }
+    {
+        id: "RestJsonServerPopulatesDefaultsInResponseWhenMissingInParams"
+        documentation: "Server populates default values in response when missing in params."
+        appliesTo: "server"
+        tags: ["defaults"]
+        protocol: restJson1
+        code: 200
+        bodyMediaType: "application/json"
+        headers: {"Content-Type": "application/json"}
+        body: """
+            {
+                "defaultString": "hi",
+                "defaultBoolean": true,
+                "defaultList": [],
+                "defaultDocumentMap": {},
+                "defaultDocumentString": "hi",
+                "defaultDocumentBoolean": true,
+                "defaultDocumentList": [],
+                "defaultTimestamp": 0,
+                "defaultBlob": "YWJj",
+                "defaultByte": 1,
+                "defaultShort": 1,
+                "defaultInteger": 10,
+                "defaultLong": 100,
+                "defaultFloat": 1.0,
+                "defaultDouble": 1.0,
+                "defaultMap": {},
+                "defaultEnum": "FOO",
+                "defaultIntEnum": 1,
+                "emptyString": "",
+                "falseBoolean": false,
+                "emptyBlob": "",
+                "zeroByte": 0,
+                "zeroShort": 0,
+                "zeroInteger": 0,
+                "zeroLong": 0,
+                "zeroFloat": 0.0,
+                "zeroDouble": 0.0
+            }"""
+        params: {}
+    }
+])
+
+@http(uri: "/OperationWithDefaults", method: "POST")
+operation OperationWithDefaults {
+    input := {
+        defaults: Defaults
+        clientOptionalDefaults: ClientOptionalDefaults
+        topLevelDefault: String = "hi" // Client should ignore default values in input shape
+        otherTopLevelDefault: Integer = 0
+    }
+
+    output := with [DefaultsMixin] {}
+}
+
+structure Defaults with [DefaultsMixin] {}
+
+structure ClientOptionalDefaults {
+    @clientOptional
+    member: Integer = 0
+}
+
+@mixin
+structure DefaultsMixin {
+    defaultString: String = "hi"
+    defaultBoolean: Boolean = true
+    defaultList: TestStringList = []
+    defaultDocumentMap: Document = {}
+    defaultDocumentString: Document = "hi"
+    defaultDocumentBoolean: Document = true
+    defaultDocumentList: Document = []
+    defaultNullDocument: Document = null
+    defaultTimestamp: Timestamp = 0
+    defaultBlob: Blob = "abc"
+    defaultByte: Byte = 1
+    defaultShort: Short = 1
+    defaultInteger: Integer = 10
+    defaultLong: Long = 100
+    defaultFloat: Float = 1.0
+    defaultDouble: Double = 1.0
+    defaultMap: TestStringMap = {}
+    defaultEnum: TestEnum = "FOO"
+    defaultIntEnum: TestIntEnum = 1
+    emptyString: String = ""
+    falseBoolean: Boolean = false
+    emptyBlob: Blob = ""
+    zeroByte: Byte = 0
+    zeroShort: Short = 0
+    zeroInteger: Integer = 0
+    zeroLong: Long = 0
+    zeroFloat: Float = 0.0
+    zeroDouble: Double = 0.0
+}
+
+list TestStringList {
+    member: String
+}
+
+map TestStringMap {
+    key: String
+    value: String
+}
+
+enum TestEnum {
+    FOO
+    BAR
+    BAZ
+}
+
+intEnum TestIntEnum {
+    ONE = 1
+    TWO = 2
+}
\ No newline at end of file
diff --git a/smithy-aws-protocol-tests/model/restJson1/main.smithy b/smithy-aws-protocol-tests/model/restJson1/main.smithy
index e5ae6f979..2a414502b 100644
--- a/smithy-aws-protocol-tests/model/restJson1/main.smithy
+++ b/smithy-aws-protocol-tests/model/restJson1/main.smithy
@@ -151,6 +151,13 @@ service RestJson {
         FractionalSeconds,
 
         // requestCompression trait tests
-        PutWithContentEncoding
+        PutWithContentEncoding,
+
+        // Content-Type header tests
+        ContentTypeParameters,
+
+        // defaults
+        OperationWithDefaults
+        OperationWithNestedStructure
     ]
 }
diff --git a/smithy-aws-protocol-tests/model/restJson1/malformedRequests/malformed-union.smithy b/smithy-aws-protocol-tests/model/restJson1/malformedRequests/malformed-union.smithy
index cd0ca8cd4..ab4571cdb 100644
--- a/smithy-aws-protocol-tests/model/restJson1/malformedRequests/malformed-union.smithy
+++ b/smithy-aws-protocol-tests/model/restJson1/malformedRequests/malformed-union.smithy
@@ -78,6 +78,28 @@ apply MalformedUnion @httpMalformedRequestTests([
             }
         }
     },
+    {
+        id: "RestJsonMalformedUnionEmptyObjectNoFieldsSet",
+        documentation: """
+            When the union is an empty object, it has no fields set, so the
+            response should be a 400 SerializationException.""",
+        protocol: restJson1,
+        request: {
+            method: "POST",
+            uri: "/MalformedUnion",
+            body: """
+                { "union" : {  } }""",
+            headers: {
+                "content-type": "application/json"
+            }
+        },
+        response: {
+            code: 400,
+            headers: {
+                "x-amzn-errortype": "SerializationException"
+            }
+        }
+    },
     {
         id: "RestJsonMalformedUnionValueIsArray",
         documentation: """
diff --git a/smithy-aws-protocol-tests/model/restJson1/nested-defaults.smithy b/smithy-aws-protocol-tests/model/restJson1/nested-defaults.smithy
new file mode 100644
index 000000000..bba92f85e
--- /dev/null
+++ b/smithy-aws-protocol-tests/model/restJson1/nested-defaults.smithy
@@ -0,0 +1,409 @@
+$version: "2.0"
+
+namespace aws.protocoltests.restjson
+
+use aws.protocols#restJson1
+use smithy.test#httpRequestTests
+use smithy.test#httpResponseTests
+
+apply OperationWithNestedStructure @httpRequestTests([
+    {
+        id: "RestJsonClientPopulatesNestedDefaultValuesWhenMissing"
+        documentation: "Client populates nested default values when missing."
+        appliesTo: "client"
+        tags: ["defaults"]
+        protocol: restJson1
+        method: "POST"
+        bodyMediaType: "application/json"
+        uri: "/OperationWithNestedStructure"
+        headers: {"Content-Type": "application/json"}
+        body: """
+            {
+                "topLevel": {
+                    "dialog": {
+                        "language": "en",
+                        "greeting": "hi"
+                    },
+                    "dialogList": [
+                        {
+                            "greeting": "hi"
+                        },
+                        {
+                            "greeting": "hi",
+                            "farewell": {
+                                "phrase": "bye"
+                            }
+                        },
+                        {
+                            "language": "it",
+                            "greeting": "ciao",
+                            "farewell": {
+                                "phrase": "arrivederci"
+                            }
+                        }
+                    ],
+                    "dialogMap": {
+                        "emptyDialog": {
+                            "greeting": "hi"
+                        },
+                        "partialEmptyDialog": {
+                            "language": "en",
+                            "greeting": "hi",
+                            "farewell": {
+                                "phrase": "bye"
+                            }
+                        },
+                        "nonEmptyDialog": {
+                            "greeting": "konnichiwa",
+                            "farewell": {
+                                "phrase": "sayonara"
+                            }
+                        }
+                    }
+                }
+            }"""
+        params: {
+            "topLevel": {
+                "dialog": {
+                    "language": "en"
+                },
+                "dialogList": [
+                    {
+                    },
+                    {
+                        "farewell": {}
+                    },
+                    {
+                        "language": "it",
+                        "greeting": "ciao",
+                        "farewell": {
+                            "phrase": "arrivederci"
+                        }
+                    }
+                ],
+                "dialogMap": {
+                    "emptyDialog": {
+                    },
+                    "partialEmptyDialog": {
+                        "language": "en",
+                        "farewell": {}
+                    },
+                    "nonEmptyDialog": {
+                        "greeting": "konnichiwa",
+                        "farewell": {
+                            "phrase": "sayonara"
+                        }
+                    }
+                }
+            }
+        }
+    }
+    {
+        id: "RestJsonServerPopulatesNestedDefaultsWhenMissingInRequestBody"
+        documentation: "Server populates nested default values when missing in request body."
+        appliesTo: "server"
+        tags: ["defaults"]
+        protocol: restJson1
+        method: "POST"
+        bodyMediaType: "application/json"
+        uri: "/OperationWithNestedStructure"
+        headers: {"Content-Type": "application/json"}
+        body: """
+            {
+                "topLevel": {
+                    "dialog": {
+                        "language": "en"
+                    },
+                    "dialogList": [
+                        {
+                        },
+                        {
+                            "farewell": {}
+                        },
+                        {
+                            "language": "it",
+                            "greeting": "ciao",
+                            "farewell": {
+                                "phrase": "arrivederci"
+                            }
+                        }
+                    ],
+                    "dialogMap": {
+                        "emptyDialog": {
+                        },
+                        "partialEmptyDialog": {
+                            "language": "en",
+                            "farewell": {}
+                        },
+                        "nonEmptyDialog": {
+                            "greeting": "konnichiwa",
+                            "farewell": {
+                                "phrase": "sayonara"
+                            }
+                        }
+                    }
+                }
+            }"""
+        params: {
+            "topLevel": {
+                "dialog": {
+                    "language": "en"
+                    "greeting": "hi",
+                }
+                "dialogList": [
+                    {
+                        "greeting": "hi",
+                    },
+                    {
+                        "greeting": "hi",
+                        "farewell": {
+                            "phrase": "bye",
+                        }
+                    },
+                    {
+                        "language": "it",
+                        "greeting": "ciao",
+                        "farewell": {
+                            "phrase": "arrivederci"
+                        }
+                    }
+                ],
+                "dialogMap": {
+                    "emptyDialog": {
+                        "greeting": "hi",
+                    },
+                    "partialEmptyDialog": {
+                        "language": "en",
+                        "greeting": "hi",
+                        "farewell": {
+                            "phrase": "bye",
+                        }
+                    },
+                    "nonEmptyDialog": {
+                        "greeting": "konnichiwa",
+                        "farewell": {
+                            "phrase": "sayonara"
+                        }
+                    }
+                }
+            }
+        }
+    }
+])
+
+apply OperationWithNestedStructure @httpResponseTests([
+    {
+        id: "RestJsonClientPopulatesNestedDefaultsWhenMissingInResponseBody"
+        documentation: "Client populates nested default values when missing in response body."
+        appliesTo: "client"
+        tags: ["defaults"]
+        protocol: restJson1
+        code: 200
+        bodyMediaType: "application/json"
+        headers: {"Content-Type": "application/json"}
+        body: """
+            {
+                "dialog": {
+                    "language": "en"
+                },
+                "dialogList": [
+                    {
+                    },
+                    {
+                        "farewell": {}
+                    },
+                    {
+                        "language": "it",
+                        "greeting": "ciao",
+                        "farewell": {
+                            "phrase": "arrivederci"
+                        }
+                    }
+                ],
+                "dialogMap": {
+                    "emptyDialog": {
+                    },
+                    "partialEmptyDialog": {
+                        "language": "en",
+                        "farewell": {}
+                    },
+                    "nonEmptyDialog": {
+                        "greeting": "konnichiwa",
+                        "farewell": {
+                            "phrase": "sayonara"
+                        }
+                    }
+                }
+            }"""
+        params: {
+            "dialog": {
+                "language": "en"
+                "greeting": "hi",
+            }
+            "dialogList": [
+                {
+                    "greeting": "hi",
+                },
+                {
+                    "greeting": "hi",
+                    "farewell": {
+                        "phrase": "bye",
+                    }
+                },
+                {
+                    "language": "it",
+                    "greeting": "ciao",
+                    "farewell": {
+                        "phrase": "arrivederci"
+                    }
+                }
+            ],
+            "dialogMap": {
+                "emptyDialog": {
+                    "greeting": "hi",
+                },
+                "partialEmptyDialog": {
+                    "language": "en",
+                    "greeting": "hi",
+                    "farewell": {
+                        "phrase": "bye",
+                    }
+                },
+                "nonEmptyDialog": {
+                    "greeting": "konnichiwa",
+                    "farewell": {
+                        "phrase": "sayonara"
+                    }
+                }
+            }
+        }
+    }
+    {
+        id: "RestJsonServerPopulatesNestedDefaultValuesWhenMissingInInResponseParams"
+        documentation: "Server populates nested default values when missing in response params."
+        appliesTo: "server"
+        tags: ["defaults"]
+        protocol: restJson1
+        code: 200
+        bodyMediaType: "application/json"
+        headers: {"Content-Type": "application/json"}
+        body: """
+            {
+                "dialog": {
+                    "language": "en",
+                    "greeting": "hi"
+                },
+                "dialogList": [
+                    {
+                        "greeting": "hi"
+                    },
+                    {
+                        "greeting": "hi",
+                        "farewell": {
+                            "phrase": "bye"
+                        }
+                    },
+                    {
+                        "language": "it",
+                        "greeting": "ciao",
+                        "farewell": {
+                            "phrase": "arrivederci"
+                        }
+                    }
+                ],
+                "dialogMap": {
+                    "emptyDialog": {
+                        "greeting": "hi"
+                    },
+                    "partialEmptyDialog": {
+                        "language": "en",
+                        "greeting": "hi",
+                        "farewell": {
+                            "phrase": "bye"
+                        }
+                    },
+                    "nonEmptyDialog": {
+                        "greeting": "konnichiwa",
+                        "farewell": {
+                            "phrase": "sayonara"
+                        }
+                    }
+                }
+            }"""
+        params: {
+            "dialog": {
+                "language": "en"
+            },
+            "dialogList": [
+                {
+                },
+                {
+                    "farewell": {}
+                },
+                {
+                    "language": "it",
+                    "greeting": "ciao",
+                    "farewell": {
+                        "phrase": "arrivederci"
+                    }
+                }
+            ],
+            "dialogMap": {
+                "emptyDialog": {
+                },
+                "partialEmptyDialog": {
+                    "language": "en",
+                    "farewell": {}
+                },
+                "nonEmptyDialog": {
+                    "greeting": "konnichiwa",
+                    "farewell": {
+                        "phrase": "sayonara"
+                    }
+                }
+            }
+        }
+    }
+])
+
+@http(uri: "/OperationWithNestedStructure", method: "POST")
+operation OperationWithNestedStructure {
+    input := {
+        @required
+        topLevel: TopLevel
+    }
+
+    output := with [NestedDefaultsMixin] {
+    }
+}
+
+structure TopLevel with [NestedDefaultsMixin] {
+
+}
+
+@mixin
+structure NestedDefaultsMixin {
+    @required
+    dialog: Dialog
+    dialogList: DialogList = []
+    dialogMap: DialogMap = {}
+}
+
+structure Dialog {
+    language: String
+    greeting: String = "hi"
+    farewell: Farewell
+}
+
+structure Farewell {
+    phrase: String = "bye"
+}
+
+list DialogList {
+    member: Dialog
+}
+
+map DialogMap {
+    key: String
+    value: Dialog
+}
\ No newline at end of file
diff --git a/smithy-aws-protocol-tests/model/restXml/content-type.smithy b/smithy-aws-protocol-tests/model/restXml/content-type.smithy
new file mode 100644
index 000000000..4dc9c578e
--- /dev/null
+++ b/smithy-aws-protocol-tests/model/restXml/content-type.smithy
@@ -0,0 +1,44 @@
+// This file defines test cases that test Content-Type headers.
+
+$version: "2.0"
+
+namespace aws.protocoltests.restxml
+
+use aws.protocols#restXml
+use aws.protocoltests.shared#DateTime
+use smithy.test#httpRequestTests
+
+/// The example tests how servers must support requests
+/// containing a `Content-Type` header with parameters.
+@http(uri: "/ContentTypeParameters", method: "PUT")
+operation ContentTypeParameters {
+    input: ContentTypeParametersInput,
+    output: ContentTypeParametersOutput
+}
+
+apply ContentTypeParameters @httpRequestTests([
+    {
+        id: "RestXmlMustSupportParametersInContentType",
+        documentation: "A server should ignore parameters added to the content type",
+        protocol: restXml,
+        method: "PUT",
+        headers: {
+            "Content-Type": "application/xml; charset=utf-8"
+        },
+        uri: "/ContentTypeParameters",
+        body: "<ContentTypeParametersInput><value>5</value></ContentTypeParametersInput>",
+        bodyMediaType: "application/xml",
+        params: {
+            value: 5,
+        },
+        appliesTo: "server"
+    }
+])
+
+@input
+structure ContentTypeParametersInput {
+    value: Integer,
+}
+
+@output
+structure ContentTypeParametersOutput {}
diff --git a/smithy-aws-protocol-tests/model/restXml/main.smithy b/smithy-aws-protocol-tests/model/restXml/main.smithy
index 348eb303c..3841d3129 100644
--- a/smithy-aws-protocol-tests/model/restXml/main.smithy
+++ b/smithy-aws-protocol-tests/model/restXml/main.smithy
@@ -105,6 +105,9 @@ service RestXml {
         FractionalSeconds,
 
         // requestCompression trait tests
-        PutWithContentEncoding
+        PutWithContentEncoding,
+
+        // Content-Type header tests
+        ContentTypeParameters,
     ]
 }
diff --git a/smithy-diff/src/test/java/software/amazon/smithy/diff/evaluators/ChangedEnumTraitTest.java b/smithy-diff/src/test/java/software/amazon/smithy/diff/evaluators/ChangedEnumTraitTest.java
index a1d6fc755..9907f615a 100644
--- a/smithy-diff/src/test/java/software/amazon/smithy/diff/evaluators/ChangedEnumTraitTest.java
+++ b/smithy-diff/src/test/java/software/amazon/smithy/diff/evaluators/ChangedEnumTraitTest.java
@@ -47,6 +47,7 @@ public class ChangedEnumTraitTest {
                 .addTrait(EnumTrait.builder()
                         .addEnum(EnumDefinition.builder().value("foo").build())
                         .addEnum(EnumDefinition.builder().value("baz").build())
+                        .addEnum(EnumDefinition.builder().value("bar").build())
                         .sourceLocation(source)
                         .build())
                 .build();
@@ -54,9 +55,14 @@ public class ChangedEnumTraitTest {
         Model modelB = Model.assembler().addShape(s2).assemble().unwrap();
         List<ValidationEvent> events = ModelDiff.compare(modelA, modelB);
 
-        assertThat(TestHelper.findEvents(events, "ChangedEnumTrait").size(), equalTo(1));
-        assertThat(TestHelper.findEvents(events, "ChangedEnumTrait").get(0).getSeverity(), equalTo(Severity.NOTE));
-        assertThat(TestHelper.findEvents(events, "ChangedEnumTrait").get(0).getSourceLocation(), equalTo(source));
+        assertThat(TestHelper.findEvents(events, "ChangedEnumTrait").size(), equalTo(2));
+        assertThat(TestHelper.findEvents(events, "ChangedEnumTrait.Appended").size(), equalTo(2));
+        assertThat(TestHelper.findEvents(events, "ChangedEnumTrait.Appended.1").size(), equalTo(1));
+        assertThat(TestHelper.findEvents(events, "ChangedEnumTrait.Appended.2").size(), equalTo(1));
+        assertThat(TestHelper.findEvents(events, "ChangedEnumTrait.Appended").stream()
+                .allMatch(e -> e.getSeverity() == Severity.NOTE), equalTo(true));
+        assertThat(TestHelper.findEvents(events, "ChangedEnumTrait.Appended").stream()
+                .allMatch(e -> source.equals(e.getSourceLocation())), equalTo(true));
     }
 
     @Test
@@ -86,9 +92,9 @@ public class ChangedEnumTraitTest {
                 equalTo(Severity.ERROR));
         assertThat(TestHelper.findEvents(events, "ChangedEnumTrait.NameChanged.0").get(0).getSourceLocation(),
                 equalTo(source));
-        assertThat(TestHelper.findEvents(events, "ChangedEnumTrait").get(1).getSeverity(),
+        assertThat(TestHelper.findEvents(events, "ChangedEnumTrait.Appended.1").get(0).getSeverity(),
                 equalTo(Severity.NOTE));
-        assertThat(TestHelper.findEvents(events, "ChangedEnumTrait").get(1).getSourceLocation(),
+        assertThat(TestHelper.findEvents(events, "ChangedEnumTrait.Appended.1").get(0).getSourceLocation(),
                 equalTo(source));
     }
 
@@ -115,10 +121,10 @@ public class ChangedEnumTraitTest {
         Model modelB = Model.assembler().addShape(s2).assemble().unwrap();
         List<ValidationEvent> events = ModelDiff.compare(modelA, modelB);
 
-        assertThat(TestHelper.findEvents(events, "ChangedEnumTrait").size(), equalTo(1));
+        assertThat(TestHelper.findEvents(events, "ChangedEnumTrait.Appended.1").size(), equalTo(1));
         assertThat(TestHelper.findEvents(events, "ChangedEnumTrait").get(0).getSourceLocation(),
                 equalTo(source));
-        assertThat(TestHelper.findEvents(events, "ChangedEnumTrait").stream()
+        assertThat(TestHelper.findEvents(events, "ChangedEnumTrait.Appended").stream()
                 .allMatch(e -> e.getSeverity() == Severity.NOTE), equalTo(true));
     }
 
@@ -455,16 +461,15 @@ public class ChangedEnumTraitTest {
 
         List<ValidationEvent> changeEvents = TestHelper.findEvents(allEvents, "ChangedEnumTrait");
         assertThat(changeEvents.size(), equalTo(2));
-        assertThat(TestHelper.findEvents(changeEvents, "ChangedEnumTrait.Removed.1").size(), equalTo(1));
-        assertThat(TestHelper.findEvents(changeEvents, "ChangedEnumTrait.Removed.1").get(0).getSourceLocation(),
-                equalTo(beforeSource));
 
-        ValidationEvent removedEvent = changeEvents.get(0);
+        assertThat(TestHelper.findEvents(changeEvents, "ChangedEnumTrait.Removed.1").size(), equalTo(1));
+        ValidationEvent removedEvent = TestHelper.findEvents(changeEvents, "ChangedEnumTrait.Removed.1").get(0);
         assertThat(removedEvent.getSeverity(), equalTo(Severity.ERROR));
         assertThat(removedEvent.getMessage(), stringContainsInOrder("Enum value `old2` was removed"));
         assertThat(removedEvent.getSourceLocation(), equalTo(beforeSource));
 
-        ValidationEvent appendedEvent = changeEvents.get(1);
+        assertThat(TestHelper.findEvents(changeEvents, "ChangedEnumTrait.Appended.2").size(), equalTo(1));
+        ValidationEvent appendedEvent = TestHelper.findEvents(changeEvents, "ChangedEnumTrait.Appended.2").get(0);
         assertThat(appendedEvent.getSeverity(), equalTo(Severity.NOTE));
         assertThat(appendedEvent.getMessage(), stringContainsInOrder("Enum value `new1` was appended"));
         assertThat(appendedEvent.getSourceLocation(), equalTo(afterSource));
@@ -512,7 +517,7 @@ public class ChangedEnumTraitTest {
                 equalTo(afterSource));
         assertThat(TestHelper.findEvents(events, "ChangedEnumTrait").subList(0, 3).stream()
                 .allMatch(e -> e.getSeverity() == Severity.ERROR), equalTo(true));
-        assertThat(TestHelper.findEvents(events, "ChangedEnumTrait").subList(3, 4).stream()
+        assertThat(TestHelper.findEvents(events, "ChangedEnumTrait.Appended.2").stream()
                 .allMatch(e -> e.getSeverity() == Severity.NOTE), equalTo(true));
     }
 
@@ -553,7 +558,7 @@ public class ChangedEnumTraitTest {
                 equalTo(source));
         assertThat(TestHelper.findEvents(events, "ChangedEnumTrait.Removed.1").stream()
                 .allMatch(e -> e.getSeverity() == Severity.ERROR), equalTo(true));
-        assertThat(TestHelper.findEvents(events, "ChangedEnumTrait").subList(1, 2).stream()
+        assertThat(TestHelper.findEvents(events, "ChangedEnumTrait.Appended.2").stream()
                 .allMatch(e -> e.getSeverity() == Severity.NOTE), equalTo(true));
     }
 }
diff --git a/smithy-model/src/test/java/software/amazon/smithy/model/validation/NodeValidationVisitorTest.java b/smithy-model/src/test/java/software/amazon/smithy/model/validation/NodeValidationVisitorTest.java
index 091284703..779a5f7b3 100644
--- a/smithy-model/src/test/java/software/amazon/smithy/model/validation/NodeValidationVisitorTest.java
+++ b/smithy-model/src/test/java/software/amazon/smithy/model/validation/NodeValidationVisitorTest.java
@@ -246,9 +246,9 @@ public class NodeValidationVisitorTest {
 
                 // http-date
                 {"ns.foo#HttpDate", "\"Tue, 29 Apr 2014 18:30:38 GMT\"", null},
-                {"ns.foo#HttpDate", "\"Tuesday, 29 April 2014 18:30:38 GMT\"", new String[] {"Invalid value provided for http-date formatted timestamp. Expected a string value that matches the IMF-fixdate production of RFC 7231 section-7.1.1.1. Found: Tuesday, 29 April 2014 18:30:38 GMT"}},
-                {"ns.foo#HttpDate", "\"Tue, 29 Apr 2014 18:30:38 PST\"", new String[] {"Invalid value provided for http-date formatted timestamp. Expected a string value that matches the IMF-fixdate production of RFC 7231 section-7.1.1.1. Found: Tue, 29 Apr 2014 18:30:38 PST"}},
-                {"ns.foo#HttpDate", "11", new String[] {"Invalid value provided for http-date formatted timestamp. Expected a string value that matches the IMF-fixdate production of RFC 7231 section-7.1.1.1. Found: number"}},
+                {"ns.foo#HttpDate", "\"Tuesday, 29 April 2014 18:30:38 GMT\"", new String[] {"Invalid value provided for http-date formatted timestamp. Expected a string value that matches the IMF-fixdate production of RFC 9110 section-5.6.7. Found: Tuesday, 29 April 2014 18:30:38 GMT"}},
+                {"ns.foo#HttpDate", "\"Tue, 29 Apr 2014 18:30:38 PST\"", new String[] {"Invalid value provided for http-date formatted timestamp. Expected a string value that matches the IMF-fixdate production of RFC 9110 section-5.6.7. Found: Tue, 29 Apr 2014 18:30:38 PST"}},
+                {"ns.foo#HttpDate", "11", new String[] {"Invalid value provided for http-date formatted timestamp. Expected a string value that matches the IMF-fixdate production of RFC 9110 section-5.6.7. Found: number"}},
 
                 // date-time
                 {"ns.foo#DateTime", "\"1985-04-12T23:20:50.52Z\"", null},
@@ -267,8 +267,8 @@ public class NodeValidationVisitorTest {
                         "0: Invalid string value, `1985-04-12T23:20:50.52-07:00`, provided for timestamp, `ns.foo#TimestampList$member`. Expected an RFC 3339 formatted timestamp (e.g., \"1985-04-12T23:20:50.52Z\")"
                 }},
                 {"ns.foo#TimestampList", "[123]", new String[] {"0: Expected a string value for a date-time timestamp (e.g., \"1985-04-12T23:20:50.52Z\")"}},
-                {"ns.foo#Structure4", "{\"httpDate\": 1234}", new String[] {"httpDate: Invalid value provided for http-date formatted timestamp. Expected a string value that matches the IMF-fixdate production of RFC 7231 section-7.1.1.1. Found: number"}},
-                {"ns.foo#Structure4", "{\"httpDateTarget\": 1234}", new String[] {"httpDateTarget: Invalid value provided for http-date formatted timestamp. Expected a string value that matches the IMF-fixdate production of RFC 7231 section-7.1.1.1. Found: number"}},
+                {"ns.foo#Structure4", "{\"httpDate\": 1234}", new String[] {"httpDate: Invalid value provided for http-date formatted timestamp. Expected a string value that matches the IMF-fixdate production of RFC 9110 section-5.6.7. Found: number"}},
+                {"ns.foo#Structure4", "{\"httpDateTarget\": 1234}", new String[] {"httpDateTarget: Invalid value provided for http-date formatted timestamp. Expected a string value that matches the IMF-fixdate production of RFC 9110 section-5.6.7. Found: number"}},
 
                 // timestamp member with no format.
                 {"ns.foo#TimestampListNoFormatTrait", "[123]", null},
diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/http-header-tchar-validator.errors b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/http-header-tchar-validator.errors
index f1242ede6..b0650ee97 100644
--- a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/http-header-tchar-validator.errors
+++ b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/http-header-tchar-validator.errors
@@ -1,1 +1,1 @@
-[DANGER] ns.foo#SayHelloInput$myHeader: `Not valid!` is not a valid HTTP header field name according to section 3.2 of RFC 7230 | HttpHeaderTrait
+[DANGER] ns.foo#SayHelloInput$myHeader: `Not valid!` is not a valid HTTP header field name according to section 5.6.2 of RFC 9110 | HttpHeaderTrait
diff --git a/smithy-protocol-tests/model/rpcv2Cbor/float16.smithy b/smithy-protocol-tests/model/rpcv2Cbor/float16.smithy
new file mode 100644
index 000000000..a74682d63
--- /dev/null
+++ b/smithy-protocol-tests/model/rpcv2Cbor/float16.smithy
@@ -0,0 +1,94 @@
+$version: "2.0"
+
+namespace smithy.protocoltests.rpcv2Cbor
+
+use smithy.protocols#rpcv2Cbor
+use smithy.test#httpResponseTests
+
+// Verifies that clients can upcast float16s in responses.
+@tags(["client-only"])
+operation Float16 {
+    output: Float16Output
+}
+
+apply Float16 @httpResponseTests([
+    {
+        id: "RpcV2CborFloat16Inf",
+        documentation: "Ensures that clients can correctly parse float16 +Inf.",
+        protocol: rpcv2Cbor,
+        code: 200,
+        // https://cbor.nemo157.com/?type=hex&value=a16576616c7565f97c00
+        body: "oWV2YWx1Zfl8AA==",
+        headers: {
+            "smithy-protocol": "rpc-v2-cbor",
+            "Content-Type": "application/cbor"
+        },
+        params: { value: "Infinity" }
+        bodyMediaType: "application/cbor",
+        appliesTo: "client"
+    },
+    {
+        id: "RpcV2CborFloat16NegInf",
+        documentation: "Ensures that clients can correctly parse float16 -Inf.",
+        protocol: rpcv2Cbor,
+        code: 200,
+        // https://cbor.nemo157.com/?type=hex&value=a16576616c7565f9fc00
+        body: "oWV2YWx1Zfn8AA==",
+        headers: {
+            "smithy-protocol": "rpc-v2-cbor",
+            "Content-Type": "application/cbor"
+        },
+        params: { value: "-Infinity" }
+        bodyMediaType: "application/cbor",
+        appliesTo: "client"
+    },
+    {
+        id: "RpcV2CborFloat16LSBNaN",
+        documentation: "Ensures that clients can correctly parse float16 NaN with high LSB.",
+        protocol: rpcv2Cbor,
+        code: 200,
+        // https://cbor.nemo157.com/?type=hex&value=a16576616c7565f97c01
+        body: "oWV2YWx1Zfl8AQ==",
+        headers: {
+            "smithy-protocol": "rpc-v2-cbor",
+            "Content-Type": "application/cbor"
+        },
+        params: { value: "NaN" }
+        bodyMediaType: "application/cbor",
+        appliesTo: "client"
+    },
+    {
+        id: "RpcV2CborFloat16MSBNaN",
+        documentation: "Ensures that clients can correctly parse float16 NaN with high MSB.",
+        protocol: rpcv2Cbor,
+        code: 200,
+        // https://cbor.nemo157.com/?type=hex&value=a16576616c7565f97e00
+        body: "oWV2YWx1Zfl+AA==",
+        headers: {
+            "smithy-protocol": "rpc-v2-cbor",
+            "Content-Type": "application/cbor"
+        },
+        params: { value: "NaN" }
+        bodyMediaType: "application/cbor",
+        appliesTo: "client"
+    },
+    {
+        id: "RpcV2CborFloat16Subnormal",
+        documentation: "Ensures that clients can correctly parse a subnormal float16.",
+        protocol: rpcv2Cbor,
+        code: 200,
+        // https://cbor.nemo157.com/?type=hex&value=a16576616c7565f90050
+        body: "oWV2YWx1ZfkAUA==",
+        headers: {
+            "smithy-protocol": "rpc-v2-cbor",
+            "Content-Type": "application/cbor"
+        },
+        params: { value: 0.00000476837158203125 } // 1.25 * 2^-18
+        bodyMediaType: "application/cbor",
+        appliesTo: "client"
+    }
+])
+
+structure Float16Output {
+    value: Double
+}
diff --git a/smithy-protocol-tests/model/rpcv2Cbor/main.smithy b/smithy-protocol-tests/model/rpcv2Cbor/main.smithy
index 9d10e723e..504491c82 100644
--- a/smithy-protocol-tests/model/rpcv2Cbor/main.smithy
+++ b/smithy-protocol-tests/model/rpcv2Cbor/main.smithy
@@ -22,7 +22,8 @@ service RpcV2Protocol {
         GreetingWithErrors,
         FractionalSeconds,
         OperationWithDefaults,
-        SparseNullsOperation
+        SparseNullsOperation,
+        Float16
     ]
 }
 
diff --git a/smithy-rules-engine/src/test/resources/software/amazon/smithy/rulesengine/language/errorfiles/invalid/inconsistent-params.errors b/smithy-rules-engine/src/test/resources/software/amazon/smithy/rulesengine/language/errorfiles/invalid/inconsistent-params.errors
index 0ac035113..9af8d2d2a 100644
--- a/smithy-rules-engine/src/test/resources/software/amazon/smithy/rulesengine/language/errorfiles/invalid/inconsistent-params.errors
+++ b/smithy-rules-engine/src/test/resources/software/amazon/smithy/rulesengine/language/errorfiles/invalid/inconsistent-params.errors
@@ -4,9 +4,14 @@
 [WARNING] example#GetResourceInput$ResourceId: This shape applies a trait that is unstable: smithy.rules#contextParam | UnstableTrait
 [WARNING] example#GetAnotherResource: This shape applies a trait that is unstable: smithy.rules#staticContextParams | UnstableTrait
 [WARNING] example#GetResource: This shape applies a trait that is unstable: smithy.rules#staticContextParams | UnstableTrait
+[WARNING] example#GetResource: This shape applies a trait that is unstable: smithy.rules#operationContextParams | UnstableTrait.smithy.rules#operationContextParams
+[WARNING] example#GetAnotherResource: This shape applies a trait that is unstable: smithy.rules#operationContextParams | UnstableTrait.smithy.rules#operationContextParams
 [ERROR] example#GetResource: Inconsistent type for `InconsistentParamType` parameter | RuleSetParameter.StaticContextParams.InconsistentType
 [ERROR] example#FizzBuzz: Type mismatch for parameter `ParameterFoo` | RuleSetParameter.RuleSet.TypeMismatch
 [ERROR] example#FizzBuzz: Parameter `ExtraParameter` exists in ruleset but not in service model | RuleSetParameter.RuleSet.UnmatchedName
 [ERROR] example#FizzBuzz: Parameter `AnotherParameterBar` exists in service model but not in ruleset | RuleSetParameter.RuleSet.UnmatchedName
 [ERROR] example#FizzBuzz: Parameter `InconsistentParamType` exists in service model but not in ruleset | RuleSetParameter.RuleSet.UnmatchedName
 [ERROR] example#FizzBuzz: Parameter `ParamNotInRuleset` exists in service model but not in ruleset | RuleSetParameter.RuleSet.UnmatchedName
+[ERROR] example#FizzBuzz: Parameter `InconsistentOperactionContextParam` exists in service model but not in ruleset | RuleSetParameter.RuleSet.UnmatchedName
+[ERROR] example#FizzBuzz: Type mismatch for parameter `StringArrayParam` | RuleSetParameter.RuleSet.TypeMismatch
+[ERROR] example#GetResource: Inconsistent type for `InconsistentOperactionContextParam` parameter | RuleSetParameter.OperationContextParams.InconsistentType
diff --git a/smithy-rules-engine/src/test/resources/software/amazon/smithy/rulesengine/language/errorfiles/invalid/inconsistent-params.smithy b/smithy-rules-engine/src/test/resources/software/amazon/smithy/rulesengine/language/errorfiles/invalid/inconsistent-params.smithy
index c3e9e39e4..4c64d3adc 100644
--- a/smithy-rules-engine/src/test/resources/software/amazon/smithy/rulesengine/language/errorfiles/invalid/inconsistent-params.smithy
+++ b/smithy-rules-engine/src/test/resources/software/amazon/smithy/rulesengine/language/errorfiles/invalid/inconsistent-params.smithy
@@ -5,6 +5,7 @@ namespace example
 use smithy.rules#clientContextParams
 use smithy.rules#contextParam
 use smithy.rules#endpointRuleSet
+use smithy.rules#operationContextParams
 use smithy.rules#staticContextParams
 
 @endpointRuleSet({
@@ -30,6 +31,10 @@ use smithy.rules#staticContextParams
             "type": "string",
             "documentation": "docs",
             "builtIn": "SDK::Endpoint"
+        },
+        "StringArrayParam": {
+            "type": "stringArray",
+            "documentation": "docs"
         }
     },
     "rules": []
@@ -47,6 +52,10 @@ service FizzBuzz {
     "ParamNotInRuleset": {value: "someValue"},
     "InconsistentParamType": {value: true}
 )
+@operationContextParams(
+    "StringArrayParam": {path: "ResourceId"},
+    "InconsistentOperactionContextParam": {path: "ResourceId"}
+)
 operation GetResource {
     input: GetResourceInput
 }
@@ -61,13 +70,21 @@ structure GetResourceInput {
     "ParamNotInRuleset": {value: "someOtherValue"},
     "InconsistentParamType": {value: "someValue"}
 )
+@operationContextParams(
+    "InconsistentOperactionContextParam": {path: "ListOfStrings[*]"}
+)
 operation GetAnotherResource {
     input: GetAnotherResourceInput
 }
 
 structure GetAnotherResourceInput {
     @contextParam(name: "AnotherParameterBar")
-    ResourceId: ResourceId
+    ResourceId: ResourceId,
+    ListOfStrings: ListOfStrings
+}
+
+list ListOfStrings {
+    member: String
 }
 
 string ResourceId