Background
Raised by cubic on PR #8 (scripts/lib/release-state.ts:117), declined there as out of scope for that PR and tracked here.
classifyRelease folds "the catalog has moved on to a newer rebuild of this pair" into complete, and isSupersededBy decides that from the tag names alone:
if (state.catalogTag !== null && isSupersededBy(state.tag, state.catalogTag))
return 'complete'
The gap
If the release for that newer rebuild is later deleted, catalog.json names a tag consumers cannot download. Re-running the older (still published) tag then classifies complete and does nothing, so the run declines to repair an index that is already lying.
ReleaseState carries releaseExists for the run's own tag only, so the pure function cannot observe this. Closing it needs a new observed fact — a gh release view "$catalogTag" probe in release.yml — plumbed through as a new ReleaseState field.
The actual question
ADR-0003 already decided how this module treats a lying index, for the same-tag case:
The last row cannot arise from an interrupted run — registration only ever follows publication — so it means the release was deleted or the catalog was hand-edited, and the run fails rather than papering over an index that is already lying.
So the open question is which behaviour the superseded-tag case should get:
- Throw, consistent with the same-tag deleted-release row (
refuse), or
- Return
register, repointing the catalog back onto the older tag that is published — a silent backward move that contradicts the fail-loud contract above, or
- Leave as-is, on the grounds that an out-of-band release deletion is outside this module's contract.
(1) looks most consistent with the existing decision, but it is a semantics change to an accepted ADR, not a drive-by fix.
Notes
- Reachable only through out-of-band deletion or a hand-edited catalog — not from any interrupted run.
- Related to the gap ADR-0003 already records under Consequences → Negative ("Supersession is only checked on the
register path"). Both are about supersession's edges and may be worth deciding together.
- Any resolution should amend ADR-0003 rather than only changing code.
Background
Raised by cubic on PR #8 (
scripts/lib/release-state.ts:117), declined there as out of scope for that PR and tracked here.classifyReleasefolds "the catalog has moved on to a newer rebuild of this pair" intocomplete, andisSupersededBydecides that from the tag names alone:The gap
If the release for that newer rebuild is later deleted,
catalog.jsonnames a tag consumers cannot download. Re-running the older (still published) tag then classifiescompleteand does nothing, so the run declines to repair an index that is already lying.ReleaseStatecarriesreleaseExistsfor the run's own tag only, so the pure function cannot observe this. Closing it needs a new observed fact — agh release view "$catalogTag"probe inrelease.yml— plumbed through as a newReleaseStatefield.The actual question
ADR-0003 already decided how this module treats a lying index, for the same-tag case:
So the open question is which behaviour the superseded-tag case should get:
refuse), orregister, repointing the catalog back onto the older tag that is published — a silent backward move that contradicts the fail-loud contract above, or(1) looks most consistent with the existing decision, but it is a semantics change to an accepted ADR, not a drive-by fix.
Notes
registerpath"). Both are about supersession's edges and may be worth deciding together.