From 5bb6df69e15f4dc109eb02e6ea89bd20f7198d87 Mon Sep 17 00:00:00 2001 From: mAi Date: Mon, 25 May 2026 15:44:35 +0200 Subject: [PATCH] =?UTF-8?q?docs:=20t-paliad-262=20=E2=80=94=20procedural-e?= =?UTF-8?q?vents=20data-model=20design=20(inventor)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Slice A (cosmetic rename) + Slice B (structural rework) for the deadline_rules → procedural_events / sequencing_rules / legal_sources split. Recommendation (R)=C (cosmetic now, structural follow-up). Umbrella-term lock: procedural event / Verfahrensschritt. Read-only design phase. No code or schema changes here. m/paliad#93. --- ...sign-procedural-events-model-2026-05-25.md | 570 ++++++++++++++++++ 1 file changed, 570 insertions(+) create mode 100644 docs/design-procedural-events-model-2026-05-25.md diff --git a/docs/design-procedural-events-model-2026-05-25.md b/docs/design-procedural-events-model-2026-05-25.md new file mode 100644 index 0000000..0435d57 --- /dev/null +++ b/docs/design-procedural-events-model-2026-05-25.md @@ -0,0 +1,570 @@ +# Design — Procedural-Events Data Model (t-paliad-262) + +**Author:** cronus (inventor) +**Date:** 2026-05-25 +**Issue:** m/paliad#93 (mai task t-paliad-262) +**Branch:** `mai/cronus/inventor-procedural` +**Status:** DESIGN — read-only, no schema or code changes in this branch +**Prior art read:** +- `docs/design-deadline-data-model-2026-05-08.md` (einstein, t-paliad-158) — proposed `proceeding_event_types` + `proceeding_event_edges`; the **graph-shape recommendation has not been built** (no `proceeding_event*` tables exist in the live DB as of 2026-05-25, verified via `information_schema.tables`). +- `docs/design-fristen-phase2-2026-05-15.md` (Phase 2/3 unified-rule columns — migs 078/079/091, **shipped**). +- `docs/design-submission-generator-2026-05-19.md` and `docs/design-submission-page-2026-05-22.md` (Slice 1 → Slice A of the Schriftsätze stack — shipped on top of today's `deadline_rules`). + +This doc names a single conflation in the schema and proposes a two-slice fix (cosmetic immediate, structural follow-up). It is intentionally narrower than einstein's 2026-05-08 graph proposal — it does **not** re-litigate the proceeding-as-DAG question. + +--- + +## §0 TL;DR + +`paliad.deadline_rules` today is **one row that wears three hats**: + +1. **The procedural-event template** — `submission_code`, `name`, `name_en`, `description`, `event_type`, `primary_party`. This is "what kind of step is this in the proceeding": Rechtsbeschwerdebegründung, mündliche Verhandlung, Entscheidung, etc. +2. **The legal-norm citation** — `legal_source`, `rule_code`, `alt_rule_code`, `rule_codes[]`. This is "the source-of-law anchor": § 102 PatG, UPC RoP R.220(1). +3. **The sequencing rule** — `parent_id`, `trigger_event_id`, `duration_value`, `duration_unit`, `timing`, `alt_duration_*`, `combine_op`, `condition_expr`, `is_spawn`, `spawn_*`, `sequence_order`, `is_court_set`, `priority`, `anchor_alt`, `proceeding_type_id`. This is "how and when does it fire relative to other events". + +The conflation surfaces most painfully in the submission-draft editor's variable sidebar (m's report 2026-05-25 15:02), where the lawyer sees field labels like `{{rule.submission_code}}` for what is plainly a *procedural-event code*, `{{rule.event_type}}` for what is plainly the *procedural-event kind*, and `{{rule.legal_source_pretty}}` for what is plainly the *legal norm* — all under a `rule.*` namespace that reads as if the lawyer were filling in arithmetic. + +**Recommendation = Q1 option (C):** + +- **Slice A (immediate, this design's coder shift):** cosmetic rename — placeholders, i18n labels, Go struct-comment naming, admin-UI page titles all shift to `procedural_event.*` as the canonical name. **Database schema, table name, column names, FK directions, JSON envelope keys on the wire all stay exactly as they are.** Old `{{rule.*}}` placeholders remain emitted in the variable bag as legacy aliases so existing Word templates and saved drafts keep working. +- **Slice B (planned follow-up, separate mai task, separate slice plan):** structural rework — extract `paliad.procedural_events`, `paliad.sequencing_rules`, `paliad.legal_sources`, with a phased dual-write migration. **Not shipped here.** This doc defines the target shape (§4) and the migration shape (§5) so the eventual coder has a brief, not so the eventual coder is hired today. + +**Umbrella term lock = Q2 option (R):** **"procedural event"** (DE: **"Verfahrensschritt"**) as the umbrella covering filings, hearings, decisions, orders. Justification in §2. + +Both Slice A and the eventual Slice B preserve the Schriftsätze surface (t-paliad-238/242/243): the submissions list query changes its predicate from `dr.event_type = 'filing'` to `pe.event_kind IN ('filing', 'reply')` (Slice B only) — same rows, cleaner predicate. + +--- + +## §1 Premises verified live (2026-05-25) + +Every load-bearing claim was checked against the running paliad codebase + youpc Supabase. Numbers and schema facts are point-in-time as of 2026-05-25 15:30. + +| Claim | Verification | +|---|---| +| `paliad.deadline_rules` carries the 38 columns listed in §0's three-hats decomposition. | `information_schema.columns WHERE table_schema='paliad' AND table_name='deadline_rules'` — 38 rows; columns confirmed verbatim. | +| Live row count = 254. | `SELECT COUNT(*) FROM paliad.deadline_rules` → 254. | +| 177 rows carry a `submission_code` (procedural-event identity); 158 distinct values. | `COUNT(*) FILTER (WHERE submission_code IS NOT NULL)` → 177; `COUNT(DISTINCT submission_code)` → 158. | +| 102 rows carry a `legal_source`; 70 distinct citations. | Same query, `legal_source` column. | +| 125 rows are linked to a `deadline_concepts` row via `concept_id`. | `COUNT(*) FILTER (WHERE concept_id IS NOT NULL)` → 125 (49 % of the corpus). | +| `event_type` distribution: 130 `filing` · 77 NULL · 25 `decision` · 21 `hearing` · 1 `order`. | `SELECT event_type, count(*) GROUP BY event_type` — confirmed; the 77 NULL rows are structural / parent-only rows in the proceeding tree. | +| 10 `submission_code` values appear on more than one row (jurisdictional / bilateral variants). | All 10 today are `_archived_litigation.*` codes (claimant/defendant splits + multi-stage hearing rows). Live non-archived codes are 1:1 with rows in the current corpus. | +| `paliad.deadlines` joins to `deadline_rules` via column `rule_id` (uuid, FK). The text `rule_code` and free-text `custom_rule_text` (mig 122, t-paliad-258) are denormalized for display when the rule row is deleted. | `internal/services/deadline_service.go:69-127`; live column list confirms `rule_id`, `rule_code`, `custom_rule_text` — there is **no** `deadline_rule_id` column on deadlines (issue body called it `deadlines.deadline_rule_id` — that's a doc-side typo; the column is `rule_id`). | +| `paliad.submission_drafts` keys to a procedural event via `submission_code` text — **no FK** to `deadline_rules`. | `information_schema.columns` for `submission_drafts`: `submission_code text` plus `(project_id, submission_code)` as the joint identifier. Confirms the Schriftsätze surface filters on the *text key*, not on `deadline_rules.id`. | +| The Schriftsätze list (t-paliad-238) filters `deadline_rules` by `event_type='filing'` and `submission_code IS NOT NULL`. | `internal/handlers/submissions.go:193-211` — verbatim. | +| The variable bag emits exactly 8 `rule.*` placeholders. | `internal/services/submission_vars.go:349-364` — `rule.submission_code`, `rule.name`, `rule.name_de`, `rule.name_en`, `rule.legal_source`, `rule.legal_source_pretty`, `rule.primary_party`, `rule.event_type`. Frontend i18n labels at `frontend/src/client/submission-draft.ts:158-185`. | +| Admin rule-edit form binds the same `rule.X` fields. | `frontend/src/admin-rules-edit.tsx:74-110` + `frontend/src/client/admin-rules-edit.ts:253-278` — same eight columns surfaced as form inputs. | +| The Fristenrechner client surface refers to `calc.rule.nameDE` / `calc.rule.nameEN`. | `frontend/src/client/fristenrechner.ts:1592,1655`. | +| einstein's 2026-05-08 `proceeding_event_types` + `proceeding_event_edges` are **not** in the DB. | `SELECT table_name FROM information_schema.tables WHERE table_schema='paliad' AND table_name LIKE '%proceeding_event%'` → 0 rows. The graph-shape proposal was never built. | +| `paliad.deadline_concepts` (57 rows in the original einstein audit; live count not directly queried this shift) still exists and is referenced via `deadline_rules.concept_id`. | `information_schema.tables` confirms `deadline_concepts`, `deadline_concept_event_types`, `deadline_event_types`, `event_types`, `trigger_events`, `event_categories` all still present — the deadline-knowledge graph from the einstein design lives on alongside the unified rule columns. | +| Phase 2/3 columns (`priority`, `condition_expr`, `is_court_set`, `lifecycle_state`, `draft_of`, `published_at`, `rule_codes[]`) are live and load-bearing. | `internal/models/models.go:622-684` + mig 091. Slice B's structural rework must preserve every one of these on the new `sequencing_rules` table — they are not legacy. | +| Live `paliad.deadlines` references to rules are sparse (1 row in prod). | `SELECT COUNT(*) FROM paliad.deadlines` → 1. The 4 `submission_drafts` rows reference a procedural event by `submission_code` text only. Tiny live FK surface → migrations can be aggressive without losing user data. | +| Migration tracker is `paliad.paliad_schema_migrations`; next available number is 124 (mig 123 = Backup Mode Slice A, just shipped). | `internal/db/migrations/` directory listing; latest applied = 123. | + +**Doc-side bug flagged for this issue's body:** the deliverable spec writes `paliad.deadlines.deadline_rule_id` in §3 (Q3 migration shape). The live column is `paliad.deadlines.rule_id`. Slice B's rename target is therefore `paliad.deadlines.procedural_event_id`, not `paliad.deadlines.procedural_event_id` after a non-existent `deadline_rule_id` step. Updating the issue body is m's call — flagged here so it doesn't propagate into a coder brief. + +--- + +## §2 m's vocabulary call (Q2 — lock the umbrella term) + +m proposed "procedural event" in the report. Options weighed: + +| Option | Reads as | Collisions | Verdict | +|---|---|---|---| +| **"procedural event"** (DE: "Verfahrensschritt") | Umbrella that naturally covers filings, hearings, decisions, orders. Matches lawyer mental model: "the next thing that happens in the proceeding". | None — no `paliad.procedural_event*` table or column today (verified). | **(R) — adopt as canonical.** | +| "submission" | Today the Schriftsätze surface uses this for *filings only* (`event_type='filing'`). Expanding the meaning would silently change Slice A's semantics for an existing UI. | Surface-level collision with the Schriftsätze nomenclature already in production. | Reject — would lose precision for an existing concept. | +| "event" / "event_type" | Existing `deadline_rules.event_type` column. | **Hard collision** with `paliad.events` (audit feed, distinct table, distinct meaning). Renaming around it would be worse than the conflation we're trying to fix. | Reject. | +| "Verfahrensschritt" only (no English) | Cleanest German but no English fallback. | Bilingual UI (DE primary, EN secondary per project CLAUDE.md) requires both. | Reject in isolation — but **adopt as the canonical German rendering** of "procedural event". | +| "Verfahrensereignis" | Closer literal translation of "procedural event". | None. | Reject in favor of "Verfahrensschritt" — m's broader vocabulary uses "Schritt" (e.g. "Antragsschritt") more naturally than "Ereignis", which already maps to `paliad.events` in the audit-feed sense. | + +**Lock:** + +| Surface | Canonical | +|---|---| +| English | **procedural event** (lowercase except sentence-initial) | +| German | **Verfahrensschritt** (m. — der Verfahrensschritt) | +| Plural EN | procedural events | +| Plural DE | Verfahrensschritte | +| Code identifier (Go struct names, TS types) | `ProceduralEvent`, `ProceduralEventKind`, `ProceduralEventTemplate` | +| Snake-case (DB columns, JSON keys, i18n keys, placeholders) | `procedural_event`, `procedural_event_kind`, `procedural_events` (table) | +| Slice A: variable-bag placeholder namespace | `procedural_event.*` (with `rule.*` kept as legacy alias) | +| Slice B: table name (if shipped) | `paliad.procedural_events` | + +`event_type` (the column) becomes `event_kind` in Slice B — using "kind" rather than "type" to free up the word "type" for the proceeding-level taxonomy (`paliad.proceeding_types`, untouched) and to mirror the "event_type vs event_kind" disambiguation einstein hit in the 2026-05-08 doc. In Slice A the column stays `event_type` (no DB change). + +**Q2 is locked by inventor recommendation.** It costs nothing structurally and clears noise across every downstream conversation. If m disagrees in the head round-trip, the only thing that flips is the term — Slice A's scope shape stays. + +--- + +## §3 Scope decision (Q1 — A vs B vs C) + +**Recommendation = (C) — cosmetic rename now, structural rework as a planned follow-up.** + +### Why not (A) — cosmetic only and stop + +(A) leaves the model wrong forever. The conflation isn't just a labelling annoyance — it makes future questions harder to answer cleanly: + +- "How many distinct procedural events does paliad model?" Today: ambiguous (rows vs distinct `submission_code`s vs distinct `(submission_code, proceeding_type_id)` tuples). +- "Where can we attach a per-procedural-event Word template that's independent of which proceeding it appears in?" Today: nowhere — the FK chain forces a per-row template registry, see `internal/handlers/files.go` template fallback. +- "Show me every sequencing rule that triggers a given procedural event across all proceedings." Today: requires joining `deadline_rules` to itself on `submission_code` + `parent_id`, brittle. + +If m signals (A) anyway — fine; the cosmetic-only slice is a strict subset of (C)'s Slice A and ships the same value (label clarity in the editor). But the recommendation is to write down the structural target now while the analysis is fresh. + +### Why not (B) — restructure immediately + +(B) means: one slice plan, one cutover. With: +- 254 live rule rows, +- 1 live `paliad.deadlines` row, +- 4 live `submission_drafts` rows, +- 12 Go services + 6 handlers touching `deadline_rules` + 8 placeholder strings on the wire + the admin rule-editor UI bound to the column shape, + +…doing this in one cutover means a big-bang migration during a downtime window. m has granted exactly one such window in recent memory (2026-05-15 for mig 091's destructive drops), and that one was constrained to a 4-column drop. A four-table restructure has a meaningfully larger blast radius; it warrants its own task with its own slice plan and its own risk review. + +### Why (C) — cosmetic-rename Slice A this design, structural Slice B as a separate task + +Three properties of (C) make it the safe call: + +1. **Slice A is reversible at any time** — every change is in i18n strings, Go struct comments, admin-UI page titles, and the variable-bag aliases. No DB migration. No drop. A revert is a `git revert` of the Slice A commit. +2. **Slice B is fully designed but uncommitted** — §4 and §5 below define the target shape and migration plan, but the design doc itself ships in Slice A. m can read it, redirect it, or park it without pressure to ship it now. +3. **The Schriftsätze surface doesn't care which slice we ship** — Slice A leaves it on `event_type='filing'`; Slice B flips it to `event_kind IN ('filing', 'reply')` over a dual-write window. Either way, the lawyer-facing behavior is unchanged. + +### Slice A's deliverable boundary (what gets renamed, what stays) + +**Renamed in Slice A:** + +- **i18n keys** for the admin rule-editor field labels: `admin.rules.edit.field.submission_code` → `admin.rules.edit.field.procedural_event_code`, etc. (16 keys total — `name`, `name_en`, `description`, `submission_code`, `rule_code`, `legal_source`, `primary_party`, `event_type` × DE/EN — full list in §7.1.) +- **Variable-bag placeholder labels** in `submission-draft.ts:158-185`: the *visible label* (`{ de: "Schriftsatz-Code", en: "Submission code" }`) is unchanged for filings (filings are still Schriftsätze on that surface), but the **namespace shown next to the placeholder string** changes: lawyer sees `{{procedural_event.code}}` in the placeholder column with the same Schriftsatz-Code label and same value. The old `{{rule.submission_code}}` stays in the catalog as an "(alt)" entry pointing at the same field. +- **Variable-bag emission** (`internal/services/submission_vars.go:351-364`): the bag emits **both** key-names for every value, so any Word template / saved draft holding `{{rule.X}}` keeps working without a touch. New templates and the in-app catalog show the canonical `{{procedural_event.X}}` name. +- **Admin page titles + section headings**: "Regel bearbeiten" → "Verfahrensschritt bearbeiten" (DE), "Edit rule" → "Edit procedural event" (EN). "Regeln verwalten" → "Verfahrensschritte verwalten" / "Procedural events". The URL path `/admin/rules` stays — URL renames have downstream cost (bookmarks, audit log entries) and would need their own redirect slice (out of scope here). +- **Go struct comments + service docstrings + worker-facing log lines** that refer to "the rule" → "the procedural event" where the referent is the procedural-event aspect (not the sequencing-rule aspect). Function names, type names, table name stay (Slice B handles those). +- **The "Submission Code / Einreichung-Kennung" label** itself stays (it's the lawyer's anchor — they recognize it). The framing around it changes: it now reads as "the code that identifies this *procedural event*", not "the code attached to this *rule*". + +**Untouched in Slice A:** + +- Database schema. Table name (`paliad.deadline_rules`). Column names. FK directions. Indexes. RLS policies. Triggers. Audit log column `rule_id`. +- Go struct names: `DeadlineRule` stays. The renames here are *prose*, not *code*. Renaming `DeadlineRule` to `ProceduralEvent` couples Slice A to Slice B's table rename — keep them decoupled. +- JSON envelope keys on the wire (`POST /api/admin/rules/:id` still accepts `submission_code` in the body — Slice B's API rename is a breaking change with its own deprecation window). +- URL paths (`/admin/rules`, `/api/admin/rules/:id`, `/api/projects/:id/submissions` etc.). +- `paliad.deadlines.rule_id` FK column name. +- The variable-bag's legacy `{{rule.X}}` keys — kept forever as aliases (cheap, zero rot). +- The `submission_drafts` table's `submission_code` text key. + +This boundary makes Slice A a one-day coder shift: scoped, reversible, label-only. + +### What Slice B inherits + +Slice B inherits a codebase + a UI where every prose surface already speaks "procedural event". It also inherits a *legacy alias contract* (the dual emission in the variable bag) that gives it freedom to rename the JSON keys on the wire and the Go struct in two separate sub-slices without rushing. + +--- + +## §4 Restructure schema (Q3 — if/when we ship Slice B) + +This is the target the eventual Slice B coder would land. **Nothing here ships in this task.** + +### §4.1 Three new tables (plus the rename of `deadline_rules`) + +```sql +-- 1. Procedural event templates — one row per (procedural-event identity) +-- For now the live corpus is 1:1 with non-archived submission_codes +-- (148 of the 158 distinct codes), so we get ~177 rows minus the 10 +-- multi-row codes' duplicates. Bilateral / jurisdictional variants +-- are modeled at the sequencing_rules layer. +CREATE TABLE paliad.procedural_events ( + id uuid PRIMARY KEY DEFAULT gen_random_uuid(), + code text NOT NULL UNIQUE, -- former submission_code + name text NOT NULL, -- DE + name_en text NOT NULL, + description text, + event_kind text NOT NULL, -- filing|reply|hearing|decision|order|other + primary_party_default text, -- claimant|defendant|both|court + legal_source_id uuid REFERENCES paliad.legal_sources(id), + concept_id uuid REFERENCES paliad.deadline_concepts(id), + lifecycle_state text NOT NULL DEFAULT 'published', -- draft|published|archived + draft_of uuid REFERENCES paliad.procedural_events(id), + published_at timestamptz, + is_active boolean NOT NULL DEFAULT true, + created_at timestamptz NOT NULL DEFAULT now(), + updated_at timestamptz NOT NULL DEFAULT now() +); +``` + +```sql +-- 2. Legal sources — the source-of-law citations the procedural event +-- anchors against. ~70 distinct values today (live corpus). +CREATE TABLE paliad.legal_sources ( + id uuid PRIMARY KEY DEFAULT gen_random_uuid(), + citation text NOT NULL UNIQUE, -- "DE.PatG.102", "UPC.RoP.220.1", … + jurisdiction text NOT NULL, -- DE|UPC|EPA|DPMA|other + pretty_de text NOT NULL, -- "§ 102 PatG" + pretty_en text NOT NULL, -- "Section 102 PatG" + notes text, + created_at timestamptz NOT NULL DEFAULT now(), + updated_at timestamptz NOT NULL DEFAULT now() +); +``` + +```sql +-- 3. Sequencing rules — the timing / trigger / condition mechanics that +-- today live alongside the procedural-event identity on deadline_rules. +-- One row per (procedural_event × proceeding × variant). The 10 +-- "_archived_litigation.*" codes that today have 2-5 rows become +-- 2-5 sequencing_rules rows for the same procedural_events row. +CREATE TABLE paliad.sequencing_rules ( + id uuid PRIMARY KEY DEFAULT gen_random_uuid(), + procedural_event_id uuid NOT NULL REFERENCES paliad.procedural_events(id), + proceeding_type_id integer REFERENCES paliad.proceeding_types(id), + parent_id uuid REFERENCES paliad.sequencing_rules(id), -- structural tree, today's parent_id + trigger_event_id bigint REFERENCES paliad.trigger_events(id), -- event-rooted variant + duration_value integer NOT NULL DEFAULT 0, + duration_unit text NOT NULL DEFAULT 'months', + timing text DEFAULT 'after', + alt_duration_value integer, + alt_duration_unit text, + alt_rule_code text, -- legacy free-text alt citation, retained + anchor_alt text, + combine_op text, -- max|min + condition_expr jsonb, + primary_party text, -- per-rule override of the procedural_event default + sequence_order integer NOT NULL DEFAULT 0, + is_spawn boolean NOT NULL DEFAULT false, + spawn_label text, + spawn_proceeding_type_id integer REFERENCES paliad.proceeding_types(id), + is_bilateral boolean NOT NULL DEFAULT false, + is_court_set boolean NOT NULL DEFAULT false, + priority text NOT NULL DEFAULT 'mandatory', + rule_code text, -- legacy short-form citation, retained on the rule + rule_codes text[], -- multi-citation array (mig pre-091) + deadline_notes text, + deadline_notes_en text, + lifecycle_state text NOT NULL DEFAULT 'published', + draft_of uuid REFERENCES paliad.sequencing_rules(id), + published_at timestamptz, + is_active boolean NOT NULL DEFAULT true, + created_at timestamptz NOT NULL DEFAULT now(), + updated_at timestamptz NOT NULL DEFAULT now() +); +``` + +```sql +-- 4. Rename downstream FK + add the link to procedural_events. +ALTER TABLE paliad.deadlines + ADD COLUMN procedural_event_id uuid REFERENCES paliad.procedural_events(id), + ADD COLUMN sequencing_rule_id uuid REFERENCES paliad.sequencing_rules(id); +-- (rule_id stays as a transitional alias during the dual-write window; +-- dropped at end of Slice B) +``` + +```sql +-- 5. Submission drafts: add procedural_event_id FK alongside submission_code. +ALTER TABLE paliad.submission_drafts + ADD COLUMN procedural_event_id uuid REFERENCES paliad.procedural_events(id); +-- (submission_code stays — it's the cosmetic anchor lawyers recognize +-- in URLs and chat, and it doubles as the procedural_events.code value) +``` + +### §4.2 What goes where (column-by-column map) + +Every column on today's `paliad.deadline_rules` lands on exactly one of the three new tables: + +| Today's `deadline_rules` column | Lands on | Notes | +|---|---|---| +| `id`, `created_at`, `updated_at` | `sequencing_rules` | The current row's identity becomes a sequencing-rule row. `procedural_events.id` is **new** — backfilled from `submission_code`. | +| `submission_code` | `procedural_events.code` | Promoted up. Multi-row codes (10 in corpus, all `_archived_litigation.*`) collapse to one row on the new table; the 2-5 sequencing rows hang off it. | +| `name`, `name_en`, `description` | `procedural_events` | Procedural-event identity. | +| `primary_party` | `procedural_events.primary_party_default` AND `sequencing_rules.primary_party` | Both. The procedural event has a default party (claimant for Klage etc.); the sequencing rule can override per-jurisdiction (bilateral variants — e.g. `litigation.reply` claimant vs defendant become two sequencing rows with overridden party). | +| `event_type` | `procedural_events.event_kind` | Hat 1, with rename to `event_kind` (term lock §2). | +| `legal_source` | `legal_sources.citation` + FK from `procedural_events.legal_source_id` | The citation moves to its own row; the procedural event points at it. `pretty_de` / `pretty_en` materialize the existing `legalSourcePretty()` function output as columns (with the function retained as the migration source). | +| `rule_code`, `alt_rule_code`, `rule_codes[]` | `sequencing_rules` | Short-form citation arrays stay on the sequencing rule — they're rule-specific. | +| `proceeding_type_id`, `parent_id`, `trigger_event_id`, `spawn_proceeding_type_id`, `is_spawn`, `spawn_label`, `is_bilateral`, `is_court_set`, `combine_op` | `sequencing_rules` | Hat 3 (mechanics) — exact copies. | +| `duration_value`, `duration_unit`, `timing`, `alt_duration_value`, `alt_duration_unit`, `anchor_alt` | `sequencing_rules` | Hat 3 (mechanics). | +| `condition_expr` (jsonb) | `sequencing_rules` | Hat 3. The grammar from mig 091 stays. | +| `priority`, `sequence_order` | `sequencing_rules` | Hat 3. | +| `is_active`, `lifecycle_state`, `draft_of`, `published_at` | **BOTH** `procedural_events` AND `sequencing_rules` | A procedural event can be retired independently of any one of its sequencing variants. Backfill: copy onto both during dual-write; new rows go through the rule-editor service which writes both sides together. | +| `concept_id` (FK to `deadline_concepts`) | `procedural_events.concept_id` | The concept layer (einstein 2026-05-08) attaches to the procedural event, not the sequencing rule. | +| `deadline_notes`, `deadline_notes_en` | `sequencing_rules` | They're rule-specific notes ("filing the appeal in DE costs €X if you also did Y") — not procedural-event-wide. | + +Three columns disappear: + +- The semantically-overloaded part of `event_type` (renamed to `event_kind` and moved). +- The "what is this thing" vs "how does it fire" name conflict — gone by construction. +- Any column that exists only because of the conflation (none of today's columns are pure overhead — they all carry data — so the count stays at 38 across the three new tables). + +### §4.3 Indexes + RLS + +`paliad.can_see_project()` is the canonical RLS predicate (mig 055). None of the three new tables hold project-scoped data — they're firm-wide reference tables. RLS = none, same posture as today's `deadline_rules` (which is firm-wide and unrestricted at the row level; access control is via the `lifecycle_state='published'` filter in the read paths). + +Indexes inherited from today: + +- `paliad.legal_sources(citation)` — UNIQUE. +- `paliad.procedural_events(code)` — UNIQUE. +- `paliad.procedural_events(concept_id)` — for the deadline-concept join. +- `paliad.sequencing_rules(procedural_event_id, proceeding_type_id, lifecycle_state)` — primary read path for the calculator. +- `paliad.sequencing_rules(parent_id)` — tree walk. +- `paliad.sequencing_rules(trigger_event_id)` — event-rooted variant. + +--- + +## §5 Migration plan (Slice B — when it ships, not in this task) + +Phased dual-write, so the cutover is **never** a single instant where the wire format flips. m gets to roll back any one phase with a `git revert` + an `ALTER TABLE` if a phase misbehaves in prod. + +### §5.1 Phase 1 — Additive (no down-time) + +1. Create `procedural_events`, `sequencing_rules`, `legal_sources`. +2. Backfill `legal_sources` from `DISTINCT legal_source` on `deadline_rules` (70 rows). Populate `pretty_de`/`pretty_en` by calling the existing `legalSourcePretty()` function in a one-shot SQL/Go shim during the migration. Verify `COUNT(DISTINCT legal_source FROM deadline_rules) = COUNT(*) FROM legal_sources`. +3. Backfill `procedural_events` from `DISTINCT submission_code` on `deadline_rules WHERE submission_code IS NOT NULL`. Take `name`, `name_en`, `event_type → event_kind`, `primary_party`, `concept_id`, `description` from the lowest-`id` rule row for each code (tie-breaker: lowest `sequence_order`). Verify `COUNT(*) FROM procedural_events = COUNT(DISTINCT submission_code FROM deadline_rules WHERE submission_code IS NOT NULL)` (= 158). +4. Backfill `sequencing_rules` 1:1 from `deadline_rules` (254 rows). FK `procedural_event_id` resolved by code lookup; sequencing-rule row inherits the `deadline_rules.id` (so existing `deadlines.rule_id` FKs continue to resolve via the new column for the dual-write window — see Phase 3). +5. Add `paliad.deadlines.procedural_event_id` + `sequencing_rule_id` columns, backfill from `deadlines.rule_id` join. +6. Add `paliad.submission_drafts.procedural_event_id`, backfill from `submission_code` join. + +This phase ships behind a feature flag (or just behind unused code) — readers + writers stay on `deadline_rules`. No behavior change. + +### §5.2 Phase 2 — Dual-write (no down-time) + +7. Update `RuleEditorService` to write to both `deadline_rules` (legacy) and (`procedural_events`, `sequencing_rules`, `legal_sources`) on every Create/Update/Publish/Archive. Audit log writes one row per side. +8. Update read paths to **read from the new tables**, falling back to `deadline_rules` if the new row is missing (defense-in-depth during backfill catch-up). +9. Run for ≥ 1 week (m's call on length). Compare row counts and a hash digest of the union daily — if drift, surface. + +### §5.3 Phase 3 — Cutover (no down-time, but reversible only via re-application of the dual-write) + +10. Flip read paths to **only** the new tables (`SubmissionVarsService.loadPublishedRule`, `DeadlineRuleService.*`, `SubmissionService.list`, `ProjectionService`, `FristenrechnerCalc`, etc.). +11. Stop writing to `deadline_rules`. +12. `paliad.deadlines.rule_id` is kept as a no-op alias for one more week; new writes go to `procedural_event_id` + `sequencing_rule_id`. +13. `submission_drafts.submission_code` is kept as the URL anchor; the FK `procedural_event_id` is the primary join key going forward. + +### §5.4 Phase 4 — Drop legacy (downtime window, destructive) + +14. `paliad.deadline_rules_pre_` snapshot of the entire table. +15. DROP TABLE paliad.deadline_rules (after CASCADE-safe FK rewires). +16. DROP COLUMN paliad.deadlines.rule_id (keep `rule_code` + `custom_rule_text` as the human-readable denormalized columns — they're the safety net for orphaned deadlines per t-paliad-258). + +m grants this destructive phase its own window (precedent: mig 091 on 2026-05-15). Until then, the legacy table sits dormant. + +### §5.5 Migration tracker + +- Slice B uses migration numbers 124 (Phase 1 — create tables + backfill) and onward — a 4-5 migration sequence, one per phase boundary, mirroring the Phase 2/3 slicing that shipped under t-paliad-195. +- Each migration includes a `paliad.audit_reason = 'mig : '` set_config like mig 091 did, so the audit log captures the schema journey. + +--- + +## §6 Service-layer impact + +### §6.1 Slice A — prose-only changes + +| File | Change | +|---|---| +| `internal/services/submission_vars.go` | `addRuleVars` → also emit `procedural_event.code`, `procedural_event.name`, `procedural_event.name_de`, `procedural_event.name_en`, `procedural_event.legal_source`, `procedural_event.legal_source_pretty`, `procedural_event.primary_party`, `procedural_event.event_kind` (8 new keys, 1:1 with the 8 existing `rule.*` keys, same values). Rename docstrings + the package-level placeholder map comment ("`rule.*`" → "`procedural_event.*` (with legacy alias `rule.*`)"). | +| `internal/services/deadline_rule_service.go` | Top-of-file comment + struct comment renames only. Method names stay (`DeadlineRuleService`, `GetByID`, etc.). | +| `internal/services/rule_editor_service.go` | Same. | +| `internal/services/projection_service.go`, `deadline_service.go`, `fristenrechner.go`, `submission_draft_service.go`, `event_trigger_service.go`, `event_deadline_service.go`, `proceeding_mapping.go`, `export_service.go` | No code changes. Comments mentioning "the rule"/"rules" stay accurate as long as the file is about sequencing — only services that surface the **identity** aspect of the rule (`submission_vars.go`) need a prose pass. | +| `internal/handlers/submissions.go` | No SQL change. Type+comment renames: the catalog response type stays `submissionListEntry` (it's still a Schriftsatz-level list); doc comments speak of "procedural events whose kind is filing" instead of "rules of type filing". | +| `internal/handlers/admin_rules.go` | URL path stays. JSON envelope stays. Page-render comments + log-line text shift to "procedural event". | +| `internal/handlers/submission_drafts.go`, `deadlines.go`, `fristenrechner.go` | No service-layer change. | + +### §6.2 Slice B — structural + +Mostly load-bearing; not enumerated here in detail (out of scope per (R)=C). The shape: + +- `RuleEditorService` splits into `ProceduralEventService` + `SequencingRuleService` + `LegalSourceService`. The Save / Publish / Archive flow on the editor coordinates all three. +- `DeadlineRuleService.GetByID` becomes `SequencingRuleService.GetByID`; the `submission_code` lookup moves to `ProceduralEventService.GetByCode`. +- `SubmissionVarsService.loadPublishedRule` becomes `loadPublishedProceduralEvent` and returns a triple (`event`, `defaultSequencingRule`, `legalSource`); the variable-bag emission consumes all three. +- `ProjectionService` and the Fristenrechner calculator read from `sequencing_rules` (same column set, same logic — only the table name changes). +- `SubmissionService.list` (handlers/submissions.go) filters `procedural_events.event_kind IN ('filing', 'reply')`. +- Backfill orphans + audit triggers (mig 079 / 089) are re-pointed at `sequencing_rules` + a new `procedural_events_audit`. + +--- + +## §7 UI / i18n impact + +### §7.1 i18n keys (Slice A) + +Existing keys (DE + EN) at `frontend/src/client/i18n.ts` lines ~2834-2920 and ~5800-5890 — surface area is *labels*, not *placeholders-in-Word*: + +| Old key | New key (Slice A) | DE label | EN label | +|---|---|---|---| +| `admin.rules.list.title` | `admin.procedural_events.list.title` | "Verfahrensschritte verwalten — Paliad" | "Manage procedural events — Paliad" | +| `admin.rules.list.heading` | `admin.procedural_events.list.heading` | "Verfahrensschritte verwalten" | "Manage procedural events" | +| `admin.rules.list.subtitle` | `admin.procedural_events.list.subtitle` | "Verfahrensschritte anlegen, bearbeiten und freigeben. Lifecycle: draft → published → archived." | "Create, edit and publish procedural events. Lifecycle: draft → published → archived." | +| `admin.rules.list.new` | `admin.procedural_events.list.new` | "+ Neuer Verfahrensschritt" | "+ New procedural event" | +| `admin.rules.col.submission_code` | `admin.procedural_events.col.code` | "Code" (drop "/ Einreichung-Kennung" — the new heading already disambiguates) | "Code" | +| `admin.rules.col.legal_citation` | `admin.procedural_events.col.legal_source` | "Rechtsgrundlage" | "Legal source" | +| `admin.rules.col.name` | `admin.procedural_events.col.name` | "Bezeichnung" | "Name" | +| `admin.rules.col.proceeding` | `admin.procedural_events.col.proceeding` | "Verfahrenstyp" | "Proceeding" | +| `admin.rules.col.priority` | `admin.procedural_events.col.priority` | "Priorität" | "Priority" | +| `admin.rules.col.lifecycle` | `admin.procedural_events.col.lifecycle` | "Lifecycle" | "Lifecycle" | +| `admin.rules.col.modified` | `admin.procedural_events.col.modified` | "Zuletzt geändert" | "Last modified" | +| `admin.rules.edit.title` | `admin.procedural_events.edit.title` | "Verfahrensschritt bearbeiten — Paliad" | "Edit procedural event — Paliad" | +| `admin.rules.edit.heading.loading` | `admin.procedural_events.edit.heading.loading` | "Verfahrensschritt laden…" | "Loading procedural event…" | +| `admin.rules.edit.breadcrumb` | `admin.procedural_events.edit.breadcrumb` | "← Verfahrensschritte verwalten" | "← Manage procedural events" | +| `admin.rules.edit.field.submission_code` | `admin.procedural_events.edit.field.code` | "Code (Schriftsatz-Code / Einreichung-Kennung)" — keep the parenthetical so lawyers familiar with the old label know what they're looking at. | "Code (submission / procedural-event identifier)" | +| `admin.rules.edit.field.rule_code` | `admin.procedural_events.edit.field.short_citation` | "Rechtsgrundlage (Kurzform)" | "Legal source (short form)" | +| `admin.rules.edit.field.legal_source` | `admin.procedural_events.edit.field.legal_source` | "Rechtsgrundlage (Langform)" | "Legal source (long form)" | +| `admin.rules.edit.field.name` | `admin.procedural_events.edit.field.name` | "Bezeichnung (DE)" | "Name (DE)" | +| `admin.rules.edit.field.name_en` | `admin.procedural_events.edit.field.name_en` | "Bezeichnung (EN)" | "Name (EN)" | +| `admin.rules.edit.field.proceeding` | `admin.procedural_events.edit.field.proceeding` | "Verfahrenstyp" | "Proceeding type" | +| `admin.rules.edit.field.trigger` | `admin.procedural_events.edit.field.trigger` | "Trigger-Ereignis" | "Trigger event" | +| `admin.rules.edit.field.parent` | `admin.procedural_events.edit.field.parent` | "Übergeordneter Verfahrensschritt (UUID)" | "Parent procedural event (UUID)" | +| `admin.rules.edit.field.concept` | `admin.procedural_events.edit.field.concept` | "Konzept (UUID)" | "Concept (UUID)" | +| `admin.rules.edit.field.sequence_order` | `admin.procedural_events.edit.field.sequence_order` | "Reihenfolge" | "Order" | +| `admin.rules.edit.field.duration_value` | `admin.procedural_events.edit.field.duration_value` | "Dauer" | "Duration" | +| `admin.rules.edit.field.primary_party` | `admin.procedural_events.edit.field.primary_party` | "Partei (typisch)" | "Primary party" | +| `admin.rules.edit.field.event_type` | `admin.procedural_events.edit.field.event_kind` | "Art des Verfahrensschritts" | "Procedural-event kind" | +| `admin.rules.edit.field.description` | `admin.procedural_events.edit.field.description` | "Beschreibung" | "Description" | + +**Legacy keys retained as aliases** so any existing translation imports or external integrations keep working — old keys point at the same DE/EN values during a deprecation window of one full Slice B cycle. + +### §7.2 Variable-bag placeholders (Slice A) + +`frontend/src/client/submission-draft.ts:155-185` — the catalog of placeholders the lawyer sees in the sidebar: + +| Old placeholder (kept as legacy alias) | New canonical placeholder | DE label | EN label | +|---|---|---|---| +| `{{rule.submission_code}}` | `{{procedural_event.code}}` | "Code (Verfahrensschritt)" | "Code (procedural event)" | +| `{{rule.name}}` | `{{procedural_event.name}}` | "Bezeichnung" | "Name" | +| `{{rule.name_de}}` | `{{procedural_event.name_de}}` | "Bezeichnung (DE)" | "Name (DE)" | +| `{{rule.name_en}}` | `{{procedural_event.name_en}}` | "Bezeichnung (EN)" | "Name (EN)" | +| `{{rule.legal_source}}` | `{{procedural_event.legal_source}}` | "Rechtsgrundlage (Code)" | "Legal source (code)" | +| `{{rule.legal_source_pretty}}` | `{{procedural_event.legal_source_pretty}}` | "Rechtsgrundlage" | "Legal source" | +| `{{rule.primary_party}}` | `{{procedural_event.primary_party}}` | "Partei (typisch)" | "Primary party" | +| `{{rule.event_type}}` | `{{procedural_event.event_kind}}` | "Art des Verfahrensschritts" | "Procedural-event kind" | + +The catalog renders the canonical name in the "copy-this-placeholder" button. The variable bag (`submission_vars.go`) emits both names with identical values, so any Word template the lawyer already has continues to work; new templates are encouraged to use the canonical name. + +### §7.3 Admin rule-editor form (Slice A) + +`frontend/src/admin-rules-edit.tsx:74-110` — i18n key rebinds + heading text update. The DOM `id` attributes (`f-submission-code`, `f-rule-code`, `f-legal-source`, …) stay — they're internal, the rename here is cosmetic, the form still POSTs the same JSON envelope (Slice A doesn't touch the API). The fieldset `legend` for the "Identität" section changes to "Verfahrensschritt-Identität" (DE) / "Procedural-event identity" (EN). The "Verfahren & Trigger" section heading stays — that section is about sequencing, and Slice A doesn't rename sequencing-level labels (those are Slice B). + +### §7.4 Project-detail Schriftsätze tab + dashboard + +`frontend/src/client/submissions.ts`, `submissions-index.ts`: no surface-level label change in Slice A. The Schriftsätze tab continues to show Schriftsätze (the lawyer's preferred term for *filings specifically*). The tab is a filtered view onto procedural events of kind `filing`/`reply` — that distinction surfaces only in admin contexts. + +### §7.5 Help text + docs + +A short addition to the in-app help: "What is a procedural event?" — one-paragraph definition explaining the umbrella term, with examples (Klage, Klageerwiderung, mündliche Verhandlung, Endurteil). Stored in `frontend/src/client/i18n.ts` under `help.procedural_events.intro`. Out of scope for the URL/router changes — added as static copy where it fits naturally. + +--- + +## §8 Slice plan + +### §8.1 Slice A (this design's downstream task) + +**Scope:** prose-only rename per §3 ("renamed in Slice A" list). + +**Mechanics:** + +1. Add 8 new placeholder keys to the variable bag in `submission_vars.go` (1:1 with the existing 8 `rule.*` keys). Keep the legacy keys. +2. Update `frontend/src/client/submission-draft.ts` placeholder catalog labels. +3. Rebind admin i18n keys per §7.1 (with legacy keys retained). +4. Update admin page titles + section headings. +5. Update Go struct comments + service docstrings in `submission_vars.go`, `deadline_rule_service.go`, `rule_editor_service.go`, `submission_draft_service.go`, `submissions.go` handler. No code-flow change. +6. Update `internal/handlers/submissions.go` doc comments. +7. Add a short `docs/glossary.md` entry (or extend an existing one) for "procedural event" / "Verfahrensschritt" — single source of truth for the term. +8. Tests: rename strings in existing test fixtures + add a regression test that the variable bag emits **both** the legacy `rule.X` and the canonical `procedural_event.X` keys with the same value. (Critical — without this test, a future commit could drop the legacy alias and silently break user templates.) +9. Manual smoke: open the admin rule editor, confirm the new title appears. Open the submission-draft editor, confirm both `{{rule.X}}` and `{{procedural_event.X}}` placeholders are listed (with canonical first). Generate a `.docx` from a project using each placeholder name — both render identically. + +**Risk:** very low. No DB change, no API change, fully reversible. + +**No hours estimate per project CLAUDE.md.** + +### §8.2 Slice B (separate mai task — designed here, hired later) + +**Scope:** structural rework per §4 + §5. + +**Mechanics:** Phase 1 → Phase 4 per §5. + +**Prerequisite:** m greenlights via a new mai task with this doc + §11's open items addressed. **Not part of Slice A.** + +**Sub-slices (suggested for Slice B's own task):** + +- **B.0** — Re-validate this doc's premises against live DB (numbers shift over weeks). +- **B.1** — Phase 1 additive migration + backfill (mig 124). +- **B.2** — Phase 2 dual-write + read-fallback. +- **B.3** — Phase 3 read cutover (no schema change). +- **B.4** — Phase 4 destructive drop (downtime window). +- **B.5** — Rename Go types `DeadlineRule` → `SequencingRule` + `ProceduralEvent`; rename JSON API envelope keys with a deprecation header. Independent of B.4. +- **B.6** — Rename admin URL paths `/admin/rules` → `/admin/procedural-events` with redirects. Optional / low-priority. + +### §8.3 Why splitting is the right call + +The conflation is real, but the *fix* for the most-painful surface (the editor sidebar) is independent of the table restructure. Splitting lets m ship the fix this week, see whether the prose change alone resolves enough of the cognitive friction, and then decide whether the structural rework is still worth the migration cost. If after Slice A m says "this reads fine now, B isn't worth it", that's a legitimate outcome — Slice B is a *good* refactor, not an *urgent* one. + +--- + +## §9 Risk assessment + +### §9.1 Slice A risks + +| Risk | Likelihood | Severity | Mitigation | +|---|---|---|---| +| Lawyer's existing Word template has `{{rule.submission_code}}` baked in; a future commit drops the legacy alias and breaks templates. | Low (Slice A keeps the alias) | High if it happens | Regression test (§8.1 step 8) asserts both keys emit. Add an audit-log line on every variable-bag call recording which keys were consumed by the merge engine — gives a 30-day window of evidence before we'd consider deprecating the legacy keys. | +| i18n key rename misses a binding, leaving an English string visible to a DE user. | Medium | Low | The build pipeline (`bun test` / `bun build`) fails on missing i18n keys in `i18n-keys.ts`. Add the new keys to the type union; leave the old keys in the union with `@deprecated` JSDoc. | +| Renamed admin page heading confuses returning admin users ("Where did 'Regeln verwalten' go?"). | Medium | Low | One-time changelog entry; the URL `/admin/rules` is unchanged so muscle memory still lands them on the page. Internal users only (whitelist-gated). | +| Slice A reads as "we're done" and Slice B never ships. | Medium | Medium (the model stays wrong) | This doc files the Slice B design as a separate task entry **before** Slice A merges, so the to-do is visible. m's call whether to schedule it. | + +### §9.2 Slice B risks (deferred; recorded for the future task) + +| Risk | Mitigation | +|---|---| +| Backfill collapses too eagerly: 10 multi-row submission_codes today are `_archived_litigation.*` — confirm they should collapse into one procedural event with 2-5 sequencing variants, vs. each row becoming its own procedural event. | The `_archived_litigation.*` codes are archived per their prefix — collapse is safe. **Decision-flag for Slice B's own design pass.** | +| `deadline_concepts` linkage (125 of 254 rules link to a concept) — does the concept attach to the procedural event or the sequencing rule? §4.2 says procedural event; verify this is right when re-validating premises in B.0. | Read-path audit: every consumer that joins `deadline_rules.concept_id` (rule_editor, projection, fristenrechner) operates on the rule-level today. Reconfirm none of them depend on per-jurisdiction concept-attachment. | +| The dual-write window introduces drift if a write hits one side and fails on the other. | Atomicity via single transaction per write in `RuleEditorService`. Daily drift-check job (one SELECT pair, alert if mismatched). | +| `paliad.deadlines.rule_id` (1 live row, but more in future) — backfilling `procedural_event_id` + `sequencing_rule_id` must not orphan the live row. | The 1 live row joins cleanly. Backfill in the same migration that adds the new columns. | +| The submission-draft `submission_code` text key — what if two `procedural_events.code` values collide post-rename (e.g. a draft was saved against a code that we then archive)? | Slice B Phase 1 enforces `procedural_events.code UNIQUE`; the backfill verifies no collision on the existing 158 distinct values. Drafts with codes that no longer exist as published procedural events are handled by the existing `submission_drafts.submission_code` text fallback (no FK enforcement). | +| Slice B's API-key rename (`submission_code` → `code` in JSON) breaks external integrations. | None exist today (paliad is internal-only); add a one-Slice deprecation header (`X-Deprecated-Field: submission_code`) before flipping. | +| **Coordination risk with future fristen/calculator work.** The Fristenrechner calculator reads `deadline_rules` directly today. Slice B Phase 2's read-fallback handles this, but a parallel calculator feature in flight could land changes that need re-merging. | B.0's job: confirm no in-flight task touches `deadline_rules` table shape before scheduling. | + +### §9.3 What rolls Slice A back + +`git revert ` + reload. Zero data side-effects (no DB writes). 30 seconds. + +### §9.4 What rolls Slice B back + +Per phase — Phases 1-3 reversible via reverting code + `DROP TABLE`. Phase 4 reversible only by restoring `deadline_rules` from the `_pre_` snapshot taken at the start of Phase 4. Same posture as mig 091 — m's call when to commit to this point. + +--- + +## §10 Out of scope + +- **Renaming `paliad.events`** (the audit feed). Distinct table, distinct concept. The umbrella-term lock (§2) deliberately uses "procedural event" not "event" to avoid colliding with it. +- **Renaming `paliad.deadline_concepts`** to align with the procedural-event taxonomy. The concept layer is the cross-proceeding semantic bridge (einstein 2026-05-08 Q5); the relationship "procedural event has-a concept" already reads cleanly under the new term. +- **Per-jurisdiction variations of the same procedural event** (issue body's explicit out-of-scope). The 10 multi-row codes in the corpus today stay multi-row. +- **Multi-tenant / cross-firm sharing of procedural events** — paliad is single-tenant per deploy via `FIRM_NAME`; cross-firm is a separate design. +- **einstein's `proceeding_event_edges` graph proposal.** That design proposed a graph of typed event-types connected by typed edges. This design's procedural-events / sequencing-rules split is **compatible** with that graph shape (the edges would attach to procedural-event-IDs rather than sequencing-rule-IDs), but the graph layer is a Slice C, not Slice B. Flagged for future continuity, not part of either slice here. +- **Renaming Go type `DeadlineRule` to `SequencingRule` or `ProceduralEvent` in Slice A.** Slice A is prose; Slice B's B.5 sub-slice handles the type rename. Coupling them costs the reversibility property. +- **API-envelope key renames** (`submission_code` → `code`, `event_type` → `event_kind` on the wire). Slice B only. +- **URL path renames** (`/admin/rules` → `/admin/procedural-events`). Slice B.6, optional. +- **Touching `paliad.trigger_events`** beyond keeping the FK path open (today `deadline_rules.trigger_event_id`; Slice B maps to `sequencing_rules.trigger_event_id`). +- **Touching `paliad.event_categories` / Pathway-B navigation.** Independent layer. + +--- + +## §11 Open questions for m (escalated via `mai instruct head` per project CLAUDE.md) + +Per project CLAUDE.md "Head answers questions — NO AskUserQuestion" rule, these are surfaced to head, not picked-as-chip with the user. + +| ID | Question | Inventor recommendation | Material to head? | +|---|---|---|---| +| **Q1** | Scope: cosmetic-only (A) · full restructure (B) · cosmetic now + B as planned follow-up (C). | **(R) = C** | Yes — material. Defines whether Slice B is hired today or filed as a future task. | +| **Q2** | Umbrella term: "procedural event" (DE: Verfahrensschritt) · "submission" (filings only) · "Verfahrensereignis" · other. | **(R) = procedural event / Verfahrensschritt** | Yes — material. The term ripples through every label in §7. Inventor's pick is the canonical choice; head can override with a single message. | +| **Q3** | Slice B migration shape: confirmed (§4 + §5) or rescope. | **(R) = §4 + §5 as written, decision deferred until Slice B is hired** | No — informational. Locked when Slice B's own design pass runs. | +| **Q4** | Effect on Schriftsätze surface: filter `procedural_events.event_kind IN ('filing', 'reply')` is acceptable replacement for today's `event_type='filing'`. | **(R) = yes, semantically equivalent under Slice B; no behavior change to lawyer.** | No — informational. | +| **Q5** | Are the 10 archived multi-row submission_codes (`_archived_litigation.*`) safe to collapse into single procedural events with multiple sequencing variants in Slice B? | **(R) = yes, prefix indicates archival; collapse-safe.** | No — informational, defers to Slice B. | +| **Q6** | `concept_id` attaches to procedural event, not sequencing rule. Confirmable? | **(R) = yes, per §4.2 (one concept per identity, not per jurisdiction).** | No — informational, defers to Slice B. | +| **Q7** | Keep the legacy `{{rule.X}}` placeholder aliases **forever**, or set a deprecation horizon (e.g. 1 year)? | **(R) = forever, with `@deprecated` annotation in the catalog. Removing them risks breaking lawyer-authored templates that paliad doesn't see.** | Yes — material to Slice A's contract (test in §8.1 step 8 asserts both keys emit). | +| **Q8** | Document side: update m/paliad#93 issue body to fix the `deadlines.deadline_rule_id` → `deadlines.rule_id` typo (§1 last paragraph). | **(R) = yes, head's call when to edit.** | No — informational, doc hygiene. | +| **Q9** | After Slice A ships, do we file Slice B as a new mai task **now** (so it's visible), or wait for m to ask? | **(R) = file now, status:planning, no owner. Visibility >> deferred surprise.** | Yes — material to "does the model stay wrong forever". | + +Q1, Q2, Q7, Q9 are the four head needs to answer before the coder shift. Q3-Q6, Q8 defer cleanly. + +--- + +## §12 Appendix — verbatim m quote + +From m's report 2026-05-25 15:02 (paliad#93 body): + +> 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 design above takes m's three-way split — *the procedural event* / *the legal norm* / *the rule by which they are sequenced* — at face value and turns it into a column-level map (§4.2), a slice plan (§8), and a deprecation contract (§9.1). + +--- + +*End of design.*