Repository navigation
fix: force co-authors drawn into a DOI conflict by re-enrichment - #60
Merged
Merged
Conversation
The monthly refresh forces enrichment for author directories that hold conflicting copies of one DOI, but it chose that set once, before any author ran. Forcing rewrites the author's copy of every DOI it holds, while a co-author's complete copy of the same DOI is skipped, so a forced author can open a conflict that finalization then rejects. The 2026-09 and 2026-10 runs forced Haque, Oore and Trappenberg. Oore's re-enriched SugarCrepe++ record (10.52202/079017-0571) took Crossref's author order while Milios and Sajjad kept their skipped copies, and finalize_run failed on Milios, Oore and Sajjad both months. run_all now re-checks after each pass and forces the authors a pass drew into a conflict, each at most once, so the loop ends within one pass per author.
gabrielspadon
added a commit
that referenced
this pull request
Oct 25, 2026
## Summary The identity check treated the published SugarCrepe++ record (10.52202/079017-0571, arXiv 2406.11171) and the VISLA Benchmark preprint (10.48550/arxiv.2404.16365) as one work. They matched at a title score of 0.713. During the refresh replay this deleted VISLA from Milios, Oore, Sajjad and a2i2. ## Root cause `_disk_identity` rejected a preprint/published pair only when both records carried arXiv ids that differed (`citeforge/identity.py`, the preprint-pair branch). `merge_utils` strips `eprint` and `archiveprefix` from every record that has a published DOI, so a published record on disk never carries an arXiv id and the guard could not fire. The pair then fell through to `SIM_PREPRINT_TITLE_THRESHOLD` (0.55), which exists for retitled preprints. `_enrichment_identity` had the same gap in its composite gate: a Crossref candidate carries no arXiv id either, and VISLA vs SugarCrepe++ scored COMPOSITE True. ## Fix If only one record carries an arXiv id, that now counts as unknown identity rather than agreement. Unless both records carry the same arXiv id, the disk preprint-pair, composite and relaxed-preprint paths require `SIM_FILE_DUPLICATE_THRESHOLD` (0.95), and the enrichment composite gate stays closed. Identical-title twins, DOI matches, DOI versions, equal arXiv ids, external ids and truncated titles are unchanged. ## Effect on the committed corpus I evaluated every same-directory pair under `DISK_SURVIVOR` before and after the change. Five pairs flip from "same work" to "distinct": - VISLA vs SugarCrepe++ in Milios, Oore and Sajjad. These are distinct works and are now kept. - Oore, `Lowe2023-ZeroShot.bib` (2023 workshop paper) vs `Lowe2024-EmpiricalStudy.bib` (arXiv 2406.02465, TMLR 2026). These are distinct titles and are now kept. - Reilly, `Porcino2022-DesignXR.bib` (arXiv 2208.14842, abbreviated title) vs `Porcino2022-DesignExtended.bib` (10.1007/978-3-031-20212-4_9). This is likely the same paper, but neither an arXiv DOI nor a Crossref relation links them, so the preprint is kept rather than removed as superseded. ## Verification - `pytest -m 'not live' --cov=citeforge --cov=main --cov-fail-under=68` on Python 3.12, macOS: 2080 passed, 2 skipped (the documented cutover contracts in `tests/test_workflow_contracts.py`), 7 deselected, coverage 84.13%. - `ruff check citeforge/ tests/ main.py` and `mypy citeforge/ main.py` are clean. - `test_published_record_never_absorbs_a_distinct_arxiv_preprint` uses the committed VISLA and SugarCrepe++ records. Both directions fail on `main`: the incoming published record replaces VISLA, and an incoming VISLA is refused. Both pass with this change. - `test_enrichment_never_adopts_a_distinct_work_as_the_published_version` fails on `main` with COMPOSITE True and passes with this change. The refresh replay on `main` plus #60 and this change is reported in a comment below.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The 2026-09-01 and 2026-10-01 scheduled refreshes both failed in
finalize_runwithconflicting copies of the same DOI remain in author directories: Milios (ME8aQywAAAAJ), Oore (cI0dYX4AAAAJ), Sajjad (t3BH6NkAAAAJ).run_allforces enrichment for author directories that hold conflicting copies of one DOI, but it chose that set once, before any author ran. Forcing rewrites the author's copy of every DOI it holds, while a co-author's complete copy of the same DOI is skipped (process_articlecomplete-entry skip). A forced author can therefore open a conflict that finalization rejects. The failed run is never committed, so the same corpus produced the same failure each month.Mechanism, from the committed corpus
output/Milios (ME8aQywAAAAJ)/Dumpala2024-SensitivityGenerative.bibandoutput/Sajjad (t3BH6NkAAAAJ)/Dumpala2024-SensitivityGenerative.bibare complete, so they are skipped and keep Sastry last. That leaves three copies with two author orders.Fix
run_allre-checks after each pass and forces the authors a pass drew into a conflict. Each author is forced at most once, so the loop ends within one pass per author. The finalization check is unchanged.Verification
pytest -m 'not live' --cov=citeforge --cov=main --cov-fail-under=68on Python 3.12, macOS: 2078 passed, 2 skipped (both the documented cutover contracts intests/test_workflow_contracts.py), 7 deselected, coverage 84.14%.ruff check citeforge/ tests/ main.pyandmypy citeforge/ main.pyare clean.test_run_all_forces_coauthors_whose_skipped_copy_a_forced_author_rewrotefails onmainwith Milios and Oore left conflicting, and it passes with this change.run_allwith live Crossref, arXiv, OpenAlex and DOI enrichment of shared-DOI records. Scholar and Gemini were not used. Onmainthe replay forced 3 authors andfinalize_runraised the production error verbatim. With this change it forced 9 authors over successive passes (Milios, Sajjad, then Rudzicz, Zeh, Gagie and He), left zero conflicts, andfinalize_runcompleted.Review before the next refresh merges
In that replay, every forced copy of SugarCrepe++ also replaced the VISLA Benchmark preprint (arXiv 2404.16365, a separate paper from SugarCrepe++, arXiv 2406.11171). The replacement comes from
PREPRINT_PAIRinciteforge/identity.py:243, at title similarity 0.713 and composite 0.662. Today only Oore's copy is affected. With this change the next refresh also drops VISLA from Milios, Sajjad and a2i2. This is a separate identity defect, and it is not fixed here.