Change:
packet number 0, and contains a CRYPTO frame carrying the ClientHello. <del> Note that multiple QUIC packets - even of different encryption levels - may be coalesced into a single UDP datagram (see packet-coalesce), </del> <ins> Note that multiple QUIC packets - even of different packet types - can be coalesced into a single UDP datagram (see packet-coalesce), </ins> and so this handshake may consist of as few as 4 UDP datagrams, or any number more.  For instance, the server's first flight contains <del> packets from the Initial encryption level (obfuscation), the Handshake level, and "0.5-RTT data" from the server at the 1-RTT encryption level. </del> <ins> Initial packets, Handshake packets, and "0.5-RTT data" in 1-RTT packets with a short header. </ins> tls-0rtt-handshake shows an example of a connection with a 0-RTT handshake and a single packet of 0-RTT data.  Note that as described <del> in packet-numbers, the server acknowledges 0-RTT data at the 1-RTT encryption level, and the client sends 1-RTT packets in the same packet number space. </del> <ins> in packet-numbers, the server acknowledges 0-RTT data in 1-RTT packets, and the client sends 1-RTT packets in the same packet number space. </ins> 7.2.

Discussion:
This allows us to avoid treating 0-RTT specially more generally. The advice on where to send CONNECTIONCLOSE seems ornate, but it reduces to "send CONNECTIONCLOSE in every space you have keys for". The trick is that we don't want to allow application error codes to leak in Initial (edit: and Handshake) packets, so this defines an error code in the transport space that says "the application decided to close the connection". I think that this is better than relying on the proxied TLS alert code, so I changed the recommendation to use 0x15a to refer to this new APPLICATION_ERROR error code.
I've updated this one to fix the other issue as well. And yes NAME that means multiple error codes. That's a little ugly, but otherwise we have cases where CONNECTIONCLOSE is initiated by the application in a way that the peer can't consume it. We either let the connection die without CONNECTIONCLOSE, do some mapping, or we leak application state into "cleartext". I like the explicit signal.
NAME can you check the recent changes here?
In fixing , I realized that it was tricky to send an application-level close in 0-RTT because of the need to potentially send a close in Initial or Handshake packets. However, the same applies to 0.5-RTT. The client might not have 1-RTT keys when the server sends a CONNECTION_CLOSE in a short header packet. Rather than try to remap application errors onto transport errors to solve this, just let the application signal an error any time. Sure, both 0-RTT and 0.5-RTT both have in common the lack of input from a peer, so signaling an error seems unnecessary, but the restriction we have only exists because we aren't sure that the condition is valid. But it might be. Imagine that an application error condition could be triggered by TLS extensions or transport parameters. Or maybe the endpoint just doesn't want to continue. Lifting the restriction should simplify a lot of things.
I am not sure if that is the correct thing to do.

IIRC, the reason we have limited use of application CONNECTION_CLOSE to 1-RTT is because the error code is part of the application protocol, and because the role of QUIC as a transport protocol is to provide "encryption" to the application protocol. That's what we agreed late last year, see . The flip side of allowing application close to be sent using Initial packets is that every application protocol using QUIC would be required to specify the error codes that can be sent using Initial packets or not, because the error code sent using an Initial packet would be observable. I would argue that that would be a complexity.
That is an entirely reasonable objection. The question then becomes: what do we do when we have a desire to signal an application-level error, but we aren't sure that the peer can receive a packet that is amply protected? For instance, if a server accepts 0-RTT only to find that the contents are gibberish, it might signal in a 1-RTT packet with the appropriate application CONNECTIONCLOSE, but it has no guarantee that this will be readable to a client. I suspect that the right answer is to switch to a transport-level CONNECTIONCLOSE with a different error code in that case. Allowing application CONNECTION_CLOSE in 0-RTT would still make sense then, if only to keep the API surface consistent.
Agreed. FWIW, we state the following, which I think is the right advice. When an application wishes to abandon a connection during the handshake, an endpoint can send a CONNECTIONCLOSE frame (type 0x1c) with an error code of 0x15a ("usercanceled" alert; see ) No objection from me.
I agree with NAME Just use the CONNECTIONCLOSE frame with the generic usercanceled TLS alert.
The exception for CONNECTIONCLOSE in 0-RTT is just odd. We currently exclude things from 0-RTT only because they are impossible. As NAME says, "feel free to send me a PROTOCOLVIOLATION when I send CONNECTION_CLOSE in 0-RTT".
IIRC, the rationale behind forbidding use of CONNECTIONCLOSE in a 0-RTT packet was that the client should send CONNECTIONCLOSE in an Initial or Handshake packet.

