feat(t-paliad-064): migration 025 reminder redesign schema (PR-2)
Schema additions for the new digest-style reminder system: paliad.users - reminder_warning_offset_days INT NOT NULL DEFAULT 7, range 1..30 Per-user customisation of how many days before each deadline the heads-up email fires. 7 matches the prior Monday-weekly behaviour. - escalation_contact_id UUID NULL FK paliad.users(id) ON DELETE SET NULL Optional override of the escalation channel for overdue / DRINGEND mail. NULL means "fall back to global_admins". UI dropdown deferred to a follow-up task per m's 2026-04-28 decision; column ships now to avoid a second migration. Self-reference forbidden by CHECK constraint. paliad.reminder_log - slot TEXT NULL, slot_date DATE NULL — digest dedup keys. - reminder_type CHECK widened to admit 'morning_digest' / 'evening_digest' alongside the legacy 'overdue' / 'tomorrow' / 'weekly' values. - Partial UNIQUE INDEX (user_id, slot, slot_date) WHERE slot IS NOT NULL enforces "one digest per user per slot per local-date". Legacy rows with slot IS NULL are unaffected. CLAUDE.md updated with a §Phase status note pointing to the design doc and explaining the deferred Settings-UI dropdown for escalation_contact_id. Migration is fully additive and idempotent (IF NOT EXISTS / DROP-then-ADD on named constraints). Down migration reverses the schema cleanly; any 'morning_digest' / 'evening_digest' rows must be deleted before downgrading.
This commit is contained in:
@@ -66,6 +66,10 @@ Phases A–G shipped (April 2026): schema + RLS, services, Fristenrechner→DB,
|
||||
|
||||
Phase J (this doc + roadmap rewrite + KanzlAI doc retirement notes) completed 2026-04-17 on `mai/ritchie/phase-j-roadmap-rewrite`. Infra retirement (KanzlAI Dokploy shutdown, `kanzlai` schema drop, Gitea archive) still pending m + head coordination.
|
||||
|
||||
**Reminder system redesign (t-paliad-064)** — landed 2026-04-28 across PR-1..PR-4 on `mai/cronus/reminder-system-redesign`. Zero-overdue SLO model: per-user bundled morning/evening digests with category sections (überfällig / heute / diese Woche), DRINGEND escalation in the evening slot, and global-admin escalation framing on overdues. See `docs/design-reminder-redesign-2026-04-28.md`.
|
||||
|
||||
> *Open follow-up:* migration 025 ships `paliad.users.escalation_contact_id` (FK to `paliad.users`, nullable, ON DELETE SET NULL). NULL means "fall back to global_admins for the escalation channel"; setting it lets a user designate a specific colleague as their escalation contact instead. The Settings → Notifications dropdown to expose this is **deferred** to a follow-up task (per m's 2026-04-28 decision). Until that ships, the column is read-only — set it via direct SQL if a user requests an explicit override.
|
||||
|
||||
## Worker Preferences
|
||||
|
||||
- Use **Opus** for design/architecture decisions
|
||||
|
||||
31
internal/db/migrations/025_reminder_redesign.down.sql
Normal file
31
internal/db/migrations/025_reminder_redesign.down.sql
Normal file
@@ -0,0 +1,31 @@
|
||||
-- Roll back t-paliad-064 reminder redesign schema.
|
||||
--
|
||||
-- Drops the partial dedup index, the new columns on paliad.users and
|
||||
-- paliad.reminder_log, and restores the previous reminder_type CHECK
|
||||
-- constraint. Reversible.
|
||||
|
||||
DROP INDEX IF EXISTS paliad.reminder_log_slot_dedup_idx;
|
||||
|
||||
ALTER TABLE paliad.reminder_log
|
||||
DROP CONSTRAINT IF EXISTS reminder_log_slot_check;
|
||||
|
||||
-- Narrow the reminder_type CHECK back to the original three values. Any
|
||||
-- 'morning_digest' / 'evening_digest' rows must be deleted before this
|
||||
-- runs, or the ALTER fails.
|
||||
ALTER TABLE paliad.reminder_log
|
||||
DROP CONSTRAINT IF EXISTS reminder_log_reminder_type_check;
|
||||
ALTER TABLE paliad.reminder_log
|
||||
ADD CONSTRAINT reminder_log_reminder_type_check
|
||||
CHECK (reminder_type IN ('overdue', 'tomorrow', 'weekly'));
|
||||
|
||||
ALTER TABLE paliad.reminder_log
|
||||
DROP COLUMN IF EXISTS slot,
|
||||
DROP COLUMN IF EXISTS slot_date;
|
||||
|
||||
ALTER TABLE paliad.users
|
||||
DROP CONSTRAINT IF EXISTS users_escalation_contact_self_check,
|
||||
DROP CONSTRAINT IF EXISTS users_reminder_warning_offset_check;
|
||||
|
||||
ALTER TABLE paliad.users
|
||||
DROP COLUMN IF EXISTS escalation_contact_id,
|
||||
DROP COLUMN IF EXISTS reminder_warning_offset_days;
|
||||
87
internal/db/migrations/025_reminder_redesign.up.sql
Normal file
87
internal/db/migrations/025_reminder_redesign.up.sql
Normal file
@@ -0,0 +1,87 @@
|
||||
-- Reminder system redesign (t-paliad-064).
|
||||
--
|
||||
-- Three things land here, in service of the new "zero-overdue SLO" model:
|
||||
--
|
||||
-- 1. paliad.users gets two new columns:
|
||||
-- * reminder_warning_offset_days — how many days before each deadline
|
||||
-- the +N-day warning email fires. Default 7 matches the prior
|
||||
-- Monday-weekly-digest behaviour. Per-user customisable on the
|
||||
-- Settings → Notifications page.
|
||||
-- * escalation_contact_id — optional pointer to another paliad.users
|
||||
-- row that should also be cc'd on overdue / DRINGEND emails. NULL
|
||||
-- means "fall back to global_admins" (the default escalation
|
||||
-- channel). The Settings UI dropdown is a follow-up task; the
|
||||
-- column ships now to avoid a second migration when wiring lands.
|
||||
--
|
||||
-- 2. paliad.reminder_log gains slot + slot_date for digest dedup. The new
|
||||
-- service writes one row per (user_id, slot, slot_date), enforced by a
|
||||
-- partial UNIQUE index. Legacy per-deadline rows (slot IS NULL) coexist
|
||||
-- and are ignored by the new dedup index — they're kept on disk for
|
||||
-- audit and a separate housekeeping migration will prune them once the
|
||||
-- new path has soaked.
|
||||
--
|
||||
-- 3. The reminder_type CHECK constraint widens to admit the two new
|
||||
-- digest values ('morning_digest', 'evening_digest'). Existing values
|
||||
-- ('overdue', 'tomorrow', 'weekly') stay valid so the legacy code path
|
||||
-- can run alongside during deploy if needed.
|
||||
--
|
||||
-- All operations are idempotent (IF NOT EXISTS on columns and index;
|
||||
-- DROP/RE-ADD on the named constraints) so a re-run is safe.
|
||||
|
||||
-- 1) Per-user warning offset (default 7).
|
||||
ALTER TABLE paliad.users
|
||||
ADD COLUMN IF NOT EXISTS reminder_warning_offset_days
|
||||
INT NOT NULL DEFAULT 7;
|
||||
|
||||
-- Range check: 1..30. A 0-day "warning" is just the same as the morning
|
||||
-- reminder, and 30+ days is so far out the email becomes noise.
|
||||
ALTER TABLE paliad.users
|
||||
DROP CONSTRAINT IF EXISTS users_reminder_warning_offset_check;
|
||||
ALTER TABLE paliad.users
|
||||
ADD CONSTRAINT users_reminder_warning_offset_check
|
||||
CHECK (reminder_warning_offset_days BETWEEN 1 AND 30);
|
||||
|
||||
-- 2) Optional escalation contact. ON DELETE SET NULL so deleting the
|
||||
-- chosen escalation user doesn't cascade-delete the source user — they
|
||||
-- just lose their override and fall back to global_admins.
|
||||
ALTER TABLE paliad.users
|
||||
ADD COLUMN IF NOT EXISTS escalation_contact_id UUID
|
||||
REFERENCES paliad.users(id) ON DELETE SET NULL;
|
||||
|
||||
-- A user can't be their own escalation contact (would just bounce mail
|
||||
-- back to themselves on overdue).
|
||||
ALTER TABLE paliad.users
|
||||
DROP CONSTRAINT IF EXISTS users_escalation_contact_self_check;
|
||||
ALTER TABLE paliad.users
|
||||
ADD CONSTRAINT users_escalation_contact_self_check
|
||||
CHECK (escalation_contact_id IS NULL OR escalation_contact_id <> id);
|
||||
|
||||
-- 3) Slot-based dedup on reminder_log.
|
||||
ALTER TABLE paliad.reminder_log
|
||||
ADD COLUMN IF NOT EXISTS slot TEXT,
|
||||
ADD COLUMN IF NOT EXISTS slot_date DATE;
|
||||
|
||||
-- slot must be 'morning' | 'evening' when present (NULL = legacy row).
|
||||
ALTER TABLE paliad.reminder_log
|
||||
DROP CONSTRAINT IF EXISTS reminder_log_slot_check;
|
||||
ALTER TABLE paliad.reminder_log
|
||||
ADD CONSTRAINT reminder_log_slot_check
|
||||
CHECK (slot IS NULL OR slot IN ('morning', 'evening'));
|
||||
|
||||
-- Widen the reminder_type CHECK to admit the new digest values. Keeps the
|
||||
-- legacy values valid so coexistence during deploy is harmless.
|
||||
ALTER TABLE paliad.reminder_log
|
||||
DROP CONSTRAINT IF EXISTS reminder_log_reminder_type_check;
|
||||
ALTER TABLE paliad.reminder_log
|
||||
ADD CONSTRAINT reminder_log_reminder_type_check
|
||||
CHECK (reminder_type IN (
|
||||
'overdue', 'tomorrow', 'weekly',
|
||||
'morning_digest', 'evening_digest'
|
||||
));
|
||||
|
||||
-- Partial unique index — one digest row per (user, slot, local-date).
|
||||
-- Partial on slot IS NOT NULL so legacy rows (which have NULL slot) don't
|
||||
-- conflict with the new model.
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS reminder_log_slot_dedup_idx
|
||||
ON paliad.reminder_log (user_id, slot, slot_date)
|
||||
WHERE slot IS NOT NULL;
|
||||
Reference in New Issue
Block a user