Problem
MedicationTake is readonly in Active Record, but direct SQL and bulk APIs can bypass model callbacks. PostgreSQL must reject those writes while remaining compatible with the existing shared CloudNativePG login and mixed historical object ownership.
Acceptance criteria
- Revoke UPDATE and DELETE on
medication_takes from med_tracker_app.
- Add an enabled BEFORE UPDATE OR DELETE trigger with a stable failure code.
- Reject every DELETE and every clinical or multi-column UPDATE through the application/shared login, direct SQL, and bulk Active Record APIs. Keep only the explicitly validated legacy repair operations.
- Preserve INSERT for the normal dose-recording boundary.
- Keep only the two approved legacy metadata repairs: nil-to-household backfill and location remapping.
- Implement those repairs as owner-only SECURITY DEFINER functions with fixed search paths, strict household/source/location validation, transaction-local RLS context, immutable audit evidence, and no public or app EXECUTE grant.
- Preserve FORCE RLS with a narrowly scoped owner repair policy; do not grant BYPASSRLS or disable RLS.
- Route HistoricalDataMigration through the narrow functions.
- Add failing integration coverage for raw SQL, bulk APIs, privileges, role escalation, trigger shape, RLS, repair validation, audit atomicity, and idempotency.
Compatibility
This work is no longer blocked by #1662. Implement against the shared login restored by #1695. Do not require separate runtime, migration, or auxiliary credentials; do not force DATABASE_ROLE=med_tracker_owner; and do not require staged ownership conversion. The cluster superuser remains the ultimate trust boundary.
Parent: #1627
Problem
MedicationTake is readonly in Active Record, but direct SQL and bulk APIs can bypass model callbacks. PostgreSQL must reject those writes while remaining compatible with the existing shared CloudNativePG login and mixed historical object ownership.
Acceptance criteria
medication_takesfrommed_tracker_app.Compatibility
This work is no longer blocked by #1662. Implement against the shared login restored by #1695. Do not require separate runtime, migration, or auxiliary credentials; do not force
DATABASE_ROLE=med_tracker_owner; and do not require staged ownership conversion. The cluster superuser remains the ultimate trust boundary.Parent: #1627