fix: audit remediation Wave 2 — identity normalization C-7, C-10 (+ T-3, ADR D-1) #13

Merged
user2595 merged 5 commits from fix/audit-wave-2 into main 2026-06-16 04:53:41 +00:00
3 changed files with 45 additions and 1 deletions
Showing only changes of commit c36e7c6ee7 - Show all commits

View File

@@ -98,6 +98,14 @@ def ingest_paper(
if paper is None: if paper is None:
raise ValueError(f"Paper not found: {paper_id}") raise ValueError(f"Paper not found: {paper_id}")
# Canonical identity is the caller-supplied id — what the CLI / ingest scripts
# use and what papers.id is contracted to hold (arXiv id or DOI). OpenAlex's
# fetch_paper fills paper.id with the URL-form doi/id (e.g.
# "https://doi.org/10.48550/arxiv.math/0603097"), which broke bare-id lookups
# and cross-citation matching (audit C-7). The OpenAlex work id is retained
# separately as openalex_id and in the paper_identifiers alias table.
paper.id = paper_id.strip()
# Auto-generate bibkey when source has none (D-04). # Auto-generate bibkey when source has none (D-04).
if paper.bibkey is None: if paper.bibkey is None:
paper.bibkey = _make_bibkey(paper) paper.bibkey = _make_bibkey(paper)

View File

@@ -106,6 +106,37 @@ def test_ingest_paper_basic() -> None:
) )
def test_ingest_canonicalises_paper_id_to_caller_arg() -> None:
"""papers.id is the caller-supplied id, not OpenAlex's URL-form id (audit C-7).
Real OpenAlex returns the doi/id as a full URL; ingest must key the paper on
the bare id the caller passed (matching ingest scripts and CLI lookups), and
keep the OpenAlex work id only as openalex_id.
"""
oa_paper = Paper(
id="https://doi.org/10.48550/arxiv.math/0603097", # URL form, as OpenAlex emits
title="A Test Paper",
openalex_id="https://openalex.org/W4301005794",
authors=["Alice", "Bob"],
year=2008,
abstract="Test abstract.",
)
mock_conn = MagicMock()
with (
patch("codex.ingest.openalex.fetch_paper", return_value=oa_paper),
patch("codex.ingest.openalex.fetch_citations", return_value=[]),
patch("codex.ingest.get_embedder", return_value=_fake_embedder()),
patch("codex.ingest.get_conn", side_effect=_make_conn_cm(mock_conn)),
):
result = ingest_paper("math/0603097")
papers_params = mock_conn.execute.call_args_list[0][0][1]
assert papers_params["id"] == "math/0603097", "papers.id must be the caller's bare id"
assert papers_params["openalex_id"] == "https://openalex.org/W4301005794"
assert result.paper_id == "math/0603097"
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# 2. test_ingest_paper_source_tex # 2. test_ingest_paper_source_tex
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------

View File

@@ -15,7 +15,8 @@ from codex.sources.openalex import _resolve_id
_SAMPLE_WORK = { _SAMPLE_WORK = {
"id": "https://openalex.org/W2741809807", "id": "https://openalex.org/W2741809807",
"doi": "10.1145/3592430", # Real OpenAlex returns the DOI as a full URL, not a bare DOI (audit T-3).
"doi": "https://doi.org/10.1145/3592430",
"title": "Conformal Prediction: A Review", "title": "Conformal Prediction: A Review",
"publication_year": 2023, "publication_year": 2023,
"authorships": [ "authorships": [
@@ -90,6 +91,10 @@ def test_fetch_paper_success(monkeypatch: pytest.MonkeyPatch) -> None:
assert paper.authors == ["Alice Smith", "Bob Jones"] assert paper.authors == ["Alice Smith", "Bob Jones"]
assert paper.abstract == "Conformal prediction is great" assert paper.abstract == "Conformal prediction is great"
assert paper.openalex_id == "https://openalex.org/W2741809807" assert paper.openalex_id == "https://openalex.org/W2741809807"
# fetch_paper maps id from the (URL-form) doi; ingest later canonicalises
# papers.id to the caller's bare id (audit C-7). Pin the real mapping here so
# the fixture cannot silently misrepresent OpenAlex again (audit T-3).
assert paper.id == "https://doi.org/10.1145/3592430"
def test_fetch_paper_arxiv_id_prefixed(monkeypatch: pytest.MonkeyPatch) -> None: def test_fetch_paper_arxiv_id_prefixed(monkeypatch: pytest.MonkeyPatch) -> None: