-- t-paliad-199 / Fristen Phase 3 Slice 9 follow-up A — drop the legacy -- Pipeline-C source tables (paliad.event_deadlines + -- paliad.event_deadline_rule_codes) and the read-only trigger from -- mig 086, now that EventDeadlineService.Calculate has been rewritten -- to read from paliad.deadline_rules. -- -- Lorenz's Slice 9 (t-paliad-195) deferred this drop because the -- legacy service still SELECTed event_deadlines.duration_value / -- duration_unit / timing / notes / alt_* / combine_op. Slice 9 -- follow-up A refactors the service onto deadline_rules (the unified -- source-of-truth since Slice 3 / mig 085) and frees us to remove the -- old tables. -- -- Sequencing — every step in this single migration is required for the -- drop to be safe: -- -- 1. Snapshot both source tables into paliad.event_deadlines_pre_092 -- + paliad.event_deadline_rule_codes_pre_092 (CREATE TABLE IF NOT -- EXISTS — idempotent re-run). The snapshots persist after the -- drop as audit anchors; the down migration restores from them. -- 2. ADD COLUMN rule_codes text[] to paliad.deadline_rules and -- backfill from paliad.event_deadline_rule_codes. Pipeline-C -- deadlines carry multi-code rules (e.g. R.198 / R.213 carry -- [RoP.029.a, RoP.030]) which don't fit deadline_rules.rule_code -- (singular text); mig 085 left rule_code NULL on the 77 -- Pipeline-C rows. Without this backfill the drop would silently -- lose 72 RoP citations. -- 3. Hard assertion: every event_deadline_rule_codes row resolves to -- a deadline_rules row via the sequence_order = 1000 + -- event_deadlines.id convention from mig 085. If any row didn't -- land, fail loudly before dropping the source. -- 4. DROP TRIGGER + FUNCTION from mig 086 — orphan once the table is -- gone. -- 5. DROP TABLE paliad.event_deadline_rule_codes (FK side first). -- 6. DROP TABLE paliad.event_deadlines. -- 7. Final assertion: paliad.deadline_rules still carries >=77 active -- rows with trigger_event_id IS NOT NULL (the Slice 3 corpus must -- not have collapsed). -- -- audit_reason wrapper at top — the mig 079 trigger on -- paliad.deadline_rules logs every row-level edit. The ALTER TABLE + -- UPDATE on rule_codes fires through that trigger, so the reason -- persists in paliad.deadline_rule_audit for forever-grade audit. SELECT set_config( 'paliad.audit_reason', 'mig 092: drop paliad.event_deadlines + event_deadline_rule_codes after backfilling rule_codes into deadline_rules (t-paliad-199, Slice 9 follow-up A, design §3.E)', true); -- ============================================================================= -- 1. Backup snapshots — full row copies so the down migration can -- rebuild both tables byte-identically. CREATE TABLE IF NOT EXISTS -- keeps the migration idempotent across reapplications; if the -- snapshot already exists from a prior aborted run, we re-use it. -- ============================================================================= CREATE TABLE IF NOT EXISTS paliad.event_deadlines_pre_092 AS SELECT *, now() AS snapshotted_at FROM paliad.event_deadlines; COMMENT ON TABLE paliad.event_deadlines_pre_092 IS 'Snapshot of paliad.event_deadlines before mig 092 dropped it. ' 'Source-of-truth for the down migration; persists post-drop as the ' 'permanent audit record of the 77 Pipeline-C source rows that ' 'seeded paliad.deadline_rules via mig 085. Drop with a focused ' 'follow-up after Slice 9 is verified in prod (pair with ' 'paliad.deadline_rules_pre_091 cleanup).'; CREATE TABLE IF NOT EXISTS paliad.event_deadline_rule_codes_pre_092 AS SELECT *, now() AS snapshotted_at FROM paliad.event_deadline_rule_codes; COMMENT ON TABLE paliad.event_deadline_rule_codes_pre_092 IS 'Snapshot of paliad.event_deadline_rule_codes before mig 092 dropped ' 'it. Restored by the down migration; persists post-drop as the ' 'permanent audit record of the legacy RoP citations attached to ' 'Pipeline-C deadlines (72 rows across 70 of 77 deadlines).'; -- ============================================================================= -- 2. Add paliad.deadline_rules.rule_codes (text[]) and backfill it for -- the 77 Pipeline-C rules. Mig 085 set rule_code = NULL on every -- Pipeline-C row because deadline_rules.rule_code is singular and -- Pipeline-C deadlines can carry multiple citations. rule_codes -- holds the array form. Pipeline-A rules keep NULL here and continue -- using rule_code; this column is a Pipeline-C-only field today. -- ============================================================================= ALTER TABLE paliad.deadline_rules ADD COLUMN IF NOT EXISTS rule_codes text[]; COMMENT ON COLUMN paliad.deadline_rules.rule_codes IS 'Array of legal-rule citations attached to this deadline, in ' 'render order. Pipeline-C rules (event-rooted, trigger_event_id IS ' 'NOT NULL) populate this column from the legacy ' 'paliad.event_deadline_rule_codes junction (mig 092 backfill); ' 'Pipeline-A rules use the singular rule_code column instead. NULL ' 'on Pipeline-A rules + on the 7 Pipeline-C deadlines that had no ' 'junction rows pre-mig.'; -- Aggregate junction rows into a text[] sorted by (sort_order, -- rule_code) — matches the legacy ORDER BY contract that -- EventDeadlineService.loadRuleCodes used. -- -- Join key: the sequence_order = 1000 + event_deadlines.id convention -- mig 085 anchored. Every active event_deadlines.id has a corresponding -- deadline_rules row at sequence_order = 1000 + id; mig 085's hard -- assertion guarantees that. WITH agg AS ( SELECT event_deadline_id, array_agg(rule_code ORDER BY sort_order, rule_code) AS codes FROM paliad.event_deadline_rule_codes GROUP BY event_deadline_id ) UPDATE paliad.deadline_rules dr SET rule_codes = agg.codes FROM agg WHERE dr.trigger_event_id IS NOT NULL AND dr.sequence_order = 1000 + agg.event_deadline_id AND dr.rule_codes IS DISTINCT FROM agg.codes; -- ============================================================================= -- 3. Hard assertion: every junction row landed on a deadline_rules row. -- Sums elements across all rule_codes arrays — if the count differs -- from the source junction count, some event_deadline_id failed to -- match any deadline_rules row (sequence_order convention broken). -- Fail loudly here BEFORE dropping the source. -- ============================================================================= DO $$ DECLARE n_codes_src int; n_codes_target int; BEGIN SELECT count(*) INTO n_codes_src FROM paliad.event_deadline_rule_codes; SELECT COALESCE(SUM(array_length(rule_codes, 1)), 0) INTO n_codes_target FROM paliad.deadline_rules WHERE rule_codes IS NOT NULL; RAISE NOTICE 'mig 092: junction rows=%, backfilled rule_codes elements=%', n_codes_src, n_codes_target; IF n_codes_target < n_codes_src THEN RAISE EXCEPTION 'mig 092: rule_codes backfill missed % junction rows ' '(source=%, target=%) — sequence_order = 1000 + ed.id ' 'convention broken? Aborting before drop.', n_codes_src - n_codes_target, n_codes_src, n_codes_target; END IF; END $$; -- ============================================================================= -- 4. Drop the read-only trigger + function from mig 086. They're orphan -- once paliad.event_deadlines goes away — explicit drop documents -- that the wrapper's job is done, and keeps the symmetric reverse in -- the down migration cleanly readable. -- ============================================================================= DROP TRIGGER IF EXISTS event_deadlines_readonly ON paliad.event_deadlines; DROP FUNCTION IF EXISTS paliad.event_deadlines_readonly_trigger(); -- ============================================================================= -- 5. Drop the legacy tables. Order: junction first (it has a FK to -- event_deadlines), then the parent. Explicit ordering is clearer -- than relying on CASCADE and mirrors the down migration's CREATE -- sequence. -- ============================================================================= DROP TABLE IF EXISTS paliad.event_deadline_rule_codes; DROP TABLE IF EXISTS paliad.event_deadlines; -- ============================================================================= -- 6. Final assertion: the unified Pipeline-C corpus is still intact. -- Mig 085 moved 77 active rows; future hand-edited Pipeline-C rules -- can only raise the count. A drop below 77 means the upstream -- deadline_rules data was clobbered while this migration ran and -- the deploy must abort. -- ============================================================================= DO $$ DECLARE n_unified int; BEGIN SELECT count(*) INTO n_unified FROM paliad.deadline_rules WHERE trigger_event_id IS NOT NULL AND is_active = true; RAISE NOTICE 'mig 092: post-drop Pipeline-C rule count = %', n_unified; IF n_unified < 77 THEN RAISE EXCEPTION 'mig 092: Pipeline-C corpus collapsed — expected >=77 ' 'active deadline_rules with trigger_event_id IS NOT NULL, got %', n_unified; END IF; END $$;