Phase 3 Slice 1 Step A (design §3.1). Additive only; no drops, no data change. Adds nine columns to paliad.deadline_rules so the calculator + rule editor can converge on a single rule shape over the following slices: trigger_event_id (bigint, FK trigger_events.id) spawn_proceeding_type_id (int, FK proceeding_types.id) combine_op (text, CHECK 'max'|'min') condition_expr (jsonb) priority (text, DEFAULT 'mandatory', 4-way CHECK) is_court_set (bool, DEFAULT false) lifecycle_state (text, DEFAULT 'published', 3-way CHECK) draft_of (uuid, self-FK) published_at (timestamptz) FK types follow the actual referenced columns (bigint on trigger_events, int4 serial on proceeding_types) — the design doc's "int FK" shorthand is widened to the precise widths. FKs are DEFERRABLE INITIALLY IMMEDIATE so Slice 3's data-move can defer FK checks within a single transaction without disturbing normal-statement semantics. Indexes: partial WHERE NOT NULL on the two FK columns (sparse; most rules have neither); plain btree on lifecycle_state so the admin filter on 'published' is O(log n).
174 lines
8.4 KiB
SQL
174 lines
8.4 KiB
SQL
-- t-paliad-182 / Fristen Phase 3 Slice 1 (Step A of
|
|
-- docs/design-fristen-phase2-2026-05-15.md §3.1).
|
|
--
|
|
-- Additive only: extends paliad.deadline_rules with the unified-rule
|
|
-- columns the Phase 3 calculator + rule editor will use.
|
|
--
|
|
-- NO drops in this slice. Legacy columns (is_mandatory, is_optional,
|
|
-- condition_flag, condition_rule_id) stay live until Slice 9. Compat-
|
|
-- mode readers consume both shapes during the transition window
|
|
-- (design §3.2 "Cutover ordering").
|
|
--
|
|
-- Column-by-column rationale:
|
|
-- trigger_event_id — event-rooted dispatch (Pipeline C unification, §2.5).
|
|
-- spawn_proceeding_type_id — cross-proceeding spawn resolution (Q7, §2.6).
|
|
-- combine_op — composite-rule arithmetic 'max'/'min' (R.198/R.213).
|
|
-- condition_expr — jsonb condition grammar replacing condition_flag (Q6, §2.4).
|
|
-- priority — 4-way enum mandatory|recommended|optional|informational (Q3, §2.3).
|
|
-- is_court_set — explicit replacement of the runtime heuristic (Q12).
|
|
-- lifecycle_state — draft|published|archived for the rule editor (Q5, §4.2).
|
|
-- draft_of — draft self-FK pointing at the published row it replaces.
|
|
-- published_at — promotion timestamp, NULL while draft.
|
|
--
|
|
-- FK type notes:
|
|
-- trigger_event_id is BIGINT (paliad.trigger_events.id is bigint, mig 028).
|
|
-- spawn_proceeding_type_id is INTEGER (paliad.proceeding_types.id is
|
|
-- serial = int4, mig 003).
|
|
-- draft_of is UUID (self-FK on paliad.deadline_rules.id).
|
|
-- The design doc (§2.1) calls them "int FK" loosely; the actual schemas
|
|
-- demand the precise int width, hence bigint/integer here.
|
|
--
|
|
-- Indexes:
|
|
-- FK lookups for trigger_event_id + spawn_proceeding_type_id (sparse,
|
|
-- most rules have neither — partial WHERE NOT NULL keeps the index
|
|
-- small).
|
|
-- lifecycle_state is queried by the admin /admin/rules listing's
|
|
-- default filter (state='published'); plain btree is fine, no
|
|
-- WHERE clause so 'draft' / 'archived' rows index too.
|
|
--
|
|
-- Idempotent: every ADD COLUMN uses IF NOT EXISTS. Re-applying is a
|
|
-- no-op. Tracker advances 77 → 78.
|
|
|
|
-- =============================================================================
|
|
-- 1. New columns on paliad.deadline_rules
|
|
-- =============================================================================
|
|
|
|
ALTER TABLE paliad.deadline_rules
|
|
ADD COLUMN IF NOT EXISTS trigger_event_id bigint,
|
|
ADD COLUMN IF NOT EXISTS spawn_proceeding_type_id integer,
|
|
ADD COLUMN IF NOT EXISTS combine_op text,
|
|
ADD COLUMN IF NOT EXISTS condition_expr jsonb,
|
|
ADD COLUMN IF NOT EXISTS priority text NOT NULL DEFAULT 'mandatory',
|
|
ADD COLUMN IF NOT EXISTS is_court_set boolean NOT NULL DEFAULT false,
|
|
ADD COLUMN IF NOT EXISTS lifecycle_state text NOT NULL DEFAULT 'published',
|
|
ADD COLUMN IF NOT EXISTS draft_of uuid,
|
|
ADD COLUMN IF NOT EXISTS published_at timestamptz;
|
|
|
|
COMMENT ON COLUMN paliad.deadline_rules.trigger_event_id IS
|
|
'Optional FK to paliad.trigger_events. When non-NULL, this rule is '
|
|
'event-rooted (Pipeline C unification, design §2.5). When NULL the '
|
|
'rule is proceeding-rooted via proceeding_type_id. Exactly one of '
|
|
'the two must be set after Slice 3 backfill (enforced by a CHECK '
|
|
'constraint added in Slice 9 after legacy callers retire).';
|
|
|
|
COMMENT ON COLUMN paliad.deadline_rules.spawn_proceeding_type_id IS
|
|
'When is_spawn=true, points at the target proceeding whose rule set '
|
|
'the calculator follows when this rule fires (cross-proceeding '
|
|
'spawn, design §2.6). Backfilled in Slice 7 for the 8 live spawn '
|
|
'rules.';
|
|
|
|
COMMENT ON COLUMN paliad.deadline_rules.combine_op IS
|
|
'NULL = single-anchor arithmetic. ''max'' / ''min'' = composite-rule '
|
|
'arithmetic combining (duration_value, duration_unit) with '
|
|
'(alt_duration_value, alt_duration_unit). Used by R.198 / R.213 '
|
|
'("31d OR 20 working_days, whichever is longer / shorter").';
|
|
|
|
COMMENT ON COLUMN paliad.deadline_rules.condition_expr IS
|
|
'jsonb gating expression replacing condition_flag (Q6, design §2.4). '
|
|
'Grammar: {"flag": "<name>"} | {"op":"and"|"or", "args":[...]} | '
|
|
'{"op":"not", "args":[<node>]}. NULL or {} = unconditional. '
|
|
'Backfilled in Slice 2 from condition_flag; new code reads this, '
|
|
'falls back to condition_flag during the transition window.';
|
|
|
|
COMMENT ON COLUMN paliad.deadline_rules.priority IS
|
|
'Unified 4-way enum (Q3, design §2.3) replacing the is_mandatory + '
|
|
'is_optional pair. Allowed: mandatory | recommended | optional | '
|
|
'informational. Default ''mandatory'' on new rows; legacy rows get '
|
|
'backfilled in Slice 2 from the (is_mandatory, is_optional) pair.';
|
|
|
|
COMMENT ON COLUMN paliad.deadline_rules.is_court_set IS
|
|
'Replaces the runtime heuristic (primary_party=''court'' OR '
|
|
'event_type IN (...)) with an explicit column (Q12). Default false '
|
|
'on new rows; Slice 2 backfills from the heuristic so behaviour is '
|
|
'unchanged at first.';
|
|
|
|
COMMENT ON COLUMN paliad.deadline_rules.lifecycle_state IS
|
|
'Rule-editor lifecycle (Q5, design §4.2). draft = work-in-progress '
|
|
'admin edit; published = live, calculator-visible; archived = '
|
|
'historical (kept for audit). Default ''published'' so every '
|
|
'existing row stays live without an UPDATE.';
|
|
|
|
COMMENT ON COLUMN paliad.deadline_rules.draft_of IS
|
|
'When lifecycle_state=''draft'', points at the published rule this '
|
|
'draft will replace on publish. NULL on published or archived '
|
|
'rows. NULL also on net-new drafts (no prior published peer).';
|
|
|
|
COMMENT ON COLUMN paliad.deadline_rules.published_at IS
|
|
'Timestamp this row entered lifecycle_state=''published''. NULL '
|
|
'while draft, populated on publish, retained through archive. '
|
|
'Distinct from updated_at (which moves on every edit).';
|
|
|
|
-- =============================================================================
|
|
-- 2. Foreign keys
|
|
-- =============================================================================
|
|
--
|
|
-- DEFERRABLE INITIALLY IMMEDIATE keeps normal-statement semantics
|
|
-- intact while still letting backfill migrations defer until end-of-
|
|
-- transaction if they need to (e.g. when Slice 3 inserts a rule row
|
|
-- whose trigger_event_id references a row inserted in the same tx).
|
|
|
|
ALTER TABLE paliad.deadline_rules
|
|
ADD CONSTRAINT deadline_rules_trigger_event_id_fkey
|
|
FOREIGN KEY (trigger_event_id)
|
|
REFERENCES paliad.trigger_events(id)
|
|
ON DELETE SET NULL
|
|
DEFERRABLE INITIALLY IMMEDIATE;
|
|
|
|
ALTER TABLE paliad.deadline_rules
|
|
ADD CONSTRAINT deadline_rules_spawn_proceeding_type_id_fkey
|
|
FOREIGN KEY (spawn_proceeding_type_id)
|
|
REFERENCES paliad.proceeding_types(id)
|
|
ON DELETE SET NULL
|
|
DEFERRABLE INITIALLY IMMEDIATE;
|
|
|
|
ALTER TABLE paliad.deadline_rules
|
|
ADD CONSTRAINT deadline_rules_draft_of_fkey
|
|
FOREIGN KEY (draft_of)
|
|
REFERENCES paliad.deadline_rules(id)
|
|
ON DELETE SET NULL
|
|
DEFERRABLE INITIALLY IMMEDIATE;
|
|
|
|
-- =============================================================================
|
|
-- 3. CHECK constraints on enum-style columns
|
|
-- =============================================================================
|
|
--
|
|
-- combine_op: NULL (unset) or one of two values.
|
|
ALTER TABLE paliad.deadline_rules
|
|
ADD CONSTRAINT deadline_rules_combine_op_check
|
|
CHECK (combine_op IS NULL OR combine_op IN ('max', 'min'));
|
|
|
|
-- priority: 4-way enum.
|
|
ALTER TABLE paliad.deadline_rules
|
|
ADD CONSTRAINT deadline_rules_priority_check
|
|
CHECK (priority IN ('mandatory', 'recommended', 'optional', 'informational'));
|
|
|
|
-- lifecycle_state: 3-way enum.
|
|
ALTER TABLE paliad.deadline_rules
|
|
ADD CONSTRAINT deadline_rules_lifecycle_state_check
|
|
CHECK (lifecycle_state IN ('draft', 'published', 'archived'));
|
|
|
|
-- =============================================================================
|
|
-- 4. Indexes
|
|
-- =============================================================================
|
|
|
|
CREATE INDEX IF NOT EXISTS deadline_rules_trigger_event_id_idx
|
|
ON paliad.deadline_rules (trigger_event_id)
|
|
WHERE trigger_event_id IS NOT NULL;
|
|
|
|
CREATE INDEX IF NOT EXISTS deadline_rules_spawn_proceeding_type_id_idx
|
|
ON paliad.deadline_rules (spawn_proceeding_type_id)
|
|
WHERE spawn_proceeding_type_id IS NOT NULL;
|
|
|
|
CREATE INDEX IF NOT EXISTS deadline_rules_lifecycle_state_idx
|
|
ON paliad.deadline_rules (lifecycle_state);
|