Data model: 'rules' conflate legal norms + procedural events — explore submission/procedural-event-first model (inventor) #93
Reference in New Issue
Block a user
No description provided.
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
m's report (2026-05-25 15:02)
While looking at a Rechtsbeschwerdebegründung in the editor sidebar:
The observation
paliad.deadline_rulestoday conflates three distinct concepts into one row: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 underlyingdeadline_rulestable shape. ~1 coder shift.B. Restructure: introduce a
paliad.procedural_eventstable as the primary entity; demote sequencing rules to asequencing_rulestable 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.Xin 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:
VerfahrensschrittorVerfahrensereignis) — covers all of filings, hearings, decisions, replies.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 isrule_id, notdeadline_rule_id— verified B.0 2026-05-26 t-paliad-273);paliad.deadlines.custom_rule_textfrom t-paliad-258 stays as-is.deadline_rulesrow becomes aprocedural_eventsrow + its ownsequencing_rulesrow +legal_sourcesrow.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_rulesfiltering. If we restructure:procedural_eventsbyevent_kind IN ('filing', 'reply')(or similar) → same content, cleaner predicate.procedural_event.code— same string, cleaner name.submission_templatesregistry keys offprocedural_event.codeinstead ofdeadline_rules.submission_code— same external behavior.Deliverable
docs/design-procedural-events-model-2026-05-25.mdon branchmai/<inventor>/procedural-events-design. Sections:deadline_rulesschema + every consumer)Hard rules
mai instruct head. Defaults to (R); escalate to head only on material picks (esp. Q1 and Q2).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 completedwith "DESIGN READY FOR REVIEW". Inventor stays parked. Head gates coder shift.Out of scope
paliad.events(audit feed) — that's a separate table; don't touch.B.0 read-only re-validation complete (t-paliad-273)
Branch:
mai/curie/researcher-slice-b-zero· Commit:c4c0a82Findings doc:
docs/design-procedural-events-b0-findings-2026-05-26.mdScope: re-checked every load-bearing premise in cronus's §1 against the live
paliadschema. Nothing written todeadline_rules. B.1 stays blocked pending m's greenlight.Drift since 2026-05-25
deadline_rulesrowssubmission_codeslegal_sourcespaliad.deadlinesrowssubmission_draftsrows10 migrations landed in the design's first 24 h (mig 124..133).
Decisions confirmed / refined
_archived_litigation.*): now moot. Zero_archived_litigation.*rows in the live DB; every activesubmission_codeis 1:1 with one rule row. B.1 backfill becomes a straight 1:1 INSERT — no GROUP-BY collapse step needed.concept_idattaches 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'sprocedural_events.concept_idFK 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).deadline_rules_pre_{091,093,095,098}exists. B.4's drop migration must includeCREATE TABLE paliad.deadline_rules_pre_<N> AS TABLE paliad.deadline_rules;beforeDROP TABLE. Non-negotiable.paliad.deadlines.rule_id(wasdeadline_rule_id— no such column ever existed). Live FK confirmed viainformation_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 becomeprocedural_events[recommended], or (c) become free-standingsequencing_ruleswith NULLprocedural_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 perinternal/db/migrate.go:9-21,53,105ispaliad.applied_migrations. The legacypaliad_schema_migrationstable 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.
Slice B reconciliation — shift-1 (kepler2, read-only)
Branch:
mai/kepler2/slice-b-procedural· Commits:45e95ae,a72c92eDoc:
docs/findings-slice-b-procedural-events-reconciliation-2026-07-27.mdNothing 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:
7583308(mig 136)38ebcccdf592f9(mig 139)deadline_rules_unified1129bab(mig 140)DROP TABLE deadline_rules;rule_id→sequencing_rule_id; INSTEAD OF triggers5c6a009code/event_kinddual-emit9359e99/admin/rules→/admin/procedural-events+ 301sThe 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)
sequencing_rulesprocedural_eventslegal_sourcesCardinality 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_idon the procedural event) and Q7 (legacy{{rule.X}}aliases forever) both confirmed honoured live.Four residues — none is "Slice B"
R1 ·
cmd/gen-upc-snapshothas been broken for two months.main.go:163still doesFROM paliad.deadline_rules— dropped by mig 140. The generator landed atce28ea915:11; mig 140 dropped the table at 19:53 the same day. Correct when written, dead by dinner. The snapshot's ownmeta.jsonrecords this verbatim ("placeholder until cmd/gen-upc-snapshot is updated for the post-mig-140 schema") and has since 2026-05-27, sopkg/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 becausemake snapshot-upcis 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:231filtersdr.event_type = 'filing'; 43 live+published rows haveevent_kind IS NULL(41 non-court), soIS NULLfails 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 — thesubmission_code IS NOT NULLguard is now inert (mig 136 madecodeNOT NULL), so 5 livenull.<hex>codes are held out only by theevent_kindarm.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 distinctadmin.rules.*keys still live (591 occurrences) vs 21admin.procedural_events.*— B.6 rebound the labels whose wording changed, not the namespace. Also: the header function's doc comment claims it writesSunset(RFC 8594); it does not.R4 · the Go type still conflates what the DB no longer does.
models.DeadlineRuleandmodels.SequencingRuleboth aliaspkg/litigationplanner.Rule— one flat struct carrying event identity + sequencing mechanics + legal source, with tags deliberately pinned to the deaddeadline_rulesrow shape (types.go:70says so). So the conflation moved up a layer;deadline_rules_unifiedis 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 becauselitigationplanneris a shared package with an embedded snapshot as wire format, so splittingRuleis a cross-product contract change.models.ProceduralEvent/LegalSourceexist 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 wasrule_id, and since mig 140 it issequencing_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_140snapshot is still on prod.R3 (envelope + i18n cleanup) is done on
mai/linus2/r3-finish-the-b-5.Commits
What changed
/admin/api/rulesemits and acceptscode/event_kindonly. The legacy keys are suppressed by depth-0 shadow fields onadminRuleResponse, becausemodels.DeadlineRulekeeps its historical json tags for the planner and the non-admin rule endpoints — that contract is separate (Q7) and untouched.CreateRuleInput/RulePatchlost their dual-accept aliases and bothCoalesceCanonicalKeys()helpers. The Go fields now carry the canonical names; the SQL still writes thesubmission_code/event_typecolumns.adminRuleDeprecationHeadersand its 8 call sites are gone. Its doc comment claimed an RFC 8594Sunsetheader the function never wrote — that claim went with it. The separate/admin/rules*URL-deprecation headers from B.6 are untouched.admin-rules-{list,edit}.tsreadcode/event_kindand PATCH backevent_kind.admin.rules.*keys moved toadmin.procedural_events.*, plusnav.admin.rulesandadmin.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.rule_code/alt_rule_codekeep their wording — those name the legal citation, not the entity.Verification
TestAdminProceduralEventsEditRoundTripdrives the real routes against the live-DB harness: create → read → patchevent_kind→ re-read → list. Canonical keys carry the values on every hop, legacy keys absent, noDeprecation/Linkheader, both page routes still admin-gated. Green.newHandlerEnvbuilt its Services bundle withoutRuleEditor, so/admin/procedural-eventsnever registered and the API answered 503. No admin-rules route had handler-level coverage before.tDynprefixes resolve in both language blocks (154 keys each, symmetric).go build,go vet, fullgo test ./...against the harness: green.bun run build+bun test(340 pass): green.Not touched: the
{{rule.X}}placeholder aliases ininternal/services/submission_vars.go— kept forever per Q7.No user-visible change other than the German/English wording fixes.
R4 designed — recommendation is against the split.
Design:
docs/design-litigationplanner-rule-conflation-2026-07-27.md— commit43fbf61onmai/hertz2/r4-litigationplanner.Do not split
litigationplanner.Rule. It is a correct denormalised read projection ofdeadline_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'sgo.modpin is live:internal/services/paliad_db_catalog.goimplementslp.Catalog(six methods, a 38-columnSELECTscanned ontoRule'sdbtags) against paliad's production schema over the shared Postgres. So thedbtags 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.ProceduralEventandmodels.LegalSourcehave 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'sname,name_en,description,event_kindandlegal_source. Zerodeadline_rule_auditrows for the published rule. In the same runduration_value— a sequencing-rule column — stayed correctly isolated.Mechanism:
CloneAsDraftcopiessubmission_code; the INSTEAD OF INSERT trigger shares the procedural eventON CONFLICT (code); the INSTEAD OF UPDATE trigger mirrors seven identity columns onto it unconditionally — while the draft/publish lifecycle lives onsequencing_rulesonly. Seven of the eight mirrored columns are reachable throughRulePatch(codeis not).Not currently instantiated — all four prod drafts have
draft_of IS NULL. Reachable through the documented Clone → edit flow,global_adminonly.Recommended work: split
RulePatchintoProceduralEventPatch+SequencingRulePatchand 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_activediverges fromsequencing_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:596claiming a lifecycle mirror the trigger deliberately refuses.§3's conclusion is an input for #149 and #151; the leak should not wait on them.