Change:
idempotent action. A simplified TLS handshake with 0-RTT application data is shown in <del> tls-full.  Note that this omits the EndOfEarlyData message, which is not used in QUIC (see remove-eoed).  Likewise, neither ChangeCipherSpec nor KeyUpdate messages are used by QUIC; ChangeCipherSpec is redundant in TLS 1.3 and QUIC has defined its own key update mechanism key-update. </del> <ins> tls-full. tls-full omits the EndOfEarlyData message, which is not used in QUIC; see remove-eoed.  Likewise, neither ChangeCipherSpec nor KeyUpdate messages are used by QUIC.  ChangeCipherSpec is redundant in TLS 1.3; see compat-mode.  QUIC has its own key update mechanism; see key- update. </ins> Data is protected using a number of encryption levels:

Discussion:
Should we use a more specific error code than PROTOCOL_VIOLATION? Something in the 0x1XX range?
I couldn't see something more applicable. You could fall back to a generic TLS alert, like handshake_failure, but the error is really a QUIC one.
NAME As NAME says, our code that emits ChangeCipherSpec would check if the underlying protocol is the TLS record protocol. If that's something else, a TLS alert is generated. See URL NAME I'm not sure if I share that view. We already have certain requirements on how TLS is used, and we use TLS error codes to communicate errors (e.g., use of unexpectedmessage when a TLS Key Update message is received). FWIW, the code above in picotls sends unexpectedmessage (I can change that TLS error code for sure, I just merged it).
That seems fine, but in my view the fault was with the ClientHello, not the CCS.
I agree that the fault is with ClientHello, it's merely a coincidence that we detect that problem when trying to build particular portion of the response. In case of picotls, return of this error will be propagated to the application alongside a partially built buffer, so QUIC stacks using picotls would just send the TLS error code.
I observe that implementation of this PR is causing interop failures during tests. Some implementations have been updated, some have not been. I recently updated Picoquic to use the latest version of Picotls, and the results are mixed: Handshake with Quant and Quickly that were failing before the update are now succeeding; handshakes with Quiche that were succeeding are now failing. This is clearly a case of spec confusion, with some stacks rushing to test this PR and some sticking to the stated spec. How about clearing that confusion, updating the spec and releasing draft-28?
NAME that is the plan as soon as the chairs declare consensus.
Yep. The consensus call for this ends tomorrow and there seems to be no push back so far...
This came up in interop testing. This mode causes the TLS stack to generate a ChangeCipherSpec message, which caused our server to crash. That's a bug on our end, but it's still a problem. We need to decide whether this mode is acceptable.

Right now, we only say that it is "redundant". A prohibition this mode would be easier all around. It would avoid spending 32 bytes for both endpoints too. Unfortunately, this is a design change.
I'm not sure what this change would get us. Isn't it simpler to ignore CCS on receipt instead of requiring the receiver to check for CCS and fail the handshake?
So yes, one answer is to ignore it and move on. But we should say that and require that implementations do so. The answer I prefer is prohibition. This is wasted bytes.
If this were purely a QUIC matter, I would agree. This one is slightly trickier because it puts additional requirements on TLS stacks and not all QUIC implementers control their TLS stack. Additionally, is this a principled approach? Should we also be banning TLS padding (RFC7685) because it is similarly redundant?
Practical question: Is there anyone for whom this restriction would be onerous? Ideally there would be a day we'd be able to get rid of compatibility mode for TLS 1.3 entirely, so prohibiting it from QUIC seems like the first step. As a corollary, it seems like TLS implementations should have a knob to enable/disable it, otherwise compatibility mode becomes the de-facto only mode.
Our client has a switch that defaults to off. Apparently picotls had no switch, but NAME already .
Just to be clear, picotls has had the switch, but it has been sending a 32-byte legacysessionid even when the switch was off. That was a violation of a MUST in RFC 8446, purely implementation bug. NAME In QUIC, it is impossible to send CCS, because the CRYPTO streams of QUIC can only carry handshake messages. CSS is not a handshake message, it's a TLS record. Therefore, it is my understanding that prohibiting the client from using Compatibility Mode is an editorial clarification. PS. This comment is about forbidding use of compatibility mode on the client-side, I have some concerns about making a design change that a MUST for servers sending PROTOCOLVIOLATION in response. Please see URL
Just to close the loop (and discuss on the issue): Having a SHOULD on the server reaction is fine with me.
Sounds like design?

the very end of Section 4.2.3 of quic-http says: PRIORITY frames received by a client MUST be treated as a stream error of type HTTPUNEXPECTEDFRAME. Elsewhere, this kind of thing is a connection error. Making this a stream error seems problematic; if otherwise valid, if this goes out on the control stream a stream error may bring everything down anyway? Here is the mailing list thread: URL I believe the consensus is to make it a conn error; PR to follow. the very end of Section 4.2.3 of quic-http says: PRIORITY frames received by a client MUST be treated as a stream error of type HTTPUNEXPECTEDFRAME. Elsewhere, this kind of thing is a connection error. Making this a stream error seems problematic; if otherwise valid, if this goes out on the control stream a stream error may bring everything down anyway? Should this be a connection error, or am I missing something?