Original text:
The client-facing server advertises a sequence of ECH configurations to clients, serialized as follows. <del> The "ECHConfigs" structure contains one or more "ECHConfig" </del>  structures in decreasing order of preference.  This allows a server to support multiple versions of ECH and multiple sets of ECH parameters. server MAY include an "encrypted_client_hello" extension in its EncryptedExtensions message with the following payload: <del> An ECHConfigs structure containing one or more ECHConfig </del>  structures, in decreasing order of preference, to be used by the client in subsequent connection attempts.  These are known as the server's "retry configurations". 6.1. <del> To offer ECH, the client first chooses a suitable ECH configuration. To determine if a given "ECHConfig" is suitable, it checks that it supports the KEM algorithm identified by "ECHConfig.contents.kem_id", at least one KDF/AEAD algorithm identified by "ECHConfig.contents.cipher_suites", and the version of ECH indicated by "ECHConfig.contents.version".  Once a suitable configuration is found, the client selects the cipher suite it will use for encryption.  It MUST NOT choose a cipher suite or version not </del>  advertised by the configuration.  If no compatible configuration is found, then the client SHOULD proceed as described in grease-ech. addition to the "encrypted_client_hello" extension, the server MUST abort with an "illegal_parameter" alert. <del> First, the server collects a set of candidate ECHConfigs.  This set is determined by one of the two following methods: </del>  <del> Compare ClientECH.config_id against identifiers of known ECHConfigs and select the ones that match, if any, as candidates. </del>  <del> Collect all known ECHConfigs as candidates, with trial decryption below determining the final selection. </del>  Some uses of ECH, such as local discovery mode, may randomize the ClientECH.config_id since it can be used as a tracking vector.  In application using (D)TLS or externally configured on both sides, implementations MUST use the first method. <del> The server then iterates over all candidate ECHConfigs, attempting to decrypt the "encrypted_client_hello" extension: </del>  The server verifies that the ECHConfig supports the cipher suite indicated by the ClientECH.cipher_suite and that the version of ECH If decryption fails, the server continues to the next candidate ECHConfig.  Otherwise, the server reconstructs ClientHelloInner from EncodedClientHelloInner, as described in encoding-inner.  It then <del> stops considering candidate ECHConfigs. </del>  Upon determining the ClientHelloInner, the client-facing server then forwards the ClientHelloInner to the appropriate backend server, client-facing server forwards all other TLS messages between the client and backend server unmodified. <del> Otherwise, if all candidate ECHConfigs fail to decrypt the extension, the client-facing server MUST ignore the extension and proceed with the connection using ClientHelloOuter.  This connection proceeds as usual, except the server MUST include the "encrypted_client_hello" extension in its EncryptedExtensions with the "retry_configs" field set to one or more ECHConfig structures with up-to-date keys. Servers MAY supply multiple ECHConfig values of different versions. This allows a server to support multiple versions at once. </del>  Note that decryption failure could indicate a GREASE ECH extension (see grease-ech), so it is necessary for servers to proceed with the not possible.  See plaintext-dns for more details regarding plaintext DNS. <del> Authenticating the ECHConfigs structure naturally authenticates the </del>  included public name.  This also authenticates any retry signals from the client-facing server because the client validates the server certificate against the public name before retrying.

Reviewer comments:
See .
I left this out because I figure there may be cases where there is no "published" ECHConfigList. (Maybe the config is baked in the app?)
Ah fair, that might be too precise. I think it still makes sense for that case to have the baked-in thing be an ECHConfigList than an ECHConfig, just for consistency. (I'm envisioning that ECHConfigList is the "public API" for client ECH and your TLS library might just that one byte string as input.) How about s/published/server's/?
That'd work :)
NAME done!
LGTMThanks, Chris!

Revised text:
The client-facing server advertises a sequence of ECH configurations to clients, serialized as follows.  <ins> The "ECHConfigList" structure contains one or more "ECHConfig" </ins> structures in decreasing order of preference.  This allows a server to support multiple versions of ECH and multiple sets of ECH parameters. server MAY include an "encrypted_client_hello" extension in its EncryptedExtensions message with the following payload:  <ins> An ECHConfigList structure containing one or more ECHConfig </ins> structures, in decreasing order of preference, to be used by the client in subsequent connection attempts.  These are known as the server's "retry configurations". 6.1.  <ins> To offer ECH, the client first chooses a suitable ECHConfig from the server's ECHConfigList.  To determine if a given "ECHConfig" is suitable, it checks that it supports the KEM algorithm identified by "ECHConfig.contents.kem_id", at least one KDF/AEAD algorithm identified by "ECHConfig.contents.cipher_suites", and the version of ECH indicated by "ECHConfig.contents.version".  Once a suitable configuration is found, the client selects the cipher suite it will use for encryption.  It MUST NOT choose a cipher suite or version not </ins> advertised by the configuration.  If no compatible configuration is found, then the client SHOULD proceed as described in grease-ech. addition to the "encrypted_client_hello" extension, the server MUST abort with an "illegal_parameter" alert.  <ins> First, the server collects a set of candidate ECHConfig values.  This list is determined by one of the two following methods: </ins>  <ins> Compare ClientECH.config_id against identifiers of each known ECHConfig and select the ones that match, if any, as candidates. </ins>  <ins> Collect all known ECHConfig values as candidates, with trial decryption below determining the final selection. </ins> Some uses of ECH, such as local discovery mode, may randomize the ClientECH.config_id since it can be used as a tracking vector.  In application using (D)TLS or externally configured on both sides, implementations MUST use the first method.  <ins> The server then iterates over the candidate ECHConfig values, attempting to decrypt the "encrypted_client_hello" extension: </ins> The server verifies that the ECHConfig supports the cipher suite indicated by the ClientECH.cipher_suite and that the version of ECH If decryption fails, the server continues to the next candidate ECHConfig.  Otherwise, the server reconstructs ClientHelloInner from EncodedClientHelloInner, as described in encoding-inner.  It then  <ins> stops iterating over the candidate ECHConfig values. </ins> Upon determining the ClientHelloInner, the client-facing server then forwards the ClientHelloInner to the appropriate backend server, client-facing server forwards all other TLS messages between the client and backend server unmodified.  <ins> Otherwise, if all candidate ECHConfig values fail to decrypt the extension, the client-facing server MUST ignore the extension and proceed with the connection using ClientHelloOuter.  This connection proceeds as usual, except the server MUST include the "encrypted_client_hello" extension in its EncryptedExtensions with the "retry_configs" field set to one or more ECHConfig structures with up-to-date keys.  Servers MAY supply multiple ECHConfig values of different versions.  This allows a server to support multiple versions at once. </ins> Note that decryption failure could indicate a GREASE ECH extension (see grease-ech), so it is necessary for servers to proceed with the not possible.  See plaintext-dns for more details regarding plaintext DNS.  <ins> Authenticating the ECHConfig structure naturally authenticates the </ins> included public name.  This also authenticates any retry signals from the client-facing server because the client validates the server certificate against the public name before retrying.