` we get the following relevant data:
+
+* `data-banner-id` is the id of the actual widget that contains the dynamic blocks
+
+* `data-ids="1,3"` are the ids of the dynamic blocks
+
+* `data-rotate="random"` is an obsolete behavior that we chose not to have in GraphQl
+
+
+Ideally we want these to be rendered with graphql uid and not be exposed with real numeric ids coming from the database.
+
+Next the PWA will receive similar JavaScript component definition that can be parsed to extract parameters and make a [GraphQL query](./dynamic-blocks.graphqls) to load dynamic blocks.
+
+```graphql
+{
+ dynamic_blocks(
+ input: {type: SPECIFIED, locations: [CONTENT], dynamic_block_uids: ["MQ==", "Mg=="]}
+ pageSize:10
+ currentPage: 1
+ ) {
+ items {
+ uid
+ content {
+ html
+ }
+ }
+ page_info {
+ current_page
+ page_size
+ total_pages
+ }
+ total_count
+ }
+}
+```
diff --git a/design-documents/graph-ql/coverage/mergeCarts-optional-destination.md b/design-documents/graph-ql/coverage/mergeCarts-optional-destination.md
new file mode 100644
index 000000000..1107ceede
--- /dev/null
+++ b/design-documents/graph-ql/coverage/mergeCarts-optional-destination.md
@@ -0,0 +1,40 @@
+# `mergeCarts` - Make `destination_cart_id` optional
+
+## What
+
+- Make the `destination_cart_id` argument optional in the `mergeCarts` mutation
+
+## Why
+
+This came up in a discussion with [@sirugh](https://github.com/sirugh) from the [`pwa-studio`](https://github.com/magento/pwa-studio) team.
+
+When a user logs in (creates a new token), one of the first things the UI needs to do is merge the current guest cart (if items are present) into the customer account's cart.
+
+If `destination_cart_id` is required, this requires 3 round trips:
+
+1. Call for `Mutation.generateCustomerToken`
+2. Call for `Query.cart` to get customer cart ID
+3. Call for `Mutation.mergeCarts` to merge guest cart ID into customer cart
+
+Because a customer can only have a single cart, and this API only works for authenticated users, `destination_cart_id` is an unnecessary requirement here. If we make it optional, the login + cart upgrade for UI can happen in a single request:
+
+```graphql
+# Mutations run serially, in-order. So `mergeCarts` will only execute
+# if `generateCustomerToken` succeeds
+mutation LoginAndMergeCarts($email: String!, $password: String!, $guestCartID: String!) {
+ generateCustomerToken(email: $email, password: $password) {
+ token
+ }
+ mergeCarts(source_cart_id: $guestCartID) {
+ ID
+ }
+}
+```
+
+## Proposed Change
+```diff
+type Mutation {
+- mergeCarts(source_cart_id: String!, destination_cart_id: String!): Cart!
++ mergeCarts(source_cart_id: String!, destination_cart_id: String): Cart!
+}
+```
\ No newline at end of file
diff --git a/design-documents/graph-ql/coverage/payment/payflowpro-vault.md b/design-documents/graph-ql/coverage/payment/payflowpro-vault.md
new file mode 100644
index 000000000..cefbda45f
--- /dev/null
+++ b/design-documents/graph-ql/coverage/payment/payflowpro-vault.md
@@ -0,0 +1,32 @@
+# Overview
+
+In the scope of extending the GraphQL coverage, we need to add Vault support to PayPal Payflow Pro payment integration.
+
+## Mutations
+
+We need to extend existing `PayflowProInput` by adding a possibility to store a Vault token:
+
+```graphql
+input PayflowProInput @doc(description:"Required input for Payflow Pro and Payments Pro payment methods.") {
+ cc_details: CreditCardDetailsInput! @doc(description: "Required input for credit card related information")
+ is_active_payment_token_enabler: Boolean! @doc(description:"States whether an entered by a customer credit/debit card should be tokenized for later usage. Required only if Vault is enabled for PayPal Payflow Pro payment integration.")
+}
+```
+
+The `is_active_payment_token_enabler` would specify that credit card details should be tokenized by a payment gateway, and a payment token can be used for further purchases.
+
+The next needed modification is extend of the existing `PaymentMethodInput` by adding Vault payment method for PayPal Payflow Pro.
+
+```graphql
+input PaymentMethodInput {
+ payflowpro_cc_vault: VaultTokenInput
+}
+```
+
+The `VaultInput` provides a generic type for all payment integrations with the Vault support.
+
+```graphql
+input VaultTokenInput @doc(description:"Required input for payment methods with Vault support.") {
+ public_hash: String! @doc(description: "The public hash of the payment token")
+}
+```
\ No newline at end of file
diff --git a/design-documents/graph-ql/coverage/quote.graphqls b/design-documents/graph-ql/coverage/quote.graphqls
new file mode 100644
index 000000000..e2b1b1110
--- /dev/null
+++ b/design-documents/graph-ql/coverage/quote.graphqls
@@ -0,0 +1,425 @@
+# Copyright © Magento, Inc. All rights reserved.
+# See COPYING.txt for license details.
+
+type Query {
+ cart(cart_id: String!): Cart @doc(description:"Returns information about shopping cart") @cache(cacheable: false)
+ customerCart: Cart! @doc(description:"Returns information about the customer shopping cart") @cache(cacheable: false)
+}
+
+type Mutation {
+ createEmptyCart(input: createEmptyCartInput): String @doc(description:"Creates an empty shopping cart for a guest or logged in user")
+ addSimpleProductsToCart(input: AddSimpleProductsToCartInput): AddSimpleProductsToCartOutput
+ addVirtualProductsToCart(input: AddVirtualProductsToCartInput): AddVirtualProductsToCartOutput
+ applyCouponToCart(input: ApplyCouponToCartInput): ApplyCouponToCartOutput
+ removeCouponFromCart(input: RemoveCouponFromCartInput): RemoveCouponFromCartOutput
+ updateCartItems(input: UpdateCartItemsInput): UpdateCartItemsOutput
+ removeItemFromCart(input: RemoveItemFromCartInput): RemoveItemFromCartOutput
+ setShippingAddressesOnCart(input: SetShippingAddressesOnCartInput): SetShippingAddressesOnCartOutput
+ setBillingAddressOnCart(input: SetBillingAddressOnCartInput): SetBillingAddressOnCartOutput
+ setShippingMethodsOnCart(input: SetShippingMethodsOnCartInput): SetShippingMethodsOnCartOutput
+ setPaymentMethodOnCart(input: SetPaymentMethodOnCartInput): SetPaymentMethodOnCartOutput
+ setGuestEmailOnCart(input: SetGuestEmailOnCartInput): SetGuestEmailOnCartOutput
+ setPaymentMethodAndPlaceOrder(input: SetPaymentMethodAndPlaceOrderInput): PlaceOrderOutput @deprecated(reason: "Should use setPaymentMethodOnCart and placeOrder mutations in single request.")
+ mergeCarts(source_cart_id: String!, destination_cart_id: String!): Cart! @doc(description:"Merges the source cart into the destination cart")
+ placeOrder(input: PlaceOrderInput): PlaceOrderOutput
+ addProductsToCart(cartId: String!, cartItems: [CartItemInput!]!): AddProductsToCartOutput @doc(description:"Add any type of product to the cart")
+}
+
+input createEmptyCartInput {
+ cart_id: String
+}
+
+input AddSimpleProductsToCartInput {
+ cart_id: String!
+ cart_items: [SimpleProductCartItemInput!]!
+}
+
+input SimpleProductCartItemInput {
+ data: CartItemInput!
+ customizable_options:[CustomizableOptionInput!]
+}
+
+input AddVirtualProductsToCartInput {
+ cart_id: String!
+ cart_items: [VirtualProductCartItemInput!]!
+}
+
+input VirtualProductCartItemInput {
+ data: CartItemInput!
+ customizable_options:[CustomizableOptionInput!]
+}
+
+input CartItemInput {
+ sku: String!
+ quantity: Float!
+ parent_sku: String @doc(description: "For child products, the SKU of its parent product")
+ selected_options: [ID!] @doc(description: "The selected options for the base product, such as color or size")
+ entered_options: [EnteredOptionInput!] @doc(description: "An array of entered options for the base product, such as personalization text")
+}
+
+input CustomizableOptionInput {
+ id: Int!
+ value_string: String!
+}
+
+input ApplyCouponToCartInput {
+ cart_id: String!
+ coupon_code: String!
+}
+
+input UpdateCartItemsInput {
+ cart_id: String!
+ cart_items: [CartItemUpdateInput!]!
+}
+
+input CartItemUpdateInput {
+ # Implementation Note: For back-compat reasons, the following rules should be applied to
+ # a resolver handling `CartItemUpdateInput` (see https://github.com/magento/architecture/pull/424)
+ #
+ # 1. Either `cart_item_id` or `cart_item_uid` _must_ be provided. If both are absent, raise
+ # a field error advising the client that `cart_item_uid` is required
+ # 2. If _both_ `cart_item_id` and `cart_item_uid` are provided, raise a field error advising
+ # the client to only use `cart_item_uid`
+ #
+ # GraphQL does not provide a way for an `input` field to allow use of only 1 field or another,
+ # which is why we're enforcing non-nullability in the resolver, instead of via the schema language.
+ # When `cart_item_id` is removed in a future version, we can mark `cart_item_uid` as non-nullable
+ # with minimal disruption
+ cart_item_id: Int @deprecated(reason: "Use the `cart_item_uid` field instead")
+ cart_item_interface_uid: ID @doc(description: "Required field. Unique Identifier from objects implementing `CartItemInterface`")
+ quantity: Float
+ customizable_options: [CustomizableOptionInput!]
+}
+
+input RemoveItemFromCartInput {
+ cart_id: String!
+ # Implementation Note: For back-compat reasons, the following rules should be applied to
+ # a resolver handling `RemoveItemFromCartInput` (see https://github.com/magento/architecture/pull/424)
+ #
+ # 1. Either `cart_item_id` or `cart_item_uid` _must_ be provided. If both are absent, raise
+ # a field error advising the client that `cart_item_uid` is required
+ # 2. If _both_ `cart_item_id` and `cart_item_uid` are provided, raise a field error advising
+ # the client to only use `cart_item_uid`
+ #
+ # GraphQL does not provide a way for an `input` field to allow use of only 1 field or another,
+ # which is why we're enforcing non-nullability in the resolver, instead of via the schema language.
+ # When `cart_item_id` is removed in a future version, we can mark `cart_item_uid` as non-nullable
+ # with minimal disruption
+ cart_item_id: Int @deprecated(reason: "Use the `cart_item_uid` field instead")
+ cart_item_interface_uid: ID @doc(description: "Required field. Unique Identifier from objects implementing `CartItemInterface`")
+}
+
+input SetShippingAddressesOnCartInput {
+ cart_id: String!
+ shipping_addresses: [ShippingAddressInput!]!
+}
+
+input ShippingAddressInput {
+ customer_address_id: Int # If provided then will be used address from address book
+ address: CartAddressInput
+ customer_notes: String
+}
+
+input SetBillingAddressOnCartInput {
+ cart_id: String!
+ billing_address: BillingAddressInput!
+}
+
+input BillingAddressInput {
+ customer_address_id: Int
+ address: CartAddressInput
+ use_for_shipping: Boolean @doc(description: "Deprecated: use `same_as_shipping` field instead")
+ same_as_shipping: Boolean @doc(description: "Set billing address same as shipping")
+}
+
+input CartAddressInput {
+ firstname: String!
+ lastname: String!
+ company: String
+ street: [String!]!
+ city: String!
+ region: String
+ region_id: Int
+ postcode: String
+ country_code: String!
+ telephone: String!
+ save_in_address_book: Boolean @doc(description: "Determines whether to save the address in the customer's address book. The default value is true")
+}
+
+input SetShippingMethodsOnCartInput {
+ cart_id: String!
+ shipping_methods: [ShippingMethodInput!]!
+}
+
+input ShippingMethodInput {
+ carrier_code: String!
+ method_code: String!
+}
+
+input SetPaymentMethodAndPlaceOrderInput {
+ cart_id: String!
+ payment_method: PaymentMethodInput!
+}
+
+input PlaceOrderInput {
+ cart_id: String!
+}
+
+input SetPaymentMethodOnCartInput {
+ cart_id: String!
+ payment_method: PaymentMethodInput!
+}
+
+input PaymentMethodInput {
+ code: String! @doc(description:"Payment method code")
+ purchase_order_number: String @doc(description:"Purchase order number")
+}
+
+input SetGuestEmailOnCartInput {
+ cart_id: String!
+ email: String!
+}
+
+interface QuotePricesInterface {
+ grand_total: Money
+ subtotal_including_tax: Money
+ subtotal_excluding_tax: Money
+ discount: CartDiscount @deprecated(reason: "Use discounts instead ")
+ subtotal_with_discount_excluding_tax: Money
+ applied_taxes: [CartTaxItem]
+ discounts: [Discount] @doc(description:"An array of applied discounts")
+}
+
+type CartPrices implements QuotePricesInterface {
+}
+
+type CartTaxItem {
+ amount: Money!
+ label: String!
+}
+
+type CartDiscount {
+ amount: Money!
+ label: [String!]!
+}
+
+type SetPaymentMethodOnCartOutput {
+ cart: Cart!
+}
+
+type SetBillingAddressOnCartOutput {
+ cart: Cart!
+}
+
+type SetShippingAddressesOnCartOutput {
+ cart: Cart!
+}
+
+type SetShippingMethodsOnCartOutput {
+ cart: Cart!
+}
+
+type ApplyCouponToCartOutput {
+ cart: Cart!
+}
+
+type PlaceOrderOutput {
+ order: Order!
+}
+
+type Cart {
+ id: ID! @doc(description: "The ID of the cart.")
+ items: [CartItemInterface]
+ applied_coupon: AppliedCoupon @doc(description:"An array of coupons that have been applied to the cart") @deprecated(reason: "Use applied_coupons instead ")
+ applied_coupons: [AppliedCoupon] @doc(description:"An array of `AppliedCoupon` objects. Each object contains the `code` text attribute, which specifies the coupon code")
+ email: String
+ shipping_addresses: [ShippingCartAddress]!
+ billing_address: BillingCartAddress
+ available_payment_methods: [AvailablePaymentMethod] @doc(description: "Available payment methods")
+ selected_payment_method: SelectedPaymentMethod
+ prices: CartPrices
+ total_quantity: Float!
+ is_virtual: Boolean!
+}
+
+interface CartAddressInterface {
+ firstname: String!
+ lastname: String!
+ company: String
+ street: [String!]!
+ city: String!
+ region: CartAddressRegion
+ postcode: String
+ country: CartAddressCountry!
+ telephone: String!
+}
+
+type ShippingCartAddress implements CartAddressInterface {
+ available_shipping_methods: [AvailableShippingMethod]
+ selected_shipping_method: SelectedShippingMethod
+ customer_notes: String
+ items_weight: Float @deprecated(reason: "This information shoud not be exposed on frontend")
+ cart_items: [CartItemQuantity] @deprecated(reason: "`cart_items_v2` should be used instead")
+ cart_items_v2: [CartItemInterface]
+}
+
+type BillingCartAddress implements CartAddressInterface {
+ customer_notes: String @deprecated (reason: "The field is used only in shipping address")
+}
+
+type CartItemQuantity @deprecated(description:"Deprecated: `cart_items` field of `ShippingCartAddress` returns now `CartItemInterface` instead of `CartItemQuantity`") {
+ cart_item_id: Int! @deprecated(reason: "`cart_items` field of `ShippingCartAddress` returns now `CartItemInterface` instead of `CartItemQuantity`")
+ quantity: Float! @deprecated(reason: "`cart_items` field of `ShippingCartAddress` returns now `CartItemInterface` instead of `CartItemQuantity`")
+}
+
+type CartAddressRegion {
+ code: String
+ label: String
+ region_id: Int
+}
+
+type CartAddressCountry {
+ code: String!
+ label: String!
+}
+
+type SelectedShippingMethod {
+ carrier_code: String!
+ method_code: String!
+ carrier_title: String!
+ method_title: String!
+ amount: Money!
+ base_amount: Money @deprecated(reason: "The field should not be used on the storefront")
+}
+
+type AvailableShippingMethod {
+ carrier_code: String!
+ carrier_title: String!
+ method_code: String @doc(description: "Could be null if method is not available")
+ method_title: String @doc(description: "Could be null if method is not available")
+ error_message: String
+ amount: Money!
+ base_amount: Money @deprecated(reason: "The field should not be used on the storefront")
+ price_excl_tax: Money!
+ price_incl_tax: Money!
+ available: Boolean!
+}
+
+type AvailablePaymentMethod {
+ code: String! @doc(description: "The payment method code")
+ title: String! @doc(description: "The payment method title.")
+}
+
+type SelectedPaymentMethod {
+ code: String! @doc(description: "The payment method code")
+ title: String! @doc(description: "The payment method title.")
+ purchase_order_number: String @doc(description: "The purchase order number.")
+}
+
+type AppliedCoupon {
+ code: String!
+}
+
+input RemoveCouponFromCartInput {
+ cart_id: String!
+}
+
+type RemoveCouponFromCartOutput {
+ cart: Cart
+}
+
+type AddSimpleProductsToCartOutput {
+ cart: Cart!
+}
+
+type AddVirtualProductsToCartOutput {
+ cart: Cart!
+}
+
+type UpdateCartItemsOutput {
+ cart: Cart!
+}
+
+type RemoveItemFromCartOutput {
+ cart: Cart!
+}
+
+type SetGuestEmailOnCartOutput {
+ cart: Cart!
+}
+
+type SimpleCartItem implements CartItemInterface @doc(description: "Simple Cart Item") {
+ customizable_options: [SelectedCustomizableOption]
+}
+
+type VirtualCartItem implements CartItemInterface @doc(description: "Virtual Cart Item") {
+ customizable_options: [SelectedCustomizableOption]
+}
+
+interface CartItemInterface {
+ id: String! @deprecated(reason: "Use CartItemInterface.uid instead")
+ uid: ID! @doc(description: "Unique identifier for a Cart Item")
+ quantity: Float!
+ prices: QuoteItemPricesInterface
+ product: ProductInterface!
+}
+
+type Discount @doc(description:"Defines an individual discount. A discount can be applied to the cart as a whole or to an item.") {
+ amount: Money! @doc(description:"The amount of the discount")
+ label: String! @doc(description:"A description of the discount")
+}
+
+type QuoteItemPricesInterface {
+ price: Money! @doc(description:"Item price that might include tax depending on display settings for cart")
+ fixed_product_taxes: [FixedProductTax] @doc(description:"Applied FPT to the cart item")
+ row_total: Money!
+ row_total_including_tax: Money!
+ discounts: [Discount] @doc(description:"An array of discounts to be applied to the cart item")
+ total_item_discount: Money @doc(description:"The total of all discounts applied to the item")
+}
+
+type CartItemPrices implements QuoteItemPricesInterface {
+}
+
+type SelectedCustomizableOption {
+ id: Int!
+ label: String!
+ is_required: Boolean!
+ values: [SelectedCustomizableOptionValue!]!
+ sort_order: Int!
+}
+
+type SelectedCustomizableOptionValue {
+ id: Int!
+ label: String!
+ value: String!
+ price: CartItemSelectedOptionValuePrice!
+}
+
+type CartItemSelectedOptionValuePrice {
+ value: Float!
+ units: String!
+ type: PriceTypeEnum!
+}
+
+type Order {
+ order_number: String!
+ order_id: String @deprecated(reason: "The order_id field is deprecated, use order_number instead.")
+}
+
+type CartUserInputError @doc(description:"An error encountered while adding an item to the the cart.") {
+ message: String! @doc(description: "A localized error message")
+ code: CartUserInputErrorType! @doc(description: "Cart-specific error code")
+}
+
+type AddProductsToCartOutput {
+ cart: Cart! @doc(description: "The cart after products have been added")
+ user_errors: [CartUserInputError!]! @doc(description: "An error encountered while adding an item to the cart.")
+}
+
+enum CartUserInputErrorType {
+ PRODUCT_NOT_FOUND
+ NOT_SALABLE
+ INSUFFICIENT_STOCK
+ UNDEFINED
+}
diff --git a/design-documents/graph-ql/coverage/re-captcha.graphqls b/design-documents/graph-ql/coverage/re-captcha.graphqls
new file mode 100644
index 000000000..404304bc5
--- /dev/null
+++ b/design-documents/graph-ql/coverage/re-captcha.graphqls
@@ -0,0 +1,44 @@
+enum ReCaptchaFormEnum {
+ PLACE_ORDER
+ CONTACT
+ CUSTOMER_LOGIN
+ CUSTOMER_FORGOT_PASSWORD
+ CUSTOMER_CREATE
+ CUSTOMER_EDIT
+ NEWSLETTER
+ PRODUCT_REVIEW
+ SENDFRIEND
+ BRAINTREE
+}
+
+type ReCaptchaConfigurationV3 {
+ website_key: String!
+ @doc(
+ description: "The website key that is created when you register your Google reCAPTCHA account"
+ )
+ minimum_score: Float!
+ @doc(
+ description: "The minimum score that identifies a user interaction as a potential risk"
+ )
+ badge_position: String!
+ @doc(
+ description: "The position of the invisible reCAPTCHA badge on each page"
+ )
+ language_code: String
+ @doc(
+ description: "A two-character code that specifies the language that is used for Google reCAPTCHA text and messaging."
+ )
+ failure_message: String!
+ @doc(
+ description: "The message that appears to the user if validation fails"
+ )
+ forms: [ReCaptchaFormEnum!]!
+ @doc(description: "A list of forms that have reCAPTCHA V3 enabled")
+}
+
+# Google reCAPTCHA config - will return null if v3 invisible is not configured
+# for at least one Storefront form.
+type Query {
+ recaptchaV3Config: ReCaptchaConfigurationV3
+ @doc(description: "Google reCAPTCHA V3-Invisible Configuration")
+}
diff --git a/design-documents/graph-ql/coverage/returns.graphqls b/design-documents/graph-ql/coverage/returns.graphqls
new file mode 100644
index 000000000..1249c534e
--- /dev/null
+++ b/design-documents/graph-ql/coverage/returns.graphqls
@@ -0,0 +1,219 @@
+type Query{
+ # See https://github.com/magento/architecture/blob/master/design-documents/graph-ql/framework/attributes-metadata.md
+ pageSpecificCustomAttributes(
+ page_type: CustomAttributesPageEnum!
+ ): CustomAttributeMetadata
+}
+
+type Mutation {
+ requestReturn(input: RequestReturnInput!): RequestReturnOutput @doc(description: "Create a new return.")
+ addReturnComment(input: AddReturnCommentInput!): AddReturnCommentOutput @doc(description: "Add a comment to an existing return.")
+ addReturnTracking(input: AddReturnTrackingInput!): AddReturnTrackingOutput @doc(description: "Add tracking information to the return.")
+ removeReturnTracking(input: RemoveReturnTrackingInput!): RemoveReturnTrackingOutput @doc(description: "Remove a single tracked shipment from the return.")
+}
+
+input RequestReturnInput {
+ order_id: ID!
+ contact_email: String
+ items: [RequestReturnItemInput!]!
+ comment_text: String
+}
+
+input RequestReturnItemInput {
+ order_item_id: ID! @doc(description: "ID of the order item associated with the return.")
+ quantity_to_return: Float! @doc(description: "The quantity of the item requested to be returned.")
+ selected_custom_attributes: [ID!] @doc(description: "Values of return item attributes defined by the merchant, e.g. select attributes.")
+ entered_custom_attributes: [EnteredCustomAttributeInput!] @doc(description: "Values of return item attributes defined by the merchant, e.g. file or text attributes.")
+}
+
+input EnteredCustomAttributeInput {
+ uid: ID!
+ value: String!
+}
+
+type RequestReturnOutput {
+ return: Return
+ returns(
+ pageSize: Int = 20 @doc(description: "Specifies the maximum number of results to return at once. Defaults to 20."),
+ currentPage: Int = 1 @doc(description: "Specifies which page of results to return. The default value is 1."),
+ ): Returns @doc(description: "Information about the customer returns.")
+}
+
+input AddReturnCommentInput {
+ return_id: ID!
+ comment_text: String!
+}
+
+type AddReturnCommentOutput {
+ return: Return
+}
+
+input AddReturnTrackingInput {
+ return_id: ID!
+ carrier_id: ID!
+ tracking_number: String!
+}
+
+type AddReturnTrackingOutput {
+ return: Return
+ return_shipping_tracking: ReturnShippingTracking
+}
+
+input RemoveReturnTrackingInput {
+ return_shipping_tracking_id: ID!
+}
+
+type RemoveReturnTrackingOutput {
+ return: Return
+}
+
+enum CustomAttributesPageEnum {
+ RETURN_ITEM_EDIT_FORM
+ RETURN_ITEMS_LISTING
+ # See https://github.com/magento/architecture/blob/master/design-documents/graph-ql/framework/attributes-metadata.md
+ # PRODUCTS_COMPARE
+ # PRODUCTS_LISTING
+ # ADVANCED_CATALOG_SEARCH
+}
+
+type Customer {
+ returns(
+ pageSize: Int = 20 @doc(description: "Specifies the maximum number of results to return at once. Defaults to 20."),
+ currentPage: Int = 1 @doc(description: "Specifies which page of results to return. The default value is 1."),
+ ): Returns @doc(description: "Information about the customer returns.")
+ return(uid: ID!): Return @doc(description: "Get customer return details by its ID.")
+}
+
+type CustomerOrder {
+ returns(
+ pageSize: Int = 20 @doc(description: "Specifies the maximum number of results to return at once. Defaults to 20."),
+ currentPage: Int = 1 @doc(description: "Specifies which page of results to return. The default value is 1."),
+ ): Returns @doc(description: "Returns associated with this order.")
+ items_eligible_for_return: [OrderItemInterface] @doc(description: "A list of order items eligible for return.")
+}
+
+type OrderItemInterface {
+ eligible_for_return: Boolean @doc(description: "Indicates whether the order item is eligible for return.")
+}
+
+type Returns {
+ items: [Return] @doc(description: "List of returns")
+ page_info: SearchResultPageInfo @doc(description: "Pagination metadata")
+ total_count: Int @doc(description: "Total count of customer returns")
+}
+
+type Return @doc(description: "Customer return") {
+ uid: ID!
+ number: String! @doc(description: "Human-readable return number")
+ order: CustomerOrder @doc(description: "The order associated with the return.")
+ created_at: String! @doc(description: "The date when the return was requested.")
+ customer: ReturnCustomer! @doc(description: "The data for the customer who created the return request")
+ status: ReturnStatus @doc(description: "Return status.")
+ shipping: ReturnShipping @doc(description: "Shipping information for the return.")
+ comments: [ReturnComment] @doc(description: "A list of comments posted for the return.")
+ items: [ReturnItem] @doc(description: "A list of items being returned.")
+ available_shipping_carriers: [ReturnShippingCarrier] @doc(description: "A list of shipping carriers available for returns.")
+}
+
+type ReturnCustomer @doc(description: "The Customer information for the return.") {
+ email: String! @doc(description: "Customer email address.")
+ firstname: String @doc(description: "Customer first name.")
+ lastname: String @doc(description: "Customer last name.")
+}
+
+type ReturnItem {
+ uid: ID!
+ order_item: OrderItemInterface! @doc(description: "Order item provides access to the product being returned, including selected/entered options information.")
+ custom_attributes: [CustomAttribute] @doc(description: "Return item custom attributes, which are marked by the admin to be visible on the storefront.")
+ request_quantity: Float! @doc(description: "The quantity of the item requested to be returned.")
+ quantity: Float! @doc(description: "The quantity of the items authorized by the merchant to be returned .")
+ status: ReturnItemStatus! @doc(description: "The return status of the item being returned.")
+}
+
+# See https://github.com/magento/architecture/blob/master/design-documents/graph-ql/custom-attributes-container.md
+type CustomAttribute {
+ uid: ID!
+ label: String!
+ value: String! @doc(description: "JSON encoded value of the attribute.")
+}
+
+type ReturnComment {
+ uid: ID! @doc(description: "Comment ID.")
+ author_name: String! @doc(description: "The name of the author who posted the comment.")
+ created_at: String! @doc(description: "The date and time when the comment was posted.")
+ text: String! @doc(description: "The comment text.")
+}
+
+type ReturnShipping {
+ address: ReturnShippingAddress @doc(description: "Return shipping address, which is specified by the admin.")
+ tracking(uid: ID): [ReturnShippingTracking] @doc(description: "Tracking information for all or a single tracking record when ID is provided.")
+}
+
+type ReturnShippingCarrier {
+ uid: ID!
+ label: String!
+}
+
+type ReturnShippingTracking {
+ uid: ID!
+ carrier: ReturnShippingCarrier!
+ tracking_number: String!
+ status: ReturnShippingTrackingStatus
+}
+
+type ReturnShippingTrackingStatus {
+ text: String!
+ type: ReturnShippingTrackingStatusType!
+}
+
+enum ReturnShippingTrackingStatusType {
+ INFORMATION
+ ERROR
+}
+
+type ReturnShippingAddress {
+ contact_name: String
+ street: [String]!
+ city: String!
+ region: Region!
+ postcode: String!
+ country: Country!
+ telephone: String
+}
+
+enum ReturnStatus {
+ PENDING
+ AUTHORIZED
+ PARTIALLY_AUTHORIZED
+ RECEIVED
+ PARTIALLY_RECEIVED
+ APPROVED
+ PARTIALLY_APPROVED
+ REJECTED
+ PARTIALLY_REJECTED
+ DENIED
+ PROCESSED_AND_CLOSED
+ CLOSED
+}
+
+enum ReturnItemStatus {
+ PENDING
+ AUTHORIZED
+ RECEIVED
+ APPROVED
+ REJECTED
+ DENIED
+}
+
+type StoreConfig {
+ # sales_magento/rma/enabled
+ returns_enabled: String! @doc(description: "Returns functionality status on the storefront: enabled/disabled.")
+}
+
+type Attribute {
+ uid: ID!
+}
+
+type AttributeOption {
+ uid: ID!
+}
diff --git a/design-documents/graph-ql/coverage/returns.md b/design-documents/graph-ql/coverage/returns.md
new file mode 100644
index 000000000..40290236e
--- /dev/null
+++ b/design-documents/graph-ql/coverage/returns.md
@@ -0,0 +1,373 @@
+## Configuration
+
+The following settings should be accessible via `storeConfig` query:
+- Returns functionality status on the storefront: enabled/disabled
+
+Scenarios which may need these settings include:
+- Rendering of the returns section in the customer account
+
+```graphql
+{
+ storeConfig {
+ sales_magento_rma_enabled
+ }
+}
+```
+
+## Use cases
+
+### View return list with pagination in customer account
+
+```graphql
+{
+ customer {
+ returns(pageSize: 10, currentPage: 2) {
+ items {
+ id
+ creation_date
+ customer_name
+ status
+ }
+ page_info {
+ current_page
+ page_size
+ total_pages
+ }
+ total_count
+ }
+ }
+}
+```
+
+### View return list with pagination in order details
+
+```graphql
+{
+ customer {
+ orders(filter: {number: {eq: "00000008"}}) {
+ items {
+ returns(pageSize: 10, currentPage: 1) {
+ items {
+ id
+ creation_date
+ customer_name
+ status
+ }
+ page_info {
+ current_page
+ page_size
+ total_pages
+ }
+ total_count
+ }
+ }
+ }
+ }
+}
+```
+
+### View return details
+
+```graphql
+{
+ customer {
+ return(id: "23as452gsa") {
+ number
+ order {
+ number
+ }
+ creation_date
+ customer_email
+ customer_name
+ status
+ comments {
+ id
+ text
+ created_at
+ created_by
+ }
+ items {
+ id
+ order_item {
+ product_sku
+ product_name
+ }
+ custom_attributes {
+ id
+ label
+ value
+ }
+ request_quantity
+ quantity
+ status
+ }
+ shipping {
+ tracking {
+ id
+ carrier {
+ label
+ }
+ tracking_number
+ }
+ address {
+ contact_name
+ street
+ city
+ region {
+ code
+ }
+ country {
+ full_name_english
+ }
+ postcode
+ telephone
+ }
+ }
+ }
+ }
+}
+```
+
+### Create a return request
+
+#### Determine whether any order items are eligible for return
+
+There is a need to know if any order items are eligible for return. In the Luma example this is dictating whether "Return" link will be displayed on the order details page.
+
+```graphql
+{
+ customer {
+ orders(filter: {number: {eq: "00000008"}}) {
+ items {
+ items_eligible_for_return {
+ id
+ product_name
+ }
+ }
+ }
+ }
+}
+```
+
+Alternative approach is to use a flag added to order items:
+
+```graphql
+{
+ customer {
+ orders(filter: {number: {eq: "00000008"}}) {
+ items {
+ items {
+ id
+ eligible_for_return
+ }
+ }
+ }
+ }
+}
+```
+
+#### Render return form with dynamic RMA attributes
+
+```graphql
+{
+ pageSpecificCustomAttributes(page_type: RETURN_ITEM_EDIT_FORM) {
+ items {
+ id
+ attribute_code
+ attribute_type
+ input_type
+ attribute_options {
+ id
+ label
+ value
+ }
+ }
+ }
+}
+```
+
+Existing schema of `Attribute` and `AttributeOption` must be extended to provide `ID` field, which will be used in mutations to specify custom attribute values for returns.
+
+#### Determine which order items are eligible for return
+
+```graphql
+{
+ customer {
+ orders(filter: {number: {eq: "00000008"}}) {
+ items {
+ items_eligible_for_return {
+ id
+ product_name
+ }
+ }
+ }
+ }
+}
+```
+
+#### Submit a return request with multiple items and comments
+
+```graphql
+mutation {
+ requestReturn(
+ input: {
+ order_id: 12345
+ contact_email: "returnemail@magento.com"
+ items: [
+ {
+ order_item_id: "absdfj2l3415",
+ quantity_to_return: 1,
+ selected_custom_attributes: ["encoded-custom-select-attribute-value-id"],
+ entered_custom_attributes: [{id: "encoded-custom-text-attribute-id", value: "Custom attribute value"}]
+ }
+ ],
+ comment_text: "Return comment"
+ }
+ ) {
+ return {
+ id
+ items {
+ id
+ quantity
+ request_quantity
+ order_item {
+ product_sku
+ product_name
+ }
+ custom_attributes {
+ id
+ label
+ value
+ }
+ }
+ comments {
+ created_at
+ created_by
+ text
+ }
+ }
+ }
+}
+```
+
+Alternatively, the collection of returns associated with the order can be requested.
+
+### Leave a return comment
+
+```graphql
+mutation {
+ addReturnComment(
+ input: {
+ return_id: "23as452gsa",
+ comment_text: "Another return comment"
+ }
+ ) {
+ return {
+ id
+ comments {
+ created_at
+ created_by
+ text
+ }
+ }
+ }
+}
+```
+
+### Create a return for guest order
+
+Guest orders are not accessible via GraphQL yet, but the schema of returns will be identical to the one for customer orders.
+
+### Specify shipping and tracking
+
+When return is authorized by the admin user, the customer can specify shipping and tracking information.
+
+First, the client needs to get shipping carriers that can be used for returns:
+
+```graphql
+{
+ customer {
+ return(id: "asdgah2341") {
+ available_shipping_carriers {
+ id
+ label
+ }
+ }
+ }
+}
+```
+
+Then tracking information can be submitted:
+
+```graphql
+mutation {
+ addReturnTracking(
+ input: {
+ return_id: "23as452gsa",
+ carrier_id: "carrier-id",
+ tracking_number: "4234213"
+ }
+ ) {
+ return {
+ shipping {
+ tracking {
+ id
+ carrier {
+ label
+ }
+ tracking_number
+ }
+ }
+ }
+ }
+}
+```
+
+If the user decides to view the status of the specific tracking item, it can be retrieved using the following query:
+
+```graphql
+{
+ customer {
+ return(id: "23as452gsa") {
+ shipping {
+ tracking(id: "return-tracking-id") {
+ id
+ carrier {
+ label
+ }
+ tracking_number
+ status {
+ text
+ type
+ }
+ }
+ }
+ }
+ }
+}
+```
+
+In case the return shipping needs to be removed, the following mutation can be used:
+
+```graphql
+mutation {
+ removeReturnTracking(
+ input: {
+ return_shipping_tracking_id: "return-tracking-id"
+ }
+ ) {
+ return {
+ shipping {
+ tracking {
+ id
+ carrier {
+ label
+ }
+ tracking_number
+ }
+ }
+ }
+ }
+}
+
+```
diff --git a/design-documents/graph-ql/coverage/routing/available-stores.md b/design-documents/graph-ql/coverage/routing/available-stores.md
new file mode 100644
index 000000000..caebce390
--- /dev/null
+++ b/design-documents/graph-ql/coverage/routing/available-stores.md
@@ -0,0 +1,75 @@
+# Get available stores for website
+
+### Use case:
+Implementing a store switcher; it is necessary to know which stores are available and some basic info about them (i.e. store code)
+
+### Security Requirement:
+Based on the store code passed via header (or default), returns the storeConfig for other stores available under the same website.
+
+This query MUST NOT expose other websites or stores available under websites other than the current website.
+
+### Proposed schema
+```graphql
+type Query {
+ availableStores: [StoreConfig] @doc(description: "Get a list of available store views and their config information.")
+}
+
+# Existing schema
+type StoreConfig @doc(description: "The type contains information about a store config") {
+ id : Int @doc(description: "The ID number assigned to the store")
+ code : String @doc(description: "A code assigned to the store to identify it")
+ website_id : Int @doc(description: "The ID number assigned to the website store belongs")
+ locale : String @doc(description: "Store locale")
+ base_currency_code : String @doc(description: "Base currency code")
+ default_display_currency_code : String @doc(description: "Default display currency code")
+ timezone : String @doc(description: "Timezone of the store")
+ weight_unit : String @doc(description: "The unit of weight")
+ base_url : String @doc(description: "Base URL for the store")
+ base_link_url : String @doc(description: "Base link URL for the store")
+ base_static_url : String @doc(description: "Base static URL for the store")
+ base_media_url : String @doc(description: "Base media URL for the store")
+ secure_base_url : String @doc(description: "Secure base URL for the store")
+ secure_base_link_url : String @doc(description: "Secure base link URL for the store")
+ secure_base_static_url : String @doc(description: "Secure base static URL for the store")
+ secure_base_media_url : String @doc(description: "Secure base media URL for the store")
+ store_name : String @doc(description: "Name of the store")
+ # ... more fields added from other modules and 3rd parties
+}
+```
+
+Sample query:
+```graphql
+query {
+ availableStores {
+ id
+ code
+ locale
+ timezone
+ base_url
+ }
+}
+```
+
+Sample response:
+```json
+{
+ "data": {
+ "availableStores": [
+ {
+ "id": 1,
+ "code": "default",
+ "locale": "en_US",
+ "timezone": "America/Chicago",
+ "base_url": "http://magento.test/"
+ },
+ {
+ "id": 2,
+ "code": "German",
+ "locale": "de_DE",
+ "timezone": "Europe/Berlin",
+ "base_url": "http://magento.test/"
+ }
+ ]
+ }
+}
+```
diff --git a/design-documents/graph-ql/coverage/storefront-route.md b/design-documents/graph-ql/coverage/routing/storefront-route.md
similarity index 100%
rename from design-documents/graph-ql/coverage/storefront-route.md
rename to design-documents/graph-ql/coverage/routing/storefront-route.md
diff --git a/design-documents/graph-ql/coverage/url-resolver-identifier.md b/design-documents/graph-ql/coverage/routing/url-resolver-identifier.md
similarity index 100%
rename from design-documents/graph-ql/coverage/url-resolver-identifier.md
rename to design-documents/graph-ql/coverage/routing/url-resolver-identifier.md
diff --git a/design-documents/graph-ql/coverage/search/layered-navigation-filter-names-change/layered_navigation.png b/design-documents/graph-ql/coverage/search/layered-navigation-filter-names-change/layered_navigation.png
new file mode 100644
index 000000000..08c199080
Binary files /dev/null and b/design-documents/graph-ql/coverage/search/layered-navigation-filter-names-change/layered_navigation.png differ
diff --git a/design-documents/graph-ql/coverage/search/product_filter_and_search_changes.md b/design-documents/graph-ql/coverage/search/product_filter_and_search_changes.md
new file mode 100644
index 000000000..abff54aa4
--- /dev/null
+++ b/design-documents/graph-ql/coverage/search/product_filter_and_search_changes.md
@@ -0,0 +1,308 @@
+# Proposed changes for Product search and filtering
+
+### Changes to Product filter input
+**Current list of available filtering and sorting**
+
+This list is currently hardcoded in the GrahpQl schema, we will be removing this list and using a dynamic list of attributes.
+
+*Notable: We will remove the ability to combine conditions using "or".*
+
+```
+name: FilterTypeInput
+sku: FilterTypeInput
+description: FilterTypeInput
+short_description: FilterTypeInput
+price: FilterTypeInput
+special_price: FilterTypeInput
+special_from_date: FilterTypeInput
+special_to_date: FilterTypeInput
+weight: FilterTypeInput
+manufacturer: FilterTypeInput
+meta_title: FilterTypeInput
+meta_keyword: FilterTypeInput
+meta_description: FilterTypeInput
+image: FilterTypeInput
+small_image: FilterTypeInput
+thumbnail: FilterTypeInput
+tier_price: FilterTypeInput
+news_from_date: FilterTypeInput
+news_to_date: FilterTypeInput
+custom_layout_update: FilterTypeInput
+min_price: FilterTypeInput
+max_price: FilterTypeInput
+category_id: FilterTypeInput
+options_container: FilterTypeInput
+required_options: FilterTypeInput
+has_options: FilterTypeInput
+image_label: FilterTypeInput
+small_image_label: FilterTypeInput
+thumbnail_label: FilterTypeInput
+created_at: FilterTypeInput
+updated_at: FilterTypeInput
+country_of_manufacture: FilterTypeInput
+custom_layout: FilterTypeInput
+gift_message_available: FilterTypeInput
+or: ProductFilterInput
+```
+
+**New available filter options (On fresh Magento installation)**
+```
+category_id: FilterEqualTypeInput
+description: FilterMatchTypeInput
+name: FilterMatchTypeInput
+price: FilterRangeTypeInput
+short_description: FilterMatchTypeInput
+sku: FilterEqualTypeInput
+(Additional custom attributes): (filter type determined by attribute type)
+```
+
+Additionally FilterTypeInput will be replaced with more specific filter types that limit the types of comparisons that can be done based on the attribute type.
+**Existing filter type**
+```
+FilterTypeInput:
+eq | String
+finset | [String]
+from | String
+gt | String
+gteq | String
+in | [String]
+like | String
+lt | String
+lteq | String
+moreq | String
+neq | String
+notnull | String
+null | String
+to | String
+nin | [String]
+```
+**New filter types**
+```
+FilterEqualTypeInput (eq: String | in: [String])
+FilterMatchTypeInput (match: String)
+FilterRangeTypeInput (from: String | to: String)
+```
+
+## Changes to Product sort input
+
+**Current sort options**
+
+Similar to filtering this hardcoded list will be replaced with a dynamic list of attributes that can be used for sorting.
+```
+name: SortEnum
+sku: SortEnum
+description: SortEnum
+short_description: SortEnum
+price: SortEnum
+special_price: SortEnum
+special_from_date: SortEnum
+special_to_date: SortEnum
+weight: SortEnum
+manufacturer: SortEnum
+meta_title: SortEnum
+meta_keyword: SortEnum
+meta_description: SortEnum
+image: SortEnum
+small_image: SortEnum
+thumbnail: SortEnum
+tier_price: SortEnum
+news_from_date: SortEnum
+news_to_date: SortEnum
+custom_layout_update: SortEnum
+options_container: SortEnum
+required_options: SortEnum
+has_options: SortEnum
+image_label: SortEnum
+small_image_label: SortEnum
+thumbnail_label: SortEnum
+created_at: SortEnum
+updated_at: SortEnum
+country_of_manufacture: SortEnum
+custom_layout: SortEnum
+gift_message_available: SortEnum
+```
+
+#### New available sort options (on fresh Magento installation)
+```
+relevance: SortEnum
+name: SortEnum
+position: SortEnum
+price: SortEnum
+(addition attributes that are available to use for sorting)
+```
+If no sort order is requested, results will be sorted by `relevance DESC` by default.
+
+
+## Changes to Layered Navigation Output
+
+Currently the schema for layered navigation is very specific to how you would render it in Luma and magento internal attributes. It doesn't make a lot of sense for GraphQl.
+
+
+
+**Use cases:**
+- Reading relevant filters after a product search and displaying them
+- The UI logic has to be able to loop through attributes and list them as sections
+- Each attribute has multiple and at least one value to be rendered. A value of an attribute can then be used to be filtered by in a product search, by using it's ID value where it's label is only used for display purposes.
+
+
+
+**Current schema:**
+
+- Query and return value:
+```graphql
+filters {
+ filter_items_count
+ name
+ request_var
+ filter_items {
+ items_count
+ label
+ value_string
+ }
+ }
+```
+
+
+**Problems in the current schema:**
+
+- `filters->name` it's actually the filter label intended for display and rendering
+- `filters->request_var` it's actually the filter name used in product filtering. this is not a HTTP request anymore, it's graphql.
+- `filters->filter_items->value_string` it's actually the comparison ID value that we use in product filtering. Indeed is a string type for now because all attributes are. We don't make that distinction and when we will the 'value_string' won't make any sense.
+
+It is used as:
+```graphql
+ products(
+ filter: {
+ request_var: {eq: "value_string"}
+ }
+ }
+```
+
+**Proposed schema:**
+
+We will deprecate the `filters` return object and replace it with `aggregations`
+
+```graphql
+aggregations {
+ count
+ label
+ attribute_code
+ options {
+ count
+ label
+ value
+ }
+ }
+```
+
+
+**Example output**
+
+```graphql
+"aggregations": [
+ {
+ "count": 2,
+ "label": "Price",
+ "attribute_code": "price",
+ "options": [
+ {
+ "count": 3,
+ "label": "*-100",
+ "value": "*_100"
+ },
+ {
+ "count": 2,
+ "label": "100-*",
+ "value": "100_*"
+ }
+ ]
+ },
+ {
+ "count": 3,
+ "label": "Category",
+ "attribute_code": "category_id",
+ "options": [
+ {
+ "count": 5,
+ "label": "Category 1",
+ "value": "3"
+ },
+ {
+ "count": 1,
+ "label": "Category 1.1",
+ "value": "4"
+ },
+ {
+ "count": 1,
+ "label": "Category 1.1.2",
+ "value": "6"
+ },
+ ]
+ },
+ ],
+```
+
+Example Query:
+```graphql
+{
+ products(
+ filter: {
+ category_id: {eq:"3" }
+ mysize: {eq:"17" }
+ }
+ pageSize:10
+ currentPage:1
+ sort: {
+ name :ASC
+ }
+ ) {
+ items {
+ sku
+ name
+ }
+ aggregations {
+ count
+ label
+ attribute_code
+ options {
+ count
+ label
+ value
+ }
+ }
+ page_info {
+ current_page
+ page_size
+ total_pages
+ }
+ total_count
+ items {
+ sku
+ url_key
+ }
+ }
+}
+
+```
+
+## Changes to customAttributeMetadata query output
+
+customAttributeMetadata returns an array of attributes `[Attribute]`
+
+```graphql
+Attribute: {
+ attribute_code: String
+
+ attribute_options: [AttributeOption]
+
+ attribute_type: String
+
+ entity_type: String
+}
+```
+
+`attribute_type` only tells us the value type of the attribute (e.g. int, float, string, etc)
+
+We propose to add an additional field (`input_type`), that will explain which UI type should be used for the attribute. (e.g. multiselect, price, checkbox, etc)
+This information can then be used to determine what type of filter applies to a particular aggregation option so the client knows how to filter it.
+
diff --git a/design-documents/graph-ql/storefront-api.md b/design-documents/graph-ql/coverage/search/storefront-api.md
similarity index 100%
rename from design-documents/graph-ql/storefront-api.md
rename to design-documents/graph-ql/coverage/search/storefront-api.md
diff --git a/design-documents/graph-ql/coverage/shipping/store-pickup.graphqls b/design-documents/graph-ql/coverage/shipping/store-pickup.graphqls
new file mode 100644
index 000000000..a4db6f882
--- /dev/null
+++ b/design-documents/graph-ql/coverage/shipping/store-pickup.graphqls
@@ -0,0 +1,75 @@
+type Query {
+ pickupLocations (
+ area: AreaInput,
+ filters: PickupLocationFilterInput,
+ sort: PickupLocationSortInput,
+ pageSize: Int = 20,
+ currentPage: Int = 1,
+ productsInfo: [ProductInfoInput]
+ ): PickupLocations
+}
+
+input AreaInput {
+ # This type is added for extensibility
+ search_term: String! # Depending on the distance calculation algorithm selected in the admin, this field will require ZIP code (for offline mode) or arbitrary part of the address (for Google mode). IMPORTANT: Current mode must be exposed as part of storeConfig query and used on the client to display different hints for the input field
+ radius: Int # This field is not part of MVP and can be added later. IMPORTANT: Radius units must be exposed as part of storeConfig query and displayed on the client
+}
+
+type PickupLocations {
+ items: [PickupLocation]!
+ page_info: SearchResultPageInfo
+ total_count: Int
+}
+
+input PickupLocationFilterInput {
+ name: FilterTypeInput
+ pickup_location_code: FilterTypeInput
+ country_id: FilterTypeInput
+ postcode: FilterTypeInput
+ region: FilterTypeInput
+ region_id: FilterTypeInput
+ city: FilterTypeInput
+ street: FilterTypeInput
+}
+
+input PickupLocationSortInput {
+ name: SortEnum
+ pickup_location_code: SortEnum
+ distance: SortEnum
+ country_id: SortEnum
+ region: SortEnum
+ region_id: SortEnum
+ city: SortEnum
+ street: SortEnum
+ postcode: SortEnum
+ longitude: SortEnum
+ latitude: SortEnum
+ email: SortEnum
+ fax: SortEnum
+ phone: SortEnum
+ contact_name: SortEnum
+ description: SortEnum
+}
+
+type PickupLocation {
+ pickup_location_code: String
+ name: String! # In the admin is called Frontend Name
+ description: String! # In the admin is called Frontend Description
+ email: String
+ fax: String
+ contact_name: String
+ latitude: Float
+ longitude: Float
+ country_id: String
+ region_id: Int
+ region: String
+ city: String
+ street: String
+ postcode: String
+ phone: String
+}
+
+# Used in products assignment intersection search - select Pickup Locations which can be used to deliver all products in the request.
+input ProductInfoInput {
+ sku: String!
+}
diff --git a/design-documents/graph-ql/coverage/store-pickup.graphqls b/design-documents/graph-ql/coverage/store-pickup.graphqls
deleted file mode 100644
index 772484150..000000000
--- a/design-documents/graph-ql/coverage/store-pickup.graphqls
+++ /dev/null
@@ -1,29 +0,0 @@
-type Query {
- pickupLocations (
- filter: PickupLocationFilterInput,
- pageSize: Int = 20,
- currentPage: Int = 1
- ): PickupLocations
-}
-
-input PickupLocationFilterInput {
- # This type is added for extensibility
- search_term: String! # Depending on the distance calculation algorithm selected in the admin, this field will require ZIP code (for offline mode) or arbitrary part of the address (for Google mode). IMPORTANT: Current mode must be exposed as part of storeConfig query and used on the client to display different hints for the input field
- radius: Int # This field is not part of MVP and can be added later. IMPORTANT: Radius units must be exposed as part of storeConfig query and displayed on the client
-}
-
-type PickupLocations {
- items: [PickupLocation]!
- page_info: SearchResultPageInfo
- total_count: Int
-}
-
-type PickupLocation {
- name: String! # In the admin is called Frontend Name
- description: String! # In the admin is called Frontend Description
- country: String!
- region: String!
- city: String!
- street: String!
- postcode: String!
-}
diff --git a/design-documents/graph-ql/directives.graphqls b/design-documents/graph-ql/directives.graphqls
new file mode 100644
index 000000000..1f25bedd9
--- /dev/null
+++ b/design-documents/graph-ql/directives.graphqls
@@ -0,0 +1,39 @@
+directive @doc(description: String="") on QUERY
+ | MUTATION
+ | FIELD
+ | FRAGMENT_DEFINITION
+ | FRAGMENT_SPREAD
+ | INLINE_FRAGMENT
+ | SCHEMA
+ | SCALAR
+ | OBJECT
+ | FIELD_DEFINITION
+ | ARGUMENT_DEFINITION
+ | INTERFACE
+ | UNION
+ | ENUM
+ | ENUM_VALUE
+ | INPUT_OBJECT
+ | INPUT_FIELD_DEFINITION
+
+directive @resolver(class: String="") on QUERY
+ | MUTATION
+ | FIELD
+ | FRAGMENT_DEFINITION
+ | FRAGMENT_SPREAD
+ | INLINE_FRAGMENT
+ | SCHEMA
+ | SCALAR
+ | OBJECT
+ | FIELD_DEFINITION
+ | ARGUMENT_DEFINITION
+ | ENUM
+ | ENUM_VALUE
+ | INPUT_OBJECT
+ | INPUT_FIELD_DEFINITION
+
+directive @typeResolver(class: String="") on UNION
+ | INTERFACE
+ | OBJECT
+
+directive @cache(cacheIdentity: String="" cacheable: Boolean=true) on QUERY
diff --git a/design-documents/graph-ql/batch-resolver.md b/design-documents/graph-ql/framework/batch-resolver.md
similarity index 100%
rename from design-documents/graph-ql/batch-resolver.md
rename to design-documents/graph-ql/framework/batch-resolver.md
diff --git a/design-documents/graph-ql/id-improvement-plan.md b/design-documents/graph-ql/id-improvement-plan.md
new file mode 100644
index 000000000..b5bd00144
--- /dev/null
+++ b/design-documents/graph-ql/id-improvement-plan.md
@@ -0,0 +1,227 @@
+# ID Improvement Plan
+
+We've recently agreed on some standardization to how object identifiers are represented in Magento GraphQL schemas, which will require some changes to existing code to reach the desired state.
+
+This work _must not introduce any breaking changes_.
+
+## Approved Proposals
+
+- [Propose renaming id_v2 to something more permanent, and change type #396](https://github.com/magento/architecture/pull/396)
+- [Add document with suggestions for ID fields #395](https://github.com/magento/architecture/pull/395)
+
+Between these 2 proposals, agreement was reached on the following guidelines:
+
+- Identifier fields and arguments _must_ use the `ID` scalar type
+- Identifier fields in Object Types _must_ be non-nullable (`!`)
+- Identifier fields in Object Types _must_ have the field name `uid`
+- Arguments that accept a `uid` value (either `Query` or `Mutation`) _must_ have `uid` in the argument name_
+- All objects representing entities that _can_ be addressed by ID _must_ have a `uid` field
+- All `ID` values _must_ be unique to their type (no collisions on IDs)
+
+## Terminology
+- **Primary Identifier**: An ID owned by the current Object Type (ex: `Product.id`)
+- **Foreign Identifier**: An ID referencing another Object Type (ex: `PaymentMethodInput.code`)
+
+## Work that needs to get done
+
+### Object Types and Interfaces with a _Primary Identifier_
+
+I found 51 Objects/Interfaces in the Open-Source schema that will need these changes.
+
+#### Changes needed
+- Add a new, non-nullable field named `uid` with type `ID`
+- Resolve the `uid` type to the same value as the object's existing _primary identifier_ field
+- Deprecate the existing _primary identifier_ field, with a message directing developers to the `uid` field
+
+#### Example Change
+```diff
+interface ProductInterface {
+- id: Int
++ id: Int @deprecated(reason: "Use the 'uid' field instead")
++ uid: ID!
+}
+```
+
+### Fields with 1 or more _Foreign Identifier_ argument
+
+I found 10 arguments across 8 fields in the Open-Source schema that will need these changes.
+
+#### Changed Needed
+- For each argument that's a _foreign identifier_
+ - Add a new argument with a name of the format `{ForeignObject}_uid`, where `ForeignObject` is the name of the Object Type with the matching `uid` field
+ - Deprecate the existing argument, with a message directing developers to the new argument
+ - Update the resolver to use _either_ the new or old argument, but throw a validation error when both are used
+ - The new argument must have the same nullability as the old argument
+
+#### Example Change
+```diff
+type Query {
+- cart(cart_id: String!): Cart
++ cart(
++ cart_id: String! @deprecated(reason: "Use the 'cart_uid' argument instead")
++ cart_uid: ID! @doc(description: "ID from the Cart.uid field")
++ ): Cart
+}
+```
+
+### Input Object Types with 1 or more _Foreign Identifier_ fields
+
+I found 49 fields across 44 Input Object Types in the Open-Source schema that will need these changes.
+
+#### Changes Needed
+- For each field that's a _foreign identifier_
+ - Add a new field with a name of the format `{ForeignObject}_uid`, where `ForeignObject` is the name of the Object Type with the matching `uid` field
+ - Deprecate the existing field, with a message directing developers to the new field
+ - Update all related resolvers to use _either_ the new or old field, but throw a validation error when both are used
+ - The new field must have the same nullability as the old field
+
+#### Example Change
+```diff
+input CartItemInput {
+- sku: String!
++ sku: String! @deprecated(reason: "Use the CartItemInput.product_uid field instead")
++ product_uid: ID! @doc(description: "ID from the ProductInterface.uid field")
+}
+```
+
+## Suggested Grouping of Work
+
+Ideally, whether this work is done all at once or incrementally, we'll make sure that changes are made in groups. That is, anytime a _primary identifier_ field changes on an Object/Interface, the same changeset should include changes to all places the matching _foreign identifier_ field/argument is used.
+
+For example, when the `uid` field is added to the `Cart` type in the `Magento/QuoteGraphQl` module, all input objects and arguments that reference the old `cart_id` should be updated to include the new `cart_uid` field.
+
+### Example Breakdown of Work
+
+Click to Expand
+
+| Object/Interface | Primary Identifier Field | References |
+|-------------------|--------------------------|------------------------------------|
+| Cart | id | Mutation.mergeCarts |
+| | | Query.cart |
+| | | AddBundleProductsToCartInput |
+| | | AddConfigurableProductsToCartInput |
+| | | AddDownloadableProductsToCartInput |
+| | | AddSimpleProductsToCartInput |
+| | | AddVirtualProductsToCartInput |
+| | | ApplyCouponToCartInput |
+| | | ApplyGiftCardToCartInput |
+| | | ApplyStoreCreditToCartInput |
+| | | createEmptyCartInput |
+| | | HostedProUrlInput |
+| | | PayflowLinkTokenInput |
+| | | PayflowProResponseInput |
+| | | PayflowProTokenInput |
+| | | PaypalExpressTokenInput |
+| | | PlaceOrderInput |
+| | | RemoveCouponFromCartInput |
+| | | RemoveGiftCardFromCartInput |
+| | | RemoveItemFromCartInput |
+| | | RemoveStoreCreditFromCartInput |
+| | | SetBillingAddressOnCartInput |
+| | | SetGuestEmailOnCartInput |
+| | | SetPaymentMethodAndPlaceOrderInput |
+| | | SetPaymentMethodOnCartInput |
+| | | SetShippingAddressesOnCartInput |
+| | | SetShippingMethodsOnCartInput |
+| | | UpdateCartItemsInput |
+| | | |
+| ProductInterface | id | CartItemInput |
+| | | ProductSortInput |
+| | | SendEmailToFriendInput |
+| | | ConfigurableProductCartItemInput |
+| | | ProductFilterInput |
+| | | BundleProduct |
+| | | ConfigurableProduct |
+| | | DownloadableProduct |
+| | | GiftCardProduct |
+| | | GroupedProduct |
+| | | SimpleProduct |
+| | | VirtualProduct |
+| | | ProductAttributeFilterInput |
+| | | ConfigurableProductOptions |
+| | | CustomizableAreaOption |
+| | | BundleItem |
+| | | CustomizableAreaValue |
+| | | CustomizableCheckboxValue |
+| | | CustomizableDateOption |
+| | | CustomizableDateValue |
+| | | CustomizableDropDownValue |
+| | | CustomizableFieldOption |
+| | | CustomizableFieldValue |
+| | | CustomizableFileOption |
+| | | CustomizableFileValue |
+| | | CustomizableMultipleValue |
+| | | CustomizableRadioValue |
+| | | ProductLinks |
+| | | ProductLinksInterface |
+| | | |
+| | | |
+| CategoryInterface | id | CategoryTree |
+| | | ProductFilterInput |
+| | | CategoryFilterInput |
+| | | StoreConfig |
+| | | Breadcrumb |
+| | | ProductAttributeFilterInput |
+| | | |
+| CartItemInterface | id | ConfigurableCartItem |
+| | | DownloadableCartItem |
+| | | SimpleCartItem |
+| | | VirtualCartItem |
+| | | BundleCartItem |
+| | | CartItemUpdateInput |
+| | | RemoveItemFromCartInput |
+
+
+
+
+## Breaking Change Risks
+
+The following changes need to be avoided to ensure we're not breaking backwards compatibility:
+- The nullability of existing fields/arguments _must not change_
+- The return type of existing fields _must not change_
+- Existing fields/arguments _must not be removed_
+
+## Current State Breakdown (Open-Source)
+
+Click to Expand
+
+- 51 Object Types and Interfaces with a _primary identifier_
+ | Primary Identifier Field Name | Count |
+ |-------------------------------|-------|
+ | id | 34 |
+ | option_id | 10 |
+ | option_type_id | 4 |
+ | order_number | 1 |
+ | value_id | 1 |
+ | agreement_id | 1 |
+
+- 10 arguments across 8 different fields take a _foreign identifier_
+ | Foreign Identifier Argument Name | Count |
+ |----------------------------------|-------|
+ | id | 4 |
+ | cart_id | 1 |
+ | identifier | 1 |
+ | identifiers | 1 |
+ | source_cart_id | 1 |
+ | destination_cart_id | 1 |
+ | orderNumber | 1 |
+
+- 49 Input Object fields across 44 different Input Objects take a _foreign identifier_
+ | Foreign Identifier Field Name | Count |
+ |-------------------------------|-------|
+ | cart_id | 26 |
+ | sku | 3 |
+ | attribute_code | 2 |
+ | cart_item_id | 2 |
+ | code | 2 |
+ | customer_address_id | 2 |
+ | carrier_code | 1 |
+ | category_id | 1 |
+ | link_id | 1 |
+ | method_code | 1 |
+ | parent_sku | 1 |
+ | product_id | 1 |
+ | region_code | 1 |
+ | variant_sku | 1 |
+
+
\ No newline at end of file
diff --git a/design-documents/graph-ql/improved-caching.md b/design-documents/graph-ql/improved-caching.md
new file mode 100644
index 000000000..791f0b22a
--- /dev/null
+++ b/design-documents/graph-ql/improved-caching.md
@@ -0,0 +1,82 @@
+## Current caching
+For GraphQL, we create a full query cache, which has results stored under unique keys as described in the [dev docs](https://devdocs.magento.com/guides/v2.4/graphql/caching.html).
+
+These unique keys are a combination of several factors which define the scope of a request. Currently, this is the formula used for GraphQL:
+
+````
+[Store Header Value] + [Content-Currency Header Value]
+````
+
+All three types of cache (FPC, Varnish, and Fastly) know about these headers and use them to compute their cache keys.
+
+## The problem
+This strategy only allows for all the components of the cache key to have public values, and for a user knowing those values to not pose any security concerns.
+There is, however, what we will call "private data" which should not be exposed, or at the very least it should not be able to be reproduced/guessed easily if it is.
+
+Take Customer Group as an example: At no point in Luma do we expose Customer Group. Instead, we compute a hash using Store and Currency but also including Customer Group and a salt:
+
+````
+hash([Store] + [Currency] + [CustomerGroup] + [Salt])
+````
+
+Using a salted hash hides the Customer Group from the user but still allows it to be considered as a cache key.
+This works in Luma because we put that value into a cookie called `X-Magento-Vary`, which Luma sends with each request for the VCL code to use to do cache lookups.
+In this case it is the server that computes the cache key for Varnish/Fastly.
+Luma also stores Store and Currency in cookies, but never the value of Customer Group.
+
+PWA and GraphQL, however, use a cookieless approach.
+PWA currently knows about Store and Currency, but not about the private components of the cache key. Because of this, we explicitly bypass the cache for logged-in customers in order to hit Magento and retrieve correct results.
+Also, each time we change what is used for the cache key (such as adding Customer Group), we have to change the VCL in Fastly and Varnish.
+
+Additionally, we missed the fact that the cache node, since it uses these values as components of its own cache key, should respond with this `Vary` header for the browser cache:
+````
+Vary: Store, Content-Currency
+````
+
+The Fastly VCL code for GraphQL currently has a bug where it returns the same `Vary` header as it does for non-GraphQL requests (which use a cookie to store `X-Magento-Vary`):
+````
+Vary: Accept-Encoding, Cookie
+````
+
+All components/headers that compose the cache key should be included in the `Vary` header read by the browser cache.
+
+## The Solution
+On every request, our GraphQL framework will compute a salted and hashed cache key using the same factors as the Luma `X-Magento-Vary` cookie and return it in a header on the response.
+````
+X-Magento-Cache-Id: 85a0b196524c60eaeb7c87d1aa4708a3fb20c6a1
+````
+
+PWA will capture this header and send it back on following GraphQL requests, which the cache node will then use as the key.
+This requires a VCL change, but only once; the VCL code will not care about the method used to generate the header, so even if that changes no further updates would be required.
+
+PWA will continue to capture the header on every response, and when it makes a new request it will send the most recent value it has received.
+This way, if something happens that changes a customer's cache key such as updating their shipping address, PWA will immediately pick it up and use the correct key on the next request.
+
+A different value for `X-Magento-Cache-Id` can be sent by PWA than Magento calculates for the response, such as when a user changes their currency (which does not use a mutation).
+When this happens, we cannot cache the response under the `X-Magento-Cache-Id` that was on the request, or else incorrect results will be returned for other requests also using that initial value.
+To avoid this issue, the VCL code will compare the `X-Magento-Cache-Id` values on the request and the response and not store the result in the cache if they do not match.
+
+The cache node will also respond with a proper `Vary` header for the browser cache to use:
+````
+Vary: Store, Content-Currency, Authorization, X-Magento-Cache-Id
+````
+There is no mutation for changing Store or Currency, so they need to remain in `Vary` for the browser cache to know to use them as keys.
+Likewise, a customer logging out does not use a mutation; instead, PWA just stops sending the `Authorization` header. The browser cache needs to know that this could cause a change in result values, so `Authorization` also needs to be in `Vary`. Of note: The VCL code will not consider the full bearer token when doing a cache lookup, just whether it exists on the request at all.
+
+Like `X-Magento-Vary`, the hash calculation for the `X-Magento-Cache-Id` header will use an unpredictably random salt. To ensure this salt is consistent between requests, we will store it in the environment configuration.
+However, as this will happen as part of the first GraphQL request without a pre-existing salt, multiple attempts to update the config could occur simultaneously before the first finishes.
+To avoid issues with concurrent writes to the same file, we will use a lock when writing to `app/etc/env.php` while adding the salt.
+Generating this value automatically instead of through an admin interaction will avoid adding a step to the upgrade process and allow us to ensure the salt is sufficiently random.
+
+For built-in FPC, there will be no changes other than outputting `X-Magento-Cache-Id` and the proper `Vary`.
+
+This should account for all issues listed above except for a major possible security flaw explained below.
+
+### The Solution's major flaw
+We're caching the whole query, requests hit the caching server first, and we don't (yet) have an auth session service, nor is there a differentiator between auth token and session token. This means we can't currently validate the bearer token before the cache node checks for a hit.
+
+This is not a problem in Luma because it has blocks, and blocks marked as private aren't cached. We use separate ajax to populate those after we retrieve the page from the cache.
+Unfortunately, GraphQL doesn't have any equivalent to Luma's blocks. We could say that a node has a set of blocks (Example: `product {block { subblock }}`), but our current use cases are catalog and prices, which we could populate and not cache.
+For category permissions and shared catalogs, suddenly the whole response stops being cacheable because the products and categories might be different.
+
+We couldn't find a viable solution for this without having some high availability service that checks session token, which will only be possible after having full oauth2 protocol in 2.5.
diff --git a/design-documents/graph-ql/mutation-error-design.md b/design-documents/graph-ql/mutation-error-design.md
new file mode 100644
index 000000000..3ea6f8f28
--- /dev/null
+++ b/design-documents/graph-ql/mutation-error-design.md
@@ -0,0 +1,154 @@
+# Mutation Error Design
+
+Mutations are _the_ single way to modify application state in a GraphQL API. Mutations are where the large majority of our errors are going to happen, as a result of changing some underlying data.
+
+Today, the Magento 2 GraphQL schema does not do a great job of describing what could go wrong as a result of running a mutation. We should fix that!
+
+## Example of the problem
+
+Take this simplified schema to add an item to a shopper's cart:
+
+```graphql
+type Mutation {
+ addItemToCart(sku: String!, quantity: Float): Cart
+}
+```
+
+This schema only describes the happy path of adding an item to the cart. But some things can go wrong when adding an item to the cart, and we know what many of those things are:
+
+- Item went out of stock since the product page was loaded
+- Merchant disabled the product since the product page was loaded
+- Cart hit the max quantity allowed of item per customer. Full qty selected was not added, but some were
+- Extensions can add additional rules that would limit adding an item to the cart
+
+Now put yourself in the shoes of a UI developer: how do you know all the error states your UI needs to cover? For most of the Magento 2 mutations today, you would need run the mutation itself (or dig through PHP) to see what will be returned in the `errors` key of the GraphQL response:
+
+```js
+{
+ "data": {
+ "cart": {
+ // cart data here
+ }
+ },
+ "errors": [{
+ "message": "Item 'cool-hat' not added to cart (Out of Stock)",
+ "path": ["addItemToCart"]
+ }]
+}
+```
+
+This has some problems:
+
+1. It's unreasonable to expect a UI developer to exercise every possible input to a resolver to find the error states manually
+2. These errors are not enforced by the schema, so they're not versioned with the schema.
+3. If the UI wants to customize the message for a specific error state, they'd have to match on the exact response string, because there's no concept of error codes
+4. Internationalization becomes challenging because the error states/strings are not known ahead of time
+
+In the world of headless UIs, it's going to be critical for our errors to be discoverable, translatable, and versioned
+
+## Solution: Design Mutation errors into the schema
+
+If we move our known error states for various mutations into the schema design itself, we'll get a few benefits:
+
+1. Versioning of errors
+2. Discoverability of error states for UI devs
+
+### Example: Fixing our `addItemToCart` mutation
+
+In the previous example of `addItemToCart`, we had the following schema:
+
+```graphql
+type Mutation {
+ addItemToCart(sku: String!, quantity: Float): Cart
+}
+```
+
+Let's design some known error states directly into the API
+
+```graphql
+type Mutation {
+ addItemToCart(sku: String!, quantity: Float): AddItemToCartOutput
+}
+
+type AddItemToCartOutput {
+ # can still get the entire state of the cart post-mutation
+ cart: Cart
+ # can directly access errors that a shopper should be notified about
+ add_item_user_errors: [AddItemUserError!]!
+}
+
+type AddItemUserError {
+ # if a UI has their own text for message, they can just
+ # not use this field, but serves as a descriptive default
+ message: string!
+ type: AddItemUserErrorType!
+}
+
+# This enum is just an example. Non-exhaustive, and of course
+# not all errors we'd want a specific type for
+enum AddItemUserErrorType {
+ OUT_OF_STOCK
+ MAX_QTY_FOR_USER
+ NOT_AVAILABLE
+}
+```
+
+With this new schema, we get the following query and response:
+
+```graphql
+mutation {
+ addItemToCart(sku: "abc", quantity: 1) {
+ cart {
+ items {
+ # select item fields
+ }
+ }
+
+ add_item_user_errors {
+ message
+ type
+ }
+ }
+}
+```
+
+```js
+{
+ "data": {
+ "cart": {
+ // cart data here
+ },
+ "add_item_user_errors": [{
+ "message": "Item 'cool-hat' not added to cart (Out of Stock)",
+ "type": "OUT_OF_STOCK"
+ }]
+ }
+}
+```
+## Should we still use the `errors` key at all?
+
+Yes! _But_, we should consider the `errors` key to be of similar use to the `catch` clause when using `try/catch` in a programming language.
+
+The `errors` key should be for _exceptional_ circumstances, not for describing well-known states in an ecommerce app. It's unlikely you'd write this application code on the backend:
+
+```javascript
+try {
+ var product = addItemToCart('sku');
+ return product;
+} catch (error) {
+ if (error.code === 'OUT_OF_STOCK') {
+ //
+ } else if (error.code === 'MAX_QTY_FOR_USER') {
+ //
+ } else {
+ // something else failed, maybe the DB connection?
+ }
+}
+```
+
+Instead, you would likely have your `addItemToCart` function return something that describes the various possible results of the operation.
+
+## Open Questions
+
+1. Should we be consistent with the field name that represents these first-class errors? `user_errors` vs something like `add_to_cart_user_errors`
+2. Should there be a basic interface that mutations should implement to enforce the pattern of returning a list of user errors?
diff --git a/design-documents/graph-ql/naming-conventions.md b/design-documents/graph-ql/naming-conventions.md
new file mode 100644
index 000000000..526ce4676
--- /dev/null
+++ b/design-documents/graph-ql/naming-conventions.md
@@ -0,0 +1,86 @@
+# GraphQL Naming Conventions
+
+The following naming guidelines should be followed when adding or modifying schemas in the Magento platform.
+
+These guidelines are based on the conventions already used within the schema. Because of this, there will be places where the style diverges from more common styles found in the GraphQL community.
+
+## Object/Interface Types
+
+Object/Interface names should always be _PascalCase_.
+
+```graphql
+# PascalCase
+type TwoWords {}
+interface TwoWords {}
+```
+
+## Object/Input Field Names
+
+Object/Input field names should always be _snake_case_.
+
+```graphql
+type Query {
+ # snake_case
+ two_words: String
+}
+
+input Data {
+ # snake_case
+ two_words: String
+}
+```
+
+## Arguments
+
+Argument names should always be _camelCase_.
+
+```graphql
+type Query {
+ example(
+ # camelCase
+ twoWords: String
+ ): String
+}
+```
+
+## Top-level Fields on Query and Mutation
+
+Top-level fields on Query and Mutation should always be _camelCase_.
+
+```graphql
+type Query {
+ # camelCase
+ twoWords: TwoWords
+}
+
+type Mutation {
+ # camelCase
+ twoWords(input: TwoWordsInput!): TwoWordsOutput
+}
+```
+
+## ID Fields
+
+These rules should apply to any field assigned the scalar type `ID`, both in Object Types and Input Object Types.
+
+There are two types of `ID` fields:
+
+- **Primary Identifier** : An ID owned by the current Object Type
+- **Foreign Identifier**: An ID referencing another Object Type
+
+A _Primary Identifier_ should _always_ be given the field name `uid`.
+A _Foreign Identifier_ should _always_ be given the field name `source_object_uid`, where `source_object` is a _snake_case_ string referring to the type that owns the `ID` (either an Object or an Interface)
+
+```graphql
+type ProductInterface {
+ # Good, ProductInterface owns `uid`
+ uid: ID!
+}
+
+type ConfigurableProductOptions {
+ # Good, field refers to ID on `ProductInterface`
+ product_interface_uid: ID!
+}
+```
+
+For additional context on ID naming requirements, see [the ID Improvement Plan](https://github.com/magento/architecture/blob/master/design-documents/graph-ql/id-improvement-plan.md_)
\ No newline at end of file
diff --git a/design-documents/graph-ql/result-status.graphqls b/design-documents/graph-ql/result-status.graphqls
new file mode 100644
index 000000000..036ce1a66
--- /dev/null
+++ b/design-documents/graph-ql/result-status.graphqls
@@ -0,0 +1,16 @@
+enum BatchMutationStatus {
+ SUCCESS
+ FAILURE
+ MIXED_RESULTS
+}
+
+interface ErrorInterface {
+ message: String!
+}
+
+type NoSuchEntityUidError implements ErrorInterface {
+ uid: ID!
+}
+
+type InternalError implements ErrorInterface {
+}
diff --git a/design-documents/img/jwt-api-authorization.png b/design-documents/img/jwt-api-authorization.png
new file mode 100644
index 000000000..565812d3c
Binary files /dev/null and b/design-documents/img/jwt-api-authorization.png differ
diff --git a/design-documents/img/jwt-class-diagram.png b/design-documents/img/jwt-class-diagram.png
new file mode 100644
index 000000000..c1f7a3a7b
Binary files /dev/null and b/design-documents/img/jwt-class-diagram.png differ
diff --git a/design-documents/img/jwt-data-exchange.png b/design-documents/img/jwt-data-exchange.png
new file mode 100644
index 000000000..cf52a5933
Binary files /dev/null and b/design-documents/img/jwt-data-exchange.png differ
diff --git a/design-documents/img/jwt-data-verification.png b/design-documents/img/jwt-data-verification.png
new file mode 100644
index 000000000..af69df6f3
Binary files /dev/null and b/design-documents/img/jwt-data-verification.png differ
diff --git a/design-documents/img/jwt-user-authorization.png b/design-documents/img/jwt-user-authorization.png
new file mode 100644
index 000000000..040ca0ba4
Binary files /dev/null and b/design-documents/img/jwt-user-authorization.png differ
diff --git a/design-documents/jwt-support.md b/design-documents/jwt-support.md
new file mode 100644
index 000000000..a9d9e7571
--- /dev/null
+++ b/design-documents/jwt-support.md
@@ -0,0 +1,239 @@
+# Support of JWT authentication out-of-box
+
+## Overview
+
+JSON Web Token (JWT) is an open standard ([RFC 7519](https://tools.ietf.org/html/rfc7519)) that defines a compact and self-contained way for securely transmitting information between parties as a JSON object. This information can be verified and trusted because it is digitally signed (also, can be encrypted). JWTs can be signed using a secret (with the HMAC algorithm) or a public/private key pair using RSA or ECDSA.
+
+The main use cases of JWT usage:
+
+#### Client side sessions
+
+
+
+JWT contains user's "session" representation. After successful login, all user session data (like ID, permissions, etc.) is stored in JWT token and user's permissions and allowed operations can be verified just based on information from JWT.
+
+#### API authorization
+
+
+
+Allows for client application to make API calls to needed resource with JWT as authorization token. The resource application can verify needed permissions from JWT claims. Authorization server can be separate application or the as resource server. In first case, the resource application should retrieve the secret key from the authorization server or send JWT to the authorization server for the verification.
+
+#### Data exchange
+
+
+
+Allows to built communication between multiple servers. An authorization server shares secret keys between servers or servers can send JWT to the authorization server for the future verification.
+
+#### Data verification
+
+
+
+Allows to verify if data is received from trusted source. The diagram shows RSA keys usage for content encryption but different types of keys can be used for the data verification like octet strings, key files, X.509 Certificates, etc.
+
+The JWT structure has three main parts:
+
+ - `header` - contains information about signature verification algorithms
+ - `payload` - contains data (JWT-claims), the RFC defines a list of standard optional claims (https://tools.ietf.org/html/rfc7519#section-4.1)
+ - `signature` - is used for data verification and represented as a hash of encoded `header` and encoded payload and secret key
+
+And in general JWT looks like this: `header`.`payload`.`signature`.
+
+The JWT usage became more and more popular for authentication and data verification purposes and Magento has a lot of integrations with different 3rd party systens, we should support JWT creation/validation/parsing out-of-box. Another benefit, that JWT is language agnostic and token generaten with one programming language can be parsed with another, the only key should be shared between applications.
+
+## Solution
+
+We do not need to implement own solution for key generation, parsing tokens, encryption/decryption, etc. [There are](https://jwt.io/#libraries) multiple PHP libraries which support all needed operations and algorithms and we need to create own wrappers.
+
+[PHP JWT Framework](https://github.com/web-token/jwt-framework) - is proposed as a library because:
+ - implements all algorithms from RFC
+ - provides implementation for JWS, JWT, JWE, JWA, JWK, JSON Web Key Thumbprint, Unencoded Payload Option
+ - supports different serialization modes
+ - supports multiple compression methods
+ - full support of JSON Web Key Set
+ - has a good documentation
+ - supports detached payload, multiple signatures, nested tokens
+
+## Implementation
+
+The following diagram represents needed interfaces to unify the workflow for JWT usage.
+
+
+
+The `\Magento\Framework\Jwt\KeyGeneratorInterface` provides a possibility to use different types of key generators like: Magento deployment secret key, X.509 certificates or RSA keys.
+```php
+interface KeyGeneratorInterface
+{
+ public function generate(): Jwk;
+}
+
+class CryptKey implements KeyGeneratorInterface
+{
+ public function __construct(SecretKeyFactory $keyFactory, DeploymentConfig $deploymentConfig)
+ {
+ $this->deploymentConfig = $deploymentConfig;
+ $this->keyFactory = $keyFactory;
+ }
+
+ public function generate(): Jwk
+ {
+ $secret = (string) $this->deploymentConfig->get('crypt/key');
+ return $this->keyFactory->create($secret);
+ }
+}
+```
+
+The `\Magento\Framework\Jwt\ManagementInterface` is a base abstraction for JWT encoding/decoding/verification:
+```php
+interface ManagementInterface
+{
+ /**
+ * Generates JWT in header.payload.signature format.
+ *
+ * @param array $claims
+ * @return string
+ * @throws \Exception
+ */
+ public function encode(array $claims): string;
+
+ /**
+ * Parses JWT and returns payload.
+ *
+ * @param string $token
+ * @return array
+ */
+ public function decode(string $token): array;
+}
+```
+
+It has implementations for JWS and JWE. The default preference is JWS implementation and looks like this:
+```php
+class Management implements ManagementInterface
+{
+ public function __construct(
+ KeyGeneratorInterface $keyGenerator,
+ SerializerInterface $serializer,
+ AlgorithmFactory $algorithmFactory,
+ Json $json,
+ Manager $claimCheckerManager,
+ BuilderFactory $builderFactory
+ ) {
+ $this->keyGenerator = $keyGenerator;
+ $this->serializer = $serializer;
+ $this->algorithmFactory = $algorithmFactory;
+ $this->json = $json;
+ $this->claimCheckerManager = $claimCheckerManager;
+ $this->builderFactory = $builderFactory;
+ }
+
+ public function encode(array $claims): string
+ {
+ // as payload represented by url encode64 on json string,
+ // the same claims structure with different key's order will get different payload hash
+ ksort($claims);
+ $payload = $this->json->serialize($claims);
+
+ $jwsBuilder = $this->builderFactory->create($this->algorithmFactory->getAlgorithmManager());
+ $jws = $jwsBuilder->create()
+ ->withPayload($payload)
+ ->addSignature(
+ $this->keyGenerator->generate()->getKey(),
+ [
+ 'alg' => $this->algorithmFactory->getAlgorithmName(),
+ 'typ' => 'JWT'
+ ]
+ )
+ ->build();
+
+ return $this->serializer->serialize(new Jwt($jws));
+ }
+
+ public function decode(string $token): array
+ {
+ $jws = $this->serializer->unserialize($token)
+ ->getToken();
+
+ if (!$this->verify($jws)) {
+ throw new \InvalidArgumentException('JWT signature verification failed');
+ }
+
+ return $this->json->unserialize($jws->getPayload());
+ }
+
+ private function verify(CoreJwt $jws): bool
+ {
+ $verifier = $this->getVerifier();
+ if (!$verifier->verifyWithKey($jws, $this->keyGenerator->generate()->getKey(), 0)) {
+ return false;
+ };
+
+ $payload = $this->json->unserialize($jws->getPayload());
+ $this->claimCheckerManager->check($payload);
+
+ return true;
+ }
+}
+```
+
+### Claims validation
+
+The `\Magento\Framework\Jwt\ClaimCheckerManager` provides a possibility to validate different set of claims like issuer, token, expiration time, audience. The list of claim checkers can be provided via `di.xml` and each checker should implement `\Magento\Framework\Jwt\ClaimCheckerInterface`.
+The list of claims can be configured via `di.xml`.
+```xml
+
+
+
+ - Magento\Framework\Jwt\ClaimChecker\ExpirationTime
+
+
+ - exp
+
+
+
+```
+
+If `mandatoryClaims` argument is not specified and needed claim is not presented in the payload the check for this claim will be skipped.
+
+The following test shows how different types of claim checkers can be used for the validation:
+```php
+public function testCheck(): void
+{
+ $claims = [
+ 'iss' => 'dev',
+ 'iat' => 1561564372,
+ 'exp' => 1593100372,
+ 'aud' => 'dev',
+ 'sub' => 'test',
+ 'key' => 'value'
+ ];
+
+ /** @var ClaimCheckerManager $claimCheckerManager */
+ $claimCheckerManager = $objectManager->create(
+ ClaimCheckerManager::class,
+ [
+ 'checkers' => [
+ IssuerChecker::class,
+ ExpirationTimeChecker::class,
+ IssuedAtChecker::class
+ ]
+ ]
+ );
+
+ $checked = $claimCheckerManager->check($claims, ['iss', 'iat', 'exp']);
+ self::assertEquals(
+ [
+ 'iss' => 'dev',
+ 'iat' => 1561564372,
+ 'exp' => 1593100372,
+ ],
+ $checked
+ );
+}
+```
+
+### Custom key generators
+
+The `\Magento\Framework\Jwt\KeyGeneratorInterface` provides a possibility to create custom key generators like based on random string, API keys, etc. The default implementation provides key generators based on env `crypt/key` (`\Magento\Framework\Jwt\KeyGenerator\CryptKey`) and simple string (`\Magento\Framework\Jwt\KeyGenerator\StringKey`).
+
+## Summary
+
+The proposed functionality can be added in a patch release. The introduced interfaces can be marked as @api in the next minor release.
diff --git a/design-documents/media/catalog-images.md b/design-documents/media/catalog-images.md
new file mode 100644
index 000000000..58cf2c54a
--- /dev/null
+++ b/design-documents/media/catalog-images.md
@@ -0,0 +1,298 @@
+# Catalog Images
+
+The document provides the vision for the desired state of assets management in Magento.
+It covers currently existing managed Magento environments, or the ones envisioned in the future:
+
+* Magento Cloud with Fastly CDN
+* Magento Cloud + AEM Assets as DAM provider
+ * Including Dynamic Media with Akamai CDN
+
+Disclaimer: Other types of setup (e.g., on-prem + different CDN/DAM) can be supported in the similar fashion.
+Current document just covers the above systems in more details to not grow in size exponentially.
+There is no intention to limit usage of other types of setup.
+
+- [Terminology](#terminology)
+- [Scenarios](#scenarios)
+ * [Asset Management](#asset-management)
+ * [Assign an image to a product](#assign-an-image-to-a-product)
+ * [Display an image on product details or products list page](#display-an-image-on-product-details-or-products-list-page)
+- [Asset Transformations](#asset-transformations)
+ * [Magento: Image Transformations](#magento-image-transformations)
+ * [Fastly: Image Transformations](#fastly-image-transformations)
+ * [AEM Assets: Image Transformations](#aem-assets-image-transformations)
+- [Watermarking](#watermarking)
+ * [Magento: Watermarking](#magento-watermarking)
+ * [Fastly: Watermarking](#fastly-watermarking)
+ * [AEM Assets: Watermarking](#aem-assets-watermarking)
+- [Placeholders](#placeholders)
+ * [Magento: Placeholders](#magento-placeholders)
+ * [Fastly: Placeholders](#fastly-placeholders)
+ * [AEM Assets: Placeholders](#aem-assets-placeholders)
+- [Risks](#risks)
+ * [Sync full image URL from Magento Admin to Store Front](#sync-full-image-url-from-magento-admin-to-store-front)
+ * [Full offload of image transformations to DAM/CDN](#full-offload-of-image-transformations-to-dam-cdn)
+ * [Storefront application provides only original URL](#storefront-application-provides-only-original-url)
+- [Questions](#questions)
+- [Breaking Changes](#breaking-changes)
+
+## Terminology
+
+* **Asset** - anything that exists in a binary format and comes with the right to use.
+ * **Image**, **video** are types of assets
+* [DAM (Digital Asset Management)](https://en.wikipedia.org/wiki/Digital_asset_management) - a system responsible for asset management (store, create, update, delete, organize)
+* [CDN (Content delivery network)](https://en.wikipedia.org/wiki/Content_delivery_network) - a system responsible for content delivery. In scope of this document, for delivery of images and video.
+* **Asset delivery** - providing publicly available URL for the asset. Usually involves CDN. This is different from asset management, which focuses on the admin side of interactions with the assets, while delivery is the cliend side of it.
+* **Image transformation** - resizing, rotation, watermarking and other automated transformations on an original image.
+ * Image transformation is responsibility of either DAM or CDN. This includes resizing, rotation, watermarking and so on.
+ * Client should be able to fetch transformed images by its original URL with additional parameters
+ * Transformation parameters may differ based on the CDN/DAM providing the transformation service
+* **Magento back office (Magento Admin)** - in scope of this document, a system for products and categories management.
+ * Links assets to products and categories
+ * Provides basic functionality for images and video transformation. Long-term, should be offloaded to specialized systems (DAM, CDN)
+* **Catalog Store-Front Application** - application providing product and category information suitable for store-front client scenarios.
+ * Serves URLs of original assets.
+ * Should not be aware of asset management functionality (no knowledge about underlying integration with external DAM systems).
+ * Might have Base CDN URL. This is similar to current Base Media URL and serves the same purpose, but might exist in case both sources of assets should be supported (Magento and CDN).
+
+## Scenarios
+
+
+
+Detailed steps of the asset flow are described below.
+
+### Asset Management
+
+1. Step 1: Asset is uploaded to DAM
+2. Step 2: DAM may perform transformations
+
+### Assign an image to a product
+
+1. Step 3: Admin opens product edit page
+2. Step 4: Admin selects necessary image using asset picker UI (provided as part of DAM integration) and saves the product
+ * Image is linked to the product as provided by DAM
+ * Image path relative to DAM base URL is stored as image path
+ * Asset is assigned to the product in DAM
+3. Step 5: Asset relation is synced to Storefront service in a form of full URL to the asset, as part of product data
+ * URL to the **original** image is synced
+ * Each asset may also include specialized type: `thumbnail`, `small`, etc. The type is not related to the image size or quality and only helps the client understand where the image is supposed to be displayed
+ * The asset itself is not synced to the Storefront and is stored in its original location (in the system responsible for its management: either external DAM or Magento Back Admin)
+
+### Display an image on product details or products list page
+
+1. Step 6: User opens PDP (product details page)
+2. Step 7: PWA application loads and requests product details from GraphQL application
+3. Step 8: GraphQL application requests product details (including asset URLs) from the SF service.
+ * SF service returns full image URL of the **original** image
+5. Step 9: PWA application fetches asset from the CDN by the provided URL
+ * PWA may include transformation parameters
+6. Step 10 (optional): CDN fetches the asset from the origin, if not cached
+7. Step 11 (optional): Origin (DAM) may perform necessary transformations
+8. Step 12 (optional): CDN may perform necessary transformations
+
+## Asset Transformations
+
+Asset transformation is responsibility of either DAM or CDN, depending on the system setup.
+Both services may provide some level of transformations.
+CDN usually provides more basic transformations (resize, rotation, crop, etc), while DAM may provide more smart transformations (e.g., smart crop).
+Client application (PWA) does not care which part is performing transformations, it must only follow supported URL format when including transformation parameters.
+
+In the first phase (and until further necessary) it is assumed that client application (PWA) is responsible for knowing format of transformed image URL, and so client developer should have knowledge of which DAM/CDN it works with.
+As a more smart step, backend application (via GraphQL) can provide information necessary for URL formatting.
+
+Image transformation can be done by web server on the Magento side (e.g., Store-Front), but this looks less efficient than using a CDN.
+In the first phase, this is not going to be supported.
+This may cause performance issues on pages with many assets loaded (such as product listing), but it is assumed that production systems should use CDN with image transformation support.
+Scenario with no CDN is assumed to be a development workflow and loading unresized images is considered less critical in this situation, especially assuming [Images Upload Configuration](./img/images-upload-config.png) allows to cap image size.
+
+Note: watermarking is a special case of asset transformation. See next section for its coverage.
+
+### Magento: Image Transformations
+
+Magento supports the following transformations for images:
+
+1. resize
+2. rotate
+3. set/change quality
+4. set background
+
+See `\Magento\Catalog\Model\Product\Image` for details.
+
+### Fastly: Image Transformations
+
+Fastly provides image transformation features with [Fastly IO](https://www.fastly.com/io):
+
+1. Convert format
+2. Rotation
+3. Crop
+4. Set background color
+5. Set quality
+6. Montage (Combine up to four images into a single displayed image.)
+
+and others. See https://docs.fastly.com/en/guides/image-optimization-api for detailed supported parameters.
+
+Provided features fully cover Magento capabilities.
+
+### AEM Assets: Image Transformations
+
+AEM Assets work in integration with Dynamic Media (DM) to deliver assets, and DM provides asset transformation capabilities.
+DM uses Akamai as CDN. Does it provide additional image transformation capabilities? Are those even needed taking into account that DM provides broad range of features?
+
+1. DefaultImage - it allows the client to specify default image. Might be useful for placeholder implementation.
+2. Resize: crop, scale, size, etc
+3. Rotate and flip
+4. Quality
+5. Background color
+6. Change image format
+
+and many others.
+See [Dynamic Media Image Serving and Rendering API - Command reference](https://docs.adobe.com/content/help/en/dynamic-media-developer-resources/image-serving-api/image-serving-api/http-protocol-reference/command-reference/c-command-reference.html) for more details.
+
+In addition, [Image Presets](https://docs.adobe.com/content/help/en/experience-manager-65/assets/dynamic/managing-image-presets.html) with image adjustments can be created in advance and then requested by the client.
+
+## Watermarking
+
+Watermarking is a special case of image transformation.
+It is special in a way that the **server** decides when and which watermark to apply.
+All other transformations are initiated by the client.
+
+### Magento: Watermarking
+
+Applied during image transformations.
+See `\Magento\Catalog\Model\Product\Image` for details.
+
+### Fastly: Watermarking
+
+See "Overlay" in https://docs.fastly.com/en/guides/image-optimization-api
+
+Overlay must be specified via `x-fastly-imageopto-overlay` header rather than via a URL parameter, which allows the server control it.
+To make sure UX is acceptable, the workflow should be described in more details, taking into account Magento scopes.
+
+### AEM Assets: Watermarking
+
+Watermarking
+- [AEM Assets Watermarking](https://docs.adobe.com/content/help/en/experience-manager-65/assets/administer/watermarking.html)
+
+Scoping?
+
+## Placeholders
+
+Placeholders are used in the following cases:
+
+1. A product has no image assigned to it.
+2. An image assigned to the product is unavailable (due to delays caused by distributed architecture or accidentally)
+
+Expectations from the placeholder delivery:
+
+1. Client application can distinguish a placeholder from a real product image. This gives the client an ability to control what (if anything) should be displayed as placeholder.
+2. Placeholder image should be possible to cache on the client side and reuse in different places.
+3. If possible, the system (Magento?) should provide placeholder URL, so the client can use it if desired (not all clients may want to use admin-configured placeholders).
+
+Based on the above, Catalog service should not handle placeholders and try to provide them if product image is absent.
+Instead, product image data should be empty, and system configuration GraphQL API should provide URL for placeholders by type.
+To fully support external DAM systems, it should be possible to specify placeholder URLs in Magento system configuration, in addition to existing "upload file" option.
+Configuration API should return full URL to the placeholder when requested by the client. This can be:
+
+1. URL pointing to Magento application for default placeholder image (example: `https://static.base.url/catalog/product/placeholder.jpg`)
+2. URL pointing to uploaded placeholder (example: `https://media.base.url/catalog/product/placeholder.jpg`)
+3. URL pointing to external DAM/CDN (example: `https://external.dam.com/my-magento/catalog/product/placeholder.jpg`)
+
+The client should not assume the placeholder base URL is the same as Media Base URL.
+It is responsibility of the client application to handle situation where an image assigned to a product is absent in the storage and handle this situation gracefully.
+
+### Magento: Placeholders
+
+Magento allows to specify a placeholder image for each type of the product image (base, small, thumbnail, swatch) per store view.
+
+Magento validates whether the file is present on the disk at the moment of URL generation, and provides URL to a placeholder image if the file is absent.
+This covers two different use cases:
+
+1. The image has never been specified for the product, so there is no image available and no file is actually validated for presence.
+2. The imaghe has been assigned to the product, but then has disappeared from the storage (due to a mistake, failure or otherwise).
+
+Existing behavior limits the system to using local storage for the assets as it has a hard dependency on the local file system, and makes it difficult to fetch images from external DAM systems.
+As mentioned before, placeholder URLs should be supported.
+
+### AEM Assets: Placeholders
+
+When integrating with AEM Assets or another external DAM system:
+
+1. Content author uploads and publishes placeholder asset in the DAM system
+2. Magento admin references the published placeholder URL in Magento system configuration
+3. Client application requests the placeholder URL via Magento GraphQL API to place where a product image can't be loaded
+
+In addition, Dynamic Media supports "DefaultImage", which allows the client to specify default image.
+This can be used as another fallback path in case an image is absent.
+
+See https://docs.adobe.com/content/help/en/dynamic-media-developer-resources/image-serving-api/image-serving-api/http-protocol-reference/command-reference/c-command-reference.html
+
+## Risks
+
+This section summarizes potential issues with certain scenarios.
+
+### Sync full image URL from Magento Admin to Store Front
+
+**Risk**: Changing Base Media URL will require full resync of Catalog to update image URLs.
+**Mitigation**: Follow this procedure for changing Base Media URL:
+
+1. Setup infrastructure so that new URL redirects to the old URL if asset doesn't exist.
+2. Wait for the sync to finish.
+3. Disable/drop old URL.
+
+**Alternative**: Sync path to the assets and Base Media URL, add logic of composing full URL to the Storefront application.
+
+The decision of selecting full sync approach is based on:
+
+1. It is expected that Base Media URL is a rare event.
+2. Mitigation steps are straightforward and may be necessary anyways.
+3. Additional logic adds complexity. In this case, potential complexity is in:
+ 1. Fallback for websites -> stores -> store views
+ 2. In the future, Magento needs to be integrated with DAM. In case of integration in mixed mode (where both Magento-managed assets and DAM-managed assets are supported), the logic of composing the URL becomes even more complex as it requires Storefront to have knowledge of assets relation to products.
+
+Base on the above, it looks more reasonable to have simple logic on Storefront side than trying to avoid full resync.
+
+### Full offload of image transformations to DAM/CDN
+
+**Risks**: Systems with no CDN/DAM will get performance hit on pages with many assets (such as product listing).
+**Mitigation**: Possible options:
+
+1. Upload pre-resized thumbnail images.
+2. Utilize "Images Upload Configuration" to ensure huge images are not uploaded.
+
+In case the feature is highly requested, it can be implemented on the level of web server.
+There was PoC made for resizing imaged by Nginx.
+
+### Storefront application provides only original URL
+
+**Risks**:
+
+1. Client application may use incorrect transformed URL.
+2. Clients may need to be rewritten when switching to a different CDN/DAM.
+3. Clients may be broken when switching to a different CDN/DAM.
+
+**Mitigation**: Align client development with infrastructure of the back office.
+
+**Alternative**: Provide information necessary for forming correct transformation URL by Storefront API.
+
+Then client can rely on this information to build correct transformed URL dynamically and doesn't need to know about CDN/DAM used behind.
+Example:
+```
+transformationParams:
+ - width: w
+ - height: h
+ - quality: q
+
+url = origUrl + '?' + transformationParams[width] + '=100&' + transformationParams[height] + '=100&' + transformationParams[quality] + '=80'
+
+> https://my.dam.com/catalog/product/my/product.jpg?w=100&h=100&q=80
+```
+
+## Questions
+
+1. Do we sync full image URL to SF or provide Base CDN URL as configuration for the store?
+ 1. What do wee do with secure/unsecure URLs in case of full URL?
+
+## Breaking Changes
+
+1. Current GraphQL returns transformed images instead of originals (❗ validate this. Might be just broken URL, as it's not clear which exact transformation GraphQL provides from the entire list). Problems with this approach:
+ 1. Transformation depends on Magento theme, which is beyond GraphQL scope (GraphQL knows nothing about old Magento themes, like Luma).
+ 2. GraphQL clients can't perform or request necessary transformation, only predefined (by irrelevant Magento themes) transformations are provided.
diff --git a/design-documents/media/img/images-upload-config.png b/design-documents/media/img/images-upload-config.png
new file mode 100644
index 000000000..977805b37
Binary files /dev/null and b/design-documents/media/img/images-upload-config.png differ
diff --git a/design-documents/message-queue/first-class-queue-configuration.md b/design-documents/message-queue/first-class-queue-configuration.md
new file mode 100644
index 000000000..7d7ae1408
--- /dev/null
+++ b/design-documents/message-queue/first-class-queue-configuration.md
@@ -0,0 +1,142 @@
+# First Class Queue Configuration
+
+This proposal intends to expose a first-class "queue" object in `queue_topology.xml` of a module and provides backward compatibility considerations for prior versions of Magento before this change.
+
+## Overview
+
+The Message Broker, RabbitMQ, supports `arguments` that are defined at queue creation time that dictate certain behaviors of the resulting queue. Currently (as of v2.4.0), Magento infers what queues will be created from properties of the `exchange.binding` defined in any given module's `queue_topology.xml`.
+
+Unfortunately, the current implementation of `queue_topology.xml` does not expose a queue object to specify arguments at queue creation time, preventing utilization of the configurable behavior of RabbitMQ queues.
+
+## Use Cases
+
+- As a developer, I would like to configure my queues with additional arguments.
+- As a developer, I would like to achieve High Availability with my queueing system.
+
+### The Scenario
+
+In order to support High Availability message processing, we would like to utilize RabbitMQ's [`x-dead-letter-exchange`](https://www.rabbitmq.com/dlx.html) and [`x-message-ttl`](https://www.rabbitmq.com/ttl.html) arguments for queues. Achieving High Availability with these two arguments would require two queues:
+
+1. A primary queue that processes messages.
+2. A secondary "retry" queue with a defined `x-message-ttl` and `x-dead-letter-exchange` arguments.
+
+The first queue's consumers attempt to process its messages, and then upon failure conditions, publish "retryable" messages (via an exchange) into the "retry" queue.
+
+After the retry queue's TTL passes, the now "expired" message is then moved (**without being consumed**) as a dead-letter back to the primary queue (via the x-dead-letter-exchange).
+
+In the end, this results in messages being retryable in whatever mechanism a developer wishes, based upon whatever failure conditions a developer desires.
+
+## Design
+
+An additional object would need to be added to the `queue_topology.xml` API, called a `queue`. A `queue` supports the following properties:
+
+```txt
+name
+connection
+durable
+autoDelete
+arguments
+```
+
+These arguments are consistent with the existing arguments for exchanges and bindings and should feel very familiar to developers accustomed to the current syntax.
+
+### Matching Behavior
+
+Since there is pre-existing behavior defined for queue creation from `exchange.binding` there must be a matching mechanism that determines which mechanism of queue-creation wins.
+
+A queue is considered a "match" (and therefore overrules the prior `exchange.binding` behavior) when the first-class queues `name` and `connection` exactly match (string comparison, case-sensitive) a queue which would be created by `exchange.binding`. That is to say, the bindings -> queue generator mechanism should not attempt to create queues where the `queue` with the same name and connection exists.
+
+### Sample Configurations
+
+- [A Simple Configuration](#a-simple-configuration)
+- [First-Class Queue Priority](#first-class-queue-priority)
+- [Queue-Connection Mismatch](#queue-connection-mismatch)
+
+#### A Simple Configuration
+
+This configuration would result in a single queue: `some-queue` which is configured by the queue object.
+
+```xml
+
+
+
+
+ arg-value
+ my-dlx
+
+
+
+```
+
+#### First-Class Queue Priority
+
+When a `queue` and an existing `exchange.binding` collide via the described matching mechanism, a single queue results: `some-queue` as described in the below case.
+
+```xml
+
+
+
+
+
+
+
+ arg-value
+ my-dlx
+
+
+
+```
+
+#### Queue-Connection Mismatch
+
+If a `queue` is defined with a connection that is different from the `exchange` that would bind to it via the old queue-creation mechanism, two queues are created, one in each connection. We should likely warn in this scenario, as likely this is an unintentional behavior. A suggested message (appearing during `setup:upgrade`) is:
+
+> `some-queue` is defined in two connections: `amqp` and `db`. If this intentional, this message can be ignored.
+
+```xml
+
+
+
+
+
+
+
+ arg-value
+ my-dlx
+
+
+
+```
+
+## Backwards Compatability
+
+Backward compatibility is well-defined and should be as follows:
+
+### No Queue Defined
+
+If no `queue` is defined, the behavior is as before, a single queue is created in the defined connection.
+
+```xml
+
+
+
+
+
+
+```
+
+## Open Questions
+
+1. What about `autoDelete` differences?
+ I suspect that connection/name is a sufficient matching pair, so this case may be irrelevant.
+2. What about `durable` differences?
+ I suspect that connection/name is a sufficient matching pair, so this case may be irrelevant.
diff --git a/design-documents/storefront/Readme.md b/design-documents/storefront/Readme.md
new file mode 100644
index 000000000..25bf15c97
--- /dev/null
+++ b/design-documents/storefront/Readme.md
@@ -0,0 +1,15 @@
+# Storefront
+
+This section of documents contains architectural and design decisions related to separation of Magento Storefront as a separate application.
+
+## High-Level Vision of Application Separation
+
+
+
+Where **Domain** is application domain, such as Catalog, Customer, etc.
+See [Magento Service Isolation Vision](../service-isolation.md) for more details on domains and services.
+
+## Resources
+
+1. [Magento Service Isolation Vision](../service-isolation.md)
+2. [Catalog Storefront project overview](https://github.com/magento/catalog-storefront/wiki/Catalog-Storefront-Service)
diff --git a/design-documents/storefront/catalog/product-options-and-variants.md b/design-documents/storefront/catalog/product-options-and-variants.md
new file mode 100644
index 000000000..e928d88ff
--- /dev/null
+++ b/design-documents/storefront/catalog/product-options-and-variants.md
@@ -0,0 +1,515 @@
+
+## Document purpose
+
+Product options are a powerful instrument that allows a shopper to customize and (or) personalize a product before adding the product to the cart.
+
+Introduction of the storefront API brings us a unique opportunity to revisit the options management, address the existing limitation, and address known issues:
+
+* Reduce redundancy caused by variants matrix creation. Magento creates variant and simple product for each intersection of options and option values selected to represent the configurable product. Not all the cases in real require a product creation, so extracting the variant as an entity could reduce system load caused by the number of products.
+
+* Allow to set up a dependency between the different options of a simple product based on previously selected option values.
+Currently, simple and bundle products do not support dependencies between option values.
+An example, bundle product that represents a computer in your store may need to correlate the list of the available motherboards with the socket of the selected processor.
+
+* Support grouped B2B prices at the options level. There is no way to specify a special price for a customer group for simple, downloadable, and some of the bundled options of the product.
+
+* Although all the options which represent product variants are pretty similar and most of them have similar properties, the options not generalized in Magento. As a result, it makes the option management more complicated, and for some cases such as option displaying, causes frontend rendering logic duplication. The ultimate goal after the options API revision is to build a generalized view that should to significantly reduce the complexity of the options domain for the storefront application.
+
+### Taxonomy of the options
+
+Due to their origin and structure options segregates on two main subtypes.
+
+**Product Option Variants** & **Shopper Input Options**.
+
+* The first subtype - **Product Option Variants**, is much often used and represents product configuration, which was predefined by a merchant. And so, product customization could be described by the selection of these predefined options, which, in their turn, creates product variants, where each variant represents the selection of one or many option values.
+This document will be focused on the first subtype, Product Option Variants, it just briefly covers the reasons for the options separation, due to the intention do not overload this document with information about the domain.
+
+* Another subtype **Shopper Input Option**s represent an approach to personalizing a product before adding it to the cart by adding custom images, entering text so on. Gift Cards with customer-defined amounts could be treated as one of the cases the Shopper Input Options. These subtype of options do not provide any predefined values, it provides constraints for the input instead like, max number of symbols, a range for amount, or allowed extensions for files. Shopper Input options do not have variants, could not be associated with a product or inventory record, but may have a reference on price. Due to the excessive list of differences from product options variants, this option subtype is out of the document. The document just points out that the options segregation by the mentioned above criteria should happen, especially to respect checkout API that we released recently. Anyway, Shopper Input Options should have their own representation as a product top-level property.
+
+*Note: Most of the logic that we use to associate with Magento product types (configurable, bundle, downloadable, etc) in fact is the logic of the different options.*
+
+### Definitions
+
+* **ProductOption** - represents a product characteristic which allows editing before adding to cart.
+ProductOption has to have a label, information on how option values should be displayed,
+and is this option mandatory.
+* **ProductOptionValue**s - belong to a particular option as to a group and represent selections which allowed by the option.
+Option value could by display label, also one or may values could be pre-selected.
+* **ProductVariant** - represent the final selection of one or multiple option values that will characterize the customized product in a shopping cart.
+Depends on the business scenario, a particular product variant could be linked with an existing product,
+with a price, or an inventory record, or no be linked to any.
+Even with no entities associated with the variant, it still has great value for a catalog because the presence of the variant says that such composition of options and their values described by the variant makes sense so that it can be purchased.
+
+## Actual usages
+
+The application distinguishes two approaches to manage options:
+
+**Approach 1, "One to many"**: Multiple options selections lead a shopper to a selection of the single variation.
+
+Example: configurable products
+
+
+**Approach 2, "One to one"**: The selection of multiple options leads to multiple product variants.
+
+Examples: bundle product, customizable product, downloadable products.
+
+Both of approaches could be used together.
+
+Example: configurable product with customizable option.
+
+## Modeling Options and Variants Data Objects
+
+The great advantage of Magento 2 - modularity was not a real thing for the product options during Magento the lifetime.
+Since times of Magento 1.x option prices coupled into options, inventory records ignored even options with assigned SKUs.
+The only thing that had to play it together "Bundle" product was designed too complex to treat it as universal solution it had to become.
+
+That's why, with the storefront project, we have an opportunity to resolve it.
+
+Taking into account the Magento experience (good, bad, ugly) and solutions proposed on the market.
+I could define the set of requirements that I would want to see in a new options implementation:
+
+* Options are a predefined list of possible product customizations. This list belongs to the product exclusively; it is measurable, and limited only by the business requirements. So it always easy to return even a complete set of options for rendering PDP initially.
+
+* Variants are a unique intersection of one or may option values. The variant matrix does not belong to a product as property. The variant matrix is stored managed separately from products. The primary role of variants is to distinguish the possible intersections of options from impossible.
+The variants matrix is never meant to be returned to the storefront as is. The variants matrix will be used for filtering options allowed at the storefront after one or many options selected.
+
+### Variants, Prices & Inventory
+Variants could link the options intersection with a product, a price, or an inventory record but any of these relations is not mandatory.
+So, a variant can request data from the different domains if such data assigned.
+Such a decision brings us unseen before the level of the flexibility, since there is no difference between product price and variant price, and as the consequence variant price, could be included in B2B pricing.
+
+We do not have such behaviors either Magento 1 or 2 and as a result, the whole layer of product types such as bundle and downloadable do not support B2B prices or special prices for options.
+
+In most cases, the product variant of configurable and bundle products may represent the child product, and via versa.
+But it does not mean that two different variants can not reference the same product. For instance, two different bundle products may contain the same product, refer to the same inventory but have different prices.
+
+
+### Enhanced option values
+
+To reach a better user experience,
+the option could be extended with an image that represents it
+or info URL that provides additional information about option value.
+
+[Magento: Swatches](https://docs.magento.com/user-guide/catalog/swatches.html)
+
+[Magento: Downloadable products, options with samples](https://docs.magento.com/user-guide/catalog/product-create-downloadable.html)
+
+Both types of resources could be specified as attributes of `ProductOptionValue`.
+
+```proto
+syntax = "proto3";
+
+message Product {
+ repeated ProductOption options = 100;
+}
+
+message ProductOptionValue {
+ string id = 1;
+ string label = 2;
+ string sortOrder = 3;
+ string isDefault = 4;
+ string imageUrl = 5;
+ string infoUrl = 6;
+}
+
+message ProductOption {
+ string id = 1;
+ string label = 2;
+ string sortOrder = 3;
+ string isRequired = 4;
+ string renderType = 6;
+ repeated ProductOptionValue values = 5;
+}
+
+message ProductVariant {
+ repeated string optionValues = 1;
+ string id = 2;
+ string productId = 3;
+ string productIdentifierInPricing = 500; #*
+ string productIdentifierInInventory = 600; #*
+}
+#* to avoid unnecessary network calls the variant has to know does it have a link on another domain, type, and proper name of a field representing this link TBD.
+```
+
+
+
+### Explaining data and operations through the pseudo SQL
+
+Lets review pseudo SQL schema, which implements the picture described above.
+This is not the instruction for implementation, tables intentionally do not have all the fields of data objects.
+The example proposed to show the relations and operations that we have in the domain.
+
+```sql
+create table products (
+ object_id char(36) not null,
+ data json not null,
+ primary key (object_id)
+);
+```
+Table `products` stores registry of products.
+
+```sql
+create table product_variant_matrix (
+ value_id char(36) not null,
+ object_id char(36) not null,
+ primary key (value_id, object_id)
+);
+
+alter table product_variant_matrix
+ add foreign key fk_product_variant_matrix_value_id (value_id) references product_option_values(value_id);
+```
+
+`product_variant_matrix` - Stores the correlation between option values and variants, by using this correlation, we can say which of option combination is real.
+
+The following script models data from the picture above.
+
+```sql
+set @product_data := '
+{
+ "id": "t-shirt",
+ "options": {
+ "color": {
+ "label": "Color",
+ "values": {
+ "red": {
+ "label": "Red"
+ },
+ "green": {
+ "label": "Green"
+ }
+ }
+ },
+ "size" : {
+ "label": "Size",
+ "values": {
+ "m": {
+ "label": "M"
+ },
+ "l": {
+ "label": "L"
+ }
+ }
+ }
+ }
+}
+';
+
+insert into products (object_id, data) values ('t-shirt', @product_data);
+
+insert into product_variant_matrix (value, object_id)
+values
+ ('t-shirt:options.size.values.l', 'l-red'), ('t-shirt:options.color.values.red', 'l-red'),
+ ('t-shirt:options.size.values.m', 'm-red'), ('t-shirt:options.color.values.red', 'm-red'),
+ ('t-shirt:options.size.values.m', 'm-green'), ('t-shirt:options.color.values.green', 'm-green')
+;
+```
+
+So far, all looks pretty nice with such an approach product may return information for all available options with the single request.
+```sql
+mysql> select
+ -> json_pretty(data->>'$.options.*') as options
+ -> from products p where p.object_id = 't-shirt'\G
+*************************** 1. row ***************************
+options: [
+ {
+ "label": "Size",
+ "values": {
+ "l": {
+ "label": "L"
+ },
+ "m": {
+ "label": "M"
+ }
+ }
+ },
+ {
+ "label": "Color",
+ "values": {
+ "red": {
+ "label": "Red"
+ },
+ "green": {
+ "label": "Green"
+ }
+ }
+ }
+]
+1 row in set (0.00 sec)
+```
+
+Let's assume that we have chosen one option value from the list.
+Starting this point we can look into variants to analyze remaining options.
+From the proposed example, we have chosen "Size": "M".
+
+```sql
+mysql> select t.*
+ -> from (
+ -> select
+ -> object_id, value, count(1) over (partition by object_id) as weight
+ -> from product_variant_matrix
+ -> ) as t
+ -> where value in ('t-shirt:options.size.values.m');
++-----------+-------------------------------+--------+
+| object_id | value | weight |
++-----------+-------------------------------+--------+
+| m-green | t-shirt:options.size.values.m | 2 |
+| m-red | t-shirt:options.size.values.m | 2 |
++-----------+-------------------------------+--------+
+2 rows in set (0.00 sec)
+
+```
+
+As you may see, our selection has matched two variants.
+Both variants have weight two,
+which means that we have to match at least two values to match the whole variant,
+but we used only one,
+which means that we can request the remaining options,
+that correspond to our current selection.
+
+```sql
+mysql> select distinct value
+ -> from product_variant_matrix pvm
+ -> where pvm.object_id in ('m-green', 'm-red')
+ -> and value not in ('t-shirt:options.size.values.m');
++------------------------------------+
+| value |
++------------------------------------+
+| t-shirt:options.color.values.green |
+| t-shirt:options.color.values.red |
++------------------------------------+
+2 rows in set (0.01 sec)
+```
+
+The remaining option values could be found in values assigned to the matched variants minus values that we selected at the previous step.
+
+```sql
+mysql> select
+ -> json_pretty(
+ -> json_extract(
+ -> data,
+ -> '$.options.color.label',
+ -> '$.options.color.values.green',
+ -> '$.options.color.values.red'
+ -> )
+ -> ) as options
+ -> from products\G
+*************************** 1. row ***************************
+options: [
+ "Color",
+ {
+ "label": "Green"
+ },
+ {
+ "label": "Red"
+ }
+]
+1 row in set (0.00 sec)
+```
+
+*Note: To achieve more advanced behavior, the variants could be "uneven" inside the single product.
+ They may have different "weight".
+ For instance, you would like to track only t-shirts XL: size separately for some reason (a different price or stock). The example above focused on covering the main case scenario. Still, the approach, overall, is meant to support extending the logic of resolving option values onto a variant under the hood.*
+
+
+### Storefront API
+
+#### Import API
+
+* Product import API should accept options as a part of the Product message.
+* Because product variant matrix not more belongs to Product message, we have to design import API, which will accept ProductVariant messages.
+
+#### Read API
+
+As were mentioned previously, options belong to a product, full options list can be retrieved from a product.
+
+Variants do not expose at the storefront but help to filter options after one or several variants were selected.
+
+
+
+Such behavior was recently [approved for configurable product](https://github.com/magento/architecture/pull/394).
+And since the most complex part of designing API was done the main thing we have to do in the scope of this
+chapter - generalize the behavior to support not only configurable products.
+
+As an input our API has to accept option values which were selected by a shopper.
+With the response API has to return:
+* List of options and option values that remains avaialble.
+* List of images & videos that should be used on PDP.
+* List of products that were exactly matched by the selected options.
+```proto
+syntax = "proto3";
+
+message OptionSelectionRequest
+{
+ string storeViewId = 1;
+ repeated string values = 2;
+}
+message OptionResponse {
+ repeated ProductOption options = 1;
+ repeated MediaGallery gallery = 2;
+ repeated ProductVarinat matchedVariants = 3;
+}
+
+service OptionSearchService {
+ rpc GetOptions(OptionSelection) returns (OptionResponse);
+}
+
+```
+
+In the perfect world, variants should not appear at the presentation level. Still, the storefront lies under the presentation, so it has to provide a way to retrieve variants depends on the scenario efficiently.
+So far, we can imagine the following cases:
+* match the variants which correspond, and do not contradict, the merchant selection - such API.
+* match the variants which exactly matched with merchant selection.
+* get all variants which contain at least one of merchant selection.
+* get all variants that belong to a product. This method is code-sugar for the previous one because all the product variants could be retrieved by using the previous method in case of passing all the options values which belong to the product.
+
+```proto
+syntax = "proto3";
+productId
+message VariantResponse {
+ repeated ProductVarinat matchedVariants = 3;
+}
+
+message ProductRequest {
+ string productId = 1;
+ string storeViewId = 2;
+}
+
+service VaraintSearchService {
+ rpc GetVariantsMatch(OptionSelection) returns (VariantResponse);
+ rpc GetVariantsExactlyMatch(OptionSelection) returns (VariantResponse);
+ rpc GetVariantsInclude(OptionSelection) returns (VariantResponse);
+ rpc GetProductVariants(ProductRequest) returns (VariantResponse);
+}
+```
+
+## Proposal cross references
+
+This proposal continues the idea of product options unification and aligned with previous design decisions that were made in this area.
+
+* [Single mutation for adding products to cart](https://github.com/magento/architecture/blob/master/design-documents/graph-ql/coverage/add-items-to-cart-single-mutation.md)
+* [Configurable options selection](https://github.com/magento/architecture/blob/master/design-documents/graph-ql/coverage/catalog/configurable-options-selection.md)
+* [Gift Registry](https://github.com/magento/architecture/blob/master/design-documents/graph-ql/coverage/gift-registry.md)
+
+
+## Question & Answers
+
+### How to express two configurable products that share the same attribute set?
+
+*Comment: Although this case natively supported by Magento,
+it could be less common than we used to think since the two regular t-shorts
+from Walmart will have different sets of values for sizes and colors depends
+on the manufacturer. proof https://www.walmart.com/search/?query=tshirt*
+
+
+#### Product #1
+```json
+{
+ "id": "a82deb7a-dee7-48e7-ab34-75026e576fab",
+ "name": "Fruit of the Loom Men's Short Sleeve Assorted Crew T-Shirt",
+ "options": {
+ "color": {
+ "label": "Color",
+ "values": {
+ "red": {
+ "label": "Red"
+ },
+ "green": {
+ "label": "Green"
+ }
+ }
+ },
+ "size" : {
+ "label": "Size",
+ "values": {
+ "m": {
+ "label": "M"
+ },
+ "l": {
+ "label": "L"
+ }
+ }
+ }
+ }
+}
+```
+
+#### Product 1 variants
+```json
+[
+ {
+ "variantId": "500d0366-777f-4a45-92b6-8e5197ce9992",
+ "optionValueIds": [
+ "a82deb7a-dee7-48e7-ab34-75026e576fab:color/red", "a82deb7a-dee7-48e7-ab34-75026e576fab:size/l"
+ ],
+ "productId": "8b6be8b0-2e21-4763-806c-f383a8591d21"
+ },
+ {
+ "variantId": "b843e139-aa04-44d0-a9a7-b439a17ce941",
+ "optionValueIds": [
+ "a82deb7a-dee7-48e7-ab34-75026e576fab:color/green", "a82deb7a-dee7-48e7-ab34-75026e576fab:size/m"
+ ],
+ "productId": "96a5a8ed-7cfe-4626-be29-f60fe0bf7b33"
+ }
+]
+```
+
+#### Product 2
+```json
+{
+ "id": "747d8c9b-e5fc-437a-8263-271dd8352976",
+ "name": "George Men's Assorted Crew T-Shirt",
+ "options": {
+ "color": {
+ "label": "Color",
+ "values": {
+ "red": {
+ "label": "Red"
+ },
+ "green": {
+ "label": "Green"
+ }
+ }
+ },
+ "size" : {
+ "label": "Size",
+ "values": {
+ "m": {
+ "label": "M"
+ },
+ "l": {
+ "label": "L"
+ }
+ }
+ }
+ }
+}
+```
+
+#### Product 2 variants
+```json
+[
+ {
+ "variantId": "edbb59fb-f303-4970-9f03-889e71374a90",
+ "optionValueIds": [
+ "747d8c9b-e5fc-437a-8263-271dd8352976:color/green", "747d8c9b-e5fc-437a-8263-271dd8352976:size/l"
+ ],
+ "productId": "7f4db047-604f-41ce-8998-a015f578e023"
+ },
+ {
+ "variantId": "2c865d16-1723-49a3-8fd1-9b46613b5c12",
+ "optionValueIds": [
+ "747d8c9b-e5fc-437a-8263-271dd8352976:color/red", "747d8c9b-e5fc-437a-8263-271dd8352976:size/m"
+ ],
+ "productId": "8eab9d67-791a-4c34-bc30-8bd034856ee2"
+ }
+]
+```
+
+## How to return all variants for the product
+
+*Comment: A client should never request all the variants within a single call
+the number of such variants is unpredictable,
+and could significantly affect store performance.*
+
+@see `rpc:GetProductVariants`
+
diff --git a/design-documents/storefront/configuraiton-propagation.md b/design-documents/storefront/configuraiton-propagation.md
new file mode 100644
index 000000000..2532fa6a8
--- /dev/null
+++ b/design-documents/storefront/configuraiton-propagation.md
@@ -0,0 +1,91 @@
+# Configuration Propagation from Admin Panel to Storefront
+
+## Problem Statement
+
+Existing Magento monolith is responsible for the whole cycle of data and user workflow.
+Magento Admin Panel contains configuration settings for all areas of the application, including Admin Panel behavior, storefront UI, etc.
+
+With Storefront being a separate service (or set of services), should configuration settings in Magento Admin Panel be propagated to storefront service?
+If yes, in what way?
+
+## Configuration Propagation Guidelines
+
+This document covers general agreements for decisions about configuration propagation from Admin Panel to storefront.
+A separate design decision should be made and documented for specific use cases.
+
+There are three use cases for the configuration, based on its impact.
+
+Magento configuration is divided into three categories, based on impact on Storefront services:
+
+1. Admin configuration - impacts Admin Panel behavior
+2. Data configuration - impacts data provided by Storefront
+3. UI Configuration - impacts UI representation of Storefront data
+
+### 1. Configuration that impacts Admin Panel behavior
+
+Examples: admin ACLs that allow or deny parts of functionality for the admin user, reindex on update or by schedule.
+
+This type of configuration has nothing to do with Storefront and should not be propagated to storefront, as well as should not be exposed via export API.
+
+### 2. Configuration that impacts data provided by Storefront
+
+Example: Base Media URL is used for calculation of media image URLs returned by Storefront API.
+
+This type of configuration should be propagated to Storefront in one of the following ways:
+
+1. Pre-calculate final data on back office side and provide final result to the Storefront during synchronization. Minimize synchronization by updating only entities that have been really affected.
+ 1. Pros: simpler implementation of Storefront due to eliminated necessity for Storefront to keep knowledge about additional configuration, including data calculation algorithms.
+ 2. Cons: massive (up to full) reindexation necessary in case the configuration is changed.
+2. Move/duplicate calculation logic in the Storefront service based on original data is synced from the back office.
+ 1. Pros and cons are opposite to option 1. So this option is valuable in case of expected frequent change of configuration that impacts a lot of data.
+
+To choose the right approach in a specific case, consider the following:
+
+1. How frequently the configuration is expected to change?
+ 1. Frequent configuration changes leading every time to massive reindexation may be unacceptable.
+ 2. Configuration changes expected a few times in the store lifespan may not be worth additional complexity on the Storefront side and full reindexation may be better in this case.
+2. Is it acceptable to have significant delay in data propagation after the configuration change?
+ 1. Changing Base URL may be not a big issue, especially if a redirect can be setup. So it may be acceptable to have URLs to be fully updated in a few hours.
+ 2. Changes in prices, on the other side, may not stand long delays.
+3. Do 3rd-party systems provide similar configuration?
+ 1. If 3rd-party systems don't have equivalent configuration, how will it be populated in the Storefront service? It might be better to avoid Magento-specific concepts to simplify integrations, and instead provide indexed data to the Storefront service.
+ 1. If a configuration option is pretty common among 3rd-party systems, it may make sense to reflect it in the Storefront service.
+
+Expected consequences are described in the decision document for each case.
+For example, time for configuration propagation in case reindexation is chosen, logic duplication/complexity in case configuration is propagated to Storefront application, performance impact for Storefront read API or for synchronization.
+
+### 3. Configuration that impacts UI representation of Storefront data
+
+Example: number of products on products listing page.
+
+This kind of configuration has nothing to do with Storefront data itself, but is still necessary for the client to know how to display the data.
+
+This section describes possible implementation options.
+Both options are possible and acceptable. The correct option should be selected based on the specific use case and client requirements.
+For other considered options see [older revision](https://github.com/magento/architecture/blob/48f2db6cc9f18a50b181b6a6b76cb0dbc81722cb/design-documents/storefront/configuraiton-propagation.md) of the document.
+
+#### 3.1. Configuration is Responsibility of the Client
+
+Client application (such as PWA) is responsible for the UI configuration, either hard-coded or by means of a service.
+
+Justification: Storefront service is responsible for providing data, client applications may vary significantly and may just want to hard-code many of the options or take settings from different sources.
+Until it is confirmed by the client developers (PWA, AEM, other teams) that UI Configuration API backed by Magento system configuration is necessary, Storefront efforts should not focus on supporting such API.
+
+UI configuration is not synced from Magento Admin Panel to Storefront.
+
+#### 3.2. Configuration is Provided by Magento Back Office API
+
+Rely on current GraphQL API for providing UI Configuration.
+No additional Store Front service is created to serve such configuration.
+GraphQL entry point can proxy to the Magento Back Office GraphQL for simplicity in API usage.
+
+Two options are possible, second expands on top of the first one.
+
+Client holds knowledge about two sources (one for data and one for config) and handles requests.
+This can be the first step.
+
+
+
+GraphQL handles requests routing to either Storefront domain service (for data) or to Magento Back Office GraphQL (for UI Config).
+
+
diff --git a/design-documents/storefront/pricing.md b/design-documents/storefront/pricing.md
new file mode 100644
index 000000000..826f6d9ec
--- /dev/null
+++ b/design-documents/storefront/pricing.md
@@ -0,0 +1,355 @@
+## Problem statement
+
+The final price of the product in Magento monolith depends on multiple variables such as current customer group, current
+website, qty of items in the shopping cart and current date/time.
+Magento monolith calculates all possible permutations of prices in advance and store them in `price index`. These
+calculations are expensive and may not be done in reasonable time for large catalogs.
+
+Few problematic use cases from real merchants:
+
+- Each customer in the system has unique prices for very few products. Per-customer prices are result of a physical contract with pricing included.
+The system has 20,000 customer groups, 10,000 products, promotions are excluded from the system. Magento will generate 200,000,000xCOUNT_OF_WEBSITES records to handle all possible combinations for this merchant. For one website it will consume 17,8 GB of space for data and 22.3 GB for index(40 GB total). Due to external promotion system, prices are synchronized periodically. The synchronization process consumes a lot of resources and time and eventually space.
+
+- Customer groups are used for many things, but they are also a multiplier for prices
+The system has 56 stores, 58 customer groups, 29,000 products, most customer groups are not global and used on one website only. Existing `price index` contains 25,000,000 records. The reindex process takes more than 7 hours. Customer groups are also used for promotions, cms content, product availability, B2B company, tax status and of course pricing. The real count of the prices is 26 times smaller. Potentially, reindex process may take 16 minutes.
+
+The impact form cases describe above will be doubled(or even tripled) if we introduce a new storefront service with existing index structure inside. Thus, we need some other way to work with prices in storefront.
+
+## Glossary
+- Customer group - allocate each customer to a group and control store behaviour(catalog restrictions, pricing, discounts, B2B, CMS, payments, shipping, etc) according to which group a customer belongs to
+- Website, Store View - abstract Magento scopes - https://docs.magento.com/user-guide/configuration/scope.html . Magento modules and third-party extensions may utilize this scope as they want.
+- Base price - the initial product price. Different values could be set per website in Magento monolith.
+- Tier price - have two meanings `customer-specific price` and `volume based price`.
+- Special price - time based price for the product. Original price is strikedout in the UI. (e.g. was ~~$100.00~~, now $99.00)
+- Catalog Rule - Magento functionality that allows to apply the same discount to multiple products
+
+
+## Goals
+
+- Support up to 5,000,000 products and 15,000 customer groups
+- Establish efficient sync of prices between Magento monolith and Magento storefront
+- Reduce the size of pricing index
+- Provide reliable support for personalized prices
+
+## Solution
+
+All price dimensions aren't used exclusively for pricing:
+multiple `websites` may have the same price, but represent different web domains; `customer group` could represent the company in b2b scenarios or class of customer service; `product` may have different pricing on one website only.
+The solution is to reuse data where possible and make separation of dimensions, so `websites` or `customer groups` which are not a part of pricing will not trigger `price index` explosion.
+
+### Price books
+
+The `price book`(price list) is a new entity that holds a list of product's prices for a sub-set of catalog.
+The `price book` is a non-scoped entity, but it may hold information about linked customers, websites, etc.
+Instead of direct lookup in price index by customer_group, website and product, we will detect the customer's price book first, then we will extract two price books from the index: default and current `price book`.
+The resulting product price will be the value from current price book if it's exists, otherwise the product price will be extracted from default price book:
+
+
+
+Guidelines:
+* There is no need to resolve price book on each HTTP call. Resolved price book could be stored in JWT during "login" step
+and reused for consequent requests.
+* In order to minimize "query-time" work, customer should have exactly one resolved price book.
+
+
+### Default price book
+
+A `default price book` is a predefined system price book that contains `base prices` for __ALL__ products in the system. User-defined `price books` may contain only sub-set of products.
+Default `price book` should be used as fallback storage if the price for a specific product doesn't exist in other resolved pricebook.
+
+Other words, there is always some price for sku in `default price book`.
+
+### Synchronization with monolith
+
+One of the goals of `price books` is to speedup reindex process. Existing reindex process lives in the monolith and
+prepares the prices for luma storefront exclusively. The data produced by this indexer is useless for the new storefront,
+so old indexer should be disabled for the installation which uses the new storefront exclusively.
+
+The following diagram shows the pricing structure for a given product, website and customer group. It also shows respective
+`price books` on the storefront.
+
+
+
+- t1 - New product was created. Every product should have some base price, so new product also introduce one price in the system.
+ - Monolith fires `price_changed` event for new product. Event specifies product_id and default website scope.
+ - Storefront receives new base price and put it in the default price book
+- t2 - New price for customer group was introduced on the monolith side
+ - Monolith detects change in product price and fires `price_changed` event. Event specifies product_id, specified websites and customer group scope.
+ - Message broker checks if price book for specified customer group exists on storefront and create new price book if needed
+ - Message broker assigns product to the new price book and set appropriate price
+- t3-t7
+ - Monolith detects products matched by the rule and fire `price_changed` events for those products. Event includes information about affected customer groups and websites. "Product by rule" matches are stored in cache for the later use.
+ - Message broker call monolith for prices of affected products
+ - Monolith calculates prices based on the cache (product matches)
+ - Message broker store prices in price book
+
+Price calculations should be done on the monolith side (see appendix for calculation details). These calculations should
+be made in runtime based on raw data from the database.
+The critical part is to have very granular price detection mechanism which should be firing `price_changed` events for
+specific websites, customer groups, products and sub-products. Example: if product price was changed for single customer
+group, only this customer group should be present in event and price calculations must be done also for single customer group only.
+
+
+
+Pros:
+- Simplicity
+- Use existing Magento EAV and catalog rule storages
+
+Cons:
+- Hard to reuse `Catalog Rules` functionality with third-party PIMs
+
+#### Other integration options
+
+__All calculations in message broker__
+
+
+
+Pros:
+- Easy to integrate `Catalog Rule` functionality with third-party PIMs
+
+Cons:
+- Stateful message broker (includes EAV data, catalog rules and matched product cache)
+- One more copy of catalog will take some system resources
+- Dependencies between asynchronous tasks in message broker. Not necessary a bad thing, but definitely introduces additional complexity
+
+__All calculations in storefront__
+
+
+
+Pros:
+- Storefront will handle `cart rule` functionality, so probably we may reuse the same services for catalog rules.
+
+Cons:
+- Storefront is designed to be lightweight. Additional functionality there may reduce performance of storefront.
+
+Notes:
+- It's possible to isolate catalog rule calculations and move only them on storefront, other calculations could be done in MB
+
+
+### Price book API
+
+```proto
+syntax = "proto3";
+
+package magento.pricing.api;
+
+// Creates a new price book
+// All fields are required.
+// Throws invalid argument error if some argument is missing
+message PriceBookInput {
+ // Client side generated price book ID
+ string id = 1;
+
+ // Price book name (e.g. "10% off on selected products")
+ string name = 2;
+
+ // Customer groups associated with price book
+ // A combination of customer group and website must be unique. Error will be returned in case when combination is
+ // already occupied by another price book.
+ repeated string customer_groups = 3;
+
+ // Websites associated with price book
+ // A combination of customer group and website must be unique. Error will be returned in case when combination is
+ // already occupied by another price book.
+ repeated string websites = 4;
+}
+
+message PriceBookDeleteInput {
+ string id = 1;
+}
+
+message AssignProductsInput {
+ message ProductPriceInput {
+ string product_id = 1;
+ float price = 2;
+ float regular_price = 3;
+ }
+ repeated ProductPriceInput prices = 1;
+}
+
+message UnassignProducts {
+ repeated string product_ids = 1;
+}
+
+message PriceBookCreateResult {
+ int32 status = 1;
+}
+
+message PriceBookDeleteResult {
+ int32 status = 1;
+}
+
+message PriceBookAssignProductsResult {
+ int32 status = 1;
+}
+
+message PriceBookUnassignProductsResult {
+ int32 status = 1;
+}
+
+
+message GetPricesInput {
+ string price_book_id = 1;
+ repeated string product_ids = 2;
+}
+
+message GetPricesOutput {
+ message ProductPrice {
+ string product_id = 2;
+ // Price without applied discounts
+ float regular_price = 3;
+ // Price with applied discounts
+ float price = 4;
+ float minimum_price = 5;
+ float maximum_price = 6;
+ }
+
+ repeated ProductPrice prices = 1;
+}
+
+
+service PriceBook {
+ rpc create(PriceBookInput) returns (PriceBookCreateResult);
+ rpc delete(PriceBookDeleteInput) returns (PriceBookDeleteResult);
+ rpc assignProducts(PriceBookInput) returns (PriceBookAssignProductsResult);
+ rpc unassignProducts(UnassignProducts) returns (PriceBookUnassignProductsResult);
+ rpc getPrices(GetPricesInput) returns (GetPricesOutput);
+}
+```
+
+### Customer tags instead of customer groups
+
+Magento monolith uses `customer groups` for customer segmentation globally. There is one-to-many relation between customer groups and customers,
+so customer could be a member of excatly one group only. However, in modern world, each customer could be a
+member of different groups based on current behavior. For example, pricing system works with wholesale and regular buyers,
+but recommendation system works with different groups of customers which are based on gender, age, ML-generated groups, etc.
+
+In order to provide more flexibility in customer segmentation, we may introduce many-to-many. Also, having `customer groups`
+which are not bound to pricing functionality make them looks like a regular tags. Thus, we may also rename them to tags:
+
+
+
+### Complex products support
+
+The `minimum prices` of complex products calculated based on variation's prices, variation's availability and variation's stock.
+Having variations as a separate products makes `minimum price` and `maximum price` dependent on products which may not
+be visible for the current group or in a current catalog. Example: configurable product contains variation #1 - price $10,
+ 2 - price $9 and 3 - price $12. Let's imagine that variation #2 is visible for people with "VIP access" only,
+then the desired `minimum price` of configurable product for basic access will be $10, for "VIP access" - $9. However, there is
+only one price book in the system, so we can hold only one value.
+
+This happens because parent product and variation are separate products which could be assigned to different access lists
+and price books. In order to mitigate this issue products should be isolated, so product options fully define complex products.
+
+The case from example above could be handled by two independent configurable products with different sets of variations
+for different access lists.
+
+Details will be provided in the separate proposal.
+
+### Prices fallback
+
+A most efficient way to work with prices on catalog scenarios is to create a prices' projection in `catalog` service. This
+way we can retrieve prices in one query together with other product's information.
+
+This approach will work fine till some limits. There are always some limits on data we can handle in any service. The
+`catalog` service is not an exception. It's designed to handle a large amount of products, but product itself is not infinite.
+The target number of EAV attributes in the product is `300`, the limit of underlying storage is `10,000` attributes per product.
+If we move closer to the limits, system may slow down and eventually fail.
+
+These limits mean that we can't implement `personalized pricing` feature by storing all prices in product documents.
+Even a large list of price books will be challenging for such approach. For such extreme use cases we may introduce a
+`prices fallback` on a service which is designed to work with the large amount of prices.
+
+
+
+Consequences:
+- Aggregation functions like facets in search service will not work properly for products with large amount of prices. Only default or limited number of prices will be available for faceting.
+- Second request obviously affects performance. A good news, performance will be affected only in queries which fetch products with large amount of prices. Other queries or slices with small products will not be affected.
+
+
+## Scenarios
+
+#### Simple
+
+- Admin sets a `base price` for the `simple` product
+- `product listing`, `PDP` and `checkout` scenarios contain the `base price`
+- Customer or guest are able to buy the product for the `base price`
+
+#### Customer specific price
+
+- Admin sets a `base price` for the `simple` product
+- Admin sets a `customer-specific price` for the same product
+- `product listing`, `PDP` and `checkout` scenarios contain `base price` for guests and `customer-specific price` for selected customer
+- Customer is able to buy the product for the `customer-specific price`
+
+#### Complex product pricing
+
+- Admin creates a `configurable` product and assign different `base prices` to variations
+- `product listing` scenario contains the minimum price of the variations
+- `PDP` scenario contains the minimum price of the variation and the price of currently selected variation
+- `product listing` scenario contains the price of currently selected variation
+- Customer or guest are able to buy the product variation for the price of selected variation
+
+#### Special prices
+
+- Admin sets a `base price` for the `simple` product
+- Admin sets a `special price` for the same product for the current date
+- `product listing`, `PDP` and `checkout` scenarios contain `special price`
+- Customers and guests are able to buy the product for the `special price`
+
+## Appendix
+
+### Price calculation algorithm for single website and customer group
+```php
+getSpecialPriceFrom >= $time && $product->getSpecialPriceTo <= $time)
+ ? $product->getSpecialPrice
+ : 0;
+
+ $fixedPrice = min($product->getBasePrice(), $specialPrice);
+
+ $groupPrice = $product->getGroupPrice($customerGroup);
+ if ($groupPrice) {
+ $fixedPrice = ($groupPrice->getType() == 'percentage')
+ ? $product->getBasePrice() - $product->getBasePrice() / 100 * $groupPrice->getValue()
+ : $groupPrice->getValue;
+
+ }
+ return $fixedPrice;
+};
+
+
+$fixedPrices = array_map($fixedCalculator, $product->getChildren());
+$fixedPrices[] = $fixedCalculator($product);
+
+$minFixedPrice = min($fixedPrices);
+
+// DISCOUNT CALCULATIONS. WE ASSUME THAT ALL RULES ALREADY RESOLVED AND WE HAVE A LIST OF RULES PER PRODUCT
+$discountCalculator = function ($product) use ($customerGroup, $time) {
+ $discountedPrice = $product->getBasePrice();
+ foreach ($product->getRules()->sortByPriority() as $rule) {
+ if ($rule->getDateFrom >= $time && $rule->getDateTo <= $time) {
+ $discountedPrice = $rule->getDiscount()->getType() == 'percentage'
+ ? $discountedPrice - $discountedPrice / 100 * $rule->getDiscount()->getValue()
+ : $discountedPrice - $rule->getDiscount()->getValue();
+
+ if ($rule->hasStopConsequentRules) {
+ break;
+ }
+ }
+ }
+ return $discountedPrice;
+};
+
+$discountedPrices = array_map($discountCalculator, $product->getChildren());
+$discountedPrices[] = $discountCalculator($product);
+$minDiscountedPrice = min($discountedPrices);
+
+$finalPrice = min($minFixedPrice, $minDiscountedPrice);
+```
+
diff --git a/design-documents/storefront/pricing/customer-tags.png b/design-documents/storefront/pricing/customer-tags.png
new file mode 100644
index 000000000..12eed5480
Binary files /dev/null and b/design-documents/storefront/pricing/customer-tags.png differ
diff --git a/design-documents/storefront/pricing/integration-option1.png b/design-documents/storefront/pricing/integration-option1.png
new file mode 100644
index 000000000..042e7c6a1
Binary files /dev/null and b/design-documents/storefront/pricing/integration-option1.png differ
diff --git a/design-documents/storefront/pricing/integration-option2.png b/design-documents/storefront/pricing/integration-option2.png
new file mode 100644
index 000000000..2c5323cd6
Binary files /dev/null and b/design-documents/storefront/pricing/integration-option2.png differ
diff --git a/design-documents/storefront/pricing/integration-option3.png b/design-documents/storefront/pricing/integration-option3.png
new file mode 100644
index 000000000..2ec5e634b
Binary files /dev/null and b/design-documents/storefront/pricing/integration-option3.png differ
diff --git a/design-documents/storefront/pricing/pricebooks.png b/design-documents/storefront/pricing/pricebooks.png
new file mode 100644
index 000000000..c01754be6
Binary files /dev/null and b/design-documents/storefront/pricing/pricebooks.png differ
diff --git a/design-documents/storefront/pricing/pricing-fallback.png b/design-documents/storefront/pricing/pricing-fallback.png
new file mode 100644
index 000000000..22a6381ab
Binary files /dev/null and b/design-documents/storefront/pricing/pricing-fallback.png differ
diff --git a/design-documents/storefront/pricing/pricing-structure.png b/design-documents/storefront/pricing/pricing-structure.png
new file mode 100644
index 000000000..0c5d8693d
Binary files /dev/null and b/design-documents/storefront/pricing/pricing-structure.png differ
diff --git a/design-documents/testing/functional/versioning-and-backward-compatibility-policy.md b/design-documents/testing/functional/versioning-and-backward-compatibility-policy.md
index a52710d10..246a2f6c3 100644
--- a/design-documents/testing/functional/versioning-and-backward-compatibility-policy.md
+++ b/design-documents/testing/functional/versioning-and-backward-compatibility-policy.md
@@ -1,117 +1,4 @@
# Magento MFTF test versioning and backward compatibility policy
-
-## Goals and requirements
-1. Release MFTF tests as a separate magento package on repo.magento.com.
-2. Define the versioning strategy for MFTF test packages.
-3. Outline what is considered a backward incompatible change to MFTF tests.
-4. List of what should be implemented.
-
-## Backwards compatibility definition for MFTF tests
-
-When a test undergoes changes, but achieves the same testing results as before and remains compatible with potential test customizations, this is defined as a 'backwards compatible' change.
-
-Types of changes:
-
-- **Test Flow change (Test/ActionGroup)** - A backwards compatible modification of a test flow would not diminish the original set of actions in the test. Some changes may change action's sequence (behavior), but they allow any extension to achieve the same test results without changing the test extension (e.g a 'merge file').
-- **Test Entity change (Data/Section/Page/Metadata)** - Compatible modifications of entities are 1) adding new entities or 2) updating a `value` of an existing entity in a way where the test will **NOT** require updates.
-- **Test Annotation change** - Annotations can be changed without limitation and will always be considered a backward compatible change, but removing or changing a `
` annotation will be considered a backward incompatible change.
-- Changes which delete and/or rename a (Test/Action Group/Data/Metadata/Page/Section/Action)'s `id` attribute will be considered a backward incompatible change. Changing a reference to a data entity will also be considered a backward incompatible change.
-
-## Versioning policy
-
-The approach of defining what each release should include was taken from [Semantic Versioning](https://semver.org/).
-
-3-component version numbers
----------------------------
-
- X.Y.Z
- | | |
- | | +-- Backward Compatible changes (bug fixes)
- | +---- Backward Compatible changes (new features)
- +------ Backward Incompatible changes
-
-### Z release
-
-Patch version **Z** MUST be incremented if only backward compatible changes to tests are introduced.
-For instance: a fix which aims to resolve test flakiness. This can be done by updating an unreliable selector, adding a `wait` to an element, or updating a data entity value.
-
-### Y release
-
-Minor version **Y** MUST be incremented if a new, backwards compatible test or test entity is introduced.
-It MUST be incremented if any test or test entity is marked as `deprecated`.
-It MAY include patch level changes. Patch version MUST be reset to 0 when the minor version is incremented.
-
-### X release
-
-Major version **X** MUST be incremented if any backwards incompatible changes are introduced to a test or test entity.
-It MAY include minor and patch level changes. Patch and minor version MUST be reset to 0 when the major version is incremented.
-
-## Implementation tasks
-
-1. Add Semantic Version analyzer to be able automatically define the release type of the MFTF tests package.
-2. Update publication infrastructure to exclude tests from `magento2-module` package type.
-3. Introduce publication functionality for publishing `magento2-test-module` package type.
-4. Create a metapackage with test packages specifically for each Magento edition.
-
-## Version increase matrix
-
-|Entity Type|Change|Version Increase|
-|---|---|---|
-|ActionGroup|`
` added|MINOR
-| |`` removed|MAJOR
-| |`` `` added|MINOR
-| |`` `` removed|MAJOR
-| |`` `` type changed|PATCH
-| |`` `` attribute changed|PATCH
-| |`` `` with `defaultValue`added|MINOR
-| |`` `` without `defaultValue` added|MAJOR
-| |`` `` removed|MAJOR
-| |`` `` changed|MAJOR
-|Data|`` added|MINOR
-| |`` removed|MAJOR
-| |`` `` added|MINOR
-| |`` `` removed|MAJOR
-| |`` `` `- ` removed|PATCH
-| |`` `` added|MINOR
-| |`` `` removed|MAJOR
-| |`` `` added|MAJOR
-| |`` `` removed|MAJOR
-| |`` `` added|MAJOR
-| |`` `` removed|MAJOR
-|Metadata|`` added|MINOR
-| |`` removed|MAJOR
-| |`` changed|MINOR
-|Page|`` added|MINOR
-| |`` removed|MAJOR
-| |`` `` added|MINOR
-| |`` `` removed|MAJOR
-|Section|`` added|MINOR
-| |`` removed|MAJOR
-| |`` `` added|MINOR
-| |`` `` removed|MAJOR
-| |`` `` `selector` changed|PATCH
-| |`` `` `type` changed|PATCH
-| |`` `` `parameterized` changed|MAJOR
-|Test|`` added|MINOR
-| |`` removed|MAJOR
-| |`` `` added|MINOR
-| |`` `` removed|MAJOR
-| |`` `` changed|PATCH
-| |`` `` sequence changed|MAJOR
-| |`` `` type (`click`, `fillField`, etc) changed|PATCH
-| |`` `` `ref` changed|MAJOR
-| |`` (before/after) `` added|MINOR
-| |`` (before/after) `` removed|MAJOR
-| |`` (before/after) `` changed|PATCH
-| |`` (before/after) `` `ref` changed|MINOR
-| |`` (before/after) `` sequence changed|MAJOR
-| |`` (before/after) `` type (`click`, `fillField`, etc) changed|PATCH
-| |`` (before/after) `` `ref` changed|MAJOR
-| |`` `` `` added|PATCH
-| |`` `` `` changed|PATCH
-| |`` `` `` GROUP removed|MAJOR
-
----------------------------
-
- ⃰ - `` refers to any of the available [MFTF Actions](https://github.com/magento/magento2-functional-testing-framework/blob/develop/docs/test/actions.md).
+Refer to:
+https://devdocs.magento.com/guides/v2.4/extension-dev-guide/versioning/mftf-tests-codebase-changes.html