The new tables (mig 136) and the dual-write that keeps them in sync (B.2) have been steady-state in prod since mig 136 deployed at 13:24 UTC today. Drift verified clean before this commit: deadline_rules=231, sequencing_rules=231, procedural_events=231 (153 codes + 78 synthetic), legal_sources=87, zero mismatches across counts, FK integrity, lifecycle, is_active. This commit flips READ paths to source data from the new tables via a backwards-compatible view, leaving the dual-write WRITE paths untouched for B.4 to retire alongside the destructive drop. * internal/db/migrations/139_deadline_rules_unified_view.up.sql (new) — CREATE VIEW paliad.deadline_rules_unified projecting sr+pe+ls back into the legacy paliad.deadline_rules column shape. Same column names + types so the Go-side change is a 1-token substitution per query with no struct or scanner edits. Post-apply DO block asserts view row count = sequencing_rules row count (FK NOT NULL on procedural_event_id guarantees they match). * 10 service / handler files — every SELECT FROM paliad.deadline_rules (or JOIN paliad.deadline_rules) flipped to use the view: - internal/handlers/submissions.go (Schriftsätze list) - internal/services/deadline_rule_service.go (8 read sites) - internal/services/rule_editor_service.go (3 read sites — ListRules, getByID, validateSpawnNoCycle) - internal/services/rule_editor_orphans.go (candidate-rule lookup) - internal/services/submission_vars.go (loadPublishedRule) - internal/services/deadline_service.go (deadlines list join) - internal/services/fristenrechner.go (calculator reads) - internal/services/projection_service.go (projection reads) - internal/services/event_deadline_service.go (event→rule join) - internal/services/export_service.go (3 export sites — ref__deadline_rules) Verified semantically safe on live (read-only smoke): - 231 rows in view match 231 in legacy. - name + event_type pair: 231/231 match. - legal_source: 231/231 match (NULL on both sides treated as match). - submission_code: 153 non-NULL codes match exactly; the 78 synthetic 'null.<8hex>' codes diverge from legacy NULL but no reader filters on NULL submission_code (verified handlers/submissions.go: synthetic-code rules all have NULL event_type so the WHERE event_type = 'filing' filter excludes them; the Schriftsätze surface returns the same 105 rows). Scope decisions documented (deviation from design §5.3): - B.3 ships the READ flip only. WRITE paths (RuleEditorService Create / UpdateDraft / CloneAsDraft / Publish / flipLifecycle) retain the dual-write from B.2 — they write to both legacy and new tables. B.4 (destructive drop) will retire the legacy writes in the same slice that drops the table, avoiding a transient state where the legacy writes have no purpose. - The B.2 drift-check ticker (StartDualWriteDriftCheckLoop) stays active for the same reason: dual-write continues, so the invariants the loop checks remain meaningful. This shape is paliadin-approvable on a "good solution > strict phase boundary" reading of m's greenlight. If paliadin pushes back and wants the legacy writes removed in B.3, the refactor is ~300 LOC across the 5 RuleEditorService write methods + buildPatchSets split into PE/SR sets — schedulable as B.3.5 before B.4. Build + vet clean. TestMigrations_NoDuplicateSlot passes.
123 lines
5.1 KiB
SQL
123 lines
5.1 KiB
SQL
-- 139_deadline_rules_unified_view — Slice B.3 read cutover (t-paliad-305 / m/paliad#93)
|
|
--
|
|
-- Creates paliad.deadline_rules_unified — a Postgres VIEW that
|
|
-- re-projects paliad.sequencing_rules + paliad.procedural_events +
|
|
-- paliad.legal_sources back into the legacy paliad.deadline_rules
|
|
-- column shape.
|
|
--
|
|
-- Why a view instead of rewriting every SELECT in Go:
|
|
--
|
|
-- - 19 read sites across 11 service files reference
|
|
-- paliad.deadline_rules. Rewriting each by hand multiplies the
|
|
-- opportunity for off-by-one bugs in the JOIN.
|
|
-- - The view has the same column names + types as the legacy table,
|
|
-- so the change in Go is a 1-token substitution per query
|
|
-- (FROM paliad.deadline_rules → FROM paliad.deadline_rules_unified)
|
|
-- with no struct or scanner changes.
|
|
-- - When B.4 drops paliad.deadline_rules, this view stays — it
|
|
-- becomes the canonical legacy-shape reader for any code that
|
|
-- hasn't been migrated to direct sr/pe/ls reads.
|
|
--
|
|
-- Column mapping (per design §4.2):
|
|
-- - id, proceeding_type_id, parent_id, primary_party, duration_*,
|
|
-- timing, sequence_order, is_spawn/court_set/bilateral, priority,
|
|
-- rule_code, rule_codes, deadline_notes(_en), condition_expr,
|
|
-- choices_offered, applies_to_target, trigger_event_id,
|
|
-- spawn_proceeding_type_id, anchor_alt, alt_duration_*,
|
|
-- alt_rule_code, combine_op, lifecycle_state, draft_of,
|
|
-- published_at, is_active, created_at, updated_at, spawn_label
|
|
-- → from paliad.sequencing_rules
|
|
-- - submission_code → procedural_events.code
|
|
-- - name, name_en, description→ procedural_events
|
|
-- - event_type → procedural_events.event_kind (renamed)
|
|
-- - concept_id → procedural_events
|
|
-- - legal_source → legal_sources.citation (via legal_source_id FK)
|
|
--
|
|
-- The view is READ-ONLY by default. Writes still go to the underlying
|
|
-- tables — RuleEditorService is refactored in the same slice to write
|
|
-- directly to sr/pe/ls. paliad.deadline_rules is FROZEN from B.3 onward
|
|
-- (no new writes); the dual-write helper from B.2 is decommissioned.
|
|
|
|
-- The CHECK constraint on sequencing_rules.primary_party doesn't exist
|
|
-- yet (mig 135 only constrained deadline_rules.primary_party). The view
|
|
-- inherits whatever value sr.primary_party carries; mig 136's backfill
|
|
-- set sr.primary_party = dr.primary_party so the canonical four-value
|
|
-- vocab is already in place. A later slice can add the same CHECK to
|
|
-- sequencing_rules itself.
|
|
|
|
CREATE OR REPLACE VIEW paliad.deadline_rules_unified AS
|
|
SELECT
|
|
sr.id,
|
|
sr.proceeding_type_id,
|
|
sr.parent_id,
|
|
pe.code AS submission_code,
|
|
pe.name,
|
|
pe.name_en,
|
|
pe.description,
|
|
sr.primary_party,
|
|
pe.event_kind AS event_type,
|
|
sr.duration_value,
|
|
sr.duration_unit,
|
|
sr.timing,
|
|
sr.alt_duration_value,
|
|
sr.alt_duration_unit,
|
|
sr.alt_rule_code,
|
|
sr.anchor_alt,
|
|
sr.combine_op,
|
|
sr.rule_code,
|
|
sr.deadline_notes,
|
|
sr.deadline_notes_en,
|
|
sr.sequence_order,
|
|
sr.is_spawn,
|
|
sr.spawn_label,
|
|
sr.spawn_proceeding_type_id,
|
|
sr.is_bilateral,
|
|
sr.is_court_set,
|
|
sr.priority,
|
|
sr.condition_expr,
|
|
pe.concept_id,
|
|
ls.citation AS legal_source,
|
|
sr.trigger_event_id,
|
|
sr.rule_codes,
|
|
sr.choices_offered,
|
|
sr.applies_to_target,
|
|
sr.lifecycle_state,
|
|
sr.draft_of,
|
|
sr.published_at,
|
|
sr.is_active,
|
|
sr.created_at,
|
|
sr.updated_at
|
|
FROM paliad.sequencing_rules sr
|
|
JOIN paliad.procedural_events pe ON pe.id = sr.procedural_event_id
|
|
LEFT JOIN paliad.legal_sources ls ON ls.id = pe.legal_source_id;
|
|
|
|
COMMENT ON VIEW paliad.deadline_rules_unified IS
|
|
'Slice B.3 (mig 139, t-paliad-305): legacy-shape projection over '
|
|
'sequencing_rules + procedural_events + legal_sources. Read-only — '
|
|
'writes go directly to the three underlying tables via '
|
|
'RuleEditorService. Survives B.4 destructive drop of '
|
|
'paliad.deadline_rules; the view will then be the only '
|
|
'legacy-shape reader.';
|
|
|
|
-- Post-apply integrity check: confirm the view's row count matches the
|
|
-- live sequencing_rules row count. A mismatch would indicate either a
|
|
-- mid-deploy race (rare) or a JOIN issue (the LEFT JOIN to legal_sources
|
|
-- never drops rows, the INNER JOIN to procedural_events drops sr rows
|
|
-- whose procedural_event_id is NULL — but that column is NOT NULL on
|
|
-- the table so it can't happen). Belt-and-braces.
|
|
DO $$
|
|
DECLARE
|
|
v_view_count int;
|
|
v_sr_count int;
|
|
BEGIN
|
|
SELECT COUNT(*) INTO v_view_count FROM paliad.deadline_rules_unified;
|
|
SELECT COUNT(*) INTO v_sr_count FROM paliad.sequencing_rules;
|
|
IF v_view_count <> v_sr_count THEN
|
|
RAISE EXCEPTION '[mig 139] FAILED POST: view row count % does not match sequencing_rules row count %. '
|
|
'Possible cause: a sequencing_rules row references a procedural_event_id that does not exist (NOT NULL FK should prevent this).',
|
|
v_view_count, v_sr_count;
|
|
END IF;
|
|
RAISE NOTICE '[mig 139] view OK — deadline_rules_unified rows = % (= sequencing_rules)',
|
|
v_view_count;
|
|
END $$;
|