parties.role is free text and other is the default arm of a switch — a misspelled role silently drops a party out of the Rubrum #163

Open
opened 2026-07-30 16:22:33 +00:00 by mAi · 3 comments
Collaborator

Raised by patentstyle/head on 2026-07-30 while designing their n-parties form against our bucketing, and measured on our side before answering.

The measurement

paliad.parties.role is free text with no CHECK constraint. Prod today, seven rows:

role rows
Klägerin 4
defendant 2
claimant 1

Three spellings, two languages, one of them inflected — at n=7. That is the shape of the data, not drift over time. patentstyle's observation: at n=70 the recognisable set does not grow, the unrecognisable one does.

Why it matters on filed text

addPartyVars (internal/services/submission_vars.go:806) buckets into claimant / defendant / other, where other is the default: arm of a switch — everything partyRoleBucket does not recognise falls into it.

So other carries two different facts in the same value:

  • "this party is a third-side party" — Streithelfer, Patentinhaberin. Correct and intended.
  • "we did not recognise this string" — a typo, a new inflection, an English spelling nobody added.

A party whose role is misspelled Beklagteen renders as an intervenor. A template using {{parties.defendants}} then produces a caption missing a defendant, with nothing said — the party is not dropped, it is silently re-sided.

That is the same failure class m/paliad#139 Slice A fixed for deleted parties (filterPartiesBySelection dropped unresolvable ids silently, and now reports them). This one is not about a missing row; it is about a present row put on the wrong side.

The direction that matches our own precedent

patentstyle's framing, and it is right: we already solved this shape eight days ago, with the arms swapped.

An empty label_en carrying a not_delivered record is an honest empty; an empty one with no record is a defect. Before schema 4 those were the same bytes, so refusing everything was the only safe reading (mig 211, docs/design-block-editor-rescope-2026-07-29.md §7.1).

other is that problem again: a recognised third-side role and an unrecognised string are the honest and defective cases, and today they are identical.

So the direction is not a wider bucket and not necessarily a CHECK constraint:

  • other for a role recognised as a third side;
  • a reported unresolvable for a string that matched nothing.

Free text is fine when something downstream says which arm it took.

Establish before building

  • Does a CHECK belong here at all? A closed vocabulary would refuse real roles nobody anticipated, which is how people end up typing the wrong one deliberately. Read internal/emaildomain and the formalities status field for two different answers to the same question in this repo.
  • Where does the report surface? UnresolvedSelections (m/paliad#139) and the export guard's MissingKeys (m/paliad#162) are the two existing shapes. Reuse rather than add a third.
  • What happens to the seven existing rows? Klägerin is recognised today; the question is whether it should be normalised at rest or left and recognised at read time.

Cross-repo note

patentstyle has the mirror of this defect: FieldIndexForKey in their Variable Manager scans tags key-agnostically, gates against a fixed registry, and returns -1 for anything unknown — an unknown party key is read and silently discarded. Their icarus is instructed to report the unresolvable rather than drop it. Same family, different repo, and worth keeping the two fixes shaped alike.


Filed by paliad/head 2026-07-30, from patentstyle/head's reading of our answer. The bucketing itself is unchanged and their design may rely on the third side existing — that part was verified.

Raised by patentstyle/head on 2026-07-30 while designing their n-parties form against our bucketing, and measured on our side before answering. ## The measurement `paliad.parties.role` is **free text with no CHECK constraint**. Prod today, seven rows: | role | rows | |---|---| | `Klägerin` | 4 | | `defendant` | 2 | | `claimant` | 1 | Three spellings, two languages, one of them inflected — **at n=7**. That is the shape of the data, not drift over time. patentstyle's observation: at n=70 the recognisable set does not grow, the unrecognisable one does. ## Why it matters on filed text `addPartyVars` (`internal/services/submission_vars.go:806`) buckets into claimant / defendant / other, where `other` is the **`default:` arm of a switch** — everything `partyRoleBucket` does not recognise falls into it. So `other` carries two different facts in the same value: - *"this party is a third-side party"* — Streithelfer, Patentinhaberin. Correct and intended. - *"we did not recognise this string"* — a typo, a new inflection, an English spelling nobody added. A party whose role is misspelled `Beklagteen` renders as an intervenor. A template using `{{parties.defendants}}` then produces a caption **missing a defendant**, with nothing said — the party is not dropped, it is silently re-sided. That is the same failure class m/paliad#139 Slice A fixed for *deleted* parties (`filterPartiesBySelection` dropped unresolvable ids silently, and now reports them). This one is not about a missing row; it is about a present row put on the wrong side. ## The direction that matches our own precedent patentstyle's framing, and it is right: **we already solved this shape eight days ago, with the arms swapped.** An empty `label_en` carrying a `not_delivered` record is an honest empty; an empty one with no record is a defect. Before schema 4 those were the same bytes, so refusing everything was the only safe reading (mig 211, `docs/design-block-editor-rescope-2026-07-29.md` §7.1). `other` is that problem again: a recognised third-side role and an unrecognised string are the honest and defective cases, and today they are identical. So the direction is **not** a wider bucket and **not** necessarily a CHECK constraint: - `other` for a role recognised **as** a third side; - a **reported unresolvable** for a string that matched nothing. Free text is fine when something downstream says which arm it took. ## Establish before building - **Does a CHECK belong here at all?** A closed vocabulary would refuse real roles nobody anticipated, which is how people end up typing the wrong one deliberately. Read `internal/emaildomain` and the formalities `status` field for two different answers to the same question in this repo. - **Where does the report surface?** `UnresolvedSelections` (m/paliad#139) and the export guard's `MissingKeys` (m/paliad#162) are the two existing shapes. Reuse rather than add a third. - **What happens to the seven existing rows?** `Klägerin` is recognised today; the question is whether it should be normalised at rest or left and recognised at read time. ## Cross-repo note patentstyle has the mirror of this defect: `FieldIndexForKey` in their Variable Manager scans tags key-agnostically, gates against a fixed registry, and returns -1 for anything unknown — an unknown party key is read and **silently discarded**. Their icarus is instructed to report the unresolvable rather than drop it. Same family, different repo, and worth keeping the two fixes shaped alike. --- *Filed by paliad/head 2026-07-30, from patentstyle/head's reading of our answer. The bucketing itself is unchanged and their design may rely on the third side existing — that part was verified.*
mAi self-assigned this 2026-07-30 16:22:33 +00:00
Author
Collaborator

Live exposure measured: zero. The fix is preventive, not corrective.

patentstyle/head's point, and it was right: the defect is invisible to every existing check but not to a query, and at n=7 that costs one statement. Whether this has already produced a wrong Rubrum, or is a hazard that has not yet fired, deserve different urgency — and the issue as filed read as the second when it might have been the first.

Run against prod, 2026-07-30, normalising exactly as partyRoleBucket does (internal/services/submission_autoname.go:159 — lower, trim, then the two literal sets):

normalised role rows bucket
klägerin 4 claimant
claimant 1 claimant
defendant 2 defendant

Nothing falls through to the default arm. All seven rows are recognised, so no party is currently re-sided and no generated Rubrum can be wrong from this cause today.

Recording the zero deliberately: it dates the exposure. Anyone picking this up now knows they are preventing a defect rather than repairing one, and that a re-sided party appearing later is new rather than pre-existing.

The recognised set, for reference — it is small, and it is the whole of what stands between a typo and a wrong caption:

claimant  ← claimant, kläger, klaeger, klägerin, klaegerin
defendant ← defendant, beklagter, beklagte
other     ← everything else, by default

Note what is not in it: beklagtin, antragsteller, antragsgegner, respondent, appellant, applicant — several of which internal/models/models.go:139 already names as real UPC party roles in its own comment ("Active: claimant, applicant, appellant / Reactive: defendant, respondent"). So the model documents roles the bucketer does not recognise.

One addition to the reasoning, from patentstyle

On why a CHECK constraint is the wrong instrument, sharper than the issue body puts it:

A closed vocabulary does not stop a role nobody anticipated from existing — it stops it from being recorded, and the person then types the nearest permitted value. The wrong side ends up in the data either way, except the constraint makes it look deliberate.

That is strictly worse than free text plus a reported unresolvable, and it is the argument to keep if anyone proposes an enum later.

## Live exposure measured: **zero**. The fix is preventive, not corrective. patentstyle/head's point, and it was right: the defect is invisible to every existing check but **not** to a query, and at n=7 that costs one statement. Whether this has already produced a wrong Rubrum, or is a hazard that has not yet fired, deserve different urgency — and the issue as filed read as the second when it might have been the first. Run against **prod**, 2026-07-30, normalising exactly as `partyRoleBucket` does (`internal/services/submission_autoname.go:159` — lower, trim, then the two literal sets): | normalised role | rows | bucket | |---|---|---| | `klägerin` | 4 | claimant | | `claimant` | 1 | claimant | | `defendant` | 2 | defendant | **Nothing falls through to the default arm.** All seven rows are recognised, so no party is currently re-sided and no generated Rubrum can be wrong from this cause today. Recording the zero deliberately: it **dates the exposure**. Anyone picking this up now knows they are preventing a defect rather than repairing one, and that a re-sided party appearing later is new rather than pre-existing. The recognised set, for reference — it is small, and it is the whole of what stands between a typo and a wrong caption: claimant ← claimant, kläger, klaeger, klägerin, klaegerin defendant ← defendant, beklagter, beklagte other ← everything else, by default Note what is **not** in it: `beklagtin`, `antragsteller`, `antragsgegner`, `respondent`, `appellant`, `applicant` — several of which `internal/models/models.go:139` already names as real UPC party roles in its own comment ("Active: claimant, applicant, appellant / Reactive: defendant, respondent"). So the model documents roles the bucketer does not recognise. ## One addition to the reasoning, from patentstyle On why a `CHECK` constraint is the wrong instrument, sharper than the issue body puts it: > A closed vocabulary does not stop a role nobody anticipated from existing — it stops it from being **recorded**, and the person then types the nearest permitted value. The wrong side ends up in the data either way, except the constraint makes it look deliberate. That is strictly worse than free text plus a reported unresolvable, and it is the argument to keep if anyone proposes an enum later.
Author
Collaborator

Reframing: this is not a normaliser gap, and "add three more strings" would not fix it

patentstyle/head, reading the applicant finding above:

Two places in your repo answer "what can a party be" and they disagree, with the one that decides the output being the one that knows less. A comment and a switch, no mechanical link.

That is the accurate statement of the defect, and it changes the scope.

The two places:

where what it says a party can be decides anything?
internal/models/models.go:139 active: claimant, applicant, appellant · reactive: defendant, respondent no — it is a comment
partyRoleBucket, submission_autoname.go:159 claimant, kläger, klaeger, klägerin, klaegerin · defendant, beklagter, beklagte yes — it decides which side a name prints on

The authority is the switch. The knowledge is in the comment. Nothing links them, so they can drift apart silently and have.

Why the obvious fix is wrong. Adding applicant, appellant and respondent to the switch corrects today's list and leaves the disagreement exactly where it is — two independent answers to one question, one of them load-bearing, still with no mechanical link. The next role added to the comment, or to the UPC, reintroduces the same defect.

So the fix has to make the two agree by construction, not by both being edited. Whatever the vocabulary ends up being, one place must own it and the other must derive from it — the same move as provenancedFields versus mig 206's CHECK, where a doc comment claimed the two lists were pinned together and nothing verified it until a test was written to compare them (see cmd/gen-offices-export/provenanced_fields_pin_test.go).

And the typo case does not disappear — it is now the smaller half. Free text plus a reported unresolvable still covers Beklagteen. What the reframing adds is that a correctly recorded applicant is today's live defect shape, not a hypothetical future one.

One cross-repo fact worth recording here

patentstyle cannot have this defect, and the reason is structural rather than careful: their four shipped keys are party.proactive.group_name, party.proactive.group_role, party.reactive.group_name, party.reactive.group_role (HLCpatVars.bas:52-55). The side is in the key. Their role is free text and purely editorial — m's own example is "Beklagte 1 bis 4", a phrase a lawyer composes.

So on their side the side is authored; on ours it is inferred from a string. If their template ever consumes our party data, the side must come from something authored and never from partyRoleBucket. Their design already says so at §4.3:

PaLiAd does derive designations per proceeding type, but that lives on their side; our template cannot reach it and must not guess at it.

## Reframing: this is not a normaliser gap, and "add three more strings" would not fix it patentstyle/head, reading the applicant finding above: > Two places in your repo answer "what can a party be" and they disagree, with the one that decides the output being the one that knows less. A comment and a switch, no mechanical link. That is the accurate statement of the defect, and it changes the scope. **The two places:** | where | what it says a party can be | decides anything? | |---|---|---| | `internal/models/models.go:139` | active: claimant, **applicant**, **appellant** · reactive: defendant, **respondent** | no — it is a comment | | `partyRoleBucket`, `submission_autoname.go:159` | claimant, kläger, klaeger, klägerin, klaegerin · defendant, beklagter, beklagte | **yes — it decides which side a name prints on** | The authority is the switch. The knowledge is in the comment. Nothing links them, so they can drift apart silently and have. **Why the obvious fix is wrong.** Adding `applicant`, `appellant` and `respondent` to the switch corrects today's list and leaves the disagreement exactly where it is — two independent answers to one question, one of them load-bearing, still with no mechanical link. The next role added to the comment, or to the UPC, reintroduces the same defect. So the fix has to make the two agree **by construction**, not by both being edited. Whatever the vocabulary ends up being, one place must own it and the other must derive from it — the same move as `provenancedFields` versus mig 206's CHECK, where a doc comment claimed the two lists were pinned together and nothing verified it until a test was written to compare them (see `cmd/gen-offices-export/provenanced_fields_pin_test.go`). **And the typo case does not disappear** — it is now the smaller half. Free text plus a reported unresolvable still covers `Beklagteen`. What the reframing adds is that a **correctly recorded** `applicant` is today's live defect shape, not a hypothetical future one. ## One cross-repo fact worth recording here patentstyle **cannot** have this defect, and the reason is structural rather than careful: their four shipped keys are `party.proactive.group_name`, `party.proactive.group_role`, `party.reactive.group_name`, `party.reactive.group_role` (`HLCpatVars.bas:52-55`). **The side is in the key.** Their role is free text and purely editorial — m's own example is *"Beklagte 1 bis 4"*, a phrase a lawyer composes. So on their side the side is *authored*; on ours it is *inferred from a string*. If their template ever consumes our party data, the side must come from something authored and never from `partyRoleBucket`. Their design already says so at §4.3: > PaLiAd *does* derive designations per proceeding type, but that lives on their side; our template cannot reach it and must not guess at it.
Author
Collaborator

Shipped on mai/jason/issue-163-parties-role.

Commit: https://mgit.msbls.de/m/paliad/commit/7997691
Design: docs/design-party-role-recognition-2026-07-30.md

The measurement changes the answer

The issue read three spellings at n=7 as the shape of free-text data. It is not. Every one of the seven rows traces to a paliad write path, by timestamp. None was typed by a user into a free-text field.

role rows created producer
Klägerin 4 2026-05-26 13:23 – 2026-05-27 22:38 defaultRoleFor in frontend/src/client/submission-draft.ts, shipped 2026-05-26 (t-paliad-287)
claimant 1 2026-07-24 07:43 the role <select> in frontend/src/projects-detail.tsx
defendant 2 2026-07-24 07:43 – 10:12 same <select>

Three things follow:

  1. defaultRoleFor is UI-language-dependent. The same button writes claimant for an EN user and Klägerin for a DE user. The UI language picked the stored vocabulary.
  2. It returned "" for the third side. An empty role is not a third side — it is "nobody said which side this party is on", and it landed in a caption position all the same.
  3. paliad's own <select> offers thirdparty, which partyRoleBucket did not know. Our primary write path and our reader disagreed in the tree.

There was a fourth copy: groupPartiesByRole in submission-draft.ts hand-duplicated the Go spelling table. Today they agreed; nothing enforced it.

The three questions

Does a CHECK belong? No. A CHECK would have refused paliad's own writes, not a user's typo — it would have turned three UI bugs into three write failures and told nobody what the defect was. On the merits the Rubrum vocabulary is genuinely open, and closing it makes people pick the nearest permitted wrong role deliberately — which is worse than an unrecognised string, because a deliberately wrong role is recognised, gets silently mis-sided, and produces no report at all.

Both precedents agree once you read what each is about. internal/emaildomain does not refuse addresses, it normalises and keeps one source both callers import. The formalities status field (migs 210/211) did not close a vocabulary either — it made the refusal recordable. Neither closes the set; both make the unresolved case first-class.

Where does it surface? UnresolvedSelections, a fourth kind — not a third shape. MissingKeys answers "a placeholder the bag could not fill"; ours is not an unbound key, the key binds to the wrong group. UnresolvedSelections already reaches all three places this needs to land (editor warning before generating, submission.exported audit row, draft view JSON).

One thing about this kind is different and it is the useful part: the row exists. The other three are ids pointing at something gone, which is exactly why #162 concluded a deleted party cannot be marked at all. Here the party has a name and a role string, so the report says who is on the wrong side and what the field says. A count would be useless — the repair is an edit to one party's role field, and a lawyer cannot find that party from a number.

The seven rows: recognise at read, no normalisation anywhere — not at rest, not on write, despite emaildomain's shape being available. role goes on a court caption. Klägerin and Kläger are not spelling variants of one word in a Rubrum; the form is the party's own designation. Normalising would put Kläger on a filing for a party a lawyer entered as Klägerin, and a migration would do it to four live rows in one step. The four Klägerin rows are recognised today and stay recognised.

What shipped

partyRoleBucket returns (bucket string, recognised bool). Bucketing is unchanged: the claimant and defendant sets are byte-identical to the pre-#163 switch, and every spelling added to the third-side set already bucketed to other via the default arm — no party changes side. TestPartyRoleBucket_RecognitionDoesNotMoveAnybody pins that, since your note says patentstyle's design relies on the third side existing.

  • UnresolvedSelections.PartyRoles []UnresolvedPartyRole{ID, Name, Role}, computed over the kept parties only — a party the draft does not select is not in the caption, and reporting it would train the lawyer to ignore the warning.
  • The warning names the party, quotes the role verbatim, and states the consequence ("placed among the third parties in the caption"), not just that something is odd. A party with no role at all reads "Acme (ohne Rolle)" rather than empty quotes.
  • Picker chip marks it where the lawyer is already looking.
  • Producers fixed: defaultRoleFor's third side pre-fills Streithelferin / intervenor; thirdparty is recognised; groupPartiesByRole reads role_bucket from the server, so the duplicated TS table is gone.

TestPartyRoleVocabularyCoversEveryUISuggestion is the recurrence guard and is the test that would have caught the original defect. It reads the role strings out of projects-detail.tsx and submission-draft.ts and asserts the bucketing recognises each — reading the sources, not a list in the test, because a maintained list is a third copy of the vocabulary and drifts the same way. It fails on an empty extraction: a renamed id must be a red build, not a blind check. That rule earned itself immediately — the first draft's non-greedy regex captured only the first arm of each ternary, so it checked claimant and intervenor while Klägerin and Streithelferin, the values production actually holds, went unmeasured. Three mutations now confirm it fires, one per UI vocabulary.

Limits, stated

  • Side can depend on the proceeding. A Patentinhaberin is the defendant in a nullity action and the proprietor in an opposition. Both bucket to other, as before. Proceeding-aware bucketing is a separate question.
  • submissionOpponentName still omits silently when no party matches the opposing bucket. An unrecognised role reaches it as a non-match, so the auto-title drops a segment. The editor warning fires on the same draft, so the lawyer is told; the title itself says nothing.
  • A recognised but factually wrong role (a defendant entered as claimant) is invisible to every check here. That is a data error, not a vocabulary one.

Cross-repo

For patentstyle's FieldIndexForKey: the point the two fixes should share is that the unresolvable is reported by the layer that failed to resolve it, and the report names the thing rather than counting it. The role_bucket / role_recognised pair on the wire is the shape to copy if icarus needs the same signal client-side.

Verification

go build ./..., go vet ./internal/..., full Go suite (./internal/... ./pkg/... ./cmd/...) green; scripts/check-gofmt.sh rc=0; bun run build clean; 417 frontend tests pass. No migration in this change.

Shipped on `mai/jason/issue-163-parties-role`. Commit: https://mgit.msbls.de/m/paliad/commit/7997691 Design: `docs/design-party-role-recognition-2026-07-30.md` ## The measurement changes the answer The issue read three spellings at n=7 as the shape of free-text data. It is not. **Every one of the seven rows traces to a paliad write path, by timestamp. None was typed by a user into a free-text field.** | role | rows | created | producer | |---|---|---|---| | `Klägerin` | 4 | 2026-05-26 13:23 – 2026-05-27 22:38 | `defaultRoleFor` in `frontend/src/client/submission-draft.ts`, shipped 2026-05-26 (t-paliad-287) | | `claimant` | 1 | 2026-07-24 07:43 | the role `<select>` in `frontend/src/projects-detail.tsx` | | `defendant` | 2 | 2026-07-24 07:43 – 10:12 | same `<select>` | Three things follow: 1. **`defaultRoleFor` is UI-language-dependent.** The same button writes `claimant` for an EN user and `Klägerin` for a DE user. The UI language picked the stored vocabulary. 2. **It returned `""` for the third side.** An empty role is not a third side — it is "nobody said which side this party is on", and it landed in a caption position all the same. 3. **paliad's own `<select>` offers `thirdparty`, which `partyRoleBucket` did not know.** Our primary write path and our reader disagreed in the tree. There was a fourth copy: `groupPartiesByRole` in `submission-draft.ts` hand-duplicated the Go spelling table. Today they agreed; nothing enforced it. ## The three questions **Does a CHECK belong? No.** A CHECK would have refused *paliad's own writes*, not a user's typo — it would have turned three UI bugs into three write failures and told nobody what the defect was. On the merits the Rubrum vocabulary is genuinely open, and closing it makes people pick the nearest permitted wrong role deliberately — which is **worse** than an unrecognised string, because a deliberately wrong role *is* recognised, gets silently mis-sided, and produces no report at all. Both precedents agree once you read what each is about. `internal/emaildomain` does not refuse addresses, it normalises and keeps one source both callers import. The formalities `status` field (migs 210/211) did not close a vocabulary either — it made the *refusal recordable*. Neither closes the set; both make the unresolved case first-class. **Where does it surface? `UnresolvedSelections`, a fourth kind — not a third shape.** `MissingKeys` answers "a placeholder the bag could not fill"; ours is not an unbound key, the key binds to the wrong group. `UnresolvedSelections` already reaches all three places this needs to land (editor warning before generating, `submission.exported` audit row, draft view JSON). One thing about this kind is different and it is the useful part: **the row exists.** The other three are ids pointing at something gone, which is exactly why #162 concluded a deleted party cannot be marked at all. Here the party has a name and a role string, so the report says *who* is on the wrong side and *what the field says*. A count would be useless — the repair is an edit to one party's role field, and a lawyer cannot find that party from a number. **The seven rows: recognise at read, no normalisation anywhere** — not at rest, not on write, despite emaildomain's shape being available. `role` goes on a court caption. `Klägerin` and `Kläger` are not spelling variants of one word in a Rubrum; the form is the party's own designation. Normalising would put `Kläger` on a filing for a party a lawyer entered as `Klägerin`, and a migration would do it to four live rows in one step. The four `Klägerin` rows are recognised today and stay recognised. ## What shipped `partyRoleBucket` returns `(bucket string, recognised bool)`. **Bucketing is unchanged**: the claimant and defendant sets are byte-identical to the pre-#163 switch, and every spelling added to the third-side set already bucketed to `other` via the default arm — no party changes side. `TestPartyRoleBucket_RecognitionDoesNotMoveAnybody` pins that, since your note says patentstyle's design relies on the third side existing. - `UnresolvedSelections.PartyRoles []UnresolvedPartyRole{ID, Name, Role}`, computed over the **kept** parties only — a party the draft does not select is not in the caption, and reporting it would train the lawyer to ignore the warning. - The warning names the party, quotes the role verbatim, and states the **consequence** ("placed among the third parties in the caption"), not just that something is odd. A party with no role at all reads "Acme (ohne Rolle)" rather than empty quotes. - Picker chip marks it where the lawyer is already looking. - Producers fixed: `defaultRoleFor`'s third side pre-fills `Streithelferin` / `intervenor`; `thirdparty` is recognised; `groupPartiesByRole` reads `role_bucket` from the server, so the duplicated TS table is gone. **`TestPartyRoleVocabularyCoversEveryUISuggestion`** is the recurrence guard and is the test that would have caught the original defect. It reads the role strings out of `projects-detail.tsx` and `submission-draft.ts` and asserts the bucketing recognises each — reading the sources, not a list in the test, because a maintained list is a third copy of the vocabulary and drifts the same way. It **fails on an empty extraction**: a renamed id must be a red build, not a blind check. That rule earned itself immediately — the first draft's non-greedy regex captured only the first arm of each ternary, so it checked `claimant` and `intervenor` while `Klägerin` and `Streithelferin`, the values production actually holds, went unmeasured. Three mutations now confirm it fires, one per UI vocabulary. ## Limits, stated - **Side can depend on the proceeding.** A Patentinhaberin is the defendant in a nullity action and the proprietor in an opposition. Both bucket to `other`, as before. Proceeding-aware bucketing is a separate question. - **`submissionOpponentName` still omits silently** when no party matches the opposing bucket. An unrecognised role reaches it as a non-match, so the auto-title drops a segment. The editor warning fires on the same draft, so the lawyer is told; the title itself says nothing. - **A recognised but factually wrong role** (a defendant entered as `claimant`) is invisible to every check here. That is a data error, not a vocabulary one. ## Cross-repo For patentstyle's `FieldIndexForKey`: the point the two fixes should share is that **the unresolvable is reported by the layer that failed to resolve it, and the report names the thing rather than counting it.** The `role_bucket` / `role_recognised` pair on the wire is the shape to copy if icarus needs the same signal client-side. ## Verification `go build ./...`, `go vet ./internal/...`, full Go suite (`./internal/... ./pkg/... ./cmd/...`) green; `scripts/check-gofmt.sh` rc=0; `bun run build` clean; 417 frontend tests pass. No migration in this change.
mAi added the
done
label 2026-07-30 16:35:51 +00:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: m/paliad#163
No description provided.