ede4bc5eab
The runner's keyupdate testcase has TESTCASE_CLIENT=keyupdate (server runs plain transfer). The runner verifies the pcap shows BOTH sides emit packets in phase 1 — pre-fix our receive-only key-update path satisfied a server-initiated rotation but not this test, because aioquic's transfer-server doesn't rotate spontaneously. Result: 0 phase-1 packets either direction, "Expected to see packets sent with key phase 1 from both client and server". QuicConnection.initiateKeyUpdate() (now public) is the send-side analogue of commitKeyUpdate: derives next-phase secrets for both directions via HKDF-Expand-Label "quic ku", installs as live (reusing old HP keys per RFC §6.1), flips currentSendKeyPhase + currentReceiveKeyPhase together. The receive side has to roll too because the peer responds in the new phase — leaving currentReceive at 0 would force feedShortHeaderPacket to take the deriveNextPhase-then-commit path on the response and orphan the keys we just installed in previousReceiveProtection. InteropClient adds an `initiateKeyUpdate` flag to runTransferTest; the keyupdate dispatch sets it true. After awaitHandshake (TLS done, 1-RTT keys derived) the flag-flow polls briefly for status=CONNECTED (HANDSHAKE_DONE arrived → handshake confirmed per RFC 9001 §6.5 prerequisite) before calling initiateKeyUpdate, then sends the GET. The GET goes out in phase 1, the server mirrors phase 1 in its response, runner is satisfied. Also added ecn, amplificationlimit, blackhole to the runTransferTest dispatch (all reuse the plain-transfer flow; the runner verifies behaviour via pcap independent of any client-side dance). aioquic phase 3 result: ✓(retry, keyupdate, blackhole), ?(resumption, zerortt, ecn — feature gaps requiring session tickets, 0-RTT, and IP-layer ECT codepoints respectively), amplificationlimit blocked by a runner-side cert-gen bug on macOS (tr LC_CTYPE=C doesn't suppress UTF-8 errors, the chainlen=9 cert inflation step fails). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>