Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 16 additions & 10 deletions .github/workflows/deposit-pdf.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@ name: deposit-pdf
# preview site is the wrong tool here. It costs minutes to publish a whole
# site, and it still leaves you clicking through a page to reach the download.
#
# So this builds the PDF for the queued notes and nothing else, and uploads it
# as a run artifact. Download it from the checks, read it, and merge if it is
# right.
# So this builds the PDF for the note being asked about and nothing else, and
# uploads it as a run artifact. Download it from the checks, read it, and merge
# if it is right.

on:
pull_request:
paths: ['deposit-queue.txt']
paths: ['articles/**/metadata.yml']

concurrency:
group: deposit-pdf-${{ github.ref }}
Expand All @@ -30,19 +30,25 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
# The base is needed to work out which lines this pull request ADDED.
# A queue entry that was already there has already been deposited, and
# rebuilding its PDF would say nothing about the decision at hand.
# The base is needed to work out what this pull request ADDED. A
# metadata edit that does not reserve a DOI is not a deposit request,
# and rebuilding a PDF for it would say nothing about any decision.
fetch-depth: 0

- name: Work out which notes are being asked about
id: slugs
run: |
BASE="${{ github.event.pull_request.base.sha }}"
ADDED=$(git diff "$BASE"...HEAD -- deposit-queue.txt \
| sed -n 's/^+\([a-z0-9][a-z0-9-]*\)$/\1/p' | tr '\n' ',' | sed 's/,$//')
# A note whose metadata.yml GAINED an archive_doi in this pull
# request. That is what a deposit request is, and it excludes every
# other metadata edit -- a keyword, a corrected banner credit -- which
# would otherwise each cost a PDF build.
ADDED=$(git diff "$BASE"...HEAD -- 'articles/*/metadata.yml' \
| awk '/^\+\+\+ b\/articles\//{split($2,a,"/"); slug=a[3]}
/^\+archive_doi:/{if (slug) print slug}' \
| sort -u | tr '\n' ',' | sed 's/,$//')
if [ -z "$ADDED" ]; then
echo "no slug added to the queue by this pull request"
echo "no DOI reserved by this pull request -- not a deposit request"
echo "slugs=" >> "$GITHUB_OUTPUT"
else
echo "building: $ADDED"
Expand Down
83 changes: 37 additions & 46 deletions .github/workflows/deposit-ready.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,68 +29,59 @@ jobs:
with:
cache: true

- name: Which notes have no DOI
- 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. deposit-queue.txt
# only records what has been merged, so without this the workflow
# re-asks on every metadata change and the same note accumulates
# requests -- four for one note, before this was noticed. Duplicate
# reminders are how a reminder becomes something you scroll past.
gh pr list --state open --limit 100 --json title \
--jq '.[] | select(.title | startswith("Deposit: ")) |
.title | sub("^Deposit: "; "")' \
| tr ',' '\n' | 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
queued = set()
try:
for line in open('deposit-queue.txt'):
line = line.split('#')[0].strip()
if line: queued.add(line)
except FileNotFoundError:
pass
try:
queued.update(l.strip() for l in open('.already-asked') if l.strip())
except FileNotFoundError:
pass
print(','.join(s for s in deposit.pending() if s not in queued))
print(','.join(deposit.unreserved()))
")
rm -f .already-asked
echo "slugs=${SLUGS}" >> "$GITHUB_OUTPUT"
echo "not yet queued: ${SLUGS:-none}"
echo "no record yet: ${SLUGS:-none}"

- name: Ask, by pull request
# ONE pull request per note, each touching only that note's own
# metadata.yml. The queue file this replaced was appended to by every
# note at the same line, so two notes in flight conflicted and merging
# one broke the other -- measured, not feared.
- name: Reserve a DOI and ask, one note at a time
if: steps.pending.outputs.slugs != ''
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
FIGSHARE_TOKEN: ${{ secrets.FIGSHARE_TOKEN }}
run: |
# RUN_ATTEMPT, not just RUN_ID: a re-run keeps the same run id, so
# without it the second attempt pushes to the branch the first one
# already created and is rejected non-fast-forward. Re-running a
# failed workflow is the obvious thing to do, and it must work.
BRANCH="deposit/queue-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}"
git config user.name "underworld-technical-notes"
git config user.email "help@underworldcode.org"
git checkout -b "$BRANCH"
for SLUG in $(echo "${{ steps.pending.outputs.slugs }}" | tr ',' ' '); do
echo "$SLUG" >> deposit-queue.txt
done
git add deposit-queue.txt
git commit -m "Deposit: ${{ steps.pending.outputs.slugs }}
# RUN_ATTEMPT, not just RUN_ID: a re-run keeps the same run id, so
# without it the second attempt pushes to the branch the first one
# already created and is rejected non-fast-forward.
BRANCH="deposit/${SLUG}-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}"
git checkout -q main
git checkout -qb "$BRANCH"
# Reserve only: a draft and a DOI, both reversible, and nothing
# uploaded or published. What reaches this pull request is the
# identifiers -- so merging it both approves the deposit AND
# records what was minted, in one act.
pixi run deposit -- --slug "$SLUG" --live --reserve-only
git add "articles/$SLUG/metadata.yml"
git commit -m "Deposit: $SLUG

Opened automatically because these notes are on main without a DOI.
Merging this runs the deposit workflow."
git push origin "$BRANCH"
gh pr create --base main --head "$BRANCH" \
--title "Deposit: ${{ steps.pending.outputs.slugs }}" \
--body "These notes are published and have no DOI.
A draft and a DOI are reserved for this note; neither is public. The
identifiers are in its metadata.yml, so merging this approves the
deposit and records it in one step.

**Merging this mints one.** The deposit workflow reserves a DOI, rebuilds the PDF so the DOI is on its title page, uploads the PDF and the archive package, publishes the record, and opens a further pull request with the identifiers.
Opened automatically because the note is on main without a record."
git push origin "$BRANCH"
DOI=$(grep '^archive_doi:' "articles/$SLUG/metadata.yml" | cut -d' ' -f2)
gh pr create --base main --head "$BRANCH" \
--title "Deposit: $SLUG" \
--body "\`$SLUG\` is published and has no archival record. A figshare draft has been created and **$DOI** reserved for it. Neither is public yet.

A published DOI cannot be withdrawn, only superseded. Close this instead if a note is not ready — it will be offered again the next time anything changes, so nothing is lost by waiting.
**Merging this publishes the record at that DOI.** The deposit workflow rebuilds the PDF so the DOI is on its title page, uploads the PDF and the archive package, and publishes. The identifiers are already in this pull request, so nothing has to be recorded afterwards.

A published DOI cannot be withdrawn, only superseded — and because figshare versions, a mistake is a new version rather than a lost identifier.

Not ready? Close this. The reserved draft is then unused and shows up in the weekly *outstanding* issue, where \`pixi run deposit -- --slug $SLUG --live --delete-draft\` clears it.

Underworld development team with AI support from [Claude Code](https://claude.com/claude-code)"
done
40 changes: 30 additions & 10 deletions .github/workflows/deposit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,21 @@ name: deposit

# Two ways in, and both are a person deciding.
#
# A push to deposit-queue.txt means somebody merged a pull request titled
# "Deposit: <note>". That is the editorial act; the merge is the consent. The
# file is only ever changed by such a merge, so this cannot fire on its own.
# A reserved DOI reaching `main` means somebody merged a pull request titled
# "Deposit: <note>". That is the editorial act; the merge is the consent. A
# reserved record can only arrive that way, so this cannot fire on its own.
#
# The trigger watches metadata, not a queue file, because the approval now
# lives in the note's own metadata.yml -- one file per note, so two notes in
# flight cannot conflict. It is narrowed to `--approved` below for the same
# reason it is not `--all`: an unrelated metadata edit must not mint anything.
#
# workflow_dispatch is the manual route, for a re-run or a mode other than
# deposit-all.
on:
push:
branches: [main]
paths: ['deposit-queue.txt']
paths: ['articles/**/metadata.yml']
workflow_dispatch:
inputs:
slug:
Expand Down Expand Up @@ -148,11 +153,24 @@ jobs:
FIGSHARE_TOKEN: ${{ secrets.FIGSHARE_TOKEN }}
run: pixi run deposit -- --slug "${{ inputs.slug }}" --live --publish

# A queue merge has no inputs, so `mode` is empty and this is the step
# that runs. Anything already holding a record is skipped, so a stale line
# in the queue does nothing.
# An approval merge has no inputs, so `mode` is empty and this is the
# step that runs.
#
# `--approved`, NOT `--all`: it acts only on notes holding a RESERVED
# record that is not yet published, which is exactly the set somebody has
# merged a request for. With `--all` this trigger would deposit any
# archival note the moment an unrelated metadata change reached main --
# the gate would be gone, and the first anyone knew would be the DOI.
- name: Publish what has been approved
if: github.event_name == 'push'
env:
FIGSHARE_TOKEN: ${{ secrets.FIGSHARE_TOKEN }}
run: pixi run deposit -- --approved --live --publish

# The manual sweep keeps `--all`: run deliberately, by a person choosing
# the mode, it is allowed to reserve and publish in one go.
- name: Deposit and publish everything outstanding
if: inputs.mode == 'deposit-all' || github.event_name == 'push'
if: inputs.mode == 'deposit-all'
env:
FIGSHARE_TOKEN: ${{ secrets.FIGSHARE_TOKEN }}
run: pixi run deposit -- --all --live --publish
Expand Down Expand Up @@ -215,9 +233,11 @@ jobs:
git push origin "$BRANCH"
gh pr create --base main --head "$BRANCH" \
--title "Deposit identifiers from run ${GITHUB_RUN_ID} (attempt ${GITHUB_RUN_ATTEMPT})" \
--body "DOIs and record ids written by the deposit workflow (\`${{ inputs.mode }}\`).
--body "Publication timestamps written by the deposit workflow (\`${{ inputs.mode }}\`).

Bookkeeping. The record id and DOI already reached \`main\` through the deposit request, so the guard against minting a second DOI is satisfied whether or not this lands; what is here is \`archive_published_at\`, which only affects which notes a batch re-version offers.

**Merge this.** Until it lands, the repository does not know these records exist, and the guard against minting a second DOI for the same note has nothing to check.
Merge it when convenient. It appears in the weekly *outstanding* issue until you do.

Underworld development team with AI support from [Claude Code](https://claude.com/claude-code)"

Expand Down
57 changes: 40 additions & 17 deletions PUBLISHING.md
Original file line number Diff line number Diff line change
Expand Up @@ -362,20 +362,43 @@ settled before code exists.

## The deposit is offered, not taken

A note reaching `main` without a DOI opens a pull request titled
**Deposit: <slug>**, adding it to `deposit-queue.txt`. Merging that pull
request runs the deposit; closing it does not, and the note is offered again
the next time anything changes.

So the reminder is automatic and the decision is not. Nothing is deposited
because a note was published — only because somebody merged the request to
deposit it. That matters because a published DOI cannot be withdrawn, only
superseded.

The deposit then opens a further pull request carrying the identifiers it
obtained. **Merge that too**: until it lands, the repository does not know the
record exists, and the guard against minting a second DOI for the same note
keys on the record id being present.

Queue entries stay after the deposit. They are a log of what was approved, and
the deposit skips anything already holding a record, so a stale line is inert.
A note reaching `main` without a record gets a pull request titled
**Deposit: <slug>**. The workflow creates a figshare draft and reserves a DOI
for it — both private, both reversible — and commits the identifiers into that
note's own `metadata.yml`. So the request you are reading already contains the
DOI it is asking about, and `deposit-pdf` attaches the archival PDF to it.

**Merging is the decision, and it is the only one.** The push runs the deposit,
which rebuilds the PDF with the DOI on its title page, uploads it with the
archive package, and publishes. Nothing has to be recorded afterwards: the
identifiers arrived with the approval.

Closing the request deposits nothing. The draft is then unused, and it appears
in the weekly *outstanding* issue until it is either merged or cleared with
`--delete-draft`. A reserved DOI never resolves publicly, so an abandoned
request costs nothing but a line in that report.

The reminder is automatic and the decision is not. Nothing is deposited because
a note was published — only because somebody merged the request. That matters
because a published DOI cannot be withdrawn, only superseded.

### Why the approval lives in the note's metadata

It used to live in one shared `deposit-queue.txt`, appended to by every note at
the same line. Two notes in flight therefore conflicted, and merging one broke
the other — so the approvals raced each other, and duplicate requests piled up
for the same note. Per-note files cannot collide.

The trigger watches `articles/**/metadata.yml` and acts on `--approved`, which
is "holds a reserved record, not yet published". A reserved record can only
reach `main` through a merged request, so an ordinary metadata edit — a
keyword, a corrected credit — mints nothing. `--all`, which would deposit
anything undeposited, stays behind an explicit manual mode.

### What still comes back afterwards

One thing: `archive_published_at`, the moment figshare published. It arrives as
a small pull request, and it is bookkeeping — the record id and DOI are already
on `main`, so the guard against a second mint is satisfied whether or not it
lands. It only decides which notes a batch re-version offers. The *outstanding*
report lists it until it is merged.
15 changes: 0 additions & 15 deletions deposit-queue.txt

This file was deleted.

Loading
Loading