feat(deadline-system): P1 — upc.apl re-split into merits/cost/order (m/paliad#149)
Phase 2 P1 / m's Q5 divergence (2026-05-27, verbatim):
"Reverse the unification as suggested in 3. They are different
proceedings, I only wanted the approach to be unified in the
'determinator' — but they are actually different proceedings!"
Mig 155 reverts the mig-096 unification:
Before: id=160 upc.apl.unified active (16 rules), id=11/19/20 inactive
After: id=11 upc.apl.merits (7 rules), id=19 upc.apl.cost (2 rules),
id=20 upc.apl.order (7 rules) all active; id=160 inactive
The 16 rules under id=160 split cleanly by event_code prefix; all 10
parent_id edges among them are bucket-local (pre-flight audit), so
the tree shape survives the rebind unchanged.
Spawn FK retarget: pi.cfi.appeal_spawn flips from 11 (merits) → 20
(orders track) per design §3.1 — PI appeals land on orders, not
merits. The inf/rev/dmgs spawns keep target=11 (merits), now active.
Determinator routing layer (proceeding_mapping.go) keeps its single
"Berufung" front door per m's intent — only the data shape changes.
Pre-flight verified: 0 projects bound to id=160, 0 scenarios reference
upc.apl. Zero data migration on the project side.
Tests: lookup_events_test.go assertions on the three appeal_target
buckets updated to the new codes (endentscheidung → upc.apl.merits,
schadensbemessung → upc.apl.merits, bucheinsicht → upc.apl.order).
Same rule set, post-split coordinates.
Snapshot regen (pkg/litigationplanner/embedded/upc/) deferred: the
current snapshot only contains inf+rev so the apl re-split doesn't
shift its contents; regenerating would surface unrelated active PTs
and pollute this slice. Tracked as a follow-up.
Verified: go vet clean, go test ./internal/services/... -run
LookupEvents|proceeding_codes clean.
Design: docs/design-deadline-system-revision-2026-05-27.md §3.1
(re-split mig), §1.3 (spawn graph post-Q5). t-paliad-331.
This commit is contained in:
43
internal/db/migrations/155_upc_apl_resplit.down.sql
Normal file
43
internal/db/migrations/155_upc_apl_resplit.down.sql
Normal file
@@ -0,0 +1,43 @@
|
||||
-- 155_upc_apl_resplit.down — t-paliad-331 / m/paliad#149 Phase 2 P1
|
||||
--
|
||||
-- Best-effort rollback. Restores from the same-TX snapshots written by
|
||||
-- mig 155. Drops the snapshots once restoration is verified.
|
||||
|
||||
BEGIN;
|
||||
|
||||
SELECT set_config(
|
||||
'paliad.audit_reason',
|
||||
'mig 155 down: revert upc.apl re-split (restore unified id=160)',
|
||||
true
|
||||
);
|
||||
|
||||
-- ----------------------------------------------------------------
|
||||
-- 1. Restore proceeding_types.is_active from snapshot.
|
||||
-- ----------------------------------------------------------------
|
||||
|
||||
UPDATE paliad.proceeding_types pt
|
||||
SET is_active = pre.is_active
|
||||
FROM paliad.proceeding_types_pre_155 pre
|
||||
WHERE pt.id = pre.id
|
||||
AND pt.is_active IS DISTINCT FROM pre.is_active;
|
||||
|
||||
-- ----------------------------------------------------------------
|
||||
-- 2. Restore rule bindings from snapshot.
|
||||
-- ----------------------------------------------------------------
|
||||
|
||||
UPDATE paliad.sequencing_rules sr
|
||||
SET proceeding_type_id = pre.proceeding_type_id,
|
||||
spawn_proceeding_type_id = pre.spawn_proceeding_type_id
|
||||
FROM paliad.sequencing_rules_pre_155 pre
|
||||
WHERE sr.id = pre.id
|
||||
AND (sr.proceeding_type_id IS DISTINCT FROM pre.proceeding_type_id
|
||||
OR sr.spawn_proceeding_type_id IS DISTINCT FROM pre.spawn_proceeding_type_id);
|
||||
|
||||
-- ----------------------------------------------------------------
|
||||
-- 3. Drop the snapshots.
|
||||
-- ----------------------------------------------------------------
|
||||
|
||||
DROP TABLE IF EXISTS paliad.sequencing_rules_pre_155;
|
||||
DROP TABLE IF EXISTS paliad.proceeding_types_pre_155;
|
||||
|
||||
COMMIT;
|
||||
191
internal/db/migrations/155_upc_apl_resplit.up.sql
Normal file
191
internal/db/migrations/155_upc_apl_resplit.up.sql
Normal file
@@ -0,0 +1,191 @@
|
||||
-- 155_upc_apl_resplit — t-paliad-331 / m/paliad#149 Phase 2 P1
|
||||
--
|
||||
-- Reverts the upc.apl unification that mig 096 introduced. m's Q5
|
||||
-- (2026-05-27, verbatim):
|
||||
--
|
||||
-- "Reverse the unification as suggested in 3. They are different
|
||||
-- proceedings, I only wanted the approach to be unified in the
|
||||
-- 'determinator' — but they are actually different proceedings!"
|
||||
--
|
||||
-- The current state (audited 2026-05-27, mig 155 pre-flight):
|
||||
--
|
||||
-- id=160 upc.apl.unified is_active=true (carries all 16 rules)
|
||||
-- id=11 upc.apl.merits is_active=false
|
||||
-- id=19 upc.apl.cost is_active=false
|
||||
-- id=20 upc.apl.order is_active=false
|
||||
--
|
||||
-- The 16 rules under id=160 split cleanly by event_code prefix:
|
||||
-- 7 rows match 'upc.apl.merits.%' → target id=11
|
||||
-- 2 rows match 'upc.apl.cost.%' → target id=19
|
||||
-- 7 rows match 'upc.apl.order.%' → target id=20
|
||||
--
|
||||
-- Every parent_id chain among those 16 rows stays inside its bucket
|
||||
-- (audited: 10/10 parent edges are bucket-local), so retargeting by
|
||||
-- event_code prefix preserves the tree shape — no extra parent_id
|
||||
-- surgery needed.
|
||||
--
|
||||
-- Spawn FKs: 4 rules currently target id=11 (was inactive — this is
|
||||
-- the R3 finding athena flagged, re-interpreted by m's Q5 as correct
|
||||
-- intent rather than broken state):
|
||||
--
|
||||
-- upc.inf.cfi.appeal_spawn → 11 (merits) — keep
|
||||
-- upc.rev.cfi.appeal_spawn → 11 (merits) — keep
|
||||
-- upc.dmgs.cfi.appeal_spawn → 11 (merits) — keep
|
||||
-- upc.pi.cfi.appeal_spawn → 11 (merits) — RETARGET to 20 (order),
|
||||
-- since PI appeals
|
||||
-- land on the orders
|
||||
-- track per design §3.1.
|
||||
--
|
||||
-- Active scenarios / projects pointing at id=160: zero (verified
|
||||
-- pre-flight: 0 projects, 0 scenarios reference 'upc.apl'). No data
|
||||
-- migration on the project side; no production traffic is mid-flight
|
||||
-- on id=160.
|
||||
--
|
||||
-- Mig 153's `projects_proceeding_type_kind_check` trigger gates
|
||||
-- inserts/updates against kind='proceeding'. id=11/19/20 already
|
||||
-- carry kind='proceeding' (verified pre-flight), so the trigger
|
||||
-- won't fire on the re-activations.
|
||||
|
||||
BEGIN;
|
||||
|
||||
SELECT set_config(
|
||||
'paliad.audit_reason',
|
||||
'mig 155: upc.apl re-split — reactivate merits/cost/order, retire unified (t-paliad-331 / m/paliad#149 P1)',
|
||||
true
|
||||
);
|
||||
|
||||
-- ----------------------------------------------------------------
|
||||
-- 1. Snapshot for audit + rollback.
|
||||
-- ----------------------------------------------------------------
|
||||
|
||||
CREATE TABLE paliad.proceeding_types_pre_155 AS
|
||||
SELECT * FROM paliad.proceeding_types WHERE id IN (11, 19, 20, 160);
|
||||
|
||||
CREATE TABLE paliad.sequencing_rules_pre_155 AS
|
||||
SELECT * FROM paliad.sequencing_rules
|
||||
WHERE proceeding_type_id = 160
|
||||
OR (is_spawn AND spawn_proceeding_type_id IN (11, 19, 20, 160));
|
||||
|
||||
COMMENT ON TABLE paliad.proceeding_types_pre_155 IS
|
||||
'Snapshot of the 4 appeal-related proceeding_types rows taken in '
|
||||
'the same TX as mig 155 (upc.apl re-split). Audit + rollback safety.';
|
||||
|
||||
COMMENT ON TABLE paliad.sequencing_rules_pre_155 IS
|
||||
'Snapshot of the 16 rules under id=160 + the 4 spawn rules targeting '
|
||||
'the appeal cluster, taken in the same TX as mig 155. Audit + rollback.';
|
||||
|
||||
-- ----------------------------------------------------------------
|
||||
-- 2. Re-activate the three discrete appeal PTs; retire the unified row.
|
||||
-- ----------------------------------------------------------------
|
||||
|
||||
UPDATE paliad.proceeding_types SET is_active = true WHERE id IN (11, 19, 20);
|
||||
UPDATE paliad.proceeding_types SET is_active = false WHERE id = 160;
|
||||
|
||||
DO $$
|
||||
DECLARE
|
||||
n_active int;
|
||||
n_inactive int;
|
||||
BEGIN
|
||||
SELECT COUNT(*) INTO n_active FROM paliad.proceeding_types
|
||||
WHERE id IN (11, 19, 20) AND is_active = true;
|
||||
SELECT COUNT(*) INTO n_inactive FROM paliad.proceeding_types
|
||||
WHERE id = 160 AND is_active = false;
|
||||
IF n_active <> 3 OR n_inactive <> 1 THEN
|
||||
RAISE EXCEPTION '[mig 155] activation check failed — active(11,19,20)=% / inactive(160)=%', n_active, n_inactive;
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
-- ----------------------------------------------------------------
|
||||
-- 3. Retarget the 16 rules on id=160 to merits/cost/order by event_code
|
||||
-- prefix. parent_id stays intact (all parent edges are bucket-local
|
||||
-- per pre-flight audit).
|
||||
-- ----------------------------------------------------------------
|
||||
|
||||
UPDATE paliad.sequencing_rules sr
|
||||
SET proceeding_type_id = 11
|
||||
FROM paliad.procedural_events pe
|
||||
WHERE pe.id = sr.procedural_event_id
|
||||
AND sr.proceeding_type_id = 160
|
||||
AND pe.code LIKE 'upc.apl.merits.%';
|
||||
|
||||
UPDATE paliad.sequencing_rules sr
|
||||
SET proceeding_type_id = 19
|
||||
FROM paliad.procedural_events pe
|
||||
WHERE pe.id = sr.procedural_event_id
|
||||
AND sr.proceeding_type_id = 160
|
||||
AND pe.code LIKE 'upc.apl.cost.%';
|
||||
|
||||
UPDATE paliad.sequencing_rules sr
|
||||
SET proceeding_type_id = 20
|
||||
FROM paliad.procedural_events pe
|
||||
WHERE pe.id = sr.procedural_event_id
|
||||
AND sr.proceeding_type_id = 160
|
||||
AND pe.code LIKE 'upc.apl.order.%';
|
||||
|
||||
DO $$
|
||||
DECLARE
|
||||
remaining int;
|
||||
merits int; cost int; ord int;
|
||||
BEGIN
|
||||
SELECT COUNT(*) INTO remaining
|
||||
FROM paliad.sequencing_rules WHERE proceeding_type_id = 160;
|
||||
IF remaining <> 0 THEN
|
||||
RAISE EXCEPTION '[mig 155] rebind failed — % rules still on id=160 (expected 0)', remaining;
|
||||
END IF;
|
||||
SELECT COUNT(*) INTO merits
|
||||
FROM paliad.sequencing_rules WHERE proceeding_type_id = 11;
|
||||
SELECT COUNT(*) INTO cost
|
||||
FROM paliad.sequencing_rules WHERE proceeding_type_id = 19;
|
||||
SELECT COUNT(*) INTO ord
|
||||
FROM paliad.sequencing_rules WHERE proceeding_type_id = 20;
|
||||
IF merits <> 7 OR cost <> 2 OR ord <> 7 THEN
|
||||
RAISE EXCEPTION
|
||||
'[mig 155] post-rebind counts wrong — merits=% (want 7) / cost=% (want 2) / order=% (want 7)',
|
||||
merits, cost, ord;
|
||||
END IF;
|
||||
RAISE NOTICE '[mig 155] rebind OK — merits=% cost=% order=%', merits, cost, ord;
|
||||
END $$;
|
||||
|
||||
-- ----------------------------------------------------------------
|
||||
-- 4. Retarget the upc.pi.cfi.appeal_spawn rule to id=20 (orders track).
|
||||
-- PI appeals don't go to the merits track — they're orders.
|
||||
-- The inf/rev/dmgs spawns keep target=11 (now active, was inactive
|
||||
-- by accident of the unification).
|
||||
-- ----------------------------------------------------------------
|
||||
|
||||
UPDATE paliad.sequencing_rules
|
||||
SET spawn_proceeding_type_id = 20
|
||||
WHERE is_spawn = true
|
||||
AND procedural_event_id = (
|
||||
SELECT id FROM paliad.procedural_events WHERE code = 'upc.pi.cfi.appeal_spawn'
|
||||
)
|
||||
AND spawn_proceeding_type_id = 11;
|
||||
|
||||
DO $$
|
||||
DECLARE
|
||||
pi_target int;
|
||||
others int;
|
||||
BEGIN
|
||||
SELECT spawn_proceeding_type_id INTO pi_target
|
||||
FROM paliad.sequencing_rules sr
|
||||
JOIN paliad.procedural_events pe ON pe.id = sr.procedural_event_id
|
||||
WHERE pe.code = 'upc.pi.cfi.appeal_spawn' AND sr.is_spawn = true
|
||||
LIMIT 1;
|
||||
IF pi_target IS DISTINCT FROM 20 THEN
|
||||
RAISE EXCEPTION '[mig 155] pi.cfi spawn retarget failed — got %, want 20', pi_target;
|
||||
END IF;
|
||||
SELECT COUNT(*) INTO others
|
||||
FROM paliad.sequencing_rules sr
|
||||
JOIN paliad.procedural_events pe ON pe.id = sr.procedural_event_id
|
||||
WHERE sr.is_spawn = true
|
||||
AND sr.spawn_proceeding_type_id = 11
|
||||
AND pe.code IN ('upc.inf.cfi.appeal_spawn',
|
||||
'upc.rev.cfi.appeal_spawn',
|
||||
'upc.dmgs.cfi.appeal_spawn');
|
||||
IF others <> 3 THEN
|
||||
RAISE EXCEPTION '[mig 155] inf/rev/dmgs spawn target check failed — % rows point at 11 (want 3)', others;
|
||||
END IF;
|
||||
RAISE NOTICE '[mig 155] spawn graph OK — pi → 20 (order); inf/rev/dmgs → 11 (merits)';
|
||||
END $$;
|
||||
|
||||
COMMIT;
|
||||
@@ -117,10 +117,13 @@ func TestLookupEvents(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("LookupEvents: %v", err)
|
||||
}
|
||||
// Should hit the 7 rules under the unified upc.apl that
|
||||
// carry applies_to_target={endentscheidung} (Slice B1 mig 134).
|
||||
// Should hit the 7 merits-track rules that carry
|
||||
// applies_to_target={endentscheidung} (Slice B1 mig 134).
|
||||
// Post-mig 155 (m/paliad#149 P1): the unified upc.apl was split
|
||||
// back into merits/cost/order — the endentscheidung anchors live
|
||||
// under upc.apl.merits (id=11).
|
||||
if len(matches) == 0 {
|
||||
t.Fatal("expected upc.apl endentscheidung rules after B1 mig")
|
||||
t.Fatal("expected upc.apl.merits endentscheidung rules after B1 mig")
|
||||
}
|
||||
for _, m := range matches {
|
||||
if m.DepthFromAnchor != 1 {
|
||||
@@ -137,8 +140,8 @@ func TestLookupEvents(t *testing.T) {
|
||||
t.Errorf("anchor row %s missing endentscheidung target: %v",
|
||||
m.Rule.Name, m.Rule.AppliesToTarget)
|
||||
}
|
||||
if m.ProceedingType.Code != "upc.apl.unified" {
|
||||
t.Errorf("anchor row %s came from %s, want upc.apl.unified",
|
||||
if m.ProceedingType.Code != "upc.apl.merits" {
|
||||
t.Errorf("anchor row %s came from %s, want upc.apl.merits",
|
||||
m.Rule.Name, m.ProceedingType.Code)
|
||||
}
|
||||
}
|
||||
@@ -153,10 +156,11 @@ func TestLookupEvents(t *testing.T) {
|
||||
t.Fatalf("LookupEvents: %v", err)
|
||||
}
|
||||
// mig 138 (t-paliad-303, m/paliad#134) extends the 7 merits-track
|
||||
// rules under upc.apl.unified with applies_to_target ⊇ {schadensbemessung}
|
||||
// because R.224 is uniform across substantive R.118 decisions.
|
||||
// rules with applies_to_target ⊇ {schadensbemessung} because
|
||||
// R.224 is uniform across substantive R.118 decisions. Post-mig
|
||||
// 155 the merits track lives at upc.apl.merits (id=11).
|
||||
if len(matches) == 0 {
|
||||
t.Fatal("expected upc.apl schadensbemessung rules after mig 138 backfill")
|
||||
t.Fatal("expected upc.apl.merits schadensbemessung rules after mig 138 backfill")
|
||||
}
|
||||
for _, m := range matches {
|
||||
if m.DepthFromAnchor != 1 {
|
||||
@@ -173,8 +177,8 @@ func TestLookupEvents(t *testing.T) {
|
||||
t.Errorf("anchor row %s missing schadensbemessung target: %v",
|
||||
m.Rule.Name, m.Rule.AppliesToTarget)
|
||||
}
|
||||
if m.ProceedingType.Code != "upc.apl.unified" {
|
||||
t.Errorf("anchor row %s came from %s, want upc.apl.unified",
|
||||
if m.ProceedingType.Code != "upc.apl.merits" {
|
||||
t.Errorf("anchor row %s came from %s, want upc.apl.merits",
|
||||
m.Rule.Name, m.ProceedingType.Code)
|
||||
}
|
||||
}
|
||||
@@ -189,11 +193,12 @@ func TestLookupEvents(t *testing.T) {
|
||||
t.Fatalf("LookupEvents: %v", err)
|
||||
}
|
||||
// mig 138 (t-paliad-303, m/paliad#134) extends the 7 order-track
|
||||
// rules under upc.apl.unified with applies_to_target ⊇ {bucheinsicht}
|
||||
// because R.220.2 / R.224.2.b / R.235.2 / R.237 / R.238.2 are
|
||||
// uniform across the orders they appeal.
|
||||
// rules with applies_to_target ⊇ {bucheinsicht} because R.220.2 /
|
||||
// R.224.2.b / R.235.2 / R.237 / R.238.2 are uniform across the
|
||||
// orders they appeal. Post-mig 155 the order track lives at
|
||||
// upc.apl.order (id=20).
|
||||
if len(matches) == 0 {
|
||||
t.Fatal("expected upc.apl bucheinsicht rules after mig 138 backfill")
|
||||
t.Fatal("expected upc.apl.order bucheinsicht rules after mig 138 backfill")
|
||||
}
|
||||
for _, m := range matches {
|
||||
if m.DepthFromAnchor != 1 {
|
||||
@@ -210,8 +215,8 @@ func TestLookupEvents(t *testing.T) {
|
||||
t.Errorf("anchor row %s missing bucheinsicht target: %v",
|
||||
m.Rule.Name, m.Rule.AppliesToTarget)
|
||||
}
|
||||
if m.ProceedingType.Code != "upc.apl.unified" {
|
||||
t.Errorf("anchor row %s came from %s, want upc.apl.unified",
|
||||
if m.ProceedingType.Code != "upc.apl.order" {
|
||||
t.Errorf("anchor row %s came from %s, want upc.apl.order",
|
||||
m.Rule.Name, m.ProceedingType.Code)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user