Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 36 additions & 36 deletions .github/workflows/publish-prerelease.yml
Original file line number Diff line number Diff line change
@@ -1,30 +1,47 @@
name: Publish Beta Package

# Creating a `v*` tag is restricted to the dotns team by the `release tags` ruleset, and the
# job below pauses on the `releases` environment for a reviewer, so a release takes two
# distinct human actions: cutting the tag, and approving the run it starts.
# Creating a `v*` tag is restricted to the dotns team by the `release tags` ruleset, the
# `guard` job refuses a tag whose commit is not on master, and the gated job pauses on the
# `releases` environment for a reviewer. A release therefore ships reviewed master code and
# takes two distinct human actions: cutting the tag, and approving the run it starts.
on:
push:
tags:
- "v[0-9]+.[0-9]+.[0-9]+-*"
workflow_dispatch:
inputs:
version:
description: "Pre-release version, e.g. v0.5.5-rc1. The tag is created from the selected branch."
required: true
type: string

permissions:
contents: write

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

jobs:
# Runs first, with no environment and read-only access: a tag that does not point at a
# commit on master stops here, before a reviewer is asked to approve and before any
# secret is read. master only takes pull requests, which need an approving review, so
# this keeps every release on reviewed code.
guard:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
persist-credentials: false

- name: Require the tagged commit to be on master
run: |
if ! git merge-base --is-ancestor "$GITHUB_SHA" origin/master; then
echo "::error::$GITHUB_REF_NAME points at $GITHUB_SHA, which is not on master. Tag a commit on master."
exit 1
fi
echo "$GITHUB_REF_NAME points at $GITHUB_SHA, which is on master."

beta-release:
needs: guard
runs-on: ubuntu-latest
# Gated: this job reads the genesis owner key, which lives only on the `releases`
# environment, so every run waits for a reviewer's approval there.
Expand All @@ -34,19 +51,12 @@ jobs:
env:
DOTNS_TLDS: testnet paseo
steps:
# On workflow_dispatch the tag does not exist yet; the release step creates it
# from the branch this run was started on. Read through an env var rather than
# interpolating the input into the script.
# The run is started by pushing the tag, so the tag already exists and is the release's
# identity. Only the tag push triggers this workflow: the `release tags` ruleset keeps
# the workflow's token from creating a `v*` tag, so a run without one could not publish.
- name: Resolve release tag
env:
GH_TOKEN: ${{ github.token }}
INPUT_VERSION: ${{ inputs.version }}
run: |
if [ "$GITHUB_EVENT_NAME" = "workflow_dispatch" ]; then
TAG="$INPUT_VERSION"
else
TAG="$GITHUB_REF_NAME"
fi
TAG="$GITHUB_REF_NAME"
# The suffix is restricted to characters GitHub keeps verbatim in an asset
# name; a space, for instance, is rewritten to a dot and would fail the
# asset check after a full build. Segmented like run.sh's semver check
Expand All @@ -56,16 +66,8 @@ jobs:
echo "::error::Pre-release version must look like v1.2.3-rc1, got '$TAG'."
exit 1
fi
# A release created for an existing tag is cut at that tag's commit: GitHub
# ignores target_commitish when the tag is already there. The ABIs would come
# from this branch while the release pointed somewhere else.
if [ "$GITHUB_EVENT_NAME" = "workflow_dispatch" ] \
&& gh api "repos/$GITHUB_REPOSITORY/git/ref/tags/$TAG" >/dev/null 2>&1; then
echo "::error::Tag $TAG already exists; use a different version."
exit 1
fi
echo "RELEASE_TAG=$TAG" >> "$GITHUB_ENV"
echo "Releasing $TAG from $GITHUB_REF_NAME."
echo "Releasing $TAG."

- name: Reject a pre-published release
env:
Expand Down Expand Up @@ -238,7 +240,7 @@ jobs:
### Usage

