mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-08 04:32:42 +00:00
Key rotation re-sealed a single-value backup in place, which holds the stored string, its bytes, the plaintext and the new ciphertext at once; a copy can be over a hundred megabytes and the in-app rotation walked 25 of them per page, enough to run a 1 GB container out of memory. Rotation now converts each such copy into pieces under the active key instead, one row at a time. The ~hlgcm1. stream of v1.38.6 to v1.39.1 is read a megabyte at a time with its tag checked at the end, inside one transaction, so a copy that fails the check is left untouched; the copy keeps its date. The scan reads only the first characters of each value. Measured on a copy of a 2.6 million-reading account (126 MB stored): converted in 25 s with the heap flat at 57 MB under a 200 MB limit, where re-sealing in place ran out of heap. The weekly and manual backup leave an account alone while a restore of its current copy is queued or running, and count it as skipped, not failed. A reader whose copy is replaced mid-read answers 409 backup_changed, "replaced by a newer one while it was being read", rather than the undecryptable error; a restore rolls back with the same code. Pieces are sealed with a fixed associated-data label, so a value the same key sealed for another purpose never opens as a piece; rotation re-seals under the same label. A row holding both a single value and pieces, which only an older release writing after a downgrade produces, is refused with a message saying so, and the next weekly run replaces it. Rotation treats a row deleted between its read and its write as gone rather than an error.
prisma/
The database layer. HealthLog runs PostgreSQL 16 via Prisma 7. This directory holds the single schema file and the forward-only migration history; the generated client is emitted elsewhere.
Contents
schema.prisma— 61 models, one file.cuid()primary keys,snake_casecolumns via@map(...), encrypted columns mostly suffixed*Encrypted(search the file; there are 24 encrypted column references). Thegenerator clientblock emits to../src/generated/prisma(imported as@/generated/prisma).migrations/— numbered, forward-only SQL migrations (0001_…upward).migration_lock.tomlpins the provider topostgresql.
Migration workflow
# author a migration from a schema edit
pnpm dlx prisma migrate dev --name <short_snake_case_name>
# regenerate the client after any schema change
pnpm dlx prisma generate
Number a new migration with the next free prefix. The production entrypoint runs prisma migrate deploy on boot, so every migration must apply cleanly forward — there is no down-migration path. Always include the migration file in the same change as the schema edit.
Conventions
- Encryption is fail-closed:
*Encryptedcolumns are written through@/lib/crypto(AES-256-GCM, versioned key ids); never store plaintext in an encrypted column. Key rotation isscripts/rotate-encryption-key.ts+docs/ops/encryption-key-rotation.md. - The generated client under
src/generated/prismais build output — do not edit it. - Schema-touching changes that affect the API surface must regenerate
docs/api/openapi.yaml(see../CLAUDE.md).
Related: ../src/lib/README.md (db.ts, rollups/), ../docs/ops/ (migrations + backup runbooks).