feat(t-paliad-184): mig 085 — Pipeline C data-move (77 rows)
Phase 3 Slice 3 Step C (design §3.C). INSERT 77 active rows from
paliad.event_deadlines into paliad.deadline_rules so the unified
backend can serve both pipelines. Source rows preserved (mig 086
wraps the source table in a read-only trigger; Slice 9 drops it).
Mapping:
trigger_event_id ← event_deadlines.trigger_event_id (bigint, mig 028)
name (DE, NOT NULL) ← event_deadlines.title_de (NOT NULL DEFAULT '')
name_en (NOT NULL) ← event_deadlines.title (EN, NOT NULL)
duration_value / unit ← event_deadlines.duration_value / unit
timing ← event_deadlines.timing (before / after)
alt_duration_value / unit ← event_deadlines.alt_duration_*
combine_op ← event_deadlines.combine_op (mig 078 column)
deadline_notes (DE) ← event_deadlines.notes (DE; NULLIF '' so empty
stays NULL on dr side)
deadline_notes_en ← event_deadlines.notes_en (mig 036)
legal_source ← event_deadlines.legal_source
published_at ← event_deadlines.created_at (chronological audit)
sequence_order = 1000 + ed.id (large offset so Pipeline-C rules
sort after any hand-authored
Pipeline-A sequence_orders; preserves
source ordering within Pipeline C)
lifecycle_state = 'published' / priority = 'mandatory' / is_active = ed.is_active
Pipeline-A-only fields stay NULL on the new rows: proceeding_type_id,
parent_id, spawn_proceeding_type_id, code, primary_party, event_type,
condition_expr, condition_flag. is_court_set = false (no court-set
rules in the Pipeline-C corpus today; legal-review pass can flip
Zustellung-* later via a separate slice).
Idempotency: WHERE NOT EXISTS guard on (trigger_event_id, name).
Re-running the migration is a no-op.
Hard assertion at end: COUNT(deadline_rules WHERE trigger_event_id
IS NOT NULL) must equal COUNT(event_deadlines WHERE is_active=true)
post-mig. RAISE EXCEPTION on mismatch — better to fail the migration
loudly than to ship a partial Pipeline-C corpus and poison Slice 4.
Audit-reason set via set_config so the mig 079 trigger writes 77
paliad.deadline_rule_audit rows with the design §3.C citation
preserved as the rationale. That's the persistent compliance trail
for the data-move.
No mandatory bool on event_deadlines (the head instruction sketch
suggested mapping it; the schema doesn't have one) — Pipeline-C
rules default priority='mandatory', consistent with the statutory
nature of the corpus.
This commit is contained in:
17
internal/db/migrations/085_pipeline_c_data_move.down.sql
Normal file
17
internal/db/migrations/085_pipeline_c_data_move.down.sql
Normal file
@@ -0,0 +1,17 @@
|
||||
-- t-paliad-184 down — reverts the Pipeline-C data-move from
|
||||
-- 085_pipeline_c_data_move.up.sql. Deletes every paliad.deadline_rules
|
||||
-- row carrying a non-NULL trigger_event_id (those are exactly the rows
|
||||
-- the up-migration created — before mig 085 no Pipeline-A rule ever
|
||||
-- carried trigger_event_id, and Slice 9 hasn't dropped the source
|
||||
-- table yet so the rows can be regenerated).
|
||||
--
|
||||
-- Audit-reason set so the mig 079 trigger captures the rollback
|
||||
-- rationale and doesn't raise on DELETE.
|
||||
|
||||
SELECT set_config(
|
||||
'paliad.audit_reason',
|
||||
'rollback 085: delete Pipeline-C unified rows (source preserved in event_deadlines)',
|
||||
true);
|
||||
|
||||
DELETE FROM paliad.deadline_rules
|
||||
WHERE trigger_event_id IS NOT NULL;
|
||||
184
internal/db/migrations/085_pipeline_c_data_move.up.sql
Normal file
184
internal/db/migrations/085_pipeline_c_data_move.up.sql
Normal file
@@ -0,0 +1,184 @@
|
||||
-- t-paliad-184 / Fristen Phase 3 Slice 3 Step C — data-move 77 rows
|
||||
-- from paliad.event_deadlines → paliad.deadline_rules so the Phase-3
|
||||
-- unified backend can serve both pipelines.
|
||||
--
|
||||
-- Source rows are PRESERVED (mig 086's read-only trigger blocks
|
||||
-- further writes; mig 090 in Slice 9 drops the table once every
|
||||
-- caller has cut over). The data-move is one-way; legacy callers
|
||||
-- continue reading event_deadlines via plain SELECTs until Slice 9.
|
||||
--
|
||||
-- Mapping (per design §3.C):
|
||||
--
|
||||
-- paliad.event_deadlines → paliad.deadline_rules
|
||||
-- ------------------------- ----------------------
|
||||
-- id (new gen_random_uuid())
|
||||
-- trigger_event_id trigger_event_id (Phase 3 column from mig 078)
|
||||
-- title (EN, NOT NULL) name_en (NOT NULL)
|
||||
-- title_de (DE, NOT NULL DEFAULT '') name (NOT NULL — every row has non-empty title_de in live data)
|
||||
-- duration_value duration_value
|
||||
-- duration_unit (days/weeks/months/working_days) duration_unit
|
||||
-- timing (before/after) timing
|
||||
-- notes (DE) deadline_notes (DE)
|
||||
-- notes_en (EN, nullable) deadline_notes_en (EN, nullable)
|
||||
-- alt_duration_value alt_duration_value
|
||||
-- alt_duration_unit alt_duration_unit
|
||||
-- combine_op (max/min, nullable) combine_op (Phase 3 column from mig 078)
|
||||
-- legal_source legal_source
|
||||
-- is_active is_active
|
||||
-- created_at published_at (preserves chronology — lifecycle_state='published' on every row)
|
||||
-- updated_at = now() (this is the publish event)
|
||||
--
|
||||
-- Pipeline-A-only fields default:
|
||||
-- proceeding_type_id = NULL (event-rooted, no proceeding)
|
||||
-- parent_id = NULL (Pipeline C is flat, no chain)
|
||||
-- spawn_proceeding_type_id = NULL (no spawn)
|
||||
-- code = NULL (no local rule code in Pipeline C)
|
||||
-- primary_party = NULL (event_deadlines has no party column)
|
||||
-- event_type = NULL (filing/hearing/decision is a
|
||||
-- Pipeline-A category)
|
||||
-- is_court_set = false (no court-set Pipeline-C rules
|
||||
-- in the corpus; legal-review
|
||||
-- pass can flip Zustellung-* if
|
||||
-- those ever land here)
|
||||
-- is_spawn = false
|
||||
-- is_mandatory = true (Pipeline C has no mandatory
|
||||
-- bool; design §2.3 says default
|
||||
-- 'mandatory' is correct for
|
||||
-- statutory event-driven deadlines)
|
||||
-- is_optional = false
|
||||
-- priority = 'mandatory'
|
||||
-- condition_expr = NULL (Pipeline C has no flag gating)
|
||||
-- condition_flag = NULL
|
||||
-- sequence_order = 1000 + event_deadlines.id
|
||||
-- (large offset so Pipeline-C
|
||||
-- rows sort AFTER any future
|
||||
-- hand-edited Pipeline-A
|
||||
-- sequence_orders without
|
||||
-- colliding with the
|
||||
-- existing 0–171 range)
|
||||
-- lifecycle_state = 'published'
|
||||
--
|
||||
-- Idempotency: WHERE NOT EXISTS guard on (trigger_event_id, name) skips
|
||||
-- rows that already exist in deadline_rules. Re-running the migration
|
||||
-- is a no-op.
|
||||
--
|
||||
-- Hard assertion at end: COUNT(deadline_rules WHERE trigger_event_id
|
||||
-- IS NOT NULL) == COUNT(event_deadlines WHERE is_active = true) (77 = 77).
|
||||
-- RAISE EXCEPTION on mismatch so a partial move fails the migration
|
||||
-- loudly instead of poisoning Slice 4.
|
||||
--
|
||||
-- Audit-reason cites design §3.C — the rationale persists in the
|
||||
-- paliad.deadline_rule_audit log forever via the mig 079 trigger.
|
||||
|
||||
SELECT set_config(
|
||||
'paliad.audit_reason',
|
||||
'pipeline C migration 085: data-move event_deadlines → deadline_rules per design §3.C — '
|
||||
|| 'preserves source rows; mig 086 wraps the source table read-only',
|
||||
true);
|
||||
|
||||
INSERT INTO paliad.deadline_rules (
|
||||
id,
|
||||
proceeding_type_id,
|
||||
parent_id,
|
||||
trigger_event_id,
|
||||
spawn_proceeding_type_id,
|
||||
code,
|
||||
name,
|
||||
name_en,
|
||||
primary_party,
|
||||
event_type,
|
||||
is_mandatory,
|
||||
is_optional,
|
||||
is_court_set,
|
||||
is_spawn,
|
||||
duration_value,
|
||||
duration_unit,
|
||||
timing,
|
||||
alt_duration_value,
|
||||
alt_duration_unit,
|
||||
combine_op,
|
||||
rule_code,
|
||||
deadline_notes,
|
||||
deadline_notes_en,
|
||||
legal_source,
|
||||
condition_expr,
|
||||
condition_flag,
|
||||
sequence_order,
|
||||
is_active,
|
||||
priority,
|
||||
lifecycle_state,
|
||||
draft_of,
|
||||
published_at,
|
||||
created_at,
|
||||
updated_at
|
||||
)
|
||||
SELECT
|
||||
gen_random_uuid() AS id,
|
||||
NULL::integer AS proceeding_type_id,
|
||||
NULL::uuid AS parent_id,
|
||||
ed.trigger_event_id AS trigger_event_id,
|
||||
NULL::integer AS spawn_proceeding_type_id,
|
||||
NULL::text AS code,
|
||||
ed.title_de AS name,
|
||||
ed.title AS name_en,
|
||||
NULL::text AS primary_party,
|
||||
NULL::text AS event_type,
|
||||
true AS is_mandatory,
|
||||
false AS is_optional,
|
||||
false AS is_court_set,
|
||||
false AS is_spawn,
|
||||
ed.duration_value AS duration_value,
|
||||
ed.duration_unit AS duration_unit,
|
||||
ed.timing AS timing,
|
||||
ed.alt_duration_value AS alt_duration_value,
|
||||
ed.alt_duration_unit AS alt_duration_unit,
|
||||
ed.combine_op AS combine_op,
|
||||
NULL::text AS rule_code,
|
||||
NULLIF(ed.notes, '') AS deadline_notes,
|
||||
ed.notes_en AS deadline_notes_en,
|
||||
ed.legal_source AS legal_source,
|
||||
NULL::jsonb AS condition_expr,
|
||||
NULL::text[] AS condition_flag,
|
||||
(1000 + ed.id)::integer AS sequence_order,
|
||||
ed.is_active AS is_active,
|
||||
'mandatory' AS priority,
|
||||
'published' AS lifecycle_state,
|
||||
NULL::uuid AS draft_of,
|
||||
ed.created_at AS published_at,
|
||||
ed.created_at AS created_at,
|
||||
now() AS updated_at
|
||||
FROM paliad.event_deadlines ed
|
||||
WHERE ed.is_active = true
|
||||
AND NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM paliad.deadline_rules dr
|
||||
WHERE dr.trigger_event_id = ed.trigger_event_id
|
||||
AND dr.name = ed.title_de
|
||||
);
|
||||
|
||||
-- Hard assertion: every active event_deadlines row must have a matching
|
||||
-- deadline_rules row by (trigger_event_id, name). If the counts diverge,
|
||||
-- something in the WHERE NOT EXISTS clause (likely a stale duplicate)
|
||||
-- prevented a real insert — fail the migration rather than ship a
|
||||
-- partial Pipeline-C corpus.
|
||||
DO $$
|
||||
DECLARE
|
||||
n_source int;
|
||||
n_target int;
|
||||
BEGIN
|
||||
SELECT count(*) INTO n_source
|
||||
FROM paliad.event_deadlines WHERE is_active = true;
|
||||
|
||||
SELECT count(*) INTO n_target
|
||||
FROM paliad.deadline_rules WHERE trigger_event_id IS NOT NULL;
|
||||
|
||||
RAISE NOTICE 'mig 085: event_deadlines(active)=%, deadline_rules(trigger_event_id IS NOT NULL)=%',
|
||||
n_source, n_target;
|
||||
|
||||
IF n_target <> n_source THEN
|
||||
RAISE EXCEPTION 'mig 085: data-move incomplete — expected % unified rows, got %. '
|
||||
'Investigate event_deadlines (trigger_event_id, title_de) duplicates '
|
||||
'OR re-applied migration on dirtied target.',
|
||||
n_source, n_target;
|
||||
END IF;
|
||||
END $$;
|
||||
Reference in New Issue
Block a user