From 2fe0c2565520b3795c58e3b814bba5c1435e3706 Mon Sep 17 00:00:00 2001 From: lmoresi Date: Wed, 2 Sep 2026 15:55:47 -0700 Subject: [PATCH] The reserve must write the DOI into the article, not only its metadata The first live reserve opened a request whose tests failed, correctly: `test_the_pdf_carries_the_archival_doi_once_a_note_is_deposited` compares the article's front matter `doi:` against `archive_doi` in metadata.yml, and the reserve had written only the second. That is not a test being fussy. The article's front matter is where the PDF's title page takes the DOI from, so publishing from that state would have produced an archival PDF with no DOI on it -- the one thing the reserve-before-build order exists to prevent. sync_archival.py already copies both the DOI and archived_at across; it was simply never called, being a script somebody ran by hand. The reserve now runs it, so the request carries both files and the publish still needs nothing written back afterwards. Found by running the flow against the live API rather than by reading it. Underworld development team with AI support from Claude Code --- scripts/deposit.py | 13 +++++++++++++ tests/test_migration.py | 18 ++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/scripts/deposit.py b/scripts/deposit.py index d000634..b745598 100644 --- a/scripts/deposit.py +++ b/scripts/deposit.py @@ -647,6 +647,19 @@ def run(slug, provider, live, publish, new_version, delete_draft, set_field(slug, "archived_version", meta.get("version") or "0.0.0") steps.append("stamped archived_at %s (version %s)" % (stamp, meta.get("version"))) + # The DOI has to reach the ARTICLE's front matter too, not only + # metadata.yml: that is where the PDF's title page takes it from, + # and `pixi run test` asserts the two agree. Run here, the request + # carries both files and the publish needs nothing further. Run at + # publish instead, it would land on the runner and have to be + # written back -- the step this flow exists to remove. + import subprocess + if subprocess.call([sys.executable, "scripts/sync_archival.py"], + cwd=ROOT) != 0: + raise DepositError( + "could not write the reserved DOI into the article's " + "front matter; nothing else was changed") + steps.append("wrote the DOI into the article front matter") print("\n".join(" " + s for s in steps)) print("\nReserved and stopped. The identifiers are in " "metadata.yml; commit them, and the deposit runs when they " diff --git a/tests/test_migration.py b/tests/test_migration.py index 84108e7..9ec8bc0 100644 --- a/tests/test_migration.py +++ b/tests/test_migration.py @@ -2372,3 +2372,21 @@ def test_an_open_request_is_not_asked_for_twice(): "it must match the requests it opens itself" assert "if s not in asked" in ask_step, \ "and it must subtract them from what it asks about" + + +def test_the_reserve_writes_the_doi_into_the_article_too(): + """A deposit request has to carry BOTH files. + + `metadata.yml` holds the record for the guard; the article's own front + matter is where the PDF's title page takes the DOI from, and + `test_the_pdf_carries_the_archival_doi_once_a_note_is_deposited` asserts + the two agree. The first live reserve wrote only the metadata and that + test failed on the request it opened -- correctly, because a publish from + that state would have produced a PDF with no DOI on it. + """ + src = (ROOT / "scripts" / "deposit.py").read_text(encoding="utf-8") + reserve = src.split("if not rebuild:")[1].split("return")[0] + assert "sync_archival.py" in reserve, \ + "the reserve must write the DOI into the article front matter" + for field in ("archived_at", "archived_version"): + assert field in reserve, "%s must be stamped by the reserve" % field