Say what is half-finished, without being asked - #42
Merged
Merged
Conversation
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 preview build died on this branch with "AMBIGUOUS: /underworld-2/ could belong to joss-publication-underworld-2 or underworld-2-10", an ambiguity that does not exist: those are two different notes with two different pages. fix_slugs restores URLs MyST truncated at 50 characters, by matching a slug against the pages that were built. A preview builds only the notes a branch changes, so in a preview most slugs have no built page -- and this branch's metadata backfill touched 43 articles, which made the build large enough for three related slugs to be in play at once. Matched loosely, `underworld-2-10` starts with `underworld-2` and `joss-publication-underworld-2` ends with it, so both claimed the same page and the second one to arrive raised the ambiguity. Neither could have been truncated: both are shorter than the cap. So the fix is to say that -- a slug at or under the cap is skipped, because there is nothing to restore, and only a longer one is matched at all. The cap is now a named constant rather than a literal in two places. Underworld development team with AI support from Claude Code
Contributor
The preview died on this branch reporting an ambiguity that does not exist: "/underworld-2/ could belong to joss-publication-underworld-2 or underworld-2-10". Those are three separate notes. fix_slugs restores URLs MyST mangled, by matching a slug against the pages that were built. A preview builds only the notes a branch changes, so most slugs have no built page at all -- and this branch's metadata backfill touched 43 articles, which put three related slugs in play at once. Matched loosely, `underworld-2-10` claimed `underworld-2` merely by starting with it and `joss-publication-underworld-2` by ending with it. My first attempt at this was wrong, and the build caught it: I assumed the only mangling was truncation at 50 characters and skipped every shorter slug, which broke the fifteen that are mangled a different way. MyST also drops a LEADING NUMBER, so `2-11-scaling` is served as /scaling/ and `30-years-of-citcom-...` as /years-of-citcom-.../. So both manglings are now matched exactly -- truncation only when the slug is longer than the cap, and a leading strip only when what was removed is a number -- and anything looser is refused. Checked against a real full build: 43 slugs already correct, 15 renamed, none unmatched, no ambiguity, and test-dois reports 50 resolved and 0 broken. Underworld development team with AI support from Claude Code
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
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.
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 possible moment.
The weakness is not the manual steps. A person merging the request to deposit is
the right design, and
deposit-readyalready catches the opposite case — a notepublished without a DOI. The weakness is that a step can fail to happen and
leave no trace.
What this adds
scripts/outstanding.pynames the half-finished states:versionhas moved pastarchived_versionarchive_doiwith norepository_record_idpixi run outstandinglocally; exit 1 if anything is outstanding.A report nobody runs has the failure mode it was built to fix, so the
outstandingworkflow runs it weekly and maintains one issue: opened whensomething is outstanding, edited while it stays that way, closed when it clears,
silent otherwise. It never opens a second issue.
A fact we were not recording
Detecting "this note has outrun its deposit" needed something new.
archived_atsays when a copy was taken, and a git timestamp cannot tell a rewrite from a
typo — I tried, and it flagged 4 notes of which 2 were the identifier
write-backs themselves. So the deposit now stamps
archived_version, the note'sown version at the moment it was deposited.
version != archived_versionisthen exactly the state where the DOI serves older text than the site, with no
false positives and no archaeology. Backfilled for the 43 deposited notes, added
to the schema, and
pixi run validateis clean.It found a second defect immediately
Unrelated to the first, and invisible until something looked:
deposit-readyre-asks on every metadata change, because it checked
deposit-queue.txt— whathas been merged — and not what was already open. One note has four open
deposit requests (#33, #37, #39, #41). Duplicate reminders are how a reminder
becomes something you scroll past, which is the same disease as the original
bug. It now counts an open request as having asked.
What this does not do
It cannot make the identifiers PR merge itself. Two independent things block
that: repo auto-merge is disabled (
allow_auto_merge: false), and a PR openedby the bot with
GITHUB_TOKENhas its required checks sit inaction_requireduntil a human approves them — which is why #25 could not have merged on its own
however long anyone waited. Removing that state entirely needs a repo settings
change and a token, and both are yours to decide; this makes the state loud
instead.
165 tests pass, including the control that matters most here: a check that
cannot answer reports "not checked" rather than nothing, because silence being
mistaken for good news is the whole bug.
Underworld development team with AI support from Claude Code