```ts
import StoreAbi from "./abis/Store.json";
import LabelStoreAbi from "./abis/LabelStore.json";
import RegistrarAbi from "./abis/DotnsRegistrar.json";
```
ENDOFBODY
Expand Down Expand Up @@ -291,10 +293,8 @@ jobs:
- name: Create draft pre-release with artifacts
uses: softprops/action-gh-release@v2
with:
# Explicit because on workflow_dispatch there is no tag to infer; the action
# creates it at this run's commit.
# The tag that started this run.
tag_name: ${{ env.RELEASE_TAG }}
target_commitish: ${{ github.sha }}
files: |
dotns-abis-*.zip
release/abis/*.json
Expand Down
77 changes: 41 additions & 36 deletions .github/workflows/publish-release.yml
Original file line number Diff line number Diff line change
@@ -1,30 +1,47 @@
name: Publish Release Package

# Creating a `v*` tag is restricted to the dotns team by the `release tags` ruleset, and the
# job below pauses on the `releases` environment for a reviewer, so a release takes two
# distinct human actions: cutting the tag, and approving the run it starts.
# Creating a `v*` tag is restricted to the dotns team by the `release tags` ruleset, the
# `guard` job refuses a tag whose commit is not on master, and the gated job pauses on the
# `releases` environment for a reviewer. A release therefore ships reviewed master code and
# takes two distinct human actions: cutting the tag, and approving the run it starts.
on:
push:
tags:
- "v[0-9]+.[0-9]+.[0-9]+"
workflow_dispatch:
inputs:
version:
description: "Version to release, e.g. v0.5.5. The tag is created from the selected branch."
required: true
type: string

permissions:
contents: write

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

jobs:
# Runs first, with no environment and read-only access: a tag that does not point at a
# commit on master stops here, before a reviewer is asked to approve and before any
# secret is read. master only takes pull requests, which need an approving review, so
# this keeps every release on reviewed code.
guard:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
persist-credentials: false

- name: Require the tagged commit to be on master
run: |
if ! git merge-base --is-ancestor "$GITHUB_SHA" origin/master; then
echo "::error::$GITHUB_REF_NAME points at $GITHUB_SHA, which is not on master. Tag a commit on master."
exit 1
fi
echo "$GITHUB_REF_NAME points at $GITHUB_SHA, which is on master."

release:
needs: guard
runs-on: ubuntu-latest
# Gated: this job reads the genesis owner key, which lives only on the `releases`
# environment, so every run waits for a reviewer's approval there.
Expand All @@ -34,33 +51,18 @@ jobs:
env:
DOTNS_TLDS: testnet paseo
steps:
# On workflow_dispatch the tag does not exist yet; the release step creates it
# from the branch this run was started on. Read through an env var rather than
# interpolating the input into the script.
# The run is started by pushing the tag, so the tag already exists and is the release's
# identity. Only the tag push triggers this workflow: the `release tags` ruleset keeps
# the workflow's token from creating a `v*` tag, so a run without one could not publish.
- name: Resolve release tag
env:
GH_TOKEN: ${{ github.token }}
INPUT_VERSION: ${{ inputs.version }}
run: |
if [ "$GITHUB_EVENT_NAME" = "workflow_dispatch" ]; then
TAG="$INPUT_VERSION"
else
TAG="$GITHUB_REF_NAME"
fi
TAG="$GITHUB_REF_NAME"
if [[ ! "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "::error::Version must look like v1.2.3, got '$TAG'."
exit 1
fi
# A release created for an existing tag is cut at that tag's commit: GitHub
# ignores target_commitish when the tag is already there. The ABIs would come
# from this branch while the release pointed somewhere else.
if [ "$GITHUB_EVENT_NAME" = "workflow_dispatch" ] \
&& gh api "repos/$GITHUB_REPOSITORY/git/ref/tags/$TAG" >/dev/null 2>&1; then
echo "::error::Tag $TAG already exists; use a different version."
exit 1
fi
echo "RELEASE_TAG=$TAG" >> "$GITHUB_ENV"
echo "Releasing $TAG from $GITHUB_REF_NAME."
echo "Releasing $TAG."

- name: Reject a pre-published release
env:
Expand Down Expand Up @@ -230,7 +232,7 @@ jobs:
### Usage

```ts
import StoreAbi from "./abis/Store.json";
import LabelStoreAbi from "./abis/LabelStore.json";
import RegistrarAbi from "./abis/DotnsRegistrar.json";
```
ENDOFBODY
Expand All @@ -251,6 +253,11 @@ jobs:
echo "|---------|---------:|"
jq -r '.networks | to_entries[] | "| \(.key) | \(.value.chainId) |"' release/deployments.json
echo ""
echo "A network can still run an earlier release until it is upgraded to this one. Its protocol"
echo "registry's \`protocolVersion()\` returns the release it runs as bare semver (\`1.0.0\`, no \`v\`),"
echo "or an empty string when the network has never declared one. \`deployments:verify --tag\`"
echo "checks the chain against a release ([Verifying a release]($GITHUB_SERVER_URL/$GITHUB_REPOSITORY/blob/$TAG/RELEASE_ARTIFACTS.md#verifying-a-release))."
echo ""
echo "- **Addresses:** [deployments.json]($ASSET_BASE/deployments.json)"
echo "- **Release contents:** [release-manifest.json]($ASSET_BASE/release-manifest.json)"
echo "- **Code identity:** [codehashes.json]($ASSET_BASE/codehashes.json) (stripped-metadata build hashes of this release's contracts; input to upgrade checks)"
Expand Down Expand Up @@ -307,10 +314,8 @@ jobs:
- name: Create draft release with artifacts
uses: softprops/action-gh-release@v2
with:
# Explicit because on workflow_dispatch there is no tag to infer; the action
# creates it at this run's commit.
# The tag that started this run.
tag_name: ${{ env.RELEASE_TAG }}
target_commitish: ${{ github.sha }}
files: |
dotns-abis-*.zip
release/abis/*.json
Expand Down
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,13 @@ Deployment notes are in [DEPLOYMENTS.md](./DEPLOYMENTS.md). Network addresses ar

A release publishes the contract ABIs and the deployed addresses as GitHub release assets, described in [RELEASE_ARTIFACTS.md](./RELEASE_ARTIFACTS.md). It does not deploy anything; deploying contracts to a network is a separate process, described in [DEPLOYMENTS.md](./DEPLOYMENTS.md).

Run **Publish Release Package** from the Actions tab, pick the branch to release from, and enter the version (`v0.5.5`). The workflow does the rest: it builds, tests, extracts the ABIs listed in [.github/abi-contracts.txt](./.github/abi-contracts.txt), generates the address and manifest files, creates the release as a draft with every asset attached, verifies the set against what the build produced, and only then publishes. Pushing a matching tag runs the same workflow, so `git tag v0.5.5 && git push origin v0.5.5` remains equivalent.
A release starts from its tag. A member of the dotns team tags the commit to release and pushes the tag: `git tag v0.5.5 <commit> && git push origin v0.5.5`. Only the team can create `v*` tags, which the `release tags` ruleset enforces, so the workflow cannot create one itself. The tagged commit must be on `master`, which only takes pull requests that need an approving review; the workflow refuses any other tag before asking for approval. The push starts **Publish Release Package**, which waits for a reviewer's approval on the `releases` environment and then does the rest: it builds, tests, extracts the ABIs listed in [.github/abi-contracts.txt](./.github/abi-contracts.txt), generates the address and manifest files, creates the release as a draft with every asset attached, verifies the set against what the build produced, and only then publishes.

Pre-releases use **Publish Beta Package** with a suffixed version, `v0.5.5-rc1`. The version is the release identity; the `version` field in `package.json` is unrelated and nothing reads it.
Pre-releases work the same way with a suffixed tag, `v0.5.5-rc1`, whose push starts **Publish Beta Package**. The tag is the release identity; the `version` field in `package.json` is unrelated and nothing reads it.

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.
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 and no empty release ships.

If a run fails partway, re-run it from the Actions tab; the draft is updated rather than duplicated. One case needs a manual step: the upload replaces an asset of the same name but never removes others, so if the contract list changed since the failed run, the draft still carries the assets it no longer expects and the verification step will keep refusing to publish. Delete the draft and re-run. If the version has already been published, use a different one, since its assets cannot be changed.
If a run fails partway, re-run it from its page in the Actions tab; the draft is updated in place. A re-run builds the same commit, because a pushed `v*` tag stays where it is: the `release tags` ruleset blocks deleting or moving one for everyone outside the dotns team, and the team does not move a pushed release tag either. So a re-run only helps when the failure is outside the code (a flaky step, an expired approval); a failure that needs a code fix needs a new version. When you abandon a version that way, delete its draft from the Releases page so it does not linger unpublished. One case needs a manual step: the upload replaces an asset of the same name but never removes others, so if the contract list changed since the failed run, the draft still carries the assets it no longer expects and the verification step will keep refusing to publish. Delete the draft and re-run. If the version has already been published, use a different one, since its assets cannot be changed.

## Economics

Expand Down
4 changes: 2 additions & 2 deletions RELEASE_ARTIFACTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,8 +129,8 @@ key that owns every contract in it. The environment holds:
pair right after the toolchain is installed and fails the run on a mismatch, so a mistyped
key dies before anything is built.
- Required reviewers: the dotns team. Every release run pauses for one approval.
- Deployment refs: `master` (for `workflow_dispatch`) and `v[0-9]*` tags. A dispatch started
from any other branch stops at the environment gate.
- Deployment refs: `v[0-9]*` tags. Both workflows run only on a tag push, so a run from any
other ref stops at the environment gate.

Creating a `v*` tag is itself restricted to the dotns team by the `release tags` ruleset, so a
release takes two distinct human actions: cutting the tag, and approving the run it starts.
Expand Down
2 changes: 2 additions & 0 deletions scripts/js/release-metadata.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -837,6 +837,8 @@ function verify(args) {
]).replace(/^"|"$/g, "");
if (declaredVersion === tag) {
console.log(` ok protocolVersion ${declaredVersion}`);
} else if (declaredVersion === "") {
problems.push(`chain declares no protocol version, expected '${tag}'`);
} else {
problems.push(`chain declares protocol version '${declaredVersion}', expected '${tag}'`);
}
Expand Down
Loading