Skeleton generator: service-address line renders raw English in DE — stale @hoganlovells.com match (needs rebrand-domain decision) #158

Open
opened 2026-07-06 11:01:56 +00:00 by mAi · 2 comments
Collaborator

Surfaced by hopper during t-paliad-368 S7 (2026-07-06).

Symptom

In scripts/gen-hl-skeleton-template/main.go, the exampleRules entry for the recitals service-address line matches on:

Electronic address for service: upc-hub@hoganlovells.com

but the current HLC Patents Style .dotm (in mAi/patentstyle, word/HLC Patents Style.dotm) now authors that line as upc-hub@hlc.com post-rebrand. Because the match text is stale, the rule never fires, so the whole paragraph — including its DE/EN branching — is inert: the DE _firm-skeleton.docx renders the line as raw, untranslated English (Electronic address for service: upc-hub@hlc.com) instead of the German Zustellungsanschrift: ….

This is a bigger effect than "the domain is stale": the DE localisation of the entire service-address line is currently dead.

(Note: the sibling user.email rule had the same stale-domain match — [name.lastname]@hoganlovells.com — and was fixed to @hlc.com in S7 (commit 5e1a67f) because user.email wasn't landing at all without it. This ticket is the remaining twin, left untouched pending the domain decision below.)

Why it wasn't fixed in S7

m's standing instruction during S7 was to leave the service hub email alone. The fix is trivial mechanically (update the exampleRules match text upc-hub@hoganlovells.comupc-hub@hlc.com, mirroring the user.email fix) — but it hinges on a firm decision:

Needs from m

  1. Is the UPC service-address hub now upc-hub@hlc.com? The .dotm already authors it that way, which strongly implies yes — but confirm before baking it into the generated skeleton that ships on court filings.
  2. Once confirmed: update the match text so the rule fires again (restores DE Zustellungsanschrift: translation), regenerate the two skeletons into mAi/patentstyle word/Paliad/HLC/, and verify the DE skeleton renders the German label.

Scope

  • scripts/gen-hl-skeleton-template/main.go (one exampleRules entry)
  • regenerate _firm-skeleton.docx + _skeleton.en.docx in mAi/patentstyle
  • programmatic render check (no Gotenberg needed) as in S7

Low priority; isolated to the service-address recitals line. The rest of t-368 (letterhead sender + court recipient address auto-fill) is shipped and live.

Surfaced by hopper during t-paliad-368 S7 (2026-07-06). ## Symptom In `scripts/gen-hl-skeleton-template/main.go`, the `exampleRules` entry for the recitals service-address line matches on: ``` Electronic address for service: upc-hub@hoganlovells.com ``` but the current HLC Patents Style `.dotm` (in `mAi/patentstyle`, `word/HLC Patents Style.dotm`) now authors that line as **`upc-hub@hlc.com`** post-rebrand. Because the match text is stale, the rule **never fires**, so the whole paragraph — including its DE/EN branching — is inert: the DE `_firm-skeleton.docx` renders the line as **raw, untranslated English** (`Electronic address for service: upc-hub@hlc.com`) instead of the German `Zustellungsanschrift: …`. This is a bigger effect than "the domain is stale": the DE localisation of the entire service-address line is currently dead. (Note: the sibling `user.email` rule had the same stale-domain match — `[name.lastname]@hoganlovells.com` — and was fixed to `@hlc.com` in S7 (commit 5e1a67f) because `user.email` wasn't landing at all without it. This ticket is the remaining twin, left untouched pending the domain decision below.) ## Why it wasn't fixed in S7 m's standing instruction during S7 was to leave the service hub email alone. The fix is trivial mechanically (update the `exampleRules` match text `upc-hub@hoganlovells.com` → `upc-hub@hlc.com`, mirroring the `user.email` fix) — but it hinges on a firm decision: ## Needs from m 1. **Is the UPC service-address hub now `upc-hub@hlc.com`?** The `.dotm` already authors it that way, which strongly implies yes — but confirm before baking it into the generated skeleton that ships on court filings. 2. Once confirmed: update the match text so the rule fires again (restores DE `Zustellungsanschrift:` translation), regenerate the two skeletons into `mAi/patentstyle` `word/Paliad/HLC/`, and verify the DE skeleton renders the German label. ## Scope - `scripts/gen-hl-skeleton-template/main.go` (one `exampleRules` entry) - regenerate `_firm-skeleton.docx` + `_skeleton.en.docx` in `mAi/patentstyle` - programmatic render check (no Gotenberg needed) as in S7 Low priority; isolated to the service-address recitals line. The rest of t-368 (letterhead sender + court recipient address auto-fill) is shipped and live.
Author
Collaborator

Fixed, plus a second inert rule this ticket did not know about. Not closing — m closes.

Commits

  • paliad 6efc14c — the fix and the detector
  • paliad 8492f90 — merge-path assertion
  • patentstyle e7dbb2c — the regenerated skeletons, branch mai/diesel/skeleton-158-regen, not merged

Branch mai/diesel/m-paliad-158-a-stale. CI runs 668 and 671 green.

1. Still stale — confirmed from two sources

Both the patentstyle source word/HLC Patents Style.dotm and paliad's own published copy (frontend/public/patentsstyle/HLC-Patents-Style.dotm, v0.260728-0005) author:

Electronic address for service: upc-hub@hlc.com

So the domain question this ticket parked is answered by the template itself, and by m's 2026-07-27 canonical-domain ruling. No new decision was needed.

2. A second rule was dead the same way

The detector found it: the letterhead date rule matched 14 April 2026; the template says 1 July 2026. So {{today.long_de}} / {{today.long_en}} never landed either.

This is visible in the artifact that is live right now. The shipped _firm-skeleton.docx (built 2026-07-06) prints the literal 22 June 2026 where the filing date belongs — that was the template's example date on the day of that build, and the rule was already stale then. Every skeleton pulled since carries a fixed June date.

Nobody had reported it. That is the point of the ticket, not a side note.

3. What was actually built

The two literals are the small part. buildSkeleton now fails with inertRuleError naming every rule that matched no paragraph, and rules_fire_test.go holds the same line in CI against the copy of the template published in this repo — the publish commit is the earliest moment paliad can know the authored text moved, so that is where the check fires.

TestInertRuleIsReported is the negative control: it pins the service-address rule back to the retired domain and requires the checker to name that rule. Watched fail, watched pass.

Both stale rules now match on shape, not on a literal another repo owns. The service-address rule captures the address out of the template and carries it into the translated label, so paliad no longer restates a firm address it does not author, and the same domain change cannot make the rule inert again. The date matches a date.

scripts/ was in no test scope at all — not make test, not ci-test-gate.sh. The check would never have run in CI. Added to both, the same gap pkg/ had.

4. Sweep, per the ticket's question

Every rule in exampleRules matches a literal patentstyle owns — that is what the table is. So the answer is not a shortlist of risky rules; it is that the whole table is exposed and the detector is what covers it. Two were dead, both now found and both re-expressed as shape matches. The rest currently fire.

5. Regeneration

Regenerated both skeletons from the current source .dotm on a patentstyle branch, not merged — shipping them is paliad/head's call.

Measured before committing:

  • Style contract intact. The template gained three firm styles since the last build (HLCpat-Base, HLCpat-Footnote, HLCpat-FootnoteChar); no renames, no removals. All 9 HLCpat keys in pkg/docforge/stylemap still exist.
  • No unrelated text drift. Three weeks of template edits changed nothing in the kept letterhead/Rubrum region.
  • Delimiter catch-up. The shipped files use legacy {{key}}; the regeneration emits <<key>>, what the renderer emits today. Both are recognised.
  • Render check (no Gotenberg, as in S7): both files pass HasMergePlaceholders and render against a full sample variable set with zero orphan placeholders. DE shows Zustellungsanschrift: upc-hub@hlc.com and a real date; EN keeps the English label.
Fixed, plus a second inert rule this ticket did not know about. Not closing — m closes. **Commits** - paliad [`6efc14c`](https://mgit.msbls.de/m/paliad/commit/6efc14c) — the fix and the detector - paliad [`8492f90`](https://mgit.msbls.de/m/paliad/commit/8492f90) — merge-path assertion - patentstyle [`e7dbb2c`](https://mgit.msbls.de/mAi/patentstyle/commit/e7dbb2c) — the regenerated skeletons, branch `mai/diesel/skeleton-158-regen`, **not merged** Branch `mai/diesel/m-paliad-158-a-stale`. CI runs 668 and 671 green. ## 1. Still stale — confirmed from two sources Both the patentstyle source `word/HLC Patents Style.dotm` and paliad's own published copy (`frontend/public/patentsstyle/HLC-Patents-Style.dotm`, v0.260728-0005) author: ``` Electronic address for service: upc-hub@hlc.com ``` So the domain question this ticket parked is answered by the template itself, and by m's 2026-07-27 canonical-domain ruling. No new decision was needed. ## 2. A second rule was dead the same way The detector found it: the letterhead date rule matched `14 April 2026`; the template says `1 July 2026`. So `{{today.long_de}}` / `{{today.long_en}}` never landed either. This is visible in the artifact that is live right now. The shipped `_firm-skeleton.docx` (built 2026-07-06) prints the literal **`22 June 2026`** where the filing date belongs — that was the template's example date on the day of that build, and the rule was already stale then. Every skeleton pulled since carries a fixed June date. Nobody had reported it. That is the point of the ticket, not a side note. ## 3. What was actually built The two literals are the small part. `buildSkeleton` now fails with `inertRuleError` naming every rule that matched no paragraph, and `rules_fire_test.go` holds the same line in CI against the copy of the template published in this repo — the publish commit is the earliest moment paliad can know the authored text moved, so that is where the check fires. `TestInertRuleIsReported` is the negative control: it pins the service-address rule back to the retired domain and requires the checker to name that rule. Watched fail, watched pass. **Both stale rules now match on shape, not on a literal another repo owns.** The service-address rule captures the address out of the template and carries it into the translated label, so paliad no longer restates a firm address it does not author, and the same domain change cannot make the rule inert again. The date matches a date. **`scripts/` was in no test scope at all** — not `make test`, not `ci-test-gate.sh`. The check would never have run in CI. Added to both, the same gap `pkg/` had. ## 4. Sweep, per the ticket's question Every rule in `exampleRules` matches a literal patentstyle owns — that is what the table is. So the answer is not a shortlist of risky rules; it is that the whole table is exposed and the detector is what covers it. Two were dead, both now found and both re-expressed as shape matches. The rest currently fire. ## 5. Regeneration Regenerated both skeletons from the current source `.dotm` on a patentstyle branch, **not merged** — shipping them is paliad/head's call. Measured before committing: - **Style contract intact.** The template gained three firm styles since the last build (`HLCpat-Base`, `HLCpat-Footnote`, `HLCpat-FootnoteChar`); no renames, no removals. All 9 `HLCpat` keys in `pkg/docforge/stylemap` still exist. - **No unrelated text drift.** Three weeks of template edits changed nothing in the kept letterhead/Rubrum region. - **Delimiter catch-up.** The shipped files use legacy `{{key}}`; the regeneration emits `<<key>>`, what the renderer emits today. Both are recognised. - **Render check** (no Gotenberg, as in S7): both files pass `HasMergePlaceholders` and render against a full sample variable set with zero orphan placeholders. DE shows `Zustellungsanschrift: upc-hub@hlc.com` and a real date; EN keeps the English label.
Author
Collaborator

Most of this was already shipped — the render check was not

Commit: https://mgit.msbls.de/m/paliad/commit/1649852 (branch mai/jason/issue-158-service-address)

No patentstyle commit from me: none was needed. Both halves of the fix landed on 2026-07-29, before this task was written.

half commit state
generator match 6efc14c — paliad main shipped
regenerated skeletons e7dbb2cmAi/patentstyle main, word/Paliad/HLC/ shipped

And 6efc14c went further than this task asks for. Rather than swapping the literal @hoganlovells.com@hlc.com, it made the match a regex that captures the address, so the generator no longer restates a firm address it does not author and the same domain change cannot make the rule inert again. It also added inertRuleError, which fails the build for any rule matching no paragraph, and pulled scripts/ into the test scope — which had been in no gate at all.

Verified on the shipped artifacts, not from the log. Unzipped both skeletons and read the paragraph back:

DE (_firm-skeleton.docx):  Zustellungsanschrift: upc-hub@hlc.com
EN (_skeleton.en.docx):    Electronic address for service: upc-hub@hlc.com

What was genuinely open

The check this task named: "Assert the German label is present, not merely that the rule fired."

Three tests guard this generator and none of them looks at what a rule wrote. TestExampleRulesAllFireAgainstReferenceTemplate proves a rule matched; TestInertRuleIsReported is its negative control; TestReferenceTemplateServiceAddressIsCanonical reads the template, not the output.

Measured, not assumed: replacing de: "Zustellungsanschrift: $1" with the English text leaves all three green. The German half of the branch can be deleted outright and the package notices nothing — #158's outcome reached from the other side, with the rule firing the whole time.

TestTranslatedRulesProduceTranslatedOutput closes it, generalised past the one line reported here. Four rules carry a DE/EN branch — In Sachen, vertreten durch, the service address, and today.long_de/long_en — and each language's skeleton must contain that rule's text for that language. Placeholders run through the generator's own renderPlaceholders, so the check cannot drift from the {{key}}<<key>> contract it asserts on.

One design note worth reading. The first draft selected rules to check by comparing their two languages (de != en). That made it blind to exactly the defect it exists for: collapsing a translation removes the rule from the selection, so all four mutations stayed green. It is now pinned by match text — the one part of a rule a lost translation does not touch. A guard must not take its subject from the field the defect changes. Caught by running the mutations, not by reading the test.

Mutation matrix, each green on the old three and red on the new one:

mutation old suite new test
DE service label → English GREEN RED
In Sachen → English GREEN RED
vertreten durch → English GREEN RED
date placeholder wired to wrong language GREEN RED

The enumeration

No exampleRules entry in any of the three gen-* scripts still matches a @hoganlovells.com address. Zero.

But there is a third instance of the same family outside exampleRules, and it bites. scripts/seed-example-projects held matthias.siebels@hoganlovells.com as a const and looked auth.users up by exact email. Prod carries exactly one matching row:

matthias.siebels@hlc.com   created 2026-04-07

So the lookup found nothing and every seeded example project silently got created_by = NULL — over a note: line, not an error. Same shape as the two generator rules: a firm-domain literal in a place that has no reason to hold one. Fixed by shape rather than by literal, mirroring 6efc14c: the local part stays in code, the domain comes from emaildomain.Canonical().

Everything else matching hoganlovells in the tree is deliberate alias handling (internal/emaildomain, the sign-in whitelist, cmd/canonicalize-emails) or prose.

Gate

scripts/ci-test-gate.sh green under -race over ./internal/... ./cmd/... ./pkg/... ./scripts/... (vet clean, 0 new failures, 0 tolerated). check-gofmt.sh rc=0. bun test 441 pass. bun run build clean. No migration. Harness only — prod was read, never written.

Unrelated, for whoever touches it next: bun run build regenerates frontend/src/i18n-keys.ts one line smaller on current main — deadlines.adjusted.vacation has no literal any more. Pre-existing drift, left out of this commit deliberately.

Not closing — that is m's call.

## Most of this was already shipped — the render check was not Commit: https://mgit.msbls.de/m/paliad/commit/1649852 (branch `mai/jason/issue-158-service-address`) **No patentstyle commit from me: none was needed.** Both halves of the fix landed on 2026-07-29, before this task was written. | half | commit | state | |---|---|---| | generator match | [`6efc14c`](https://mgit.msbls.de/m/paliad/commit/6efc14c) — paliad main | shipped | | regenerated skeletons | `e7dbb2c` — `mAi/patentstyle` main, `word/Paliad/HLC/` | shipped | And `6efc14c` went further than this task asks for. Rather than swapping the literal `@hoganlovells.com` → `@hlc.com`, it made the match a **regex that captures the address**, so the generator no longer restates a firm address it does not author and the same domain change cannot make the rule inert again. It also added `inertRuleError`, which fails the build for *any* rule matching no paragraph, and pulled `scripts/` into the test scope — which had been in no gate at all. **Verified on the shipped artifacts, not from the log.** Unzipped both skeletons and read the paragraph back: ``` DE (_firm-skeleton.docx): Zustellungsanschrift: upc-hub@hlc.com EN (_skeleton.en.docx): Electronic address for service: upc-hub@hlc.com ``` ## What was genuinely open The check this task named: *"Assert the German label is present, not merely that the rule fired."* Three tests guard this generator and **none of them looks at what a rule wrote**. `TestExampleRulesAllFireAgainstReferenceTemplate` proves a rule matched; `TestInertRuleIsReported` is its negative control; `TestReferenceTemplateServiceAddressIsCanonical` reads the *template*, not the output. Measured, not assumed: replacing `de: "Zustellungsanschrift: $1"` with the English text leaves **all three green**. The German half of the branch can be deleted outright and the package notices nothing — #158's outcome reached from the other side, with the rule firing the whole time. `TestTranslatedRulesProduceTranslatedOutput` closes it, generalised past the one line reported here. Four rules carry a DE/EN branch — `In Sachen`, `vertreten durch`, the service address, and `today.long_de`/`long_en` — and each language's skeleton must contain that rule's text for that language. Placeholders run through the generator's own `renderPlaceholders`, so the check cannot drift from the `{{key}}` → `<<key>>` contract it asserts on. **One design note worth reading.** The first draft selected rules to check by comparing their two languages (`de != en`). That made it blind to exactly the defect it exists for: collapsing a translation removes the rule from the selection, so all four mutations stayed green. It is now pinned by **match text** — the one part of a rule a lost translation does not touch. A guard must not take its subject from the field the defect changes. Caught by running the mutations, not by reading the test. Mutation matrix, each green on the old three and red on the new one: | mutation | old suite | new test | |---|---|---| | DE service label → English | GREEN | **RED** | | `In Sachen` → English | GREEN | **RED** | | `vertreten durch` → English | GREEN | **RED** | | date placeholder wired to wrong language | GREEN | **RED** | ## The enumeration **No `exampleRules` entry in any of the three `gen-*` scripts still matches a `@hoganlovells.com` address. Zero.** But there is a **third instance of the same family** outside `exampleRules`, and it bites. `scripts/seed-example-projects` held `matthias.siebels@hoganlovells.com` as a `const` and looked `auth.users` up by exact email. Prod carries exactly one matching row: ``` matthias.siebels@hlc.com created 2026-04-07 ``` So the lookup found nothing and **every seeded example project silently got `created_by = NULL`** — over a `note:` line, not an error. Same shape as the two generator rules: a firm-domain literal in a place that has no reason to hold one. Fixed by shape rather than by literal, mirroring `6efc14c`: the local part stays in code, the domain comes from `emaildomain.Canonical()`. Everything else matching `hoganlovells` in the tree is deliberate alias handling (`internal/emaildomain`, the sign-in whitelist, `cmd/canonicalize-emails`) or prose. ## Gate `scripts/ci-test-gate.sh` green under `-race` over `./internal/... ./cmd/... ./pkg/... ./scripts/...` (vet clean, 0 new failures, 0 tolerated). `check-gofmt.sh` rc=0. `bun test` 441 pass. `bun run build` clean. No migration. Harness only — prod was read, never written. **Unrelated, for whoever touches it next:** `bun run build` regenerates `frontend/src/i18n-keys.ts` one line smaller on current main — `deadlines.adjusted.vacation` has no literal any more. Pre-existing drift, left out of this commit deliberately. Not closing — that is m's call.
mAi added the
done
label 2026-07-31 00:08:17 +00:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: m/paliad#158
No description provided.