Skip to content

fix(resolver): FamilyResolver — alias inside mutex + UPPER case-normalization#129

Merged
sroussey merged 3 commits into
mainfrom
claude/wonderful-hypatia-Mqpl3
Jun 8, 2026
Merged

fix(resolver): FamilyResolver — alias inside mutex + UPPER case-normalization#129
sroussey merged 3 commits into
mainfrom
claude/wonderful-hypatia-Mqpl3

Conversation

@sroussey

@sroussey sroussey commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

Summary

Two HIGH-priority fixes for the shared FamilyResolver powering SponsorFamilyResolver and UnderwriterFamilyResolver.

  • Alias lookup serialised inside the per-key mutex. Pre-fix code acquired the per-key mutex for find-or-create, released it, then ran resolveAlias() OUTSIDE the lock. Two parallel resolve() calls on the same family name could split if an alias was installed between the create and the alias lookup — one returned the alias target, the other the pre-alias id. This is the same race PR fix: DRSLTR dispatch, SPAC sponsor span verification, resolver test + RFC-9112 Content-Length #127 fixed for PersonResolver / CompanyResolver in commit 8fe6fd0; applying the same fix here so the family resolvers benefit. New regression tests in both family-resolver test files wrap aliasRepo.resolve with an in-flight counter and assert maxInFlight === 1 under two parallel resolves on the same key (pre-fix code drives it to 2).

  • normalizeFamilyName reverted to UPPER. The shared helper introduced in commit 960d707 silently flipped the family-name fold from UPPER (the pre-refactor SponsorFamilyResolver convention) to lower. CanonicalSponsorFamilyRepo.findByResolverAndName runs an exact-match storage query with no case fold, so any existing UPPER canonical_sponsor_family.normalized_name rows became unreachable, and operator-installed aliases keyed on those canonical ids would silently orphan. Reverts to UPPER and pins it via a new dedicated FamilyResolver.test.ts covering UPPER output, case-insensitivity, internal-whitespace collapse, and empty/whitespace-only input. Three test fixtures (CanonicalSponsorFamilyRepo, CanonicalUnderwriterFamilyRepo, underwriterFamily command tests) that hardcoded lowercase normalized_name literals are updated to match.

Test plan

  • bun test src/resolver/FamilyResolver.test.ts — new file pinning the UPPER case convention.
  • bun test src/resolver/SponsorFamilyResolver.test.ts — adds the alias-in-mutex regression test.
  • bun test src/resolver/UnderwriterFamilyResolver.test.ts — adds the alias-in-mutex regression test.
  • bun test src/resolver/PersonResolver.test.ts — sibling resolver, unchanged but exercised to confirm no regression.
  • bun test src/resolver/CompanyResolver.test.ts — sibling resolver, unchanged but exercised to confirm no regression.
  • bun test src/storage/canonical/CanonicalSponsorFamilyRepo.test.ts — fixture flipped to UPPER.
  • bun test src/storage/canonical/CanonicalUnderwriterFamilyRepo.test.ts — fixture flipped to UPPER.
  • bun test src/commands/sponsorFamily.test.ts — uses normalizeSponsorFamilyName() dynamically; passes after the case flip.
  • bun test src/commands/underwriterFamily.test.ts — three lowercase normalized_name literals flipped to UPPER.
  • bun test src/resolver/ src/storage/canonical/ final sanity sweep: 116 tests pass, 0 fail.
  • bun run build-types — TypeScript strict type-check clean.

Generated by Claude Code

claude added 2 commits June 7, 2026 08:26
…Resolver

The shared FamilyResolver acquired the per-key mutex, ran find-or-create,
released, then ran resolveAlias() OUTSIDE the lock. Two parallel resolve()
calls on the same family name could split if an alias was installed between
the create and the alias-lookup — one returned the alias target, the other
the pre-alias id. This is the same race PR #127 fixed for PersonResolver
and CompanyResolver in commit 8fe6fd0; applying the same fix here so
SponsorFamilyResolver and UnderwriterFamilyResolver benefit.

Adds regression tests in both family-resolver test files that wrap
aliasRepo.resolve with an in-flight counter and assert maxInFlight === 1
under two parallel resolves on the same key (pre-fix code drives it to 2).
…a FamilyResolver.test.ts

The shared normalizeFamilyName introduced in commit 960d707 silently flipped
the family-name fold from UPPER (the pre-refactor SponsorFamilyResolver
convention) to lower. CanonicalSponsorFamilyRepo.findByResolverAndName runs
an exact-match storage query with no case fold, so any existing UPPER
canonical_sponsor_family.normalized_name rows became unreachable, and
operator-installed aliases keyed on those canonical ids would silently
orphan.

Reverts the fold to UPPER and pins it via a new dedicated FamilyResolver.test.ts
that covers UPPER output, case-insensitivity, internal-whitespace collapse,
and empty/whitespace-only input. Also updates three test fixtures
(CanonicalSponsorFamilyRepo, CanonicalUnderwriterFamilyRepo,
underwriterFamily command tests) that hardcoded lowercase normalized_name
literals so they match the live convention.
PR #129 pinned FamilyResolver normalization to UPPER but two callers were
missed:

- Form_S_1.storage.underwriters.test.ts: findByResolverAndName lookup still
  used "goldman sachs", returning undefined and crashing the next line's
  family! dereference. Updated to "GOLDMAN SACHS" to match the three other
  test files already flipped in PR #129.
- SponsorFamilyResolver.normalizeSponsorFamilyName JSDoc still described
  lower-casing; the function delegates to normalizeFamilyName which now
  UPPER-cases. Updated the doc to reflect actual behavior and the
  canonical_sponsor_family.normalized_name column it matches against.
  UnderwriterFamilyResolver was checked and has no equivalent stale doc.

Co-authored-by: Claude <noreply@anthropic.com>
@sroussey sroussey merged commit 330523d into main Jun 8, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants