import { randomBytes } from "node:crypto"; import { KeyAlreadyExistsError, KeyErasedError, KeyNotFoundError, type KmsHealth, type LocalKeyKmsAdapter, type SubjectDek, type SubjectId, type SubjectKey, subjectIdToKey, } from "./kms-adapter"; interface KeyEntry { key: Buffer | null; erased: boolean; } // Non-persistent adapter for tests and dev mode. Erased entries stay as // tombstones so the create-after-erase contract holds within a process. export class InMemoryKmsAdapter implements LocalKeyKmsAdapter { readonly capabilities = { mode: "local-key" } as const; private readonly keys = new Map(); async createKey(subject: SubjectId): Promise { const subjectKey = subjectIdToKey(subject); if (this.keys.has(subjectKey)) throw new KeyAlreadyExistsError(subject); this.keys.set(subjectKey, { key: randomBytes(32), erased: false }); } async getKey(subject: SubjectId): Promise { const entry = this.keys.get(subjectIdToKey(subject)); if (!entry) throw new KeyNotFoundError(subject); if (entry.erased || entry.key === null) throw new KeyErasedError(subject); return entry.key; } async eraseKey(subject: SubjectId): Promise { const entry = this.keys.get(subjectIdToKey(subject)); // skip: eraseKey is contractually idempotent — unknown subject is a no-op if (!entry) return; entry.key = null; entry.erased = true; } async health(): Promise { return { ok: true, latencyMs: 0 }; } }