decryptField in src/lib/encryption.ts documents the key-rotation flow: old ciphertext keeps decrypting under a retired key (looked up via keyById) while new writes use the active (first) key. There is no code anywhere in this module (or, per the earlier exploration of webhookSubscriberRepository.ts, in its consumers) that tracks or reports how much stored ciphertext still references a given retired kid, so an operator has no built-in way to confirm it's actually safe to remove an old key from FIELD_ENCRYPTION_KEYS — doing so prematurely causes decryptField to throw DecryptionError: No field encryption key configured for key id "..." for every row still encrypted under it (as the docstring on line 226-229 explicitly warns can happen). Add an audit query/report that counts rows still encrypted under each known kid before a key is removed from configuration.
decryptFieldinsrc/lib/encryption.tsdocuments the key-rotation flow: old ciphertext keeps decrypting under a retired key (looked up viakeyById) while new writes use the active (first) key. There is no code anywhere in this module (or, per the earlier exploration ofwebhookSubscriberRepository.ts, in its consumers) that tracks or reports how much stored ciphertext still references a given retiredkid, so an operator has no built-in way to confirm it's actually safe to remove an old key fromFIELD_ENCRYPTION_KEYS— doing so prematurely causesdecryptFieldto throwDecryptionError: No field encryption key configured for key id "..."for every row still encrypted under it (as the docstring on line 226-229 explicitly warns can happen). Add an audit query/report that counts rows still encrypted under each knownkidbefore a key is removed from configuration.