Skip to content

Reserve the DOI on the request, so nothing has to come back afterwards - #43

Merged
lmoresi merged 3 commits into
mainfrom
docs/deposit-flow
Sep 2, 2026
Merged

lmoresi merged 3 commits into
mainfrom
docs/deposit-flow

Conversation

@lmoresi

@lmoresi lmoresi commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Stacked on #42 — review that first, or read this diff alone with
git diff docs/outstanding-report...docs/deposit-flow.

The identifiers write-back was a gate with no decision in it. By the time that
pull request opened, the DOI was already public; merging it only told the
repository what had happened. The fortnight it sat unmerged left the
duplicate-mint guard blind the whole time.

The identifiers go in, rather than come back

A note reaching main without a record now gets a request that has already
created the figshare draft, reserved the DOI, and committed it into that note's
own metadata.yml. Both are private and reversible. Merging that request
approves the deposit and records it, in one act — and deposit-pdf attaches
the archival PDF, so the DOI on the title page is the one being asked about.

The publish that follows finds everything already on main, including
archived_at, which the PDF prints and the package README states. That is why
the reserve stamps it: stamped at publish, it would exist only on the runner.

The race goes with it

The approval used to live in one shared deposit-queue.txt, appended to by
every note at the same line. Two notes in flight conflicted and merging one
broke the other — measured, not feared, and the reason four duplicate requests
piled up for one note. Per-note metadata cannot collide.

The trigger moves from the queue file to articles/**/metadata.yml, narrowed to
--approved (holds a reserved record, not yet published). That set can only be
entered by merging a request, so an ordinary metadata edit — a keyword, a
corrected credit — mints nothing. --all keeps its old meaning behind the
manual deposit-all mode, where a person has chosen it.

What still comes back

archive_published_at, and only that. It is bookkeeping: the record id and DOI
are on main already, so the guard is satisfied whether or not it lands, and it
only decides which notes a batch re-version offers. The report lists it until
merged. Automating it away needs a stored credential — the ruleset bypass that
would have avoided one is refused by GitHub, because the Actions app is not an
actor of this organisation.

Checked

168 tests. The design test covering the old mechanism is rewritten, not
dropped
— it still asserts that nothing mints a DOI without somebody merging
something, now by checking the push trigger uses --approved and never --all,
and that the request workflow can reserve but never publish. Two new tests cover
the per-note isolation and that the reserve stamps what the publish needs.

preview.yml already excludes deposit/** by name rather than by path, which
becomes load-bearing here: request branches now touch articles/**, so a paths
filter alone would have started building a preview site for every one.

Not covered

The reserve path has not been exercised against the live figshare API — the
tests assert the wiring, not a round trip. The first real request is the test,
and it is reversible at every step: the draft can be deleted, and nothing
resolves publicly until the merge.

Underworld development team with AI support from Claude Code

A deposit sat unrecorded for a fortnight. Nothing was broken and nothing
complained: the only thing that would have was the NEXT deposit, which
refused and made the backlog somebody's problem at the worst moment. The
weakness is not the manual steps -- a person merging the request to
deposit is the right design -- it is that a step can fail to happen and
leave no trace.

scripts/outstanding.py names the half-finished states: identifiers
written but never merged, a deposit asked for and never run, a note whose
version has moved past the copy on its DOI, an archival note published
without one, and a DOI with no record id. `pixi run outstanding` locally;
exit 1 if anything is outstanding.

A report nobody runs has the failure mode it was built to fix, so the
`outstanding` workflow runs it weekly and keeps ONE issue: opened when
something is outstanding, edited while it stays that way, closed when it
clears, and silent otherwise.

Detecting a note that has outrun its deposit needs a fact we were not
recording. `archived_at` says when a copy was taken, and a git timestamp
cannot tell a rewrite from a typo, so the deposit now stamps
`archived_version` -- the note's own version at the moment it was
deposited. `version != archived_version` is then exactly the state where
the DOI serves older text than the site. Backfilled for the 43 deposited
notes and added to the schema.

Running it found a second defect, unrelated to the first and invisible
until now: deposit-ready re-asks on every metadata change, because it
checked deposit-queue.txt (what was MERGED) and not what was already
open. One note had four open requests. Duplicate reminders are how a
reminder becomes something you scroll past, so it now counts an open
request as having asked.

Underworld development team with AI support from Claude Code
The identifiers write-back was a gate with no decision in it. By the time
that pull request opened the DOI was already public; merging it only told
the repository what had happened, and the fortnight it once sat unmerged
left the duplicate-mint guard blind the whole time.

So the identifiers now go IN rather than come back. A note reaching main
without a record gets a request that has already created the figshare
draft, reserved the DOI and committed it into that note's metadata.yml --
both private, both reversible. Merging that request approves the deposit
AND records it, in one act. The publish that follows finds everything it
needs already on main, including archived_at, which the PDF prints and the
package README states.

That also removes the race. The approval used to live in one shared
deposit-queue.txt appended to by every note at the same line, so two notes
in flight conflicted and merging one broke the other -- measured, not
feared, and the reason four duplicate requests piled up for one note. Per
note metadata cannot collide.

The trigger moves with it, from the queue file to articles/**/metadata.yml,
narrowed to --approved: notes holding a reserved record that is not yet
published. That set can only be entered by merging a request, so an
ordinary metadata edit mints nothing. --all keeps its old meaning behind
the manual deposit-all mode, where a person has chosen it.

One thing still comes back: archive_published_at. It is bookkeeping -- the
guard is satisfied without it -- and the outstanding report lists it until
it is merged.

The design test that covered the old mechanism is rewritten rather than
dropped: it still asserts that nothing mints a DOI without somebody
merging something, now by checking the push trigger uses --approved and
never --all, and that the request workflow can reserve but never publish.

Underworld development team with AI support from Claude Code
# Conflicts:
#	.github/workflows/deposit-ready.yml
#	scripts/outstanding.py
#	tests/test_outstanding.py
@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Preview: https://underworld-technical-notes.github.io/underworldcode.org-preview/4ace55d00b/

No article changed on this branch. Built from 81b7727. Shows notes at draft and review, which the published site withholds. Not indexed, no comments, and not the citable version.

@lmoresi
lmoresi merged commit 5675f0a into main Sep 2, 2026
3 checks passed
lmoresi added a commit that referenced this pull request Sep 2, 2026
I dropped this guard when I rewrote the step in #43. It had been added in
#42 for the queue design, and the rewrite replaced the whole step without
carrying it across.

`unreserved()` reads main, and a reserved record only reaches main when a
request is MERGED. So between opening a request and merging it the note
still looks unreserved, and the next run of the reminder creates a second
figshare draft, reserves a second DOI, and opens a second request --
abandoning the first draft. That is the duplicate-mint hazard the whole
flow exists to prevent.

It is already wrong on the push trigger, where two metadata pushes before
a merge are enough. It would be worse on a schedule, which is what
prompted looking: a weekly run would mint a fresh DOI for every waiting
request, every week.

So an OPEN request counts as having asked, and the reminder subtracts
those before it reserves anything. The test asserts the guard rather than
its wording, since this is the second time the mechanism has been
rewritten around it.

Underworld development team with AI support from Claude Code
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant