import { describe, it, expect, vi, beforeEach } from "vitest";

// --- Mocks must be declared before imports ---
// The full E2EE protocol (announce verification, TOFU pinning, rotation-on-leave,
// timeout paths) is exercised end-to-end through the LiveKitSession facade in
// livekit-session.test.ts. This file is a focused smoke test of the extracted
// E2EEManager module surface.

const mockSetKey = vi.hoisted(() => vi.fn());

vi.mock("livekit-client", () => ({
  // vitest 4 mocks honor construct semantics — `new` needs a real function, not an arrow.
  ExternalE2EEKeyProvider: vi.fn(function () {
    return {
      setKey: mockSetKey,
      getKeys: vi.fn().mockReturnValue([]),
    };
  }),
}));

const mockKeyPair = vi.hoisted(() => ({
  publicKey: { type: "public" } as unknown as CryptoKey,
  privateKey: { type: "private" } as unknown as CryptoKey,
}));

const mockIdentityKeyPair = vi.hoisted(() => ({
  publicKey: { type: "id-public" } as unknown as CryptoKey,
  privateKey: { type: "id-private" } as unknown as CryptoKey,
}));

vi.mock("@lib/e2eeCrypto", () => ({
  generateECDHKeyPair: vi.fn(async () => mockKeyPair),
  exportPublicKey: vi.fn(async () => "bW9ja2VwaGVtZXJhbA=="),
  importPublicKey: vi.fn(async () => ({ type: "public" }) as unknown as CryptoKey),
  generateRoomKey: vi.fn(() => new Uint8Array(32)),
  roomKeyToBase64: vi.fn(() => "mock-room-key-base64"),
  wrapRoomKey: vi.fn(async () => ({ encryptedKey: "enc", iv: "iv" })),
  unwrapRoomKey: vi.fn(async () => ({ roomKey: new Uint8Array(32), epoch: 0 })),
  signEphemeralKey: vi.fn(async () => "mock-signature"),
  verifyEphemeralKeySignature: vi.fn(async () => true),
  importIdentityPublicKey: vi.fn(
    async () => ({ type: "id-public-imported" }) as unknown as CryptoKey,
  ),
  computeKeyFingerprint: vi.fn(async () => "AB12 CD34 EF56 7890"),
  computeRawKeyFingerprint: vi.fn(async () => "5E55 1234 5678 9ABC"),
}));

vi.mock("@lib/identity", () => ({
  getOrCreateIdentityKeyPair: vi.fn(async () => mockIdentityKeyPair),
  getIdentityPin: vi.fn(async () => ({ status: "unpinned" })),
  storeIdentityPin: vi.fn(async () => "stored"),
}));

vi.mock("@stores/auth.store", () => ({
  authStore: { getState: vi.fn(() => ({ user: { id: 1 } })) },
}));

const mockMembers = vi.hoisted(() => new Map<number, { identityPublicKey: string | null }>());

vi.mock("@stores/members.store", () => ({
  membersStore: { getState: vi.fn(() => ({ members: mockMembers })) },
}));

const mockVoiceState = vi.hoisted(() => ({
  voiceUsers: new Map<number, Map<number, unknown>>(),
}));

vi.mock("@stores/voice.store", () => ({
  voiceStore: { getState: vi.fn(() => mockVoiceState) },
  setPeerVerification: vi.fn(),
  clearPeerVerification: vi.fn(),
  clearPeerVerifications: vi.fn(),
  setLocalSessionFingerprint: vi.fn(),
}));

vi.mock("@lib/logger", () => ({
  createLogger: () => ({
    debug: vi.fn(),
    info: vi.fn(),
    warn: vi.fn(),
    error: vi.fn(),
  }),
}));

// Now import
import { E2EEManager } from "../../src/lib/livekitE2EE";
import {
  setPeerVerification,
  clearPeerVerification,
  setLocalSessionFingerprint,
} from "@stores/voice.store";
import {
  unwrapRoomKey,
  roomKeyToBase64,
  wrapRoomKey,
  generateECDHKeyPair,
  generateRoomKey,
  importPublicKey,
  exportPublicKey,
  computeRawKeyFingerprint,
} from "@lib/e2eeCrypto";
import { getOrCreateIdentityKeyPair, getIdentityPin, storeIdentityPin } from "@lib/identity";
import { authStore } from "@stores/auth.store";

const PEER_ID = 42;

function createManager(ws: { send: ReturnType<typeof vi.fn> }): E2EEManager {
  return new E2EEManager({
    getWs: () => ws as never,
    getServerHost: () => "localhost:7880",
    getCurrentChannelId: () => 1,
  });
}

function sendsOfType(ws: { send: ReturnType<typeof vi.fn> }, type: string): unknown[] {
  return ws.send.mock.calls.map((c) => c[0]).filter((m: any) => m?.type === type);
}

describe("E2EEManager", () => {
  beforeEach(() => {
    vi.clearAllMocks();
    mockMembers.clear();
    mockMembers.set(PEER_ID, { identityPublicKey: "peer-identity-b64" });
    mockVoiceState.voiceUsers.clear();
  });

  it("setupKeyExchange as key holder generates the room key and sends a signed announce", async () => {
    const ws = { send: vi.fn() };
    const mgr = createManager(ws);

    const ok = await mgr.setupKeyExchange(true, 1);

    expect(ok).toBe(true);
    expect(mgr.epoch).toBe(1);
    expect(mockSetKey).toHaveBeenCalledWith("mock-room-key-base64");
    const announces = sendsOfType(ws, "voice_e2ee_announce");
    expect(announces).toHaveLength(1);
    expect((announces[0] as any).payload.signature).toBe("mock-signature");
  });

  it("queues an announce before the keypair exists and drains it on setup, sending an offer", async () => {
    const ws = { send: vi.fn() };
    const mgr = createManager(ws);

    await mgr.handleAnnounce(PEER_ID, "cGVlcg==", "sig");
    expect(mgr.pendingAnnounces).toHaveLength(1);
    expect(mgr.peerPublicKeys.has(PEER_ID)).toBe(false);

    await mgr.setupKeyExchange(true, 1);

    // Drained through the verifying receive path and stored. The room key is
    // generated BEFORE the drain, so each drained peer gets its offer right
    // here — mid-call peers never re-announce, and the only other delivery
    // is the 5-minute rotation timer, which would strand them on a dead key
    // whenever a new key holder joins an ongoing call.
    expect(mgr.pendingAnnounces).toHaveLength(0);
    expect(mgr.peerPublicKeys.has(PEER_ID)).toBe(true);
    expect(setPeerVerification).toHaveBeenCalledWith(
      expect.objectContaining({ userId: PEER_ID, status: "verified" }),
    );
    expect(sendsOfType(ws, "voice_e2ee_offer")).toHaveLength(1);

    // A repeat announce after keying (dedupe path) re-sends the room-key offer.
    await mgr.handleAnnounce(PEER_ID, "cGVlcg==", "sig");
    expect(sendsOfType(ws, "voice_e2ee_offer")).toHaveLength(2);
  });

  it("setupKeyExchange as non-key-holder resolves once the key holder's offer arrives", async () => {
    const ws = { send: vi.fn() };
    const mgr = createManager(ws);
    // Seed the peer's ECDH key so the offer sender is known.
    await mgr.setupKeyExchange(true, 1);
    mgr.clearState();
    await mgr.handleAnnounce(PEER_ID, "cGVlcg==", "sig");
    ws.send.mockClear();

    const setupPromise = mgr.setupKeyExchange(false, 1);
    // Announce goes out first so the key holder can offer immediately.
    await vi.waitFor(() => {
      expect(sendsOfType(ws, "voice_e2ee_announce").length).toBeGreaterThan(0);
    });
    await mgr.handleOffer(PEER_ID, "enc", "iv");

    await expect(setupPromise).resolves.toBe(true);
    expect(mockSetKey).toHaveBeenCalledWith("mock-room-key-base64");
  });

  it("clearState aborts a waiting key exchange so setup fails instead of hanging", async () => {
    const ws = { send: vi.fn() };
    const mgr = createManager(ws);

    const setupPromise = mgr.setupKeyExchange(false, 1);
    await vi.waitFor(() => {
      expect(sendsOfType(ws, "voice_e2ee_announce").length).toBeGreaterThan(0);
    });
    mgr.clearState();

    await expect(setupPromise).resolves.toBe(false);
    expect(mgr.epoch).toBe(0);
    expect(mgr.peerPublicKeys.size).toBe(0);
  });

  it("elects a still-connecting client when the holder leaves mid-setup, instead of stranding it", async () => {
    const ws = { send: vi.fn() };
    // The session publishes no channel id for the whole "connecting" phase,
    // which spans the entire key-exchange wait.
    const mgr = new E2EEManager({
      getWs: () => ws as never,
      getServerHost: () => "localhost:7880",
      getCurrentChannelId: () => null,
    });
    // After the old holder (PEER_ID) leaves, we (uid 1) are the only participant.
    mockVoiceState.voiceUsers.set(1, new Map([[1, {}]]));

    const setupPromise = mgr.setupKeyExchange(false, 1);
    await vi.waitFor(() => {
      expect(sendsOfType(ws, "voice_e2ee_announce").length).toBeGreaterThan(0);
    });

    // The holder leaves before offering, while we are still inside
    // setupKeyExchange. The re-election must use the channel id the exchange
    // was started with (getCurrentChannelId is still null) and must unblock
    // the wait — the offer we were waiting for will never arrive.
    await mgr.handleParticipantLeft(PEER_ID);

    await expect(setupPromise).resolves.toBe(true);
    expect(mgr.epoch).toBe(1); // became holder and generated a fresh key
    expect(mockSetKey).toHaveBeenCalledWith("mock-room-key-base64");
  });

  it("applies concurrent offers in arrival order, not completion order", async () => {
    const ws = { send: vi.fn() };
    const mgr = createManager(ws);
    await mgr.setupKeyExchange(true, 1); // establishes our keypair
    await mgr.handleAnnounce(PEER_ID, "cGVlcg==", "sig"); // known peer
    mockSetKey.mockClear();

    // The first offer's unwrap stalls (WebCrypto gives no cross-operation
    // ordering guarantee); the second resolves immediately. Delivery order
    // must still win — otherwise the receiver ends on the first (dead) key.
    let releaseFirst!: () => void;
    const firstUnwrap = new Promise<{ roomKey: Uint8Array; epoch: number | null }>((resolve) => {
      releaseFirst = () => resolve({ roomKey: new Uint8Array(32).fill(1), epoch: 0 });
    });
    vi.mocked(unwrapRoomKey)
      .mockReturnValueOnce(firstUnwrap)
      .mockResolvedValueOnce({ roomKey: new Uint8Array(32).fill(2), epoch: 0 });
    vi.mocked(roomKeyToBase64).mockImplementation((k: Uint8Array) => `key-${k[0]}`);
    try {
      const first = mgr.handleOffer(PEER_ID, "enc1", "iv1");
      const second = mgr.handleOffer(PEER_ID, "enc2", "iv2");
      releaseFirst();
      await Promise.all([first, second]);

      // The second (newer) key must be the one left applied.
      expect(mockSetKey).toHaveBeenLastCalledWith("key-2");
    } finally {
      vi.mocked(roomKeyToBase64).mockImplementation(() => "mock-room-key-base64");
    }
  });

  it("stops rotating once it accepts an offer, so a re-elected holder is not fought", async () => {
    const ws = { send: vi.fn() };
    const mgr = createManager(ws);
    // We joined first, so the server elected us key holder.
    await mgr.setupKeyExchange(true, 1);
    await mgr.handleAnnounce(PEER_ID, "cGVlcg==", "sig");

    // A lower-userID participant then joined; the server re-elected them and
    // they sent us the room key. Accepting an offer proves they are the
    // server-authoritative holder (the server gates offers on IsVoiceKeyHolder).
    await mgr.handleOffer(PEER_ID, "enc", "iv");
    const epochAfterOffer = mgr.epoch;
    ws.send.mockClear();

    // The stale rotation timer must no longer rotate: the server rejects those
    // offers with NOT_KEY_HOLDER, but only after we have already applied the new
    // key locally — leaving us deaf and mute until the real holder rotates again.
    await mgr.rotateKeyPeriodically();

    expect(sendsOfType(ws, "voice_e2ee_offer")).toHaveLength(0);
    expect(mgr.epoch).toBe(epochAfterOffer);
  });

  it("keeps peer public keys across a reconnect so a later offer can still be unwrapped", async () => {
    const ws = { send: vi.fn() };
    const mgr = createManager(ws);
    await mgr.setupKeyExchange(true, 1);
    await mgr.handleAnnounce(PEER_ID, "cGVlcg==", "sig");
    expect(mgr.peerPublicKeys.has(PEER_ID)).toBe(true);

    await mgr.reannounceForReconnect();

    // Nothing repopulates this map after an SFU-level reconnect: handleAnnounce
    // replies with an offer rather than a counter-announce, and the server
    // relays stored peer keys only on voice_join. Peers' public keys stay valid
    // when we regenerate our own pair, so dropping them only strands us on the
    // pre-reconnect key.
    expect(mgr.peerPublicKeys.has(PEER_ID)).toBe(true);

    mockSetKey.mockClear();
    await mgr.handleOffer(PEER_ID, "enc", "iv");
    expect(mockSetKey).toHaveBeenCalledWith("mock-room-key-base64");
  });

  it("rotateKeyPeriodically advances the epoch and redistributes the key to peers", async () => {
    const ws = { send: vi.fn() };
    const mgr = createManager(ws);
    await mgr.setupKeyExchange(true, 1);
    await mgr.handleAnnounce(PEER_ID, "cGVlcg==", "sig");
    ws.send.mockClear();

    await mgr.rotateKeyPeriodically();

    expect(mgr.epoch).toBe(2);
    const offers = sendsOfType(ws, "voice_e2ee_offer");
    expect(offers).toHaveLength(1);
    expect((offers[0] as any).payload.target_user_id).toBe(PEER_ID);
  });

  // ── Batch C3 findings ───────────────────────────────────────────────────

  it("[finding 1] resumes as key holder when re-elected while a prior rotation is still in flight", async () => {
    const ws = { send: vi.fn() };
    const mgr = createManager(ws);
    mockVoiceState.voiceUsers.set(1, new Map([[1, {}]])); // we (uid 1) are always lowest

    await mgr.setupKeyExchange(true, 1); // epoch 1, holder
    await mgr.handleAnnounce(PEER_ID, "cGVlcg==", "sig"); // peer key known

    // Start a periodic rotation and stall it right after the epoch bump, at
    // the keyProvider.setKey await — mirrors an in-flight become-holder
    // rotation (same _rotatingKey guard).
    let releaseRotationSetKey!: () => void;
    const stall = new Promise<void>((resolve) => {
      releaseRotationSetKey = resolve;
    });
    mockSetKey.mockImplementationOnce(() => stall);
    const rotationPromise = mgr.rotateKeyPeriodically();
    expect(mgr.rotatingKey).toBe(true);
    expect(mgr.epoch).toBe(2);

    // While that rotation is in flight, an offer from the "real" elected
    // holder arrives and stands us down (handleOfferInner clears
    // _isKeyHolder but not _rotatingKey).
    await mgr.handleOffer(PEER_ID, "enc", "iv");

    // The new holder immediately leaves — we are re-elected. This must not
    // be a silent no-op just because a rotation is still in flight.
    await mgr.handleParticipantLeft(99);

    // Let the original (stalled) rotation finish.
    releaseRotationSetKey();
    await rotationPromise;

    // The re-election's finally -> drainPendingRotationOrArmTimer must have
    // run a fresh rotation as holder: one more epoch bump and key-provider
    // update beyond the two already accounted for (initial holder setup +
    // the stalled rotation).
    expect(mgr.epoch).toBe(3);
    expect(mgr.rotatingKey).toBe(false);
  });

  it("[finding 2] marks a first-sight peer 'unverified' (not 'verified') when the identity-pin write fails", async () => {
    const ws = { send: vi.fn() };
    const mgr = createManager(ws);
    await mgr.setupKeyExchange(true, 1); // establishes our keypair
    vi.mocked(storeIdentityPin).mockResolvedValueOnce("failed");

    await mgr.handleAnnounce(PEER_ID, "cGVlcg==", "sig");

    expect(storeIdentityPin).toHaveBeenCalledWith(
      "localhost:7880",
      String(PEER_ID),
      "peer-identity-b64",
    );
    expect(setPeerVerification).toHaveBeenCalledWith(
      expect.objectContaining({ userId: PEER_ID, status: "unverified", safetyNumber: null }),
    );
    expect(setPeerVerification).not.toHaveBeenCalledWith(
      expect.objectContaining({ userId: PEER_ID, status: "verified" }),
    );
  });

  it("[finding 3] discards a stale offer even when epoch is unchanged, because the keypair belongs to a cleared session", async () => {
    const ws = { send: vi.fn() };
    const mgr = createManager(ws);

    // Session 1 (non-key-holder): our keypair + the peer's key.
    const setup1 = mgr.setupKeyExchange(false, 1);
    await vi.waitFor(() => {
      expect(sendsOfType(ws, "voice_e2ee_announce").length).toBeGreaterThan(0);
    });
    await mgr.handleAnnounce(PEER_ID, "cGVlcg==", "sig");

    // This offer's unwrap stalls — still in flight when the user leaves voice.
    let releaseUnwrap!: (v: { roomKey: Uint8Array; epoch: number | null }) => void;
    const stalledUnwrap = new Promise<{ roomKey: Uint8Array; epoch: number | null }>((resolve) => {
      releaseUnwrap = resolve;
    });
    vi.mocked(unwrapRoomKey).mockReturnValueOnce(stalledUnwrap);
    const offerPromise = mgr.handleOffer(PEER_ID, "enc-old", "iv-old");
    await vi.waitFor(() => expect(unwrapRoomKey).toHaveBeenCalled());

    // Leave voice mid-unwrap.
    mgr.clearState();
    await expect(setup1).resolves.toBe(false);

    // A brand-new voice session generates a fresh keypair (forward secrecy).
    // Epoch is 0 in both sessions (non-key-holder never bumps it), so only
    // keypair identity can distinguish session 1's stale offer from session 2.
    const newKeyPair = {
      publicKey: { type: "public-2" } as unknown as CryptoKey,
      privateKey: { type: "private-2" } as unknown as CryptoKey,
    };
    vi.mocked(generateECDHKeyPair).mockResolvedValueOnce(newKeyPair);
    await mgr.reannounceForReconnect();
    mockSetKey.mockClear();

    // The stale session-1 offer now resolves.
    releaseUnwrap({ roomKey: new Uint8Array(32).fill(9), epoch: 0 });
    await offerPromise;

    // Must be discarded: epoch alone (0 === 0) would have let it through.
    expect(mockSetKey).not.toHaveBeenCalled();
  });

  it("[finding 4] discards a stale announce-offer if the room key rotates while wrapRoomKey is in flight", async () => {
    const ws = { send: vi.fn() };
    const mgr = createManager(ws);
    await mgr.setupKeyExchange(true, 1); // holder, epoch 1

    let releaseWrap!: (v: { encryptedKey: string; iv: string }) => void;
    const stalledWrap = new Promise<{ encryptedKey: string; iv: string }>((resolve) => {
      releaseWrap = resolve;
    });
    vi.mocked(wrapRoomKey).mockReturnValueOnce(stalledWrap);

    const announcePromise = mgr.handleAnnounce(PEER_ID, "cGVlcg==", "sig");
    await vi.waitFor(() => expect(wrapRoomKey).toHaveBeenCalled());

    // A rotation completes while the announce's own wrap is still pending —
    // it sees PEER_ID already in _peerPublicKeys (stored synchronously before
    // the stalled wrap) and sends it the fresh key.
    await mgr.rotateKeyPeriodically();
    ws.send.mockClear();

    // The stale (pre-rotation) wrap now resolves.
    releaseWrap({ encryptedKey: "stale-enc", iv: "stale-iv" });
    await announcePromise;

    // Must be discarded — the peer already has the fresh key from rotation.
    expect(sendsOfType(ws, "voice_e2ee_offer")).toHaveLength(0);
  });

  it("[finding 5] retries with a fresh promise after a decrypt failure, so a second good offer still completes setup", async () => {
    const ws = { send: vi.fn() };
    const mgr = createManager(ws);

    vi.mocked(unwrapRoomKey).mockRejectedValueOnce(new Error("bad offer"));

    const setupPromise = mgr.setupKeyExchange(false, 1);
    await vi.waitFor(() => {
      expect(sendsOfType(ws, "voice_e2ee_announce").length).toBeGreaterThan(0);
    });
    await mgr.handleAnnounce(PEER_ID, "cGVlcg==", "sig");

    // A corrupt/undecryptable offer arrives — handleOfferInner's catch
    // rejects the wait.
    await mgr.handleOffer(PEER_ID, "enc-bad", "iv-bad");

    // The retry must re-announce and give a FRESH window — a second, valid
    // offer must still be able to complete setup.
    await vi.waitFor(() => {
      expect(sendsOfType(ws, "voice_e2ee_announce").length).toBeGreaterThan(1);
    });
    await mgr.handleOffer(PEER_ID, "enc-good", "iv-good");

    await expect(setupPromise).resolves.toBe(true);
  });

  it("[finding 6] queues (does not drop) a live announce that arrives during setup, before isKeyHolder/roomKey are ready", async () => {
    const ws = { send: vi.fn() };
    const mgr = createManager(ws);

    // Stall the identity-key load inside buildAnnouncePayload so a "live"
    // handleAnnounce can land while setup is still in its prefix — before
    // _isKeyHolder/_roomKey (and, with the fix, _ecdhKeyPair) are ready.
    let releaseIdentity!: () => void;
    const stalledIdentity = new Promise<typeof mockIdentityKeyPair>((resolve) => {
      releaseIdentity = () => resolve(mockIdentityKeyPair);
    });
    vi.mocked(getOrCreateIdentityKeyPair).mockReturnValueOnce(stalledIdentity);

    const setupPromise = mgr.setupKeyExchange(true, 1); // becoming key holder
    await vi.waitFor(() => expect(getOrCreateIdentityKeyPair).toHaveBeenCalled());

    // A peer announces "live" (server-relayed) while our own setup is still
    // in flight.
    await mgr.handleAnnounce(PEER_ID, "cGVlcg==", "sig");

    releaseIdentity();
    await setupPromise;

    // The peer must receive a room-key offer — either queued-then-drained, or
    // handled live after isKeyHolder/roomKey became ready. It must never be
    // silently stored with no offer ever sent.
    expect(mgr.peerPublicKeys.has(PEER_ID)).toBe(true);
    expect(sendsOfType(ws, "voice_e2ee_offer")).toHaveLength(1);
  });

  // ── Batch ts:livekit-e2ee findings ──────────────────────────────────────

  it("[finding v011] keeps the mismatch block when the re-pin write fails", async () => {
    const ws = { send: vi.fn() };
    const mgr = createManager(ws);
    vi.mocked(storeIdentityPin).mockResolvedValueOnce("failed");

    const result = await mgr.rePinPeerIdentity(PEER_ID, "verified-key-b64");

    // A failed write must not report the re-pin as successful — the OLD pin
    // is still on disk, so clearing the mismatch block here would let the
    // UI claim trust was re-established when it wasn't.
    expect(result).toBe(false);
    expect(clearPeerVerification).not.toHaveBeenCalled();
  });

  it("[finding v011] clears the mismatch block when the re-pin write succeeds", async () => {
    const ws = { send: vi.fn() };
    const mgr = createManager(ws);

    const result = await mgr.rePinPeerIdentity(PEER_ID, "verified-key-b64");

    expect(result).toBe(true);
    expect(clearPeerVerification).toHaveBeenCalledWith(PEER_ID);
  });

  it("[finding v043] a superseded attempt's retry guard checks keypair ownership, not just null, so it never re-announces a dead key over a live session", async () => {
    const ws = { send: vi.fn() };
    const mgr = createManager(ws);

    const keypairA = {
      publicKey: { type: "pubA" } as unknown as CryptoKey,
      privateKey: { type: "privA" } as unknown as CryptoKey,
    };
    const keypairB = {
      publicKey: { type: "pubB" } as unknown as CryptoKey,
      privateKey: { type: "privB" } as unknown as CryptoKey,
    };
    vi.mocked(generateECDHKeyPair).mockResolvedValueOnce(keypairA).mockResolvedValueOnce(keypairB);

    // Session A: non-key-holder, waiting for the key holder's offer.
    const setupA = mgr.setupKeyExchange(false, 1);
    await vi.waitFor(() => {
      expect(sendsOfType(ws, "voice_e2ee_announce").length).toBeGreaterThan(0);
    });

    // Session A is superseded by a fresh session B (e.g. the user left and
    // rejoined) before A's 10s window elapses. B publishes its own keypair,
    // overwriting A's — a plain `=== null` check on _ecdhKeyPair can't see
    // this, since it is now non-null (B's).
    const setupB = mgr.setupKeyExchange(true, 2);
    await expect(setupB).resolves.toBe(true);

    // Re-populate the peer key B's setup wiped, so a decrypt failure can
    // reach A's still-installed rejector without an early return.
    await mgr.handleAnnounce(PEER_ID, "cGVlcg==", "sig");
    ws.send.mockClear();

    // A corrupt offer rejects A's still-pending roomKeyPromise via its
    // rejector — the only field B's (key-holder) setup never touches.
    vi.mocked(unwrapRoomKey).mockRejectedValueOnce(new Error("bad offer"));
    await mgr.handleOffer(PEER_ID, "enc-bad", "iv-bad");

    // A must give up instead of re-announcing its dead ephemeral key over
    // B's live session and reinstalling a resolver nothing will ever call.
    await expect(setupA).resolves.toBe(false);
    expect(sendsOfType(ws, "voice_e2ee_announce")).toHaveLength(0);
  });

  it("[finding v043] a superseded setup does not wipe the live session's peer keys", async () => {
    const ws = { send: vi.fn() };
    const mgr = createManager(ws);

    // Attempt A stalls inside generateECDHKeyPair — before it has written
    // anything, so clearState() has no rejector to reject and A survives its
    // own teardown.
    let releaseGen!: (v: CryptoKeyPair) => void;
    const stalledGen = new Promise<CryptoKeyPair>((resolve) => {
      releaseGen = resolve;
    });
    vi.mocked(generateECDHKeyPair).mockReturnValueOnce(stalledGen);
    const setupA = mgr.setupKeyExchange(false, 1);
    await vi.waitFor(() => expect(generateECDHKeyPair).toHaveBeenCalled());

    // The user leaves and joins another channel; that session completes and
    // learns a peer's ECDH key.
    mgr.clearState();
    await expect(mgr.setupKeyExchange(true, 2)).resolves.toBe(true);
    await mgr.handleAnnounce(PEER_ID, "cGVlcg==", "sig");

    // Attempt A only now resumes, long after its own teardown.
    releaseGen({
      publicKey: { type: "stale-pub" } as unknown as CryptoKey,
      privateKey: { type: "stale-priv" } as unknown as CryptoKey,
    });
    await expect(setupA).resolves.toBe(false);

    // The live session's peer key must survive: nothing repopulates this map
    // mid-call, so clearing it here would make handleOffer's unknown-peer
    // guard drop every later rotation from that peer.
    expect(mgr.peerPublicKeys.has(PEER_ID)).toBe(true);
  });

  it("[finding v043] a superseded setup does not clobber the live session's key-holder role", async () => {
    const ws = { send: vi.fn() };
    const mgr = createManager(ws);

    // Attempt A (non-key-holder) stalls inside buildAnnouncePayload's keyring
    // round trip — the widest pre-publication window.
    let releaseIdentity!: () => void;
    const stalledIdentity = new Promise<typeof mockIdentityKeyPair>((resolve) => {
      releaseIdentity = () => resolve(mockIdentityKeyPair);
    });
    vi.mocked(getOrCreateIdentityKeyPair).mockReturnValueOnce(stalledIdentity);
    const setupA = mgr.setupKeyExchange(false, 1);
    await vi.waitFor(() => expect(getOrCreateIdentityKeyPair).toHaveBeenCalled());

    // The user leaves and joins another channel as key holder.
    mgr.clearState();
    await expect(mgr.setupKeyExchange(true, 2)).resolves.toBe(true);
    const epochAfterLiveSetup = mgr.epoch;

    releaseIdentity();
    await expect(setupA).resolves.toBe(false);
    ws.send.mockClear();

    // A's `_isKeyHolder = false` must never land on the live session: it
    // would silently stop its rotations AND its offers to peers that
    // announce later, with nothing to re-elect it.
    expect(mgr.epoch).toBe(epochAfterLiveSetup);
    await mgr.handleAnnounce(PEER_ID, "cGVlcg==", "sig");
    expect(sendsOfType(ws, "voice_e2ee_offer")).toHaveLength(1);
  });

  it("[finding v015] applies concurrent announces in arrival order, not completion order", async () => {
    const ws = { send: vi.fn() };
    const mgr = createManager(ws);
    await mgr.setupKeyExchange(true, 1); // establishes our keypair + room key

    // The first announce's key import stalls; the second is unimpeded. WS
    // delivery order must still win — otherwise _peerPublicKeys ends up on
    // the peer's superseded key and every later rotation is wrapped for a
    // private key they no longer hold.
    const keyOne = { type: "peer-key-1" } as unknown as CryptoKey;
    const keyTwo = { type: "peer-key-2" } as unknown as CryptoKey;
    let releaseFirst!: (v: CryptoKey) => void;
    const firstImport = new Promise<CryptoKey>((resolve) => {
      releaseFirst = resolve;
    });
    vi.mocked(importPublicKey).mockReturnValueOnce(firstImport).mockResolvedValueOnce(keyTwo);

    const first = mgr.handleAnnounce(PEER_ID, "b2xk", "sig1");
    const second = mgr.handleAnnounce(PEER_ID, "bmV3", "sig2");
    // Drain every pending microtask: unserialized, the second announce runs
    // to completion right here while the first is still stalled.
    await new Promise((resolve) => setTimeout(resolve, 0));
    releaseFirst(keyOne);
    await Promise.all([first, second]);

    expect(mgr.peerPublicKeys.get(PEER_ID)).toBe(keyTwo);
  });

  it("[finding v093] does not resurrect the keypair or send a stray announce if clearState() runs during reannounceForReconnect", async () => {
    const ws = { send: vi.fn() };
    const mgr = createManager(ws);
    await mgr.setupKeyExchange(true, 1);
    ws.send.mockClear();

    let releaseGen!: (v: CryptoKeyPair) => void;
    const stalledGen = new Promise<CryptoKeyPair>((resolve) => {
      releaseGen = resolve;
    });
    vi.mocked(generateECDHKeyPair).mockReturnValueOnce(stalledGen);

    const reconnectPromise = mgr.reannounceForReconnect();
    await vi.waitFor(() => expect(generateECDHKeyPair).toHaveBeenCalled());

    // The user disconnects while the reconnect's keypair generation is
    // still in flight.
    mgr.clearState();

    releaseGen({
      publicKey: { type: "reconnect-pub" } as unknown as CryptoKey,
      privateKey: { type: "reconnect-priv" } as unknown as CryptoKey,
    });
    await reconnectPromise;

    // Must not resurrect the torn-down keypair or send a stray announce for
    // a channel we already left.
    expect((mgr as unknown as { _ecdhKeyPair: unknown })._ecdhKeyPair).toBeNull();
    expect(sendsOfType(ws, "voice_e2ee_announce")).toHaveLength(0);
  });

  it("[finding v101] discards a stale announce-offer if the keypair (not epoch) changes while wrapRoomKey is in flight", async () => {
    const ws = { send: vi.fn() };
    const mgr = createManager(ws);
    await mgr.setupKeyExchange(true, 1); // holder, epoch 1

    let releaseWrap!: (v: { encryptedKey: string; iv: string }) => void;
    const stalledWrap = new Promise<{ encryptedKey: string; iv: string }>((resolve) => {
      releaseWrap = resolve;
    });
    vi.mocked(wrapRoomKey).mockReturnValueOnce(stalledWrap);

    const announcePromise = mgr.handleAnnounce(PEER_ID, "cGVlcg==", "sig");
    await vi.waitFor(() => expect(wrapRoomKey).toHaveBeenCalled());

    // A concurrent reconnect swaps the keypair WITHOUT bumping the epoch —
    // an epoch-only staleness check would miss this entirely.
    vi.mocked(generateECDHKeyPair).mockResolvedValueOnce({
      publicKey: { type: "public-2" } as unknown as CryptoKey,
      privateKey: { type: "private-2" } as unknown as CryptoKey,
    });
    await mgr.reannounceForReconnect();
    ws.send.mockClear();

    // The stale (pre-reconnect) wrap now resolves.
    releaseWrap({ encryptedKey: "stale-enc", iv: "stale-iv" });
    await announcePromise;

    expect(mgr.epoch).toBe(1); // epoch never moved — proves the epoch-only check would miss this
    expect(sendsOfType(ws, "voice_e2ee_offer")).toHaveLength(0);
  });

  it("[finding v045] aborts room-key distribution mid-loop when a concurrent reconnect swaps the keypair", async () => {
    const ws = { send: vi.fn() };
    const mgr = createManager(ws);
    mockVoiceState.voiceUsers.set(1, new Map([[1, {}]])); // we (uid 1) are always lowest

    await mgr.setupKeyExchange(true, 1); // epoch 1, holder
    await mgr.handleAnnounce(PEER_ID, "cGVlcg==", "sig");
    await mgr.handleAnnounce(PEER_ID + 1, "cGVlcjI=", "sig2");

    // A lower-userID peer's offer stands us down (mirrors the "stops
    // rotating" test above), so the next participant-left re-elects us.
    await mgr.handleOffer(PEER_ID, "enc", "iv");
    expect(mgr.rotatingKey).toBe(false);

    // Stall the first peer's wrap in the become-holder redistribution loop.
    // (wrapRoomKey was already called twice above, wrapping for each peer's
    // announce — clear that history so the count below tracks only the
    // redistribution loop.)
    vi.mocked(wrapRoomKey).mockClear();
    let releaseWrap!: (v: { encryptedKey: string; iv: string }) => void;
    const stalledWrap = new Promise<{ encryptedKey: string; iv: string }>((resolve) => {
      releaseWrap = resolve;
    });
    vi.mocked(wrapRoomKey).mockReturnValueOnce(stalledWrap);

    const leftPromise = mgr.handleParticipantLeft(99); // re-elects us as holder
    await vi.waitFor(() => expect(wrapRoomKey).toHaveBeenCalledTimes(1));
    ws.send.mockClear();

    // A concurrent reconnect swaps the keypair mid-loop, without bumping
    // the epoch.
    vi.mocked(generateECDHKeyPair).mockResolvedValueOnce({
      publicKey: { type: "reconnect-pub" } as unknown as CryptoKey,
      privateKey: { type: "reconnect-priv" } as unknown as CryptoKey,
    });
    await mgr.reannounceForReconnect();
    ws.send.mockClear();

    releaseWrap({ encryptedKey: "stale-enc", iv: "stale-iv" });
    await leftPromise;

    // Neither peer should receive an offer wrapped under the abandoned
    // keypair — the loop must abort as soon as it notices the swap.
    expect(sendsOfType(ws, "voice_e2ee_offer")).toHaveLength(0);
  });

  // ── Batch B3 findings ───────────────────────────────────────────────────

  it("[B3-2] preserves a key-holder promotion that lands during setupKeyExchange's pre-publish awaits, instead of clobbering it with the stale server value", async () => {
    const ws = { send: vi.fn() };
    const mgr = createManager(ws);
    // After the real holder (PEER_ID) leaves, we (uid 1) are the only
    // remaining participant — client-side election promotes us.
    mockVoiceState.voiceUsers.set(1, new Map([[1, {}]]));

    // Stall the identity-key load inside buildAnnouncePayload so a
    // participant-left promotion can land BEFORE setupKeyExchange assigns
    // this._isKeyHolder from the (now-stale) server value.
    let releaseIdentity!: () => void;
    const stalledIdentity = new Promise<typeof mockIdentityKeyPair>((resolve) => {
      releaseIdentity = () => resolve(mockIdentityKeyPair);
    });
    vi.mocked(getOrCreateIdentityKeyPair).mockReturnValueOnce(stalledIdentity);

    // Server said we are NOT the key holder when we started joining...
    const setupPromise = mgr.setupKeyExchange(false, 1);
    await vi.waitFor(() => expect(getOrCreateIdentityKeyPair).toHaveBeenCalled());

    // ...but the real holder leaves before we finish setting up, and since we
    // are the only participant left, client-side election promotes us.
    await mgr.handleParticipantLeft(PEER_ID);

    releaseIdentity();
    // On the buggy path this falls through to the non-holder wait-for-offer
    // branch and burns the full 10s + 5s timeout before resolving false —
    // fast-forward past it so the test does not block on a real 15s wait.
    vi.useFakeTimers();
    try {
      await vi.advanceTimersByTimeAsync(20_000);
    } finally {
      vi.useRealTimers();
    }

    // The promotion must win: we end up as key holder (generated + applied a
    // room key and announced) instead of waiting for an offer that only WE
    // could have sent — the exact interleaving that times out and gets the
    // joiner ejected from voice.
    await expect(setupPromise).resolves.toBe(true);
    expect(mockSetKey).toHaveBeenCalledWith("mock-room-key-base64");
  });

  it("[B3-7] does not resurrect peer key/verification state into a torn-down session when clearState() runs during verifyPeerAnnounce's pin lookup", async () => {
    const ws = { send: vi.fn() };
    const mgr = createManager(ws);
    await mgr.setupKeyExchange(true, 1); // establishes our keypair

    let releasePin!: (v: { status: "unpinned" }) => void;
    const stalledPin = new Promise<{ status: "unpinned" }>((resolve) => {
      releasePin = resolve;
    });
    vi.mocked(getIdentityPin).mockReturnValueOnce(stalledPin);

    const announcePromise = mgr.handleAnnounce(PEER_ID, "cGVlcg==", "sig");
    await vi.waitFor(() => expect(getIdentityPin).toHaveBeenCalled());

    // Disconnect mid-verify.
    mgr.clearState();
    vi.mocked(setPeerVerification).mockClear();

    releasePin({ status: "unpinned" });
    await announcePromise;

    // The torn-down session's peer map and verification state must not be
    // resurrected by a continuation that resumes after teardown.
    expect(mgr.peerPublicKeys.has(PEER_ID)).toBe(false);
    expect(setPeerVerification).not.toHaveBeenCalled();
  });

  it("[identity-scope guard] does not mint an identity keypair when no user is authenticated yet — announce goes out unsigned instead of under a placeholder host:0 scope", async () => {
    // If this ever ran before auth state landed, falling back to `?? 0`
    // would mint (or migrate-and-DELETE the real legacy key into) a bogus
    // `host:0` keyring scope; the ready hook's later, authenticated call
    // then mints a SECOND, DIFFERENT keypair under `host:<realId>` — so the
    // published key and the announce signing key permanently disagree and
    // every peer's verifyPeerAnnounce reports a false MITM "mismatch".
    const ws = { send: vi.fn() };
    const mgr = createManager(ws);
    vi.mocked(authStore.getState).mockReturnValueOnce({ user: null } as never);

    const ok = await mgr.setupKeyExchange(true, 1);

    expect(ok).toBe(true);
    expect(getOrCreateIdentityKeyPair).not.toHaveBeenCalled();
    const announces = sendsOfType(ws, "voice_e2ee_announce");
    expect(announces).toHaveLength(1);
    // Same contract as "no server host": degrade to an unsigned announce
    // rather than sign/scope under a placeholder id.
    expect((announces[0] as any).payload.signature).toBeUndefined();
  });

  // ── Ledger findings OC-0098 / OC-0004 / OC-0006 / OC-0005 / OC-0007 ──

  it("[OC-0098] sends its own announce before offering the room key to a drained peer", async () => {
    const ws = { send: vi.fn() };
    const mgr = createManager(ws);

    // A peer's announce arrives (relayed by voice_join sync) before our own
    // keypair is ready — it queues.
    await mgr.handleAnnounce(PEER_ID, "cGVlcg==", "sig");
    expect(mgr.pendingAnnounces).toHaveLength(1);

    // We become key holder and drain the queued announce, which sends that
    // peer a voice_e2ee_offer. The peer can only unwrap it if it already has
    // OUR ephemeral public key on file — which only our own announce
    // provides. Both land in the same inbound WS queue on the peer's side,
    // so the order we send them in here IS the order they arrive there.
    await mgr.setupKeyExchange(true, 1);

    const calls = ws.send.mock.calls.map((c) => c[0] as { type: string });
    const announceIndex = calls.findIndex((m) => m.type === "voice_e2ee_announce");
    const offerIndex = calls.findIndex((m) => m.type === "voice_e2ee_offer");
    expect(announceIndex).toBeGreaterThanOrEqual(0);
    expect(offerIndex).toBeGreaterThanOrEqual(0);
    expect(announceIndex).toBeLessThan(offerIndex);
  });

  it("[OC-0004] elects us key holder on a participant-left even when our own voice_state hasn't landed in the roster yet", async () => {
    const ws = { send: vi.fn() };
    const mgr = createManager(ws); // getCurrentChannelId() => 1
    // mockVoiceState.voiceUsers has NO entry for channel 1: our own
    // voice_state broadcast hasn't landed (it's still queued behind the
    // leaver's on the server), and the leaver's own roster entry was just
    // deleted by removeVoiceUser before this handler runs.
    expect(mockVoiceState.voiceUsers.has(1)).toBe(false);

    await mgr.handleParticipantLeft(PEER_ID);

    // Client-side election must still run using our own authenticated id
    // (uid 1) even though the local roster has nothing recorded for the
    // channel yet — otherwise the server's promotion is silently missed and
    // the join that provoked it times out 15s later.
    expect(mgr.epoch).toBe(1);
    expect(mockSetKey).toHaveBeenCalledWith("mock-room-key-base64");
  });

  it("[OC-0006] self-heals the shared key provider when a rotation's setKey resolves after clearState() tore the session down", async () => {
    const ws = { send: vi.fn() };
    const mgr = createManager(ws);
    mockVoiceState.voiceUsers.set(1, new Map([[1, {}]]));

    await mgr.setupKeyExchange(true, 1); // epoch 1, holder

    vi.mocked(roomKeyToBase64).mockImplementation((k: Uint8Array) => `key-${k[0]}`);
    try {
      vi.mocked(generateRoomKey).mockReturnValueOnce(new Uint8Array(32).fill(9)); // this rotation's key
      let releaseStale!: () => void;
      const staleSetKey = new Promise<void>((resolve) => {
        releaseStale = resolve;
      });
      mockSetKey.mockImplementationOnce(() => staleSetKey);

      const rotationPromise = mgr.rotateKeyPeriodically();
      await vi.waitFor(() => expect(mockSetKey).toHaveBeenCalledWith("key-9"));

      // The session is torn down (user hit Disconnect) while that setKey
      // call is still in flight, and a brand-new session becomes holder
      // with its OWN key before the stale call resolves.
      mgr.clearState();
      vi.mocked(generateRoomKey).mockReturnValueOnce(new Uint8Array(32).fill(7)); // live session's key
      mockSetKey.mockClear();
      await mgr.setupKeyExchange(true, 2);
      expect(mockSetKey).toHaveBeenCalledWith("key-7");
      mockSetKey.mockClear();

      // The abandoned rotation's setKey call now resolves.
      releaseStale();
      await rotationPromise;

      // The shared key provider must end up on the LIVE session's key, not
      // silently left on the abandoned one — narrow race, but real: nothing
      // else re-applies the live key once the stale call lands.
      expect(mockSetKey).toHaveBeenCalledWith("key-7");
    } finally {
      vi.mocked(roomKeyToBase64).mockImplementation(() => "mock-room-key-base64");
    }
  });

  it("[OC-0005] paces room-key offers so a large channel's rotation stays under the server's per-second rate limit", async () => {
    const ws = { send: vi.fn() };
    const mgr = createManager(ws);
    mockVoiceState.voiceUsers.set(1, new Map([[1, {}]]));
    await mgr.setupKeyExchange(true, 1); // holder

    // Seed 62 peers directly — well past the server's 64/sec cap for a
    // single rotation's worth of offers.
    for (let i = 0; i < 62; i++) {
      mgr.peerPublicKeys.set(1000 + i, { type: `peer-${i}` } as unknown as CryptoKey);
    }
    ws.send.mockClear();

    vi.useFakeTimers();
    try {
      const rotationPromise = mgr.rotateKeyPeriodically();
      // Let every microtask-bound send that doesn't need a real timer run.
      await vi.advanceTimersByTimeAsync(0);
      const sentBeforePause = sendsOfType(ws, "voice_e2ee_offer").length;
      // Must not have blown through the whole 62 in one burst.
      expect(sentBeforePause).toBeLessThan(62);
      expect(sentBeforePause).toBeGreaterThan(0);

      await vi.advanceTimersByTimeAsync(2000);
      await rotationPromise;

      expect(sendsOfType(ws, "voice_e2ee_offer")).toHaveLength(62);
    } finally {
      vi.useRealTimers();
    }
  });

  it("[OC-0155] shares the offer-pacing budget across back-to-back rotations instead of resetting it per call", async () => {
    const ws = { send: vi.fn() };
    const mgr = createManager(ws);
    mockVoiceState.voiceUsers.set(1, new Map([[1, {}]]));
    await mgr.setupKeyExchange(true, 1); // holder

    // Seed 40 peers — under the server's 60-offer cap for a SINGLE rotation,
    // but two back-to-back rotations of 40 each (80 offers total) blow
    // through the server's shared per-second window if each rotation gets
    // its own fresh pacing budget instead of sharing one.
    for (let i = 0; i < 40; i++) {
      mgr.peerPublicKeys.set(2000 + i, { type: `peer-${i}` } as unknown as CryptoKey);
    }
    ws.send.mockClear();

    // A second keyed-peer leave lands while this rotation is conceptually
    // in flight — drainPendingRotationOrArmTimer (livekitE2EE.ts:1256-1263)
    // runs a second rotation immediately once this one finishes, exactly
    // like handleParticipantLeft's wasKeyHolder branch does.
    mgr.rotationPending = true;

    vi.useFakeTimers();
    try {
      const rotationPromise = mgr.rotateKeyPeriodically();
      // Let every microtask-bound send that doesn't need a real timer run —
      // this covers BOTH rotations if neither individually hits the cap.
      await vi.advanceTimersByTimeAsync(0);
      const sentBeforePause = sendsOfType(ws, "voice_e2ee_offer").length;
      // The combined 80 offers across both rotations must not all go out
      // unpaced just because neither rotation's own 40-offer batch exceeds
      // the 60 cap in isolation — the budget must be shared.
      expect(sentBeforePause).toBeLessThanOrEqual(60);

      await vi.advanceTimersByTimeAsync(2000);
      await rotationPromise;

      // Both rotations' offers (40 + 40) eventually go out.
      expect(sendsOfType(ws, "voice_e2ee_offer")).toHaveLength(80);
    } finally {
      vi.useRealTimers();
    }
  });

  it("[OC-0167] paces announce-driven offers through the same shared budget as rotation offers", async () => {
    const ws = { send: vi.fn() };
    const mgr = createManager(ws);

    // 70 existing participants' announces are relayed to the future key
    // holder before its own keypair is ready — exactly what voiceJoinComplete
    // does for a joiner elected key holder in a large ongoing channel
    // (Server/ws/voice_join.go:490-495). They queue.
    for (let i = 0; i < 70; i++) {
      await mgr.handleAnnounce(3000 + i, "cGVlcg==", "sig");
    }
    expect(mgr.pendingAnnounces).toHaveLength(70);

    mockVoiceState.voiceUsers.set(1, new Map([[1, {}]]));
    ws.send.mockClear();

    vi.useFakeTimers();
    try {
      // setupKeyExchange generates the room key, then drains all 70 queued
      // announces in a tight loop — each drained announce sends a
      // voice_e2ee_offer directly (handleAnnounceInner), bypassing
      // distributeRoomKey's pacing entirely.
      const setupPromise = mgr.setupKeyExchange(true, 1);
      await vi.advanceTimersByTimeAsync(0);
      const sentBeforePause = sendsOfType(ws, "voice_e2ee_offer").length;
      // Must not blow through all 70 announce-driven offers in one unpaced
      // burst.
      expect(sentBeforePause).toBeLessThan(70);
      expect(sentBeforePause).toBeGreaterThan(0);

      await vi.advanceTimersByTimeAsync(2000);
      await setupPromise;

      expect(sendsOfType(ws, "voice_e2ee_offer")).toHaveLength(70);
    } finally {
      vi.useRealTimers();
    }
  });

  // ── Ledger findings OC-0010 / OC-0011 ─────────────────────────────────

  it("[OC-0010] does not stand down a new session's key-holder role when a stale offer's setKey resolves after teardown+rejoin", async () => {
    const ws = { send: vi.fn() };
    const mgr = createManager(ws);

    // Session A: we are the holder in channel 1, with PEER_ID's key on file.
    await mgr.setupKeyExchange(true, 1);
    await mgr.handleAnnounce(PEER_ID, "cGVlcg==", "sig");

    // An offer from PEER_ID arrives and stalls at the keyProvider.setKey
    // await — AFTER the epoch/keypair guard (checked right after unwrap) has
    // already passed.
    let releaseSetKey!: () => void;
    const stalledSetKey = new Promise<void>((resolve) => {
      releaseSetKey = resolve;
    });
    mockSetKey.mockClear();
    mockSetKey.mockImplementationOnce(() => stalledSetKey);
    const offerPromise = mgr.handleOffer(PEER_ID, "enc", "iv");
    await vi.waitFor(() => expect(mockSetKey).toHaveBeenCalled());

    // Mid-flight: the user leaves channel 1 and rejoins channel 2 as the new
    // key holder — a distinct keypair, exactly as real ECDH keygen produces.
    mgr.clearState();
    vi.mocked(generateECDHKeyPair).mockResolvedValueOnce({
      publicKey: { type: "chan2-pub" } as unknown as CryptoKey,
      privateKey: { type: "chan2-priv" } as unknown as CryptoKey,
    });
    await mgr.setupKeyExchange(true, 2);
    expect((mgr as unknown as { _isKeyHolder: boolean })._isKeyHolder).toBe(true);

    // The stale (session-1) offer's setKey now resolves.
    releaseSetKey();
    await offerPromise;

    // Channel 2's holder role must survive — the stale continuation must not
    // stand it down (it re-checks staleness before the setKey await, not
    // after — the write happens on the far side of that await).
    expect((mgr as unknown as { _isKeyHolder: boolean })._isKeyHolder).toBe(true);
  });

  it("[OC-0010] does not write a stale peer key into a new session's map when clearState()+rejoin lands during the announce's key-import await", async () => {
    const ws = { send: vi.fn() };
    const mgr = createManager(ws);

    // Session A: holder in channel 1.
    await mgr.setupKeyExchange(true, 1);

    // The announce's importPublicKey stalls — the "final await" before the
    // _peerPublicKeys.set write, which today has no re-check after it.
    let releaseImport!: (v: CryptoKey) => void;
    const stalledImport = new Promise<CryptoKey>((resolve) => {
      releaseImport = resolve;
    });
    vi.mocked(importPublicKey).mockReturnValueOnce(stalledImport);

    const announcePromise = mgr.handleAnnounce(PEER_ID, "cGVlcg==", "sig");
    await vi.waitFor(() => expect(importPublicKey).toHaveBeenCalled());

    // Mid-flight: the user leaves channel 1 and rejoins channel 2.
    mgr.clearState();
    await mgr.setupKeyExchange(true, 2);

    // The stale announce's import now resolves.
    releaseImport({ type: "stale-peer-key" } as unknown as CryptoKey);
    await announcePromise;

    // The new session's peer map must not be polluted by the torn-down
    // session's announce.
    expect(mgr.peerPublicKeys.has(PEER_ID)).toBe(false);
  });

  it("[OC-0011] rejects a replayed announce carrying a previously-retired peer key instead of overwriting the live key", async () => {
    const ws = { send: vi.fn() };
    const mgr = createManager(ws);
    await mgr.setupKeyExchange(true, 1); // establishes our keypair

    // Make import/export round-trip faithfully on the announced base64
    // string (the shared mock default returns a fixed constant from
    // exportPublicKey regardless of input, which would mask this bug).
    vi.mocked(importPublicKey).mockImplementation(
      async (b64: string) => ({ type: `peer-key-${b64}` }) as unknown as CryptoKey,
    );
    vi.mocked(exportPublicKey).mockImplementation(async (key: CryptoKey) =>
      (key as unknown as { type: string }).type.replace("peer-key-", ""),
    );

    // Valid base64 (must decode cleanly — rawFromBase64 uses atob() to build
    // the signed message bytes). "b2xk"/"bmV3" already prove out elsewhere in
    // this suite as distinct valid ephemeral-key payloads.
    const KEY_A = "b2xk";
    const KEY_B = "bmV3";

    try {
      // Peer announces key A — accepted as their first (live) key.
      await mgr.handleAnnounce(PEER_ID, KEY_A, "sigA");
      expect(mgr.peerPublicKeys.get(PEER_ID)).toEqual({ type: `peer-key-${KEY_A}` });

      // Peer reconnects and announces a genuinely new key B — a legitimate
      // change, so key A is now retired.
      await mgr.handleAnnounce(PEER_ID, KEY_B, "sigB");
      expect(mgr.peerPublicKeys.get(PEER_ID)).toEqual({ type: `peer-key-${KEY_B}` });

      // A malicious relay re-emits the OLD, still validly-signed announce for
      // key A. No channel/epoch/nonce binds the signed message, so it
      // verifies cleanly — it must still be rejected as a replay, not
      // overwrite the live key B.
      await mgr.handleAnnounce(PEER_ID, KEY_A, "sigA");

      expect(mgr.peerPublicKeys.get(PEER_ID)).toEqual({ type: `peer-key-${KEY_B}` });
    } finally {
      vi.mocked(importPublicKey).mockImplementation(
        async () => ({ type: "public" }) as unknown as CryptoKey,
      );
      vi.mocked(exportPublicKey).mockImplementation(async () => "bW9ja2VwaGVtZXJhbA==");
    }
  });

  it("[OC-0007] confirms the room key after a reconnect re-announce instead of declaring it fresh unconditionally", async () => {
    const ws = { send: vi.fn() };
    const mgr = createManager(ws);
    // Non-key-holder session with an already-established room key from
    // before the (simulated) disconnect.
    await mgr.setupKeyExchange(true, 1);
    await mgr.handleAnnounce(PEER_ID, "cGVlcg==", "sig"); // peer key known, so the offer below is accepted
    await mgr.handleOffer(PEER_ID, "enc", "iv"); // stands us down — now a non-holder
    ws.send.mockClear();

    vi.useFakeTimers();
    try {
      await mgr.reannounceForReconnect();
      // Nothing has confirmed the re-applied key is current yet. If the
      // holder's fresh offer never arrives, this must not be a silent,
      // unbounded wait for the next 5-minute rotation — it must retry.
      await vi.advanceTimersByTimeAsync(6000);

      const announces = sendsOfType(ws, "voice_e2ee_announce");
      expect(announces.length).toBeGreaterThan(1); // the reconnect announce PLUS a retry
    } finally {
      vi.useRealTimers();
    }
  });

  // ── Ledger findings OC-0002 / OC-0020 ─────────────────────────────────

  it("[OC-0002] applies an offer that arrives while its sender's own announce is still verifying, instead of dropping it as an unknown peer", async () => {
    const ws = { send: vi.fn() };
    const mgr = createManager(ws);
    // We were previously key holder (mirrors B in the repro): own keypair +
    // room key already established.
    await mgr.setupKeyExchange(true, 1);
    mockSetKey.mockClear();
    vi.mocked(unwrapRoomKey).mockClear();

    // Stall the identity-pin lookup inside verifyPeerAnnounce so the
    // announce is still mid-flight (queued on _announceChain, not yet
    // applied to _peerPublicKeys) when the offer from the SAME sender
    // arrives right behind it — exactly the WS delivery order OC-0098
    // guarantees the sender used.
    let releasePin!: (v: { status: "unpinned" }) => void;
    const stalledPin = new Promise<{ status: "unpinned" }>((resolve) => {
      releasePin = resolve;
    });
    vi.mocked(getIdentityPin).mockReturnValueOnce(stalledPin);

    const announcePromise = mgr.handleAnnounce(PEER_ID, "cGVlcg==", "sig");
    await vi.waitFor(() => expect(getIdentityPin).toHaveBeenCalled());

    // The offer is dispatched immediately behind the announce, before the
    // announce has stored the peer's ECDH key.
    const offerPromise = mgr.handleOffer(PEER_ID, "enc", "iv");

    releasePin({ status: "unpinned" });
    await Promise.all([announcePromise, offerPromise]);

    // The offer must have been applied once the announce (which arrived
    // first) finished verifying — not silently dropped as "unknown peer"
    // with no retry until the next 5-minute rotation.
    expect(mgr.peerPublicKeys.has(PEER_ID)).toBe(true);
    expect(unwrapRoomKey).toHaveBeenCalled();
    expect(mockSetKey).toHaveBeenCalledWith("mock-room-key-base64");
  });

  it("[OC-0020] retires a departed peer's key on leave, so a replay of it after they rejoin with a fresh key cannot resurrect it", async () => {
    const ws = { send: vi.fn() };
    const mgr = createManager(ws);
    await mgr.setupKeyExchange(true, 1); // establishes our keypair, holder

    // Make import/export round-trip faithfully on the announced base64
    // string (the shared mock default returns a fixed constant regardless
    // of input, which would mask this bug).
    vi.mocked(importPublicKey).mockImplementation(
      async (b64: string) => ({ type: `peer-key-${b64}` }) as unknown as CryptoKey,
    );
    vi.mocked(exportPublicKey).mockImplementation(async (key: CryptoKey) =>
      (key as unknown as { type: string }).type.replace("peer-key-", ""),
    );

    const KEY_A = "b2xk";
    const KEY_B = "bmV3";

    try {
      // Peer announces key A — accepted as their first (live) key.
      await mgr.handleAnnounce(PEER_ID, KEY_A, "sigA");
      expect(mgr.peerPublicKeys.get(PEER_ID)).toEqual({ type: `peer-key-${KEY_A}` });

      // Peer leaves the channel.
      await mgr.handleParticipantLeft(PEER_ID);
      expect(mgr.peerPublicKeys.has(PEER_ID)).toBe(false);

      // Peer rejoins and announces a fresh key B.
      await mgr.handleAnnounce(PEER_ID, KEY_B, "sigB");
      expect(mgr.peerPublicKeys.get(PEER_ID)).toEqual({ type: `peer-key-${KEY_B}` });

      // A malicious relay re-emits the pre-leave, still validly-signed
      // announce for key A. No channel/epoch/nonce binds the signed
      // message, so it verifies cleanly — it must still be rejected as a
      // replay of a retired key, not overwrite the live key B.
      await mgr.handleAnnounce(PEER_ID, KEY_A, "sigA");

      expect(mgr.peerPublicKeys.get(PEER_ID)).toEqual({ type: `peer-key-${KEY_B}` });
    } finally {
      vi.mocked(importPublicKey).mockImplementation(
        async () => ({ type: "public" }) as unknown as CryptoKey,
      );
      vi.mocked(exportPublicKey).mockImplementation(async () => "bW9ja2VwaGVtZXJhbA==");
    }
  });

  // ── OC-0001: per-sender offer epoch high-water mark ───────────────────────

  const unwrapAt = (epoch: number | null, fill = 1) =>
    vi.mocked(unwrapRoomKey).mockResolvedValueOnce({
      roomKey: new Uint8Array(32).fill(fill),
      epoch,
    });

  async function holderWithPeer(): Promise<{
    ws: { send: ReturnType<typeof vi.fn> };
    mgr: E2EEManager;
  }> {
    const ws = { send: vi.fn() };
    const mgr = createManager(ws);
    await mgr.setupKeyExchange(true, 1);
    await mgr.handleAnnounce(PEER_ID, "cGVlcg==", "sig");
    mockSetKey.mockClear();
    vi.mocked(roomKeyToBase64).mockImplementation((k: Uint8Array) => `key-${k[0]}`);
    return { ws, mgr };
  }

  it("[OC-0001] wraps each offer with the sender's current epoch", async () => {
    const ws = { send: vi.fn() };
    const mgr = createManager(ws);
    await mgr.setupKeyExchange(true, 1);
    await mgr.handleAnnounce(PEER_ID, "cGVlcg==", "sig");
    expect(wrapRoomKey).toHaveBeenLastCalledWith(
      expect.anything(),
      expect.anything(),
      expect.anything(),
      mgr.epoch,
    );

    await mgr.rotateKeyPeriodically();
    expect(wrapRoomKey).toHaveBeenLastCalledWith(
      expect.anything(),
      expect.anything(),
      expect.anything(),
      mgr.epoch,
    );
  });

  it("[OC-0001] discards an offer carrying an older epoch than one already applied from that sender", async () => {
    const { mgr } = await holderWithPeer();
    try {
      unwrapAt(3, 3);
      await mgr.handleOffer(PEER_ID, "enc3", "iv3");
      expect(mockSetKey).toHaveBeenLastCalledWith("key-3");

      unwrapAt(2, 2);
      await mgr.handleOffer(PEER_ID, "enc2", "iv2");
      expect(mockSetKey).toHaveBeenLastCalledWith("key-3");
      expect(mockSetKey).toHaveBeenCalledTimes(1);
    } finally {
      vi.mocked(roomKeyToBase64).mockImplementation(() => "mock-room-key-base64");
    }
  });

  it("[OC-0001] applies an offer at the same epoch as the last applied one (holder re-sends the current key)", async () => {
    const { mgr } = await holderWithPeer();
    try {
      unwrapAt(3, 3);
      await mgr.handleOffer(PEER_ID, "enc3", "iv3");
      unwrapAt(3, 4);
      await mgr.handleOffer(PEER_ID, "enc3b", "iv3b");
      expect(mockSetKey).toHaveBeenLastCalledWith("key-4");
    } finally {
      vi.mocked(roomKeyToBase64).mockImplementation(() => "mock-room-key-base64");
    }
  });

  it("[OC-0001] resets the sender's high-water mark when they announce a fresh ephemeral key", async () => {
    const { mgr } = await holderWithPeer();
    vi.mocked(importPublicKey).mockImplementation(
      async (b64: string) => ({ type: `peer-key-${b64}` }) as unknown as CryptoKey,
    );
    vi.mocked(exportPublicKey).mockImplementation(async (key: CryptoKey) =>
      (key as unknown as { type: string }).type.replace("peer-key-", ""),
    );
    try {
      unwrapAt(5, 5);
      await mgr.handleOffer(PEER_ID, "enc5", "iv5");
      expect(mockSetKey).toHaveBeenLastCalledWith("key-5");

      // Peer rejoined: new ephemeral key, new (local) epoch counter from 1.
      await mgr.handleAnnounce(PEER_ID, "bmV3", "sigB");
      unwrapAt(1, 1);
      await mgr.handleOffer(PEER_ID, "enc1", "iv1");
      expect(mockSetKey).toHaveBeenLastCalledWith("key-1");
    } finally {
      vi.mocked(roomKeyToBase64).mockImplementation(() => "mock-room-key-base64");
      vi.mocked(importPublicKey).mockImplementation(
        async () => ({ type: "public" }) as unknown as CryptoKey,
      );
      vi.mocked(exportPublicKey).mockImplementation(async () => "bW9ja2VwaGVtZXJhbA==");
    }
  });

  it("[OC-0001] still applies a legacy offer (no epoch) from a holder on the old build", async () => {
    const { mgr } = await holderWithPeer();
    try {
      unwrapAt(null, 7);
      await mgr.handleOffer(PEER_ID, "legacy", "iv");
      expect(mockSetKey).toHaveBeenLastCalledWith("key-7");
    } finally {
      vi.mocked(roomKeyToBase64).mockImplementation(() => "mock-room-key-base64");
    }
  });

  // ── OC-0003: per-session fingerprint for every peer ───────────────────────

  it("[OC-0003] publishes a session fingerprint of the ephemeral key for a legacy (unverified) peer while safetyNumber stays null", async () => {
    const ws = { send: vi.fn() };
    const mgr = createManager(ws);
    mockMembers.set(PEER_ID, { identityPublicKey: null });
    await mgr.setupKeyExchange(true, 1);

    await mgr.handleAnnounce(PEER_ID, "cGVlcg==", undefined);

    expect(setPeerVerification).toHaveBeenCalledWith({
      userId: PEER_ID,
      status: "unverified",
      safetyNumber: null,
      sessionFingerprint: "5E55 1234 5678 9ABC",
    });
  });

  it("[OC-0003] publishes the session fingerprint alongside the safety number for a verified peer", async () => {
    const ws = { send: vi.fn() };
    const mgr = createManager(ws);
    await mgr.setupKeyExchange(true, 1);

    await mgr.handleAnnounce(PEER_ID, "cGVlcg==", "sig");

    expect(setPeerVerification).toHaveBeenCalledWith({
      userId: PEER_ID,
      status: "verified",
      safetyNumber: "AB12 CD34 EF56 7890",
      sessionFingerprint: "5E55 1234 5678 9ABC",
    });
  });

  it("[OC-0003] publishes the local session fingerprint on setup and clears it on teardown", async () => {
    const ws = { send: vi.fn() };
    const mgr = createManager(ws);

    await mgr.setupKeyExchange(true, 1);
    expect(setLocalSessionFingerprint).toHaveBeenLastCalledWith("5E55 1234 5678 9ABC");

    mgr.clearState();
    expect(setLocalSessionFingerprint).toHaveBeenLastCalledWith(null);
  });

  // ── Guards the suite reached but never pinned (mutation audit T-2026-08-19-47) ──

  it("[T-47] aborts setup when a teardown lands during the key holder's keyProvider.setKey", async () => {
    const ws = { send: vi.fn() };
    const mgr = createManager(ws);

    // Stall the holder's setKey — the last await before the keypair is
    // published. A teardown here leaves nothing about our local state null,
    // so only the session-generation check can see it.
    let releaseSetKey!: () => void;
    mockSetKey.mockReturnValueOnce(
      new Promise<void>((resolve) => {
        releaseSetKey = resolve;
      }),
    );

    const setupPromise = mgr.setupKeyExchange(true, 1);
    await vi.waitFor(() => expect(mockSetKey).toHaveBeenCalled());

    mgr.clearState();
    releaseSetKey();
    await new Promise((resolve) => setTimeout(resolve, 0));

    // Publishing the keypair here would defeat handleAnnounceInner's queue
    // guard and go on to announce a dead ephemeral key over a live call.
    expect((mgr as unknown as { _ecdhKeyPair: unknown })._ecdhKeyPair).toBeNull();
    expect(sendsOfType(ws, "voice_e2ee_announce")).toHaveLength(0);
    await expect(setupPromise).resolves.toBe(false);
  });

  it("[T-47] a superseded reconnect re-announce publishes neither its fingerprint nor a stray announce", async () => {
    const ws = { send: vi.fn() };
    const mgr = createManager(ws);
    await mgr.setupKeyExchange(true, 1);
    ws.send.mockClear();
    vi.mocked(setLocalSessionFingerprint).mockClear();

    // Attempt A stalls after publishing its keypair, while computing its own
    // session fingerprint.
    let releaseFingerprint!: (v: string) => void;
    const stalledFingerprint = new Promise<string>((resolve) => {
      releaseFingerprint = resolve;
    });
    vi.mocked(computeRawKeyFingerprint)
      .mockReturnValueOnce(stalledFingerprint)
      .mockResolvedValueOnce("FP-B");

    const reconnectA = mgr.reannounceForReconnect();
    await vi.waitFor(() => expect(computeRawKeyFingerprint).toHaveBeenCalled());

    // A second reconnect wins the race and publishes its own keypair.
    vi.mocked(generateECDHKeyPair).mockResolvedValueOnce({
      publicKey: { type: "pub-B" } as unknown as CryptoKey,
      privateKey: { type: "priv-B" } as unknown as CryptoKey,
    });
    await mgr.reannounceForReconnect();

    releaseFingerprint("FP-A-STALE");
    await reconnectA;

    // A owns neither the published keypair nor the announce: showing the user
    // A's fingerprint (or re-announcing A's dead key) misrepresents the live
    // session's identity.
    expect(setLocalSessionFingerprint).toHaveBeenCalledWith("FP-B");
    expect(setLocalSessionFingerprint).not.toHaveBeenCalledWith("FP-A-STALE");
    expect(sendsOfType(ws, "voice_e2ee_announce")).toHaveLength(1);
  });

  it("[T-47] does not arm a reconnect-confirmation retry when there was no room key to go stale", async () => {
    vi.useFakeTimers();
    try {
      const ws = { send: vi.fn() };
      const mgr = createManager(ws); // never keyed: non-holder, no room key

      await mgr.reannounceForReconnect();
      expect(sendsOfType(ws, "voice_e2ee_announce")).toHaveLength(1);

      // Nothing could have gone stale — waiting for the first offer is
      // setupKeyExchange's concern, so no confirmation retry belongs here.
      await vi.advanceTimersByTimeAsync(6000);
      expect(sendsOfType(ws, "voice_e2ee_announce")).toHaveLength(1);
    } finally {
      vi.useRealTimers();
    }
  });

  it("[T-47] skips the reconnect-confirmation retry once a fresh offer replaced the room key", async () => {
    const ws = { send: vi.fn() };
    const mgr = createManager(ws);
    await mgr.setupKeyExchange(true, 1);
    await mgr.handleAnnounce(PEER_ID, "cGVlcg==", "sig");
    await mgr.handleOffer(PEER_ID, "enc", "iv"); // stands us down — now a non-holder with a key
    ws.send.mockClear();

    vi.useFakeTimers();
    try {
      await mgr.reannounceForReconnect();
      // The holder's confirming offer arrives well inside the window.
      vi.mocked(unwrapRoomKey).mockResolvedValueOnce({
        roomKey: new Uint8Array(32).fill(9),
        epoch: 1,
      });
      await mgr.handleOffer(PEER_ID, "enc2", "iv2");

      await vi.advanceTimersByTimeAsync(6000);
      expect(sendsOfType(ws, "voice_e2ee_announce")).toHaveLength(1); // no retry
    } finally {
      vi.useRealTimers();
    }
  });

  it("[T-47] a duplicate announce does not reset the sender's offer high-water mark", async () => {
    const ws = { send: vi.fn() };
    const mgr = createManager(ws);
    await mgr.setupKeyExchange(true, 1);
    // Announce the exact key the export mock echoes back, so the repeat below
    // takes the dedupe path rather than the key-changed path.
    await mgr.handleAnnounce(PEER_ID, "bW9ja2VwaGVtZXJhbA==", "sig");
    mockSetKey.mockClear();
    vi.mocked(roomKeyToBase64).mockImplementation((k: Uint8Array) => `key-${k[0]}`);

    try {
      unwrapAt(5, 5);
      await mgr.handleOffer(PEER_ID, "enc5", "iv5");
      expect(mockSetKey).toHaveBeenLastCalledWith("key-5");

      // A replayed (validly signed) announce carrying the peer's CURRENT key
      // must not clear the high-water mark — that would re-open the OC-0001
      // downgrade window for every superseded offer of theirs.
      await mgr.handleAnnounce(PEER_ID, "bW9ja2VwaGVtZXJhbA==", "sig");

      unwrapAt(2, 2);
      await mgr.handleOffer(PEER_ID, "enc2", "iv2");
      expect(mockSetKey).toHaveBeenLastCalledWith("key-5");
    } finally {
      vi.mocked(roomKeyToBase64).mockImplementation(() => "mock-room-key-base64");
    }
  });

  it("[T-47] does not promote itself on a leave while a lower-id participant remains", async () => {
    const ws = { send: vi.fn() };
    const mgr = createManager(ws);
    // We are uid 50; uid 10 stays behind, so the election must pick them.
    vi.mocked(authStore.getState).mockImplementation(() => ({ user: { id: 50 } }) as never);
    mockVoiceState.voiceUsers.set(1, new Map([[10, {}] as const, [50, {}] as const]));

    try {
      await mgr.handleParticipantLeft(PEER_ID);

      // Self-promoting here means two holders rotating against each other:
      // our offers get NOT_KEY_HOLDER'd only after we applied our own key.
      expect(mgr.epoch).toBe(0);
      expect(sendsOfType(ws, "voice_e2ee_offer")).toHaveLength(0);
    } finally {
      vi.mocked(authStore.getState).mockImplementation(() => ({ user: { id: 1 } }) as never);
    }
  });

  it("[T-47] arms the periodic rotation timer for the key holder so forward secrecy actually fires", async () => {
    vi.useFakeTimers();
    try {
      const ws = { send: vi.fn() };
      const mgr = createManager(ws);
      await mgr.setupKeyExchange(true, 1);
      await mgr.handleAnnounce(PEER_ID, "cGVlcg==", "sig");
      ws.send.mockClear();

      await vi.advanceTimersByTimeAsync(5 * 60 * 1000);

      expect(mgr.epoch).toBe(2);
      expect(sendsOfType(ws, "voice_e2ee_offer")).toHaveLength(1);
    } finally {
      vi.useRealTimers();
    }
  });

  // ── Ledger findings OC-0209 / OC-0212 / OC-0213 ───────────────────────────

  it("[OC-0209] rejects a replayed retired-key announce before it overwrites the peer's verification badge", async () => {
    const ws = { send: vi.fn() };
    const mgr = createManager(ws);
    await mgr.setupKeyExchange(true, 1); // establishes our keypair, holder

    // Make import/export round-trip faithfully on the announced base64
    // string (the shared mock default returns a fixed constant regardless
    // of input, which would mask this bug).
    vi.mocked(importPublicKey).mockImplementation(
      async (b64: string) => ({ type: `peer-key-${b64}` }) as unknown as CryptoKey,
    );
    vi.mocked(exportPublicKey).mockImplementation(async (key: CryptoKey) =>
      (key as unknown as { type: string }).type.replace("peer-key-", ""),
    );

    const KEY_A = "b2xk";
    const KEY_B = "bmV3";

    try {
      // Peer announces key A, then a genuine key change to B — A is now retired.
      await mgr.handleAnnounce(PEER_ID, KEY_A, "sigA");
      await mgr.handleAnnounce(PEER_ID, KEY_B, "sigB");
      expect(mgr.peerPublicKeys.get(PEER_ID)).toEqual({ type: `peer-key-${KEY_B}` });

      vi.mocked(setPeerVerification).mockClear();

      // A malicious relay replays the old, still validly-signed announce for
      // the retired key A. The replay guard must reject it BEFORE any
      // verification write — a replay that reaches verifyPeerAnnounce first
      // would overwrite the peer's badge (status + sessionFingerprint) with
      // the retired key's, even though the guard then rejects the announce
      // and _peerPublicKeys is left untouched.
      await mgr.handleAnnounce(PEER_ID, KEY_A, "sigA");

      expect(setPeerVerification).not.toHaveBeenCalled();
      expect(mgr.peerPublicKeys.get(PEER_ID)).toEqual({ type: `peer-key-${KEY_B}` });
    } finally {
      vi.mocked(importPublicKey).mockImplementation(
        async () => ({ type: "public" }) as unknown as CryptoKey,
      );
      vi.mocked(exportPublicKey).mockImplementation(async () => "bW9ja2VwaGVtZXJhbA==");
    }
  });

  it("[OC-0212] replays the blocked announce after a successful re-pin, restoring the peer instead of leaving them un-keyed with the badge cleared", async () => {
    const ws = { send: vi.fn() };
    const mgr = createManager(ws);
    await mgr.setupKeyExchange(true, 1); // holder in channel 1

    const NEW_IDENTITY = "new-identity-key-b64";
    mockMembers.set(PEER_ID, { identityPublicKey: NEW_IDENTITY });

    // The peer reinstalled (new identity key). A still has them pinned to
    // their OLD identity key, so the announce under the new identity is
    // blocked as a TOFU mismatch.
    vi.mocked(getIdentityPin).mockResolvedValueOnce({
      status: "pinned",
      pin: "old-identity-key-b64",
    });
    await mgr.handleAnnounce(PEER_ID, "cGVlcg==", "sig");

    expect(mgr.peerPublicKeys.has(PEER_ID)).toBe(false);
    expect(setPeerVerification).toHaveBeenCalledWith(
      expect.objectContaining({ userId: PEER_ID, status: "mismatch" }),
    );
    vi.mocked(setPeerVerification).mockClear();

    // The user confirms the fingerprint out of band and re-pins to the
    // peer's new identity key.
    vi.mocked(getIdentityPin).mockResolvedValueOnce({ status: "pinned", pin: NEW_IDENTITY });
    const result = await mgr.rePinPeerIdentity(PEER_ID, NEW_IDENTITY);

    expect(result).toBe(true);
    // The blocked announce must be replayed against the new pin — not just
    // discarded with the badge cleared — so the peer actually re-enters
    // _peerPublicKeys and is offered the room key for the rest of the call.
    expect(mgr.peerPublicKeys.has(PEER_ID)).toBe(true);
    expect(setPeerVerification).toHaveBeenCalledWith(
      expect.objectContaining({ userId: PEER_ID, status: "verified" }),
    );
  });

  it("[OC-0213] does not permanently retire a peer's key on a stale voice_leave when the peer is still listed as present in the channel roster", async () => {
    const ws = { send: vi.fn() };
    const mgr = createManager(ws);
    await mgr.setupKeyExchange(true, 1); // holder in channel 1

    vi.mocked(importPublicKey).mockImplementation(
      async (b64: string) => ({ type: `peer-key-${b64}` }) as unknown as CryptoKey,
    );
    vi.mocked(exportPublicKey).mockImplementation(async (key: CryptoKey) =>
      (key as unknown as { type: string }).type.replace("peer-key-", ""),
    );

    const KEY = "b2xk";

    try {
      // The peer's rejoin announce (carrying a fresh key) arrives first —
      // the OC-0213 repro's reordering, where the directly-published
      // voice_e2ee_announce overtakes the still-queued voice_leave for the
      // join instance it superseded.
      await mgr.handleAnnounce(PEER_ID, KEY, "sig");
      expect(mgr.peerPublicKeys.has(PEER_ID)).toBe(true);

      // voice_state (the local roster) still lists the peer as present in
      // the channel — this is what tells apart a stale, lagging leave for a
      // superseded join instance from a genuine departure.
      mockVoiceState.voiceUsers.set(1, new Map([[PEER_ID, {}]]));

      // The stale voice_leave for the superseded join instance now arrives.
      await mgr.handleParticipantLeft(PEER_ID);

      // Removed from the live peer map (unchanged behavior)...
      expect(mgr.peerPublicKeys.has(PEER_ID)).toBe(false);

      // ...but the key must not be permanently retired: the peer's own,
      // still-valid re-announce of the SAME key must be accepted again, not
      // rejected as a replay of a "retired" key — otherwise the peer is
      // stranded, un-re-announceable, for the rest of the call.
      await mgr.handleAnnounce(PEER_ID, KEY, "sig");
      expect(mgr.peerPublicKeys.get(PEER_ID)).toEqual({ type: `peer-key-${KEY}` });
    } finally {
      vi.mocked(importPublicKey).mockImplementation(
        async () => ({ type: "public" }) as unknown as CryptoKey,
      );
      vi.mocked(exportPublicKey).mockImplementation(async () => "bW9ja2VwaGVtZXJhbA==");
    }
  });

  // OC-0239 tried to have the caller pass a pre-mutation "was this peer
  // still in the roster?" snapshot into handleParticipantLeft so a stale,
  // superseded-rejoin voice_leave (OC-0213) would not retire a peer's live
  // key. OC-0283 found that snapshot is true on every genuine departure too
  // — a departing peer's roster entry is only ever removed by the very
  // voice_leave event being handled, so a read taken relative to it, before
  // or after the mutation, cannot distinguish the two cases. Threading that
  // flag through therefore disabled the OC-0020 retirement defense in
  // production outright. The parameter is gone; this test now pins the
  // corrected behavior: a genuine departure retires the key even though the
  // roster looked "still present" right up until this event, matching what
  // production's real (post-mutation) roster read looks like.
  it("[OC-0283] retires a departed peer's key even though the roster listed them as present right up until this event", async () => {
    const ws = { send: vi.fn() };
    const mgr = createManager(ws);
    await mgr.setupKeyExchange(true, 1); // holder in channel 1

    vi.mocked(importPublicKey).mockImplementation(
      async (b64: string) => ({ type: `peer-key-${b64}` }) as unknown as CryptoKey,
    );
    vi.mocked(exportPublicKey).mockImplementation(async (key: CryptoKey) =>
      (key as unknown as { type: string }).type.replace("peer-key-", ""),
    );

    const KEY = "b2xk";

    try {
      await mgr.handleAnnounce(PEER_ID, KEY, "sig");
      expect(mgr.peerPublicKeys.has(PEER_ID)).toBe(true);

      // Reproduce dispatcher.ts's real VOICE_LEAVE ordering: removeVoiceUser()
      // deletes the peer from the roster BEFORE handleParticipantLeft runs, so
      // by the time this fires the local roster no longer lists them.
      mockVoiceState.voiceUsers.set(1, new Map()); // peer already removed from roster
      await mgr.handleParticipantLeft(PEER_ID);

      // Removed from the live peer map...
      expect(mgr.peerPublicKeys.has(PEER_ID)).toBe(false);

      // ...AND retired: a replay of the peer's old, still-validly-signed
      // announce must now be rejected, not accepted as if they never left.
      await mgr.handleAnnounce(PEER_ID, KEY, "sig");
      expect(mgr.peerPublicKeys.get(PEER_ID)).toBeUndefined();
    } finally {
      vi.mocked(importPublicKey).mockImplementation(
        async () => ({ type: "public" }) as unknown as CryptoKey,
      );
      vi.mocked(exportPublicKey).mockImplementation(async () => "bW9ja2VwaGVtZXJhbA==");
    }
  });

  it("[OC-0257] a stale (demoted) key holder must not send a doomed voice_e2ee_offer to a peer with a lower user id", async () => {
    const ws = { send: vi.fn() };
    const mgr = createManager(ws);
    // We are uid 10 and became holder while we were the lowest connected id.
    vi.mocked(authStore.getState).mockImplementation(() => ({ user: { id: 10 } }) as never);

    try {
      const ok = await mgr.setupKeyExchange(true, 1);
      expect(ok).toBe(true);
      ws.send.mockClear();

      // A peer with a LOWER user id (5) joins and announces. The server
      // always elects the lowest connected user id as holder
      // (Server/ws/voice_e2ee.go) and re-runs election on every join, but
      // sends no demotion message — voice_token_refresh's corrected
      // is_key_holder is thrown away too (handleVoiceTokenRefresh) — so our
      // stale _isKeyHolder flag would otherwise wrap the room key and send a
      // voice_e2ee_offer the server refuses with NOT_KEY_HOLDER, surfacing
      // as a spurious error toast for a call that is actually fine.
      await mgr.handleAnnounce(5, "cGVlcg==", "sig");

      expect(sendsOfType(ws, "voice_e2ee_offer")).toHaveLength(0);
    } finally {
      vi.mocked(authStore.getState).mockImplementation(() => ({ user: { id: 1 } }) as never);
    }
  });
});

// ── HP-2 question 4: adversarial membership and key-change rules ───────────
// Each test pins one rule from docs/trust-model.md §"What is end-to-end
// encrypted" that had no dedicated test before HP-2, or records a known gap
// so the fix has a RED waiting for it.

describe("E2EEManager — HP-2 adversarial membership and key-change rules", () => {
  beforeEach(() => {
    vi.clearAllMocks();
    mockMembers.clear();
    mockMembers.set(PEER_ID, { identityPublicKey: "peer-identity-b64" });
    mockVoiceState.voiceUsers.clear();
    vi.mocked(getIdentityPin).mockResolvedValue({ status: "unpinned" });
    vi.mocked(storeIdentityPin).mockResolvedValue("stored");
  });

  it("[HP-2 known gap] a modified server that adds an unknown member at first contact gets the room key wrapped to it", async () => {
    // Membership is server-controlled and the client accepts any first-sight
    // identity (verifyPeerAnnounce). A server that inserts a member row it
    // holds the identity key for, then relays a well-signed announce for it,
    // is keyed by the holder like any real peer. The client has no
    // independent membership evidence — the voice roster is server state
    // too, and here it does not even list the newcomer.
    //
    // This pins TODAY's behaviour. When authenticated membership (or
    // "refuse unrecognised participants") lands, this test goes RED and the
    // expectations below invert. docs/trust-model.md §"What beta does not
    // claim" names the gap.
    const INTRUDER = 99;
    mockMembers.set(INTRUDER, { identityPublicKey: "server-supplied-identity-b64" });
    expect(mockVoiceState.voiceUsers.size).toBe(0);
    const ws = { send: vi.fn() };
    const mgr = createManager(ws);
    await mgr.setupKeyExchange(true, 1);
    ws.send.mockClear();

    await mgr.handleAnnounce(INTRUDER, "aW50cnVkZXI=", "sig-the-server-can-make");

    // Today: keyed, pinned, verified — the RED of the desired rule (no offer,
    // no pin) is recorded in docs/plans/hp-2-scorecard-2026-08-29.md Q4.
    const offers = sendsOfType(ws, "voice_e2ee_offer");
    expect(offers).toHaveLength(1);
    expect((offers[0] as any).payload.target_user_id).toBe(INTRUDER);
    expect(mgr.peerPublicKeys.has(INTRUDER)).toBe(true);
    expect(storeIdentityPin).toHaveBeenCalledWith(
      "localhost:7880",
      String(INTRUDER),
      "server-supplied-identity-b64",
    );
    expect(setPeerVerification).toHaveBeenCalledWith(
      expect.objectContaining({ userId: INTRUDER, status: "verified" }),
    );
  });

  it("[HP-2] a second device's key, once trusted, overwrites the account pin — the first device then mismatches", async () => {
    // Pins are one per account ({host}:{userId}) while identity keys are per
    // install (identity.ts; migration 017 holds one identity_public_key per
    // user). Trusting device 2 therefore evicts device 1's pin, and device
    // 1's next announce is blocked as a mismatch. docs/trust-model.md
    // §"What is end-to-end encrypted" states the flip-flop; this pins it.
    const DEVICE1 = "device1-identity-b64";
    const DEVICE2 = "device2-identity-b64";
    vi.mocked(getIdentityPin).mockResolvedValue({ status: "pinned", pin: DEVICE1 });
    mockMembers.set(PEER_ID, { identityPublicKey: DEVICE2 }); // server row: last announcer wins
    const ws = { send: vi.fn() };
    const mgr = createManager(ws);
    await mgr.setupKeyExchange(true, 1);
    ws.send.mockClear();

    // Device 2 announces: pinned key differs → blocked, nothing wrapped.
    await mgr.handleAnnounce(PEER_ID, "ZGV2aWNlMg==", "sig2");
    expect(setPeerVerification).toHaveBeenLastCalledWith(
      expect.objectContaining({ userId: PEER_ID, status: "mismatch" }),
    );
    expect(sendsOfType(ws, "voice_e2ee_offer")).toHaveLength(0);

    // The human clicks "Trust new key": the ONE slot is overwritten and the
    // buffered announce replays against the new pin, so device 2 is keyed.
    vi.mocked(getIdentityPin).mockResolvedValue({ status: "pinned", pin: DEVICE2 });
    expect(await mgr.rePinPeerIdentity(PEER_ID, DEVICE2)).toBe(true);
    expect(storeIdentityPin).toHaveBeenCalledTimes(1);
    expect(storeIdentityPin).toHaveBeenCalledWith("localhost:7880", String(PEER_ID), DEVICE2);
    expect(sendsOfType(ws, "voice_e2ee_offer")).toHaveLength(1);
    const importsBefore = vi.mocked(importPublicKey).mock.calls.length;

    // Device 1 comes back (the server row carries its key again) and is the
    // one that mismatches now — no offer, no key imported, no second pin.
    mockMembers.set(PEER_ID, { identityPublicKey: DEVICE1 });
    await mgr.handleAnnounce(PEER_ID, "ZGV2aWNlMQ==", "sig1");
    expect(setPeerVerification).toHaveBeenLastCalledWith(
      expect.objectContaining({ userId: PEER_ID, status: "mismatch" }),
    );
    expect(sendsOfType(ws, "voice_e2ee_offer")).toHaveLength(1);
    expect(vi.mocked(importPublicKey).mock.calls.length).toBe(importsBefore);
    expect(storeIdentityPin).toHaveBeenCalledTimes(1);
  });

  it("[HP-2 / OC-0316] a peer whose socket dropped across a rotation is re-keyed with the rotated key when the server replays its announce", async () => {
    // Server side: hub.go re-relays the resumed client's stored announce to
    // the channel (TestRegisterNow_ReannouncesOwnKeyOnResume). Holder side,
    // pinned here: that replay is a duplicate announce, and the offer it
    // triggers must carry the CURRENT room key and epoch — not the key the
    // peer held before its outage.
    // Round-trip import/export so the replayed announce is recognised as the
    // SAME ephemeral key (the duplicate path), not a changed one.
    vi.mocked(importPublicKey).mockImplementation(
      async (b64: string) => ({ type: `peer-key-${b64}` }) as unknown as CryptoKey,
    );
    vi.mocked(exportPublicKey).mockImplementation(async (key: CryptoKey) =>
      (key as unknown as { type: string }).type.replace("peer-key-", ""),
    );
    try {
      const ws = { send: vi.fn() };
      const mgr = createManager(ws);
      await mgr.setupKeyExchange(true, 1);
      await mgr.handleAnnounce(PEER_ID, "cGVlcg==", "sig");
      expect(mgr.epoch).toBe(1);

      // Peer's WebSocket drops (media stays up, so no participant-left); the
      // periodic rotation fires meanwhile.
      await mgr.rotateKeyPeriodically();
      expect(mgr.epoch).toBe(2);
      const rotatedKey = (mgr as any)._roomKey as Uint8Array;
      ws.send.mockClear();
      vi.mocked(wrapRoomKey).mockClear();
      vi.mocked(importPublicKey).mockClear();

      // Peer resumes; the server replays its unchanged announce to us.
      await mgr.handleAnnounce(PEER_ID, "cGVlcg==", "sig");

      expect(importPublicKey).not.toHaveBeenCalled(); // duplicate, not a new key
      const offers = sendsOfType(ws, "voice_e2ee_offer");
      expect(offers).toHaveLength(1);
      expect((offers[0] as any).payload.target_user_id).toBe(PEER_ID);
      expect(wrapRoomKey).toHaveBeenCalledTimes(1);
      const [, , wrappedKey, wrappedEpoch] = vi.mocked(wrapRoomKey).mock.calls[0]!;
      expect(wrappedKey).toBe(rotatedKey);
      expect(wrappedEpoch).toBe(2);
    } finally {
      vi.mocked(importPublicKey).mockImplementation(
        async () => ({ type: "public" }) as unknown as CryptoKey,
      );
      vi.mocked(exportPublicKey).mockImplementation(async () => "bW9ja2VwaGVtZXJhbA==");
    }
  });
});
