Data model: 'rules' conflate legal norms + procedural events — explore submission/procedural-event-first model (inventor) #93

Closed
opened 2026-05-25 13:04:01 +00:00 by mAi · 4 comments
Collaborator

m's report (2026-05-25 15:02)

While looking at a Rechtsbeschwerdebegründung in the editor sidebar:

Submission                Submission
Rechtsbeschwerdebegründung   {{rule.name}}
Legal source                § 102 PatG              {{rule.legal_source_pretty}}
Primary party               both                    {{rule.primary_party}}
Event type                  filing                  {{rule.event_type}}
Submission code             dpma.appeal.bgh.begruendung  {{rule.submission_code}}

This shows how our 'rule' table system may need a revision?! It feels like we are rule based not submission based. But here we have a specific submission that is connected to a rule (as in: legal norm). And of course also connected to other 'procedural events' (which is a good term for it all) by rules how they are sequenced. But it makes it sound weird in the fields...

The observation

paliad.deadline_rules today conflates three distinct concepts into one row:

  1. The procedural event — "Rechtsbeschwerdebegründung" (the thing that happens / gets filed). m proposes procedural event as the umbrella term covering filings, oral hearings, decisions, replies, etc.
  2. The legal norm anchoring it — "§ 102 PatG" / "UPC RoP R.220(1)" (the source-of-law citation).
  3. The sequencing rule — the period ("+2 months"), the trigger event, the party role, the dependency on other procedural events.