I am fine with allowing use of CONNECTION_CLOSE in 0-RTT, as long as that advice is kept (or stated).
After sending CONNECTIONCLOSE, the client is supposed to enter the closing state and stops responding other than to repeat the connection close. However, the server might reject 0-RTT, in which case the server doesn't know the connection has been closed. I see the principle for including it, but I agree with NAME that we should advise clients that it's kinder to send the CONNECTIONCLOSE at other encryption levels as well / instead to ensure the server can read it.
There's already text about sending connection close in the highest encryption level the sender knows the peer can process. When 0-RTT is sent, the client doesn't know whether the server can process the packets. See text starting with "When sending CONNECTION_CLOSE, the goal is to ensure that the peer will process the frame. Generally, this means sending the frame in a packet with the highest level of packet protection to avoid the packet being discarded." in URL I think the existing text advises clients not do this, but making it illegal is silly.
The testing of that feature is going to be interesting. Besides the case of servers that just don't negotiate 0-RTT because they don't feel like it today, there is also the case of 0-RTT packets being lost. The loss is not guaranteed to be detected before the TLS handshake complete. A bad faith server could just ignore any 0-RTT packet that carries a connection close request, and do as if it was not received. I get the desire of treating 0-RTT just like 1-RTT, but the number of corner cases is going to be fun.
Let's be clear: we have a problem of this shape already. A server can sent CONNECTION_CLOSE prior to confirming that the client has the keys necessary to interpret it. It might even have good cause to do so. Ignoring signals is always possible; these signals exist largely as a courtesy, so that endpoints don't dedicate resources to connections their peer no longer wants.
To add to what NAME has pointed out, the server possibly ignoring frames contained in 0-RTT packet is not limited to CONNECTION_CLOSE.

While I have a mixed feeling about allowing most if not all 1-RTT frames in 0-RTT too, it is my understanding that that has been the principle we have agreed. I would very much prefer applying the principle everywhere rather than making decisions on a case-by-case basis.
In TLS draft, we state that all other frame types MUST only be sent in the 0-RTT and 1-RTT levels, then state that it is impossible to send some of those frames in 0-RTT (HANDSHAKEDONE, NEWTOKEN, PATHRESPONSE, RETIRECONNECTION_ID). As it seems that we think use of these frames in 0-RTT to be forbidden, I think we should be clear about that.
See also .
It seemed to me that the principle MT was espousing was that we don't forbid any frames in 0-RTT and just note in the draft the frames where some other rule made it impossible to send a conformant frame in 0-RTT.
It's not forbidden, it's impossible -- or more precisely, it's a protocol violation to send them for other reasons. HANDSHAKEDONE and NEWTOKEN frames are sent only by servers, and servers don't send 0-RTT. A client sending them at any time is forbidden. PATHRESPONSE is sent only in response to a PATHCHALLENGE; PATHCHALLENGE can only be sent in 0-RTT or 1-RTT, and servers don't send 0-RTT. Therefore, the client can't be responding to a PATHCHALLENGE while it's still sending 0-RTT. RETIRECONNECTIONID can't retire the CID of its own packet. Until the transport parameters have been parsed, the client only has one CID for the server, so it's illegal to retire it. Once the transport parameters have been parsed, in the "simple" handshake flow, the client shouldn't be sending 0-RTT. If the handshake requires an extra round-trip, is it possible to keep sending 0-RTT after having processed the Server Hello? Should this update the table in 12.4?
NAME yes, I think that is what I was saying.
Does that mean I can't send a FRAMEPARSING error?

That would complicate things quite a bit, since I'd have to build extra tracking logic e.g. for a HANDSHAKEDONE that is not sent in 1-RTT.
Why bother? The client can't send HANDSHAKE_DONE, so you only need to check the role of the endpoint when receiving it.
I think you need both pieces of logic anyway. If a peer sends HANDSHAKEDONE in a Handshake frame, that's an error regardless of role. You need to check the packet type on receipt. If a client sends HANDSHAKEDONE, that's an error regardless of packet type. You need to check the peer's role on receipt. The assertion here is that we don't need to expand the packet type check to overlap with the role check. Then we get into discussion about which of multiple overlapping errors you should use. We've done that already.
FWIW, I think is the way to go. Though to avoid confusion, I think it might be helpful to clarify what th "Packets" column means in of the transport draft. At the moment, the column is being explained as the types of packets that each frame type can appear in (URL), but we could specifically say that it reflects the rules defined in plus prohibitions stated elsewhere.
Modulo clearing the reason phrase, this looks good.