easterbrooka/mike: Phase 2 crypto wired into live request path with dual-write rollout
easterbrooka flipped user API keys, model settings, and workflow-share records onto envelope-encrypted columns across every read and write path, with a dual-write strategy so rolling back to a previous image doesn't lose data during the soak.
Reads now prefer the ciphertext column and decrypt under the user's data encryption key (DEK), falling back to the legacy plaintext column for rows the backfill hasn't reached yet. Writes go to both: the sealed value lands in the new _ct column and the plaintext column is also written, keeping older images safe to run during the cutover window. The legacy columns stay populated until a follow-up migration drops them.
The shared-workflow lookups were the more involved part. Anywhere the code previously matched on plaintext email - access resolution, the shared-workflows list, the chat tool's workflow store, and the share-creation upsert - now keys off an HMAC index. The upsert's conflict target was updated to match. The share-listing endpoint keeps its response shape but decrypts when the ciphertext column is present.
A one-shot backfill script walks user profiles and workflow shares, mints DEKs as needed, and populates the ciphertext and HMAC columns idempotently against the production Supabase project. For startup safety, production builds now assert that KMS is configured and the email HMAC pepper is present - a misconfigured deploy will fail fast rather than silently using plaintext. A process-wide TenantCrypto singleton amortizes KMS Decrypt calls across requests via an in-process DEK cache; test code continues to instantiate per-call.
All 86 backend tests pass without modification. The existing crypto round-trip coverage and dual-path reads exercise both the ciphertext and plaintext branches.
Spotted something wrong? Or know the PR text has fresher detail than the writeup above?