Skip to content

ci: attach release assets before publishing and verify the set - #237

Open
re-gius wants to merge 3 commits into
masterfrom
re-gius/release-asset-publication-order
Open

ci: attach release assets before publishing and verify the set#237
re-gius wants to merge 3 commits into
masterfrom
re-gius/release-asset-publication-order

Conversation

@re-gius

@re-gius re-gius commented Aug 17, 2026

Copy link
Copy Markdown

Description

Releases were occasionally. published without ABI assets because assets were being attached to an already-published release. This repository has immutable releases enabled, so GitHub refuses that upload. Both publish workflows now create the release as a draft, verify its asset set, and publish only afterwards, with a guard that rejects a pre-published version before the build rather than after it. Releases are also cut from the Actions tab with a version input, so nobody has to create a tag or a release by hand.

Type

  • Bug fix
  • Feature
  • Breaking change
  • Documentation
  • Chore
  • Refactor
  • Security

Scope

  • Registration
  • Resolver
  • Store
  • Proof of Personhood
  • Deployment scripts
  • Tests

Related Issues

Part of #222 — the release-workflow half. The SDK half (dotns-sdk's scripts/sync-abis.mjs keeping stale ABIs when assets are missing) is a separate PR in that repository.

Unblocks #118, which cannot attach deployments.json as a release asset until the publication order is fixed. Downstream of that, #221 and #223 stop transcribing addresses by hand

Fixes

Changes, applied to both publish-release.yml and publish-prerelease.yml:

  • Guard, first step. If a release for the tag exists and is not a draft, fail immediately with an explanation. Previously this case consumed the whole build and died at the last step.
  • Extract step reads the contract list from the new .github/abi-contracts.txt and writes release/expected-assets.txt recording what the build produced.
  • Verify step diffs the draft's actual assets against that file plus the zip, and refuses to publish on any mismatch.
  • Publish step runs gh release edit --draft=false, so publication happens only after verification. softprops keeps draft: true.
  • workflow_dispatch with a version input, alongside the existing tag-push trigger. A Resolve release tag step takes the version from either source, validates it against the same pattern as the trigger, and exports RELEASE_TAG for the downstream steps. The input is read through an env var rather than interpolated into the script.
  • Concurrency keyed on inputs.version || github.ref, because github.ref is the branch on a dispatch and two versions from one branch would otherwise queue behind each other.

.github/abi-contracts.txt. The 32-name list was duplicated verbatim in both workflows, so a new contract had to be added twice or the release silently omitted it, and the verification step would have needed a third copy to check against. It now lives in one file that both read, and verification compares against what the build actually emitted rather than against a list.

See #222 for more context.

Checklist

Code

  • Follows project style
  • forge build passes
  • forge test passes
  • No new compiler warnings

Testing

  • New tests added for changed behavior
  • Fuzz tests added where applicable
  • Invariant tests verified

Security

  • No new selfdestruct or delegatecall
  • Access control reviewed
  • No storage layout conflicts (for upgradeable contracts)

Documentation

  • NatSpec updated on changed interfaces
  • README updated if needed

Breaking Changes

  • No breaking changes
  • Breaking changes documented below

Breaking changes:

Breaking changes: no API or contract change, but the release process changes. Releases can no longer be created through the GitHub UI's release form or with gh release create, because doing so publishes immediately and the workflow can then no longer attach assets. Cutting a release is now either "Publish Release Package" from the Actions tab with a version input, or pushing a matching tag; both run the same workflow, which creates, verifies and publishes. The guard turns the old habit into an immediate failure.

How to test

Dispatch the workflow from the Actions tab.
Not yet exercised end to end. It should be tested on a fork, not here.

Notes

A release from current master is still needed.

@github-actions

github-actions Bot commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

CI Summary

Check Result
PR Title PR Title Valid
Labels Unknown
Secret Scan Passed - No secrets detected

Labels

other, P0, type: docs, type: bug

@re-gius re-gius changed the title CI: attach release assets before publishing + verify ci: attach release assets before publishing + verify Aug 17, 2026
@re-gius re-gius changed the title ci: attach release assets before publishing + verify ci: attach release assets before publishing and verify the set Aug 17, 2026
@re-gius re-gius added P0 Highest Priority type: bug Bug report labels Aug 17, 2026
@re-gius
re-gius marked this pull request as ready for review August 17, 2026 13:47

