Skip to content

fix(promote): variety insert must not abort promote on duplicate-parent collision - #1

Open
vbeefy wants to merge 1 commit into
mainfrom
fix/promote-variety-collision
Open

vbeefy wants to merge 1 commit into
mainfrom
fix/promote-variety-collision

Conversation

@vbeefy

@vbeefy vbeefy commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Problem

resolveEntityForClaim auto-creates cultivar entities with a plain INSERT. When a cultivar already exists under a different species parent than the claim's species name resolves to, the parent-scoped lookup misses it and the INSERT collides on UNIQUE(scientific_name, variety_name) — aborting the entire promote transaction (0 rows promoted, everything rolled back).

This is triggered by duplicate species rows that COLLATE NOCASE cannot fold — e.g. two Fragaria × ananassa entities differing only by the × (U+00D7) vs ASCII x glyph. The cultivars sit under one; the species name resolves to the other.

Note the asymmetry this fixes: getOrCreateEntity (the species path) already uses a collision-safe INSERT OR IGNORE; only the variety path used a bare INSERT.

Fix

Mirror the existing collision-safe pattern:

  • INSERTINSERT OR IGNORE
  • add a compound-scientific_name fallback lookup, so the claim attaches to the existing cultivar rather than crashing the batch.

Duplicate species rows remain a separate dedup concern (both stay needs_dedup=1). The promote pipeline should never be hostage to one bad row.

Test plan

  • New regression test reproduces the exact SQLITE_CONSTRAINT: UNIQUE constraint failed: entities.scientific_name, entities.variety_name in-memory (the test DB now also creates the composite idx_entities_name_variety index, matching production).
  • Verified failing before the fix, passing after.
  • node --test backend/promote-staged-claims.test.js15/15 pass.

Context

Surfaced while promoting 7,065 UC IPM Pest Management Guideline claims across 33 sources, where 3 strawberry cultivars ('Portola', 'San Andreas', 'Albion') aborted the whole run.

🤖 Generated with Claude Code

…nt collision

resolveEntityForClaim auto-created cultivar entities with a plain INSERT.
When a cultivar already existed under a DIFFERENT (typo/synonym duplicate)
species parent — e.g. two "Fragaria × ananassa" rows differing only by the
× vs x glyph, which COLLATE NOCASE doesn't fold — the parent-scoped lookup
missed it and the INSERT collided on UNIQUE(scientific_name, variety_name),
rolling back the ENTIRE promote transaction (0 rows promoted).

Fix mirrors getOrCreateEntity's collision-safe pattern: INSERT OR IGNORE
+ a compound-scientific_name fallback lookup, so the claim attaches to the
existing cultivar instead of crashing. Duplicate species rows remain a
separate dedup concern (both stay needs_dedup=1).

TDD: reproduces the exact SQLITE_CONSTRAINT in-memory with the composite
unique index; promote-staged-claims.test.js 15/15 green.

Surfaced while promoting 7,065 UC IPM Pest Management Guideline claims
(33 sources — the known-biocontrol denominator).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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.

1 participant