Files
amethyst/quic
Claude 9b9ede2e1e fix(quic): audit fixes for client path validation + DCID rotation
Addresses seven bugs surfaced by post-landing audit of the path
validation feature.

Spec fixes (RFC 9000 §9):
  - Bug 1: PATH_CHALLENGE was going out on the OLD DCID because the
    writer reads conn.destinationConnectionId per packet and the
    rotation only happened on PATH_RESPONSE arrival. Now rotate the
    DCID inside triggerPathMigrationLocked (abrupt-migration model
    appropriate for the "old path looks dead" trigger condition).
    Fixes the headline feature — without this the challenge cannot
    actually exercise the new path.
  - Bug 2: 3 * PTO timeout dropped the failed CID without queuing a
    RETIRE_CONNECTION_ID. The peer kept the routing entry forever.
    checkValidationTimeout now queues the failed sequence per §5.1.2.
  - Bug 3: RETIRE_CONNECTION_ID for seq 0 was silently honored. We
    have no replacement SCID to give the peer (we don't issue our
    own NEW_CONNECTION_ID frames), so the connection is unusable.
    Close with INTERNAL_ERROR instead.
  - Bug 4: triggerPathMigration had no handshake-confirmed gate;
    §9.1 forbids migration before handshake confirmation. Returns
    new PathMigrationResult.NotConnected when status != CONNECTED.

Implementation fixes:
  - Bug 5: driver was calling Clock.System.now() directly instead
    of conn.nowMillis(), breaking virtual-clock tests.
  - Bug 6: PTO threshold check ran BEFORE the consecutive-PTO
    counter increment, so threshold=2 actually required 3 PTOs.
    Increment first; threshold semantics now match the constant.
  - Bug 7: applyPeerPathResponseLocked didn't reset
    consecutivePtoCount on successful validation; the next sleep
    inherited a stale exponential-backoff multiplier even though
    the peer just proved liveness.

Code quality:
  - Rename ValidationOutcome.Validated.newConnectionIdBytes →
    connectionId; PathValidationState.Validating.newCidBytes →
    newConnectionId. The "Bytes" suffix was redundant.
  - Drop unused PathValidator(initialActiveCidSequence) parameter.
  - Drop dead coerceAtLeast(2) in pool size calculation.
  - Make pendingChallenges and pendingRetireSequences internal.
  - Fix stale KDoc references (activatePendingValidatedCid,
    forceRetireActiveIfNeeded, "retirePriorTo decreased" — none
    survived the §19.15 clamp fix).
  - PathValidator.RecordResult: drop RetirePriorToRegressed
    enum value (clamped, never returned).
  - Surface qlogObserver.onConnectionIdRetired in both the
    success and timeout paths.

Tests:
  - ClientPathMigrationTest: existing fullMigrationRoundTrip
    test now asserts DCID rotates AT challenge time, not on
    PATH_RESPONSE.
  - New retireConnectionIdForSequenceZeroClosesConnection.
  - New pathResponseSuccessResetsConsecutivePtoCount.
  - New triggerPathMigrationBeforeHandshakeReturnsNotConnected.
  - PathValidatorTest:
    validationTimeoutAfter3PtoTransitionsToFailedAndRetiresFailedCid
    now asserts the failed sequence is queued for retire.
  - retirePriorToRegressionIsRejected → renamed to
    retirePriorToRegressionIsClampedNotRejected.

All :quic:jvmTest and :nestsClient:jvmTest pass.

https://claude.ai/code/session_01PVVhSQXvw4K4oQ46FzpgaT
2026-05-08 19:37:33 +00:00
..