fix(ingest): make both upsert UNIQUE gaps visible (audit C-15 follow-up)

Per review: the two latent upsert conflicts should be observable even where not
auto-fixed.
- bibkey collision (auto-suffixed) now logs a warning so a paper landing as
  'BobenkoLutz2023a' instead of '…2023' isn't silent.
- openalex_id collision (two papers claiming the same OpenAlex work — a real data
  conflict, not auto-renamable) now raises a clear ValueError naming the
  openalex_id and the offending paper, instead of the raw psycopg UniqueViolation.

Regression test added for the openalex_id error path.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Tarik Moussa
2026-06-15 22:25:30 +02:00
parent 1f677211f7
commit e1c0a98262
2 changed files with 47 additions and 2 deletions

View File

@@ -164,10 +164,26 @@ def ingest_paper(
)
break
except psycopg.errors.UniqueViolation as exc:
if base_bibkey is None or "bibkey" not in str(exc).lower() or attempt == 26:
raise
msg = str(exc).lower()
conn.rollback()
if "openalex" in msg:
# Two different papers.id claim the same OpenAlex work — a real data
# conflict, not something to auto-rename. Surface it clearly instead
# of the raw psycopg error (companion gap to C-15's bibkey case).
raise ValueError(
f"openalex_id {paper.openalex_id!r} is already attached to a "
f"different paper; ingesting '{paper.id}' would duplicate it — "
"resolve the conflicting paper first."
) from exc
if base_bibkey is None or "bibkey" not in msg or attempt == 26:
raise
paper.bibkey = f"{base_bibkey}{chr(ord('a') + attempt)}"
logger.warning(
"bibkey %r already exists; ingesting '%s' as %r instead",
base_bibkey,
paper.id,
paper.bibkey,
)
# Register openalex_id in paper_identifiers (alias table).
# Every ingest records the primary openalex_id so the graph JOIN