Mixing these forces awkward field labels ({{rule.submission_code}} for what's really a procedural-event identifier; {{rule.event_type}} for the procedural-event type) and confuses the mental model.

Phase: inventor design (READ-ONLY)

Inventor → coder gate per project CLAUDE.md.

Open design questions

Q1 — Scope: relabel vs restructure

A. Cosmetic / relabel only: rename {{rule.X}} placeholders + UI field labels to use "submission" / "procedural event" wording. Keep the underlying deadline_rules table shape. ~1 coder shift.
B. Restructure: introduce a paliad.procedural_events table as the primary entity; demote sequencing rules to a sequencing_rules table linking procedural events; demote legal sources to a join. Migration + service-layer + UI refactor.
C. Mixed: rename in code + UI now, plan B as a follow-up that requires its own migration window.

(R) = C: name the entities right immediately (refactor rule.Xprocedural_event.X in placeholders + UI labels; keep DB shape); design B in detail with a clear migration plan but DON'T ship it in this slice. The cosmetic win is immediate; the structural rework needs its own breathing room.

Q2 — "Procedural event" or "Submission" or "Verfahrensschritt" as the umbrella term?

m proposed "procedural event". Some surfaces already use "submission" (Schriftsätze = filings only). The umbrella covers more than just submissions (oral hearings, decisions). Pick one:

  • (R) "procedural event" (DE: Verfahrensschritt or Verfahrensereignis) — covers all of filings, hearings, decisions, replies.
  • Alternative: keep "submission" but expand its meaning. Risk: existing Schriftsätze UI surfaces would be misleading.
  • Alternative: keep "event" / "event_type" (existing column name). Risk: collides with paliad.events (audit feed).

Lock the term in the design doc + i18n.

Q3 — Migration shape if Q1=B

If restructure ships:

  • paliad.procedural_events(id, code, name, event_kind, proceeding_type_id, default_party_role, default_legal_source_id, sequence_ordinal, …) — one row per template procedural event.
  • paliad.sequencing_rules(id, procedural_event_id, trigger_event_id, period_months, period_days, …) — extracted timing rules.
  • paliad.legal_sources(id, citation, jurisdiction, pretty_name, …) — extracted.
  • paliad.deadlines.rule_idpaliad.deadlines.procedural_event_id (rename + migrate; live column is rule_id, not deadline_rule_id — verified B.0 2026-05-26 t-paliad-273); paliad.deadlines.custom_rule_text from t-paliad-258 stays as-is.
  • Backfill: every existing deadline_rules row becomes a procedural_events row + its own sequencing_rules row + legal_sources row.

Q4 — Effect on Schriftsätze / Submissions feature

t-paliad-238 + t-paliad-242 + t-paliad-243 built the Submissions/Schriftsätze surface on top of the current deadline_rules filtering. If we restructure:

  • The Submissions surface filters procedural_events by event_kind IN ('filing', 'reply') (or similar) → same content, cleaner predicate.
  • The submission_code becomes procedural_event.code — same string, cleaner name.
  • submission_templates registry keys off procedural_event.code instead of deadline_rules.submission_code — same external behavior.

Deliverable

docs/design-procedural-events-model-2026-05-25.md on branch mai/<inventor>/procedural-events-design. Sections:

  • §0 TL;DR
  • §1 Premises verified live (audit current deadline_rules schema + every consumer)
  • §2 m's vocabulary call (Q2 — lock the umbrella term)
  • §3 Scope decision (Q1 — A vs B vs C, default (R)=C)
  • §4 Restructure schema (Q3 — if we go B/C, the target shape)
  • §5 Migration plan (data backfill, dual-write window if needed, drop old columns)
  • §6 Service-layer impact (ProjectionService, SubmissionVarsService, deadline_service, projection)
  • §7 UI/i18n impact (placeholder renames, field labels, surface copy)
  • §8 Slice plan (Slice A: cosmetic rename within the existing table — ship; Slice B: restructure with migration — ship later in its own task)
  • §9 Risk assessment (what breaks if migration goes wrong; rollback strategy)
  • §10 Out of scope
  • §11 Open questions for m

Hard rules

  • READ-ONLY design phase. No code, no migrations.
  • Head answers questions — NO AskUserQuestion. Inventor uses mai instruct head. Defaults to (R); escalate to head only on material picks (esp. Q1 and Q2).
  • Inventor → coder gate per project CLAUDE.md.
  • Verify every current consumer of deadline_rules (services + handlers + frontend bundles + i18n keys) before designing — the rename surface is wider than it looks.

When done

Push design doc + mai report completed with "DESIGN READY FOR REVIEW". Inventor stays parked. Head gates coder shift.

Out of scope

  • Modeling per-jurisdiction variations of the same procedural event as a single row vs N rows (existing design choice; keep).
  • Renaming paliad.events (audit feed) — that's a separate table; don't touch.
  • Cross-firm sharing / multi-tenant model considerations.
## m's report (2026-05-25 15:02) While looking at a Rechtsbeschwerdebegründung in the editor sidebar: ``` Submission Submission Rechtsbeschwerdebegründung {{rule.name}} Legal source § 102 PatG {{rule.legal_source_pretty}} Primary party both {{rule.primary_party}} Event type filing {{rule.event_type}} Submission code dpma.appeal.bgh.begruendung {{rule.submission_code}} ``` > This shows how our 'rule' table system may need a revision?! It feels like we are rule based not submission based. But here we have a specific submission that is connected to a rule (as in: legal norm). And of course also connected to other 'procedural events' (which is a good term for it all) by rules how they are sequenced. But it makes it sound weird in the fields... ## The observation `paliad.deadline_rules` today conflates **three distinct concepts** into one row: 1. **The procedural event** — "Rechtsbeschwerdebegründung" (the thing that happens / gets filed). m proposes **procedural event** as the umbrella term covering filings, oral hearings, decisions, replies, etc. 2. **The legal norm** anchoring it — "§ 102 PatG" / "UPC RoP R.220(1)" (the source-of-law citation). 3. **The sequencing rule** — the period ("+2 months"), the trigger event, the party role, the dependency on other procedural events. Mixing these forces awkward field labels (`{{rule.submission_code}}` for what's really a procedural-event identifier; `{{rule.event_type}}` for the procedural-event type) and confuses the mental model. ## Phase: inventor design (READ-ONLY) Inventor → coder gate per project CLAUDE.md. ## Open design questions ### Q1 — Scope: relabel vs restructure A. **Cosmetic / relabel only**: rename `{{rule.X}}` placeholders + UI field labels to use "submission" / "procedural event" wording. Keep the underlying `deadline_rules` table shape. ~1 coder shift. B. **Restructure**: introduce a `paliad.procedural_events` table as the primary entity; demote sequencing rules to a `sequencing_rules` table linking procedural events; demote legal sources to a join. Migration + service-layer + UI refactor. C. **Mixed**: rename in code + UI now, plan B as a follow-up that requires its own migration window. **(R) = C**: name the entities right immediately (refactor `rule.X` → `procedural_event.X` in placeholders + UI labels; keep DB shape); design B in detail with a clear migration plan but DON'T ship it in this slice. The cosmetic win is immediate; the structural rework needs its own breathing room. ### Q2 — "Procedural event" or "Submission" or "Verfahrensschritt" as the umbrella term? m proposed "procedural event". Some surfaces already use "submission" (Schriftsätze = filings only). The umbrella covers more than just submissions (oral hearings, decisions). Pick one: - **(R)** "procedural event" (DE: `Verfahrensschritt` or `Verfahrensereignis`) — covers all of filings, hearings, decisions, replies. - Alternative: keep "submission" but expand its meaning. Risk: existing Schriftsätze UI surfaces would be misleading. - Alternative: keep "event" / "event_type" (existing column name). Risk: collides with `paliad.events` (audit feed). Lock the term in the design doc + i18n. ### Q3 — Migration shape if Q1=B If restructure ships: - `paliad.procedural_events(id, code, name, event_kind, proceeding_type_id, default_party_role, default_legal_source_id, sequence_ordinal, …)` — one row per template procedural event. - `paliad.sequencing_rules(id, procedural_event_id, trigger_event_id, period_months, period_days, …)` — extracted timing rules. - `paliad.legal_sources(id, citation, jurisdiction, pretty_name, …)` — extracted. - `paliad.deadlines.rule_id` → `paliad.deadlines.procedural_event_id` (rename + migrate; live column is `rule_id`, not `deadline_rule_id` — verified B.0 2026-05-26 t-paliad-273); `paliad.deadlines.custom_rule_text` from t-paliad-258 stays as-is. - Backfill: every existing `deadline_rules` row becomes a `procedural_events` row + its own `sequencing_rules` row + `legal_sources` row. ### Q4 — Effect on Schriftsätze / Submissions feature t-paliad-238 + t-paliad-242 + t-paliad-243 built the Submissions/Schriftsätze surface on top of the current `deadline_rules` filtering. If we restructure: - The Submissions surface filters `procedural_events` by `event_kind IN ('filing', 'reply')` (or similar) → same content, cleaner predicate. - The submission_code becomes `procedural_event.code` — same string, cleaner name. - `submission_templates` registry keys off `procedural_event.code` instead of `deadline_rules.submission_code` — same external behavior. ## Deliverable `docs/design-procedural-events-model-2026-05-25.md` on branch `mai/<inventor>/procedural-events-design`. Sections: - §0 TL;DR - §1 Premises verified live (audit current `deadline_rules` schema + every consumer) - §2 m's vocabulary call (Q2 — lock the umbrella term) - §3 Scope decision (Q1 — A vs B vs C, default (R)=C) - §4 Restructure schema (Q3 — if we go B/C, the target shape) - §5 Migration plan (data backfill, dual-write window if needed, drop old columns) - §6 Service-layer impact (ProjectionService, SubmissionVarsService, deadline_service, projection) - §7 UI/i18n impact (placeholder renames, field labels, surface copy) - §8 Slice plan (Slice A: cosmetic rename within the existing table — ship; Slice B: restructure with migration — ship later in its own task) - §9 Risk assessment (what breaks if migration goes wrong; rollback strategy) - §10 Out of scope - §11 Open questions for m ## Hard rules - READ-ONLY design phase. No code, no migrations. - **Head answers questions** — NO AskUserQuestion. Inventor uses `mai instruct head`. Defaults to (R); escalate to head only on material picks (esp. Q1 and Q2). - Inventor → coder gate per project CLAUDE.md. - Verify every current consumer of `deadline_rules` (services + handlers + frontend bundles + i18n keys) before designing — the rename surface is wider than it looks. ## When done Push design doc + `mai report completed` with "DESIGN READY FOR REVIEW". Inventor stays parked. Head gates coder shift. ## Out of scope - Modeling per-jurisdiction variations of the same procedural event as a single row vs N rows (existing design choice; keep). - Renaming `paliad.events` (audit feed) — that's a separate table; don't touch. - Cross-firm sharing / multi-tenant model considerations.
mAi self-assigned this 2026-05-25 13:04:01 +00:00
Author
Collaborator

B.0 read-only re-validation complete (t-paliad-273)

Branch: mai/curie/researcher-slice-b-zero · Commit: c4c0a82
Findings doc: docs/design-procedural-events-b0-findings-2026-05-26.md

Scope: re-checked every load-bearing premise in cronus's §1 against the live paliad schema. Nothing written to deadline_rules. B.1 stays blocked pending m's greenlight.

Drift since 2026-05-25

Metric Design Live Δ
deadline_rules rows 254 231 -23
distinct submission_codes 158 153 -5
distinct legal_sources 70 87 +17
concept-linked rules 125 129 +4
paliad.deadlines rows 1 5 +4
submission_drafts rows 4 7 +3
Next available mig 124 134 +10

10 migrations landed in the design's first 24 h (mig 124..133).

Decisions confirmed / refined

  • Q5 — archived multi-row codes (10 _archived_litigation.*): now moot. Zero _archived_litigation.* rows in the live DB; every active submission_code is 1:1 with one rule row. B.1 backfill becomes a straight 1:1 INSERT — no GROUP-BY collapse step needed.
  • Q6 — concept_id attaches to procedural event, not sequencing rule: confirmed N:1. Empirical: 53 distinct concepts cover 129 rule rows; 38% of concepts span >1 submission_code (max 15). §4.1's procedural_events.concept_id FK shape is already correct — no UNIQUE index. Wording in the design's Q6 should be tightened to "many procedural events → one concept" (mechanical edit, not a structural change; flagged in findings §3).
  • Snapshot precedent confirmed: deadline_rules_pre_{091,093,095,098} exists. B.4's drop migration must include CREATE TABLE paliad.deadline_rules_pre_<N> AS TABLE paliad.deadline_rules; before DROP TABLE. Non-negotiable.
  • Doc-side bug fixed: issue body line 56 now reads paliad.deadlines.rule_id (was deadline_rule_id — no such column ever existed). Live FK confirmed via information_schema.referential_constraints: paliad.deadlines.rule_id → paliad.deadline_rules.id.

One decision to surface to m before B.1 starts

78 rule rows have submission_code IS NULL (231 - 153). They are the "structural / parent-only rows in the proceeding tree". B.1 must explicitly decide whether they (a) get skipped and lost at B.4 drop time [unacceptable], (b) get synthetic codes minted and become procedural_events [recommended], or (c) become free-standing sequencing_rules with NULL procedural_event_id [would relax §4.1's NOT NULL FK]. See findings §7 point 2.

Out-of-scope observation

Project CLAUDE.md still says "Migration tracker is paliad.paliad_schema_migrations". Canonical tracker per internal/db/migrate.go:9-21,53,105 is paliad.applied_migrations. The legacy paliad_schema_migrations table is frozen at v106 and only used to bootstrap the new tracker. Separate doc-fix slice recommended; not part of B.0.


Status: B.0 COMPLETE, AWAITING B.1 GREENLIGHT. Researcher (curie) parked.

## B.0 read-only re-validation complete (t-paliad-273) **Branch:** [`mai/curie/researcher-slice-b-zero`](https://mgit.msbls.de/m/paliad/src/branch/mai/curie/researcher-slice-b-zero) · **Commit:** [`c4c0a82`](https://mgit.msbls.de/m/paliad/commit/c4c0a82) **Findings doc:** [`docs/design-procedural-events-b0-findings-2026-05-26.md`](https://mgit.msbls.de/m/paliad/src/branch/mai/curie/researcher-slice-b-zero/docs/design-procedural-events-b0-findings-2026-05-26.md) Scope: re-checked every load-bearing premise in cronus's §1 against the live `paliad` schema. Nothing written to `deadline_rules`. **B.1 stays blocked pending m's greenlight.** ### Drift since 2026-05-25 | Metric | Design | Live | Δ | |---|--:|--:|--:| | `deadline_rules` rows | 254 | 231 | -23 | | distinct `submission_code`s | 158 | 153 | -5 | | distinct `legal_source`s | 70 | 87 | +17 | | concept-linked rules | 125 | 129 | +4 | | `paliad.deadlines` rows | 1 | 5 | +4 | | `submission_drafts` rows | 4 | 7 | +3 | | Next available mig | 124 | **134** | +10 | 10 migrations landed in the design's first 24 h (mig 124..133). ### Decisions confirmed / refined - **Q5 — archived multi-row codes (10 `_archived_litigation.*`):** **now moot.** Zero `_archived_litigation.*` rows in the live DB; every active `submission_code` is 1:1 with one rule row. B.1 backfill becomes a straight 1:1 INSERT — no GROUP-BY collapse step needed. - **Q6 — `concept_id` attaches to procedural event, not sequencing rule:** **confirmed N:1.** Empirical: 53 distinct concepts cover 129 rule rows; 38% of concepts span >1 submission_code (max 15). §4.1's `procedural_events.concept_id` FK shape is already correct — no UNIQUE index. Wording in the design's Q6 should be tightened to "many procedural events → one concept" (mechanical edit, not a structural change; flagged in findings §3). - **Snapshot precedent confirmed:** `deadline_rules_pre_{091,093,095,098}` exists. B.4's drop migration must include `CREATE TABLE paliad.deadline_rules_pre_<N> AS TABLE paliad.deadline_rules;` before `DROP TABLE`. Non-negotiable. - **Doc-side bug fixed:** issue body line 56 now reads `paliad.deadlines.rule_id` (was `deadline_rule_id` — no such column ever existed). Live FK confirmed via `information_schema.referential_constraints`: `paliad.deadlines.rule_id → paliad.deadline_rules.id`. ### One decision to surface to m before B.1 starts **78 rule rows have `submission_code IS NULL`** (231 - 153). They are the "structural / parent-only rows in the proceeding tree". B.1 must explicitly decide whether they (a) get skipped and lost at B.4 drop time [unacceptable], (b) get synthetic codes minted and become `procedural_events` [recommended], or (c) become free-standing `sequencing_rules` with NULL `procedural_event_id` [would relax §4.1's NOT NULL FK]. See findings §7 point 2. ### Out-of-scope observation Project CLAUDE.md still says "Migration tracker is `paliad.paliad_schema_migrations`". Canonical tracker per `internal/db/migrate.go:9-21,53,105` is `paliad.applied_migrations`. The legacy `paliad_schema_migrations` table is frozen at v106 and only used to bootstrap the new tracker. Separate doc-fix slice recommended; not part of B.0. --- **Status: B.0 COMPLETE, AWAITING B.1 GREENLIGHT.** Researcher (curie) parked.
Author
Collaborator

Slice B reconciliation — shift-1 (kepler2, read-only)

Branch: mai/kepler2/slice-b-procedural · Commits: 45e95ae, a72c92e
Doc: docs/findings-slice-b-procedural-events-reconciliation-2026-07-27.md

Nothing written to prod. No code changed.

Slice B shipped in full on 2026-05-26

All six sub-slices landed in one seven-hour train under t-paliad-273 + t-paliad-305:

Sub-slice Commit Time What
B.1 7583308 (mig 136) 15:12 three tables created + backfilled
B.2 38ebccc 17:49 dual-write + drift check
B.3 df592f9 (mig 139) 17:59 read cutover to deadline_rules_unified
B.4 1129bab (mig 140) 19:53 DROP TABLE deadline_rules; rule_idsequencing_rule_id; INSTEAD OF triggers
B.5 5c6a009 20:07 Go type aliases + code/event_kind dual-emit
B.6 9359e99 20:12 /admin/rules/admin/procedural-events + 301s

The task brief expected B.5/B.6 to be outstanding. They are not. The four-phase migration plan in §5 of the design doc is history, not a plan — #93's stated goal is achieved in the database.

Re-measured premises (design doc §1 is from May)

Design Live 2026-07-27
rules 254 242 sequencing_rules
distinct codes 158 242 procedural_events
legal sources 70 89 legal_sources
concept-linked 125 131
deadlines 1 11

Cardinality is 1:1 on 228 of 242 events (9 have 0 rules — archived backfill residue; 4 have 2; 1 has 6). The structural conflation is gone; the modelling gain — one event, several sequencing variants — is realised on 5 events. Consistent with B.0's finding that the _archived_litigation.* multi-row codes had already vanished. Worth stating so nobody re-opens #93 expecting the table split to have changed how the catalog is authored — that is #151 / #149.

Q6 (concept_id on the procedural event) and Q7 (legacy {{rule.X}} aliases forever) both confirmed honoured live.

Four residues — none is "Slice B"

R1 · cmd/gen-upc-snapshot has been broken for two months. main.go:163 still does FROM paliad.deadline_rules — dropped by mig 140. The generator landed at ce28ea9 15:11; mig 140 dropped the table at 19:53 the same day. Correct when written, dead by dinner. The snapshot's own meta.json records this verbatim ("placeholder until cmd/gen-upc-snapshot is updated for the post-mig-140 schema") and has since 2026-05-27, so pkg/litigationplanner/embedded/upc/ ships 2 rules against 192 live ones — the embedded catalog m/paliad#124 §19 built for youpc.org reuse is a stub. Fix is a one-word table swap; the view carries all 38 selected columns. Survived because make snapshot-upc is operator-run and in no CI workflow. Same drop caused two prod 500s in 48h (t-paliad-344) because those paths were hot; this one was cold and just rotted.

R2 · 41 draftable filings invisible in the Composer base picker. handlers/submissions.go:231 filters dr.event_type = 'filing'; 43 live+published rows have event_kind IS NULL (41 non-court), so IS NULL fails the predicate and they never appear. Not edge cases — de.inf.lg.wiedereinsetzung, de.inf.lg.einspruch_versaeumnis, epa.opp.opd.wiedereinsetzung, upc.apl.merits.appeal_r220_1ab, upc.apl.merits.grounds_r220_1ab. Note the irony: the Rechtsbeschwerdebegründung screenshot that opened this issue has cousins in the appeal track that are missing from the picker. Editorial fix (lawyer judgement per row) + data migration. Sequence after m/paliad#144 — the submission_code IS NOT NULL guard is now inert (mig 136 made code NOT NULL), so 5 live null.<hex> codes are held out only by the event_kind arm.

R3 · B.5's envelope migration was never finished. The Deprecation header says "clients should migrate within one slice cycle" — two months ago. The only client is paliad's own admin bundle, which still declares, reads and posts submission_code/event_type (client/admin-rules-edit.ts:18,24,262,279,469; admin-rules-list.ts:25,247). i18n likewise: 157 distinct admin.rules.* keys still live (591 occurrences) vs 21 admin.procedural_events.* — B.6 rebound the labels whose wording changed, not the namespace. Also: the header function's doc comment claims it writes Sunset (RFC 8594); it does not.

R4 · the Go type still conflates what the DB no longer does. models.DeadlineRule and models.SequencingRule both alias pkg/litigationplanner.Rule — one flat struct carrying event identity + sequencing mechanics + legal source, with tags deliberately pinned to the dead deadline_rules row shape (types.go:70 says so). So the conflation moved up a layer; deadline_rules_unified is the adapter, and B.3's "read cutover" cut over to a compatibility view, not to the new model — 8 services read the view, 4 read the new tables. Harder to fix now than in May because litigationplanner is a shared package with an embedded snapshot as wire format, so splitting Rule is a cross-product contract change. models.ProceduralEvent/LegalSource exist and compile but have zero consumers.

Recommendation

Close #93 as delivered (label, not close — m closes issues). Then: R1 → file and fix now, cheapest item here. R2 → editorial ticket after #144. R3 → cleanup ticket, no user-visible change. R4 → no ticket; fold the question into #149 / #151, it wants its own design that first answers whether a flat read-model over a normalised store is actually wrong.

Doc bug still open: this issue's body says deadlines.deadline_rule_id. That column never existed — it was rule_id, and since mig 140 it is sequencing_rule_id. B.0 fixed the design doc; the issue body is still wrong.

No downtime window needed for any of it. The one destructive step (mig 140) was taken 2026-05-26 and its deadline_rules_pre_140 snapshot is still on prod.

## Slice B reconciliation — shift-1 (kepler2, read-only) **Branch:** [`mai/kepler2/slice-b-procedural`](https://mgit.msbls.de/m/paliad/src/branch/mai/kepler2/slice-b-procedural) · **Commits:** [`45e95ae`](https://mgit.msbls.de/m/paliad/commit/45e95ae), [`a72c92e`](https://mgit.msbls.de/m/paliad/commit/a72c92e) **Doc:** [`docs/findings-slice-b-procedural-events-reconciliation-2026-07-27.md`](https://mgit.msbls.de/m/paliad/src/branch/mai/kepler2/slice-b-procedural/docs/findings-slice-b-procedural-events-reconciliation-2026-07-27.md) Nothing written to prod. No code changed. ### Slice B shipped in full on 2026-05-26 All six sub-slices landed in one seven-hour train under t-paliad-273 + t-paliad-305: | Sub-slice | Commit | Time | What | |---|---|---|---| | B.1 | `7583308` (mig **136**) | 15:12 | three tables created + backfilled | | B.2 | `38ebccc` | 17:49 | dual-write + drift check | | B.3 | `df592f9` (mig **139**) | 17:59 | read cutover to `deadline_rules_unified` | | B.4 | `1129bab` (mig **140**) | 19:53 | `DROP TABLE deadline_rules`; `rule_id` → `sequencing_rule_id`; INSTEAD OF triggers | | B.5 | `5c6a009` | 20:07 | Go type aliases + `code`/`event_kind` dual-emit | | B.6 | `9359e99` | 20:12 | `/admin/rules` → `/admin/procedural-events` + 301s | The task brief expected B.5/B.6 to be outstanding. They are not. **The four-phase migration plan in §5 of the design doc is history, not a plan — #93's stated goal is achieved in the database.** ### Re-measured premises (design doc §1 is from May) | | Design | Live 2026-07-27 | |---|--:|--:| | rules | 254 | **242** `sequencing_rules` | | distinct codes | 158 | **242** `procedural_events` | | legal sources | 70 | **89** `legal_sources` | | concept-linked | 125 | **131** | | deadlines | 1 | **11** | Cardinality is **1:1** on 228 of 242 events (9 have 0 rules — archived backfill residue; 4 have 2; 1 has 6). The structural conflation is gone; the *modelling gain* — one event, several sequencing variants — is realised on 5 events. Consistent with B.0's finding that the `_archived_litigation.*` multi-row codes had already vanished. Worth stating so nobody re-opens #93 expecting the table split to have changed how the catalog is authored — that is #151 / #149. Q6 (`concept_id` on the procedural event) and Q7 (legacy `{{rule.X}}` aliases forever) both confirmed honoured live. ### Four residues — none is "Slice B" **R1 · `cmd/gen-upc-snapshot` has been broken for two months.** `main.go:163` still does `FROM paliad.deadline_rules` — dropped by mig 140. The generator landed at `ce28ea9` 15:11; mig 140 dropped the table at 19:53 the **same day**. Correct when written, dead by dinner. The snapshot's own `meta.json` records this verbatim (*"placeholder until cmd/gen-upc-snapshot is updated for the post-mig-140 schema"*) and has since 2026-05-27, so `pkg/litigationplanner/embedded/upc/` ships **2 rules** against 192 live ones — the embedded catalog m/paliad#124 §19 built for youpc.org reuse is a stub. Fix is a one-word table swap; the view carries all 38 selected columns. Survived because `make snapshot-upc` is operator-run and in no CI workflow. Same drop caused two prod 500s in 48h (t-paliad-344) *because* those paths were hot; this one was cold and just rotted. **R2 · 41 draftable filings invisible in the Composer base picker.** `handlers/submissions.go:231` filters `dr.event_type = 'filing'`; 43 live+published rows have `event_kind IS NULL` (41 non-court), so `IS NULL` fails the predicate and they never appear. Not edge cases — `de.inf.lg.wiedereinsetzung`, `de.inf.lg.einspruch_versaeumnis`, `epa.opp.opd.wiedereinsetzung`, `upc.apl.merits.appeal_r220_1ab`, `upc.apl.merits.grounds_r220_1ab`. Note the irony: the Rechtsbeschwerdebegründung screenshot that opened this issue has cousins in the appeal track that are missing from the picker. Editorial fix (lawyer judgement per row) + data migration. **Sequence after m/paliad#144** — the `submission_code IS NOT NULL` guard is now inert (mig 136 made `code` NOT NULL), so 5 live `null.<hex>` codes are held out only by the `event_kind` arm. **R3 · B.5's envelope migration was never finished.** The Deprecation header says *"clients should migrate within one slice cycle"* — two months ago. The only client is paliad's own admin bundle, which still declares, reads and **posts** `submission_code`/`event_type` (`client/admin-rules-edit.ts:18,24,262,279,469`; `admin-rules-list.ts:25,247`). i18n likewise: **157** distinct `admin.rules.*` keys still live (591 occurrences) vs **21** `admin.procedural_events.*` — B.6 rebound the labels whose wording changed, not the namespace. Also: the header function's doc comment claims it writes `Sunset` (RFC 8594); it does not. **R4 · the Go type still conflates what the DB no longer does.** `models.DeadlineRule` *and* `models.SequencingRule` both alias `pkg/litigationplanner.Rule` — one flat struct carrying event identity + sequencing mechanics + legal source, with tags deliberately pinned to the dead `deadline_rules` row shape (`types.go:70` says so). So the conflation moved up a layer; `deadline_rules_unified` is the adapter, and B.3's "read cutover" cut over to a compatibility **view**, not to the new model — 8 services read the view, 4 read the new tables. Harder to fix now than in May because `litigationplanner` is a shared package with an embedded snapshot as wire format, so splitting `Rule` is a cross-product contract change. `models.ProceduralEvent`/`LegalSource` exist and compile but have zero consumers. ### Recommendation **Close #93 as delivered** (label, not close — m closes issues). Then: R1 → file and fix now, cheapest item here. R2 → editorial ticket after #144. R3 → cleanup ticket, no user-visible change. R4 → **no ticket**; fold the question into #149 / #151, it wants its own design that first answers whether a flat read-model over a normalised store is actually wrong. **Doc bug still open:** this issue's body says `deadlines.deadline_rule_id`. That column never existed — it was `rule_id`, and since mig 140 it is `sequencing_rule_id`. B.0 fixed the design doc; the issue body is still wrong. No downtime window needed for any of it. The one destructive step (mig 140) was taken 2026-05-26 and its `deadline_rules_pre_140` snapshot is still on prod.
mAi closed this issue 2026-07-27 15:10:34 +00:00
Author
Collaborator

R3 (envelope + i18n cleanup) is done on mai/linus2/r3-finish-the-b-5.

Commits

  • f68bc52 — refactor(admin-rules): finish the B.5 envelope rename, retire the lapsed deprecation
  • 70e8d4e — test(admin-rules): end-to-end round-trip of a procedural-event edit
  • 828d57f — docs(findings): mark R3 resolved

What changed

  1. /admin/api/rules emits and accepts code / event_kind only. The legacy keys are suppressed by depth-0 shadow fields on adminRuleResponse, because models.DeadlineRule keeps its historical json tags for the planner and the non-admin rule endpoints — that contract is separate (Q7) and untouched.
  2. CreateRuleInput / RulePatch lost their dual-accept aliases and both CoalesceCanonicalKeys() helpers. The Go fields now carry the canonical names; the SQL still writes the submission_code / event_type columns.
  3. adminRuleDeprecationHeaders and its 8 call sites are gone. Its doc comment claimed an RFC 8594 Sunset header the function never wrote — that claim went with it. The separate /admin/rules* URL-deprecation headers from B.6 are untouched.
  4. Frontend: admin-rules-{list,edit}.ts read code / event_kind and PATCH back event_kind.
  5. i18n: 157 admin.rules.* keys moved to admin.procedural_events.*, plus nav.admin.rules and admin.card.rules.*. Ten legacy keys turned out to be dead duplicates of the alias block B.6 parked after the EN block; that block is folded back in and gone. Where the two disagreed (col.proceeding: "Verfahrenstyp" vs "Verfahren") the newer B.6/t-321 wording wins.
  6. Wording pass over the moved corpus and the pre-hydration fallback text in the TSX/TS: user-facing "Regel"/"rule" now reads "Verfahrensschritt"/"procedural event". rule_code / alt_rule_code keep their wording — those name the legal citation, not the entity.

Verification

  • New TestAdminProceduralEventsEditRoundTrip drives the real routes against the live-DB harness: create → read → patch event_kind → re-read → list. Canonical keys carry the values on every hop, legacy keys absent, no Deprecation/Link header, both page routes still admin-gated. Green.
  • This needed a harness fix: newHandlerEnv built its Services bundle without RuleEditor, so /admin/procedural-events never registered and the API answered 503. No admin-rules route had handler-level coverage before.
  • DE/EN: all 132 static keys the two bundles reference plus all 5 tDyn prefixes resolve in both language blocks (154 keys each, symmetric).
  • go build, go vet, full go test ./... against the harness: green. bun run build + bun test (340 pass): green.

Not touched: the {{rule.X}} placeholder aliases in internal/services/submission_vars.go — kept forever per Q7.

No user-visible change other than the German/English wording fixes.

R3 (envelope + i18n cleanup) is done on `mai/linus2/r3-finish-the-b-5`. **Commits** - [f68bc52](https://mgit.msbls.de/m/paliad/commit/f68bc52) — refactor(admin-rules): finish the B.5 envelope rename, retire the lapsed deprecation - [70e8d4e](https://mgit.msbls.de/m/paliad/commit/70e8d4e) — test(admin-rules): end-to-end round-trip of a procedural-event edit - [828d57f](https://mgit.msbls.de/m/paliad/commit/828d57f) — docs(findings): mark R3 resolved **What changed** 1. `/admin/api/rules` emits and accepts `code` / `event_kind` only. The legacy keys are suppressed by depth-0 shadow fields on `adminRuleResponse`, because `models.DeadlineRule` keeps its historical json tags for the planner and the non-admin rule endpoints — that contract is separate (Q7) and untouched. 2. `CreateRuleInput` / `RulePatch` lost their dual-accept aliases and both `CoalesceCanonicalKeys()` helpers. The Go fields now carry the canonical names; the SQL still writes the `submission_code` / `event_type` columns. 3. `adminRuleDeprecationHeaders` and its 8 call sites are gone. Its doc comment claimed an RFC 8594 `Sunset` header the function never wrote — that claim went with it. The separate `/admin/rules*` URL-deprecation headers from B.6 are untouched. 4. Frontend: `admin-rules-{list,edit}.ts` read `code` / `event_kind` and PATCH back `event_kind`. 5. i18n: 157 `admin.rules.*` keys moved to `admin.procedural_events.*`, plus `nav.admin.rules` and `admin.card.rules.*`. Ten legacy keys turned out to be dead duplicates of the alias block B.6 parked after the EN block; that block is folded back in and gone. Where the two disagreed (`col.proceeding`: "Verfahrenstyp" vs "Verfahren") the newer B.6/t-321 wording wins. 6. Wording pass over the moved corpus and the pre-hydration fallback text in the TSX/TS: user-facing "Regel"/"rule" now reads "Verfahrensschritt"/"procedural event". `rule_code` / `alt_rule_code` keep their wording — those name the legal citation, not the entity. **Verification** - New `TestAdminProceduralEventsEditRoundTrip` drives the real routes against the live-DB harness: create → read → patch `event_kind` → re-read → list. Canonical keys carry the values on every hop, legacy keys absent, no `Deprecation`/`Link` header, both page routes still admin-gated. Green. - This needed a harness fix: `newHandlerEnv` built its Services bundle without `RuleEditor`, so `/admin/procedural-events` never registered and the API answered 503. No admin-rules route had handler-level coverage before. - DE/EN: all 132 static keys the two bundles reference plus all 5 `tDyn` prefixes resolve in both language blocks (154 keys each, symmetric). - `go build`, `go vet`, full `go test ./...` against the harness: green. `bun run build` + `bun test` (340 pass): green. Not touched: the `{{rule.X}}` placeholder aliases in `internal/services/submission_vars.go` — kept forever per Q7. No user-visible change other than the German/English wording fixes.
Author
Collaborator

R4 designed — recommendation is against the split.

Design: docs/design-litigationplanner-rule-conflation-2026-07-27.md — commit 43fbf61 on mai/hertz2/r4-litigationplanner.

Do not split litigationplanner.Rule. It is a correct denormalised read projection of deadline_rules_unified. SubmissionCode — a procedural-event column — is the calculation engine's most-referenced field (46 uses) because it is the anchor key. And youpc.org's go.mod pin is live: internal/services/paliad_db_catalog.go implements lp.Catalog (six methods, a 38-column SELECT scanned onto Rule's db tags) against paliad's production schema over the shared Postgres. So the db tags are a cross-repo contract and the view is a cross-repo runtime contract. A split breaks an interface implementation — type aliases cannot help — for zero measured benefit: models.ProceduralEvent and models.LegalSource have had zero consumers since B.5 added them.

But R4 sits on top of a live defect, reproduced on a prod-snapshot harness. On upc.inf.cfi.soc (Klageerhebung / Statement of Claim — rendered by paliad's Fristenrechner and youpc.org's public /deadlines page), patching only an unpublished draft clone rewrote the published rule's name, name_en, description, event_kind and legal_source. Zero deadline_rule_audit rows for the published rule. In the same run duration_value — a sequencing-rule column — stayed correctly isolated.

Mechanism: CloneAsDraft copies submission_code; the INSTEAD OF INSERT trigger shares the procedural event ON CONFLICT (code); the INSTEAD OF UPDATE trigger mirrors seven identity columns onto it unconditionally — while the draft/publish lifecycle lives on sequencing_rules only. Seven of the eight mirrored columns are reachable through RulePatch (code is not).

Not currently instantiated — all four prod drafts have draft_of IS NULL. Reachable through the documented Clone → edit flow, global_admin only.

Recommended work: split RulePatch into ProceduralEventPatch + SequencingRulePatch and write each to its own table. No migration, no wire change, no youpc.org coordination.

Also measured on prod: 17 rows where procedural_events.lifecycle_state/is_active diverges from sequencing_rules (no Go type can see it), and 5 procedural events with more than one sequencing rule — so the trigger comment's "1:1 today; revisit when 1:N becomes a real pattern" is already overtaken.

Four open questions for m in §9 (the sharpest: what should editing a draft's name do to the published rule sharing its identity — copy-on-write, reject, or accept-and-disclose). Five documentation bugs in §8, including rule_editor_service.go:596 claiming a lifecycle mirror the trigger deliberately refuses.

§3's conclusion is an input for #149 and #151; the leak should not wait on them.

**R4 designed — recommendation is against the split.** Design: `docs/design-litigationplanner-rule-conflation-2026-07-27.md` — commit [`43fbf61`](https://mgit.msbls.de/m/paliad/commit/43fbf61) on `mai/hertz2/r4-litigationplanner`. **Do not split `litigationplanner.Rule`.** It is a correct denormalised read projection of `deadline_rules_unified`. `SubmissionCode` — a procedural-event column — is the calculation engine's most-referenced field (46 uses) because it is the anchor key. And youpc.org's `go.mod` pin is live: `internal/services/paliad_db_catalog.go` *implements* `lp.Catalog` (six methods, a 38-column `SELECT` scanned onto `Rule`'s `db` tags) against paliad's **production** schema over the shared Postgres. So the `db` tags are a cross-repo contract and the view is a cross-repo *runtime* contract. A split breaks an interface implementation — type aliases cannot help — for zero measured benefit: `models.ProceduralEvent` and `models.LegalSource` have had zero consumers since B.5 added them. **But R4 sits on top of a live defect, reproduced on a prod-snapshot harness.** On `upc.inf.cfi.soc` (Klageerhebung / Statement of Claim — rendered by paliad's Fristenrechner *and* youpc.org's public /deadlines page), patching only an **unpublished draft** clone rewrote the **published** rule's `name`, `name_en`, `description`, `event_kind` and `legal_source`. Zero `deadline_rule_audit` rows for the published rule. In the same run `duration_value` — a sequencing-rule column — stayed correctly isolated. Mechanism: `CloneAsDraft` copies `submission_code`; the INSTEAD OF INSERT trigger shares the procedural event `ON CONFLICT (code)`; the INSTEAD OF UPDATE trigger mirrors seven identity columns onto it unconditionally — while the draft/publish lifecycle lives on `sequencing_rules` only. Seven of the eight mirrored columns are reachable through `RulePatch` (`code` is not). Not currently instantiated — all four prod drafts have `draft_of IS NULL`. Reachable through the documented Clone → edit flow, `global_admin` only. **Recommended work:** split `RulePatch` into `ProceduralEventPatch` + `SequencingRulePatch` and write each to its own table. No migration, no wire change, no youpc.org coordination. Also measured on prod: **17 rows** where `procedural_events.lifecycle_state`/`is_active` diverges from `sequencing_rules` (no Go type can see it), and **5 procedural events with more than one sequencing rule** — so the trigger comment's *"1:1 today; revisit when 1:N becomes a real pattern"* is already overtaken. Four open questions for m in §9 (the sharpest: what *should* editing a draft's name do to the published rule sharing its identity — copy-on-write, reject, or accept-and-disclose). Five documentation bugs in §8, including `rule_editor_service.go:596` claiming a lifecycle mirror the trigger deliberately refuses. §3's conclusion is an input for #149 and #151; the leak should not wait on them.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: m/paliad#93
No description provided.