# RB-29 — Thread `at` through `LetterHtml.ResolveAuto`'s `datum` case Status: **implemented** · 2026-08-27 · Source findings: `02-testability.md` TE-007 · `99-backlog.md` RB-29 ## What was wrong `LetterHtml.Render(BriefEntity brief, OrgTemplateDto template, string at, bool watermark)` (`backend/src/BigRegister.Api/Domain/Letters/LetterHtml.cs`) already took the letter's instant and used it correctly for the letterhead date (`sb.Append(Enc(FormatDatumNl(at)))`). The body's `datum` placeholder resolved through the private `ResolveAuto(string key, string label)`, which ignored `at` and called `FormatDatumNl(DateTimeOffset.UtcNow.ToString("o"))` instead — a pure `Domain/` rule class reading the wall clock. `ResolveAuto` is reached only through the private chain `RenderNode` ← `RenderParagraphs` ← `Render`, so no caller outside this file could pin the value a test would see. The ticket read as filed against the current code: `Render`'s signature, the letterhead's correct use of `at`, and `ResolveAuto`'s `UtcNow` read were all exactly as TE-007 described (line numbers had moved — CC around the file has grown since the finding was written — but the code shape had not). One thing TE-007 named as the visible symptom also checked out: `LetterHtmlTests.cs` already declares a `new PlaceholderDefDto("datum", "Datum", true)` in its golden-file fixture, but no `RichTextNodeDto` in that fixture's `Sections` actually references the `datum` key in the body — it is declared but never rendered there, so the existing golden-file test could not have caught this even if it asserted on dates (which it does not either). ## What changed | File | Change | | -------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `backend/src/BigRegister.Api/Domain/Letters/LetterHtml.cs` | `ResolveAuto(string key, string label)` → `ResolveAuto(string key, string label, string at)`; `"datum" => FormatDatumNl(at)`. `at` threaded down through the two private call sites in the chain: `RenderParagraphs` and `RenderNode` both gained an `at` parameter, passed from `Render`'s own `at`. | | `backend/tests/BigRegister.Tests/LetterHtmlTests.cs` | New fixture `FixtureBriefWithDatumInBody()` — a minimal brief whose body actually references the `datum` placeholder (the golden fixture never does). Two new `[Fact]`s (see below) plus two small extraction helpers. | | `docs/project/refactor-backlog-setup/refactor-backlog/99-backlog.md` | RB-29 status cell: `open` → `implemented`. | `Render`'s own signature is unchanged — TE-007's "zero public API change, zero call-site change" held exactly. `Program.cs:697`, `:708`, and `BriefStore.cs:120` (the three callers) needed no edit. ## What the fix looks like ```csharp private static void RenderParagraphs( StringBuilder sb, IReadOnlyList paragraphs, IReadOnlyDictionary defs, string at) { // ... unchanged body, forwards `at` to RenderNode ... } private static void RenderNode( StringBuilder sb, RichTextNodeDto node, IReadOnlyDictionary defs, string at) { // ... unchanged body, forwards `at` to ResolveAuto ... } private static string ResolveAuto(string key, string label, string at) => key switch { "naam_zorgverlener" => SeedData.Registration.Naam, "big_nummer" => SeedData.Registration.BigNummer, "datum" => FormatDatumNl(at), _ => label, }; ``` Both call sites already had `at` in scope (`Render`'s own parameter), so this is a pure threading change — no new state, no new dependency. ## Tests added TE-007 named the exact gap: the golden-file fixture declares the `datum` placeholder but never renders it in the body, so no existing assertion could catch a body/letterhead mismatch. A new fixture and two focused tests close it: 1. **`Render_resolves_the_body_datum_placeholder_from_the_given_at_not_the_wall_clock`** — renders `FixtureBriefWithDatumInBody()` with a fixed historical `at` (`2019-03-14T08:00:00.0000000+00:00`) and asserts the body's rendered paragraph is the exact string `"14 maart 2019"`. A test using today's date would have passed before and after the fix and proven nothing — this one pins a date nowhere near "now", so it fails whenever the resolver reads the wall clock instead of `at`. 2. **`Render_keeps_the_letterhead_date_and_the_body_datum_in_agreement_for_a_historical_at`** — same fixture and historical `at`, asserts the letterhead `
` date and the body's rendered `datum` paragraph are equal. This is TE-007's stated payoff: not a shipped bug today (every current caller passes `Now()` at render time, so the two dates always coincided even with the bug present), but a latent one — the moment `Render` is ever called with a historical `at` (re-rendering an archive, back-dating a letter), the letterhead and body would disagree within a single document. This test is the one that would have caught that. Both tests use the repo's one date formatter (`FormatDatumNl`, already used by both call sites under test) only indirectly, through the literal expected string `"14 maart 2019"` — no second hand-rolled `ToString` format was introduced in the test file either. ## Verification - **Verified red without the fix.** Reverted only the `ResolveAuto` expression (via `Edit`, not `git checkout`) back to `"datum" => FormatDatumNl(DateTimeOffset.UtcNow.ToString("o"))`, leaving the new tests and the threaded signatures in place. Ran the two new tests: ``` Render_resolves_the_body_datum_placeholder_from_the_given_at_not_the_wall_clock [FAIL] Assert.Equal() Failure: Strings differ Expected: "14 maart 2019" Actual: "27 augustus 2026" Render_keeps_the_letterhead_date_and_the_body_datum_in_agreement_for_a_historical_at [FAIL] Assert.Equal() Failure: Strings differ Expected: "14 maart 2019" Actual: "27 augustus 2026" ``` Both failures show the body rendering the run's actual wall-clock date (today, 2026-08-27) instead of the pinned historical `at` — the precise defect TE-007 describes. Restored the fix with a second `Edit` and re-ran: all 4 tests in `LetterHtmlTests` green (2 pre-existing + 2 new). - `grep -n "UtcNow\|DateTime.Now\|DateTime.Today\|DateTimeOffset.Now" backend/src/BigRegister.Api/Domain/Letters/LetterHtml.cs` — no matches. No ambient clock read remains anywhere in the file. - `npm run ci` (foreground, no background/Monitor): see result reported alongside this ticket. ## What this ticket did not touch `LetterHtml.cs`'s overall structure and CC (TE-007 records it at 21, the third-highest in the backend) are unchanged — reducing that is out of scope for this ticket, per its own text. `Data/BriefStore.cs` and any `BriefRules.cs` file were not touched — a concurrent ticket owns that file.