Review noted the "never throws" claim was overstated: the exact-case Optional
lookups still surface a NonUniqueResultException on two byte-identical
same-case rows. That is a true data anomaly out of #731's scope (ambiguous =
case-insensitive) and resolves to the opaque INTERNAL_ERROR, never a wrong
row. Record that boundary at both resolution points and in ADR-032 so the gap
is not silently assumed covered.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
findByName resolved via Optional<Person>
findByFirstNameIgnoreCaseAndLastNameIgnoreCase, which threw
NonUniqueResultException once two people shared a first+last name case-
insensitively (hans müller / Hans Müller) — a 500 on the routine upload path
(DocumentService.storeDocument sender resolution).
findByName now resolves exact-case → single case-insensitive match → else
empty. The sender path deliberately diverges from the alias path: an
ambiguous name leaves the sender UNSET rather than guessing the lowest id,
because correct provenance beats a confidently-wrong pre-fill a reviewer
won't re-check. The two new name queries use explicit HQL equality so a null
first name binds as `= NULL` (no match) instead of the derived-query fold to
`first_name IS NULL`, which would widen a last-name-only row in as a sender.
Pins the opaque error path (IncorrectResultSizeDataAccessException stays
INTERNAL_ERROR with no Hibernate/SQL/row-count leak) and extends ADR-032 with
the Person section.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Records the lasting decision behind the #730 fix: exact-case-first
resolution, deterministic lowest-id case-insensitive fallback, and the
explicit refusal of a unique(lower(name)) constraint (collisions are
valid canonical nodes). Previously the rationale lived only in code
comments and the issue body. Raised as a blocker in the PR #733 review.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>