Skip to content

feat(release): take absence attestations per dispatch and publish each destination separately - #126

Merged
BharathASL merged 3 commits into
mainfrom
feat/per-dispatch-release-attestation
Nov 10, 2026
Merged

BharathASL merged 3 commits into
mainfrom
feat/per-dispatch-release-attestation

Conversation

@BharathASL

Copy link
Copy Markdown
Contributor

Description

Adopts N6-REL-05. The publisher no longer reads a stored absence record, and each destination publishes in its own job.

release-candidate.yml (tag push) builds, smoke-tests and binds evidence for the candidate and publishes nothing. Candidate and evidence artifacts are now retained for 90 days, and the run summary prints the candidate run ID and the owner-inventory checklist.

release.yml is now a workflow_dispatch publisher with inputs tag, candidate_run_id, destinations (default github,modrinth,hangar), modrinth_absence_confirmed, hangar_absence_confirmed and nonce (in run-name). Every job requires github.ref == refs/tags/<tag>, the tag input is checked against the release tag grammar, and the concurrency group is keyed on github.ref.

plan (no environment, no publishing secret): candidate run provenance (release-candidate.yml, push event, head branch = tag, head SHA = tag commit on origin, success), pins the newest candidate attempt with evidence from the same attempt, stops on an expired artifact, verifies candidate and evidence, refuses true attestations beside another publisher run for the tag, rejects an attestation for an unselected destination, runs anonymous lookups per selected destination (a registry not shown complete needs its attestation), checks Modrinth project approval and the Hangar project/channel, and dry-runs the Hangar task graph.

publish-github, publish-modrinth, publish-hangar: all environment: release and needs: [plan]. Each first runs scripts/release-preflight.sh, the non-writing credential checks for every selected destination (migration flag, tokens, GitHub releases readable, Modrinth token accepted and project approved, Hangar key, target, permission bits and project permissions, task graph). Those checks deliberately do not read other destinations' version state, since another job may be mid-upload. Then each job checks its own destination with the owner token, refuses a true attestation unless GITHUB_RUN_ATTEMPT is 1 (and re-checks overlapping runs) in the step immediately before its registry upload, uploads only if absent and verifies the consumer download. The Hangar page sync runs only when the version is the newest public version on its Hangar channel and otherwise logs the skip.

scripts/release-destination.py takes --absence-confirmed, --public-only (plan) and --credentials-only (preflight); reconciled_absence(), the inventory destination and release-candidate.py reconciliation are gone. New scripts/release-publisher.py holds destination parsing, candidate provenance and attempt pinning, the attestation guard and the Hangar sync decision.

recover-beta1.yml, recover-hangar-rc1.yml, scripts/recover-beta1.py and its test are deleted: they were one-off recoveries, nothing else referenced them, and they read the stored record.

scripts/release.sh keeps every existing guard. After pushing the tag it waits for the candidate run, prints the owner-inventory checklist, asks for each registry attestation (absent or present, no default, end of input aborts) and dispatches the publisher, then finds the run by its nonce. --resume <destinations> --candidate-run <id> re-dispatches for an existing tag. release.ps1 stays a thin wrapper.

RELEASE_PROCESS.md documents the two workflows, the inputs, the attestation, the plan and publish jobs, prerequisites only an upload can prove (Modrinth VERSION_CREATE scope and owner permission, registry-side metadata validation), a per-destination retry table that replaces the "Re-run failed jobs" guidance, and a table for destinations left unpublished when a candidate expires. CHANGELOG is unchanged: it records plugin changes only.

After merge: delete the RELEASE_ABSENCE_RECONCILIATION variable from the release environment. On the first real dispatch, confirm that one approval releases all three publish jobs.

Related Issues

Closes #125

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation / Hygiene update

Checklist

  • My code follows the code style of this project.
  • I have verified that all unit tests pass locally (./gradlew test). Not run: no Java or Gradle source changed. python -m unittest discover -s scripts/tests -p 'test_*.py' passes (105 tests).
  • I have added tests that prove my fix is effective or that my feature works.
  • I have updated documentation / CHANGELOG as appropriate.
  • My commits follow Conventional Commits.

…h destination separately

Split the tag-push workflow into a candidate workflow and a dispatched publisher with one gated job per destination; remove the stored absence record and the one-off recovery workflows.

Signed-off-by: Bharath Sendhurpandi <bharathasl74185@gmail.com>

@ninja6-agent ninja6-agent Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

0 blockers, 1 nit.

Comment thread scripts/release.sh Resolved
Signed-off-by: Bharath Sendhurpandi <bharathasl74185@gmail.com>

@ninja6-agent ninja6-agent Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found.

…dispatches

Check the attempt before approval, ignore later dispatches waiting in the concurrency group, detect draft GitHub releases, and derive the Hangar upload flag from the dispatch input.

Signed-off-by: Bharath Sendhurpandi <bharathasl74185@gmail.com>

@ninja6-agent ninja6-agent Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found.

@BharathASL
BharathASL added this pull request to the merge queue Nov 10, 2026
Merged via the queue into main with commit cccaa9f Nov 10, 2026
11 checks passed
@BharathASL
BharathASL deleted the feat/per-dispatch-release-attestation branch November 10, 2026 14:49
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.

feat(release): take absence attestations per dispatch and publish each destination separately

1 participant