diff --git a/.github/workflows/deposit-ready.yml b/.github/workflows/deposit-ready.yml index d192f39..4ecd551 100644 --- a/.github/workflows/deposit-ready.yml +++ b/.github/workflows/deposit-ready.yml @@ -31,13 +31,32 @@ jobs: - name: Which notes have no record at all id: pending + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | + # A request that is already OPEN counts as asked. `unreserved()` + # reads main, and a reserved record only reaches main when a request + # is MERGED -- so without this, every run reserves a second DOI for a + # note whose request is still waiting, and abandons the draft behind + # it. That is the duplicate-mint hazard the whole flow exists to + # prevent, so the guard has to be here and not in the report. + gh pr list --state open --limit 100 --json title \ + --jq '.[] | select(.title | startswith("Deposit: ")) | + .title | sub("^Deposit: "; "")' \ + | tr -d ' ' | sed '/^$/d' > .already-asked || true + echo "already asked: $(tr '\n' ' ' < .already-asked)" SLUGS=$(pixi run -q python3 -c " import sys; sys.path.insert(0, 'scripts'); import deposit - print(','.join(deposit.unreserved())) + asked = set() + try: + asked = {l.strip() for l in open('.already-asked') if l.strip()} + except FileNotFoundError: + pass + print(','.join(s for s in deposit.unreserved() if s not in asked)) ") + rm -f .already-asked echo "slugs=${SLUGS}" >> "$GITHUB_OUTPUT" - echo "no record yet: ${SLUGS:-none}" + echo "to ask about: ${SLUGS:-none}" # ONE pull request per note, each touching only that note's own # metadata.yml. The queue file this replaced was appended to by every diff --git a/tests/test_migration.py b/tests/test_migration.py index f60d95c..84108e7 100644 --- a/tests/test_migration.py +++ b/tests/test_migration.py @@ -2349,3 +2349,26 @@ def candidates(slug, built): ["years-of-citcom-ellipsis-and-underworld"] long_slug = "a" * 60 assert candidates(long_slug, {"a" * cap}) == ["a" * cap] + + +def test_an_open_request_is_not_asked_for_twice(): + """`unreserved()` reads main, and a reserved record only reaches main when + a request is MERGED. + + So without a guard on what is already open, every run of the reminder + reserves a SECOND DOI for a note whose request is still waiting, and + abandons the draft behind it -- the exact duplicate-mint hazard the flow + exists to prevent. It bites hardest on a schedule, where the reminder runs + whether or not anything changed. + """ + ready = (ROOT / ".github" / "workflows" / "deposit-ready.yml").read_text( + encoding="utf-8") + config = "\n".join(l for l in ready.splitlines() + if not l.lstrip().startswith("#")) + ask_step = config.split("id: pending")[1].split("- name:")[0] + assert "gh pr list --state open" in ask_step, \ + "the reminder must look at what is already open before reserving" + assert 'startswith("Deposit: ")' in ask_step, \ + "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"