@sphamjoli sphamjoli left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Superseded by the inline review below.

@sphamjoli
sphamjoli dismissed their stale review August 17, 2026 19:52

Superseded by an inline review referencing the specific lines.

@sphamjoli sphamjoli left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Nice work, the draft, verify, publish order is the right call for immutable releases. Reading the version through an env var and checking it before use keeps injection out, and building the draft only after forge test passes is spot on.

One thing to sort before merge: the concurrency key does not quite do what its comment says, detail is on the line. Could we also prove it in CI on this PR rather than a manual dispatch? Wire the beta workflow to run against this branch so it cuts a v0.5.5-rc pre-release here, then we confirm from the run that the pre-release has every ABI plus the zip, it published cleanly, and creating the tag did not start a second release run. The rest of the inline notes are small hardening bits, not blockers.

# Two runs for the same version would race to attach assets to the same draft. On a
# dispatch `github.ref` is the branch, so key on the requested version instead.
concurrency:
group: ${{ github.workflow }}-${{ inputs.version || github.ref }}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Blocking: this does not serialise the two triggers for the same version, which is what the comment above it promises. On a tag push inputs.version is empty, so the group is ...-refs/tags/v0.5.5; on a dispatch it is ...-v0.5.5. The two strings differ, so a dispatch and a tag push for the same version are not mutually exclusive: both clear the pre-published guard while neither is published, both build, both attach to the same draft tag, and both reach publish, where immutable releases fail the second one or the asset sets race. Use github.ref_name instead of github.ref. On a tag push it is the tag v0.5.5; on a dispatch inputs.version wins. Both then collapse to ...-v0.5.5.

# Two runs for the same version would race to attach assets to the same draft. On a
# dispatch `github.ref` is the branch, so key on the requested version instead.
concurrency:
group: ${{ github.workflow }}-${{ inputs.version || github.ref }}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Same blocking issue as the release workflow. Switch github.ref to github.ref_name so a dispatch and a tag push for the same pre-release version share one concurrency group.

else
TAG="$GITHUB_REF_NAME"
fi
if [[ ! "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+-.+$ ]]; then

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

-.+$ accepts anything after the hyphen. Not exploitable, since $RELEASE_TAG is quoted everywhere downstream and a newline cannot reach $GITHUB_ENV through the anchored match, but it lets junk suffixes through. Tighten to -[0-9A-Za-z.]+$.

)

for name in "${contracts[@]}"; do
while IFS= read -r name || [ -n "$name" ]; do

@sphamjoli sphamjoli Aug 17, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Hardening: a CRLF-saved abi-contracts.txt puts a trailing carriage return in name, breaks the artifact path, and fails the release. Add a pre-commit hook that rejects CR line endings in this file so a bad checkout cannot reach the workflow. Pinning it to LF in .gitattributes covers the checkout side too, and a run-loop strip (name="${name%$'\r'}") is a last resort, but the hook stops it at the source.

Comment thread README.md

Do not create releases through the GitHub UI's release form, or with `gh release create`. Both publish immediately, and because this repository has immutable releases enabled, a published release can no longer accept assets: only its title and notes stay editable. A release made that way carries no ABIs at all. The workflow rejects an already-published version before building, so the mistake fails in seconds rather than silently shipping an empty release.

If a run fails partway, re-run it from the Actions tab; the draft is updated rather than duplicated. If the version has already been published, use a different one, since its assets cannot be changed.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

softprops never deletes assets, so this holds only when the asset set is unchanged. Remove a contract from abi-contracts.txt and re-run against an existing draft, and the stale asset survives; the verify step then refuses to publish, which is correct but not what this line implies. Add a note to delete the draft first when the contract list changed.

@sphamjoli

sphamjoli commented Aug 17, 2026

Copy link
Copy Markdown
Member

Could we also please prove it in CI on this PR rather than a manual dispatch? Wire the beta workflow to run against this branch so it cuts a v0.5.5-rc pre-release automatically here, and we read the result straight from the PR run: the pre-release has every ABI plus the zip, it published cleanly, and creating the tag did not start a second release run. Both workflows share the fix, so a green pre-release covers the real release too. Thanks for putting this together!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

other P0 Highest Priority type: bug Bug report type: docs

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants