Cortez E2EE Key Wrapping & Production Hardening

A series of targeted fixes harden the E2EE implementation — Cortez gets its own RSA-OAEP keypair, encrypted messages are excluded from AI history, and E2EE history survives page refreshes.

A series of tightly related fixes hardened the E2EE implementation after the initial ship.

Cortez Gets Its Own Keypair

The AI assistant Cortez was given a persistent RSA-OAEP keypair stored server-side (CORTEZ_PRIVATE_KEY env var). When a client initializes an E2EE room, it now wraps the room key for Cortez's public key in addition to the human members. This lets Cortez actually read and respond to encrypted messages — previously it was either replying blindly to ciphertext or staying silent.

Skip AI on Decrypt Failure

When Cortez's key bundle hasn't been set up yet, decryptForCortez returns null. The handler now returns early in that case rather than passing the raw base64 ciphertext string to OpenRouter — which was causing Cortez to generate bizarre replies "analyzing" the encrypted blob. A e2ee_key_needed event prompts the client to re-wrap the key.

Exclude Encrypted Messages from History

Ciphertext was being added to Cortez's conversation history, polluting the context it used to generate subsequent replies. Encrypted messages are now filtered out of the history array before it's passed to the AI.

Fix for Username Save

PATCH /auth/me was returning { user } without a success: true field, causing the frontend to treat a successful update as an error. Fixed with a one-line addition.

E2EE History Recovery

After a page refresh, the frontend was losing the decrypted history because it was fetching room_history before the locally-stored room key had been loaded from session storage. The load order was corrected so decryption happens before the history render.

E2EE Health Endpoint

GET /api/admin/e2ee-health returns Cortez's public key JWK and whether a private key is configured — useful for diagnosing E2EE issues in production.

User Profile Retheme

The user profile panel was restyled to match the room UI's purple palette.

Why it matters

Without these fixes, E2EE rooms were either non-functional or actively confusing. This sprint got the feature to a state where it worked correctly end-to-end in production.