Skip to content

ci: run signing, Store and Pages deploys in environment-gated jobs. Principle XII. - #6571

Merged
ten9876 merged 8 commits into
mainfrom
ci/release-environments
Oct 18, 2026
Merged

ten9876 merged 8 commits into
mainfrom
ci/release-environments

Conversation

@ten9876

@ten9876 ten9876 commented Oct 18, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

The release and docs workflows now run their signing, publishing and Store steps in their own jobs, under GitHub Environments, and pass tags and inputs to scripts through env:.

  • docs.yml: the build job only builds, and npm ci runs with --ignore-scripts. A new deploy job (main push/dispatch only, environment cloudflare-pages) downloads the built site and runs the pinned wrangler-action with an exact wrangler version, with no checkout. For a v* release, release-pdf renders the manual with a read-only token. A new sign-release-pdf job (environment release, no checkout) checks that the artifact is exactly the expected PDF, then signs it with a temporary GPG keyring (passphrase on stdin) and attaches it.
  • macos-dmg.yml: build-dmg builds with a read-only token and uploads the unsigned bundle as a tar. A new sign-dmg job (environment release, v* tags and main; each leg runs even if the other leg's build failed) unpacks it into input/. It accepts only the staged directories, with no symlinks pointing outside them, then signs, builds the DAX PKG and DMG, notarizes, and publishes only the regular files it wrote to out/. It checks out nothing and builds nothing. The keychain is per-run with a random, masked password, both identities are imported non-exportable, and the keychain is deleted when the job ends. notarytool uses a keychain profile.
  • windows-installer.yml: the draft Store submission moves to its own stage-store-draft job (environment release), and the developer-flight job runs under store-flight. Both jobs remove the Store CLI's stored credential and settings at the end. Checkouts in these jobs don't persist credentials.
  • sign-release.yml: the tag is read from env: and must match a release-tag pattern. Checkout is refs/tags/<tag>, and the run stops unless the triggering build was of that tag's commit. workflow_run runs are accepted only from this repository's push/dispatch builds. The job runs under release, the workflow default token drops to contents: read, the keyring is temporary and the passphrase comes in on stdin.
  • build-sherpa-onnx-aarch64.yml, static-checks.yml: dispatch inputs and base_ref reach scripts through env:.

GitHub creates the release, cloudflare-pages and store-flight environments on first use. They need their deployment rules and secrets configured before the next tag.

Constitution principle honored

Principle XII: each job gets only the credentials it uses, and the scoping is enforced by infrastructure. Principle VII: tag and input values are validated or passed as data at the boundary.

Test plan

  • actionlint: no new findings on any workflow (one existing shellcheck note fixed)
  • Docs site builds locally with npm ci --ignore-scripts && npm run build; this PR's own Docs run builds it again and skips deploy
  • The sign-release tag check was run against release tags and malformed values
  • The macOS unpack check and output check, and the release-PDF download check, were run against well-formed and malformed inputs
  • macOS and Windows signing/Store paths run on the next release tag

Checklist

  • Commits are signed (docs/COMMIT-SIGNING.md)
  • No CHANGELOG.md edits

🤖 Generated with Claude Code

https://claude.ai/code/session_012U8wmr3AQGVrmQLp2zNBwa

ten9876 and others added 6 commits October 18, 2026 11:24
The build job now only builds: it installs with --ignore-scripts, checks out
without persisted credentials, and uploads the finished site. A new deploy
job (main push or dispatch only) downloads that artifact and runs the pinned
wrangler-action under the cloudflare-pages environment, with no checkout.
The deploy gate reads a boolean of whether the token is configured.

The release PDF job runs under the release environment, keeps the signing
key in a keyring of its own under RUNNER_TEMP that is removed at the end,
reads the passphrase from stdin, and scopes GH_TOKEN to the upload step.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012U8wmr3AQGVrmQLp2zNBwa
build-dmg builds with a read-only token and no signing material, then
hands sign-dmg a tar of the unsigned bundle, DAX driver root, symbols and
entitlements. sign-dmg runs under the release environment on v* tags and
main, checks out nothing and compiles nothing.

Its keychain lives under RUNNER_TEMP with a random, masked password and
a six-hour lock, is prepended to the search list rather than made the
default, and is deleted when the job ends. Both identities are imported
non-exportable (-x), each allowed to its one tool. The .p12 files are
removed by trap. notarytool reads a keychain profile stored in that
keychain, so later calls no longer pass the app password.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012U8wmr3AQGVrmQLp2zNBwa
…le XII.

The draft Store submission moves out of release-windows into its own
stage-store-draft job under the release environment; release-windows only
attaches assets. The developer-flight job runs under store-flight. Both
remove the Store CLI's stored credential and settings when they finish,
and the checkouts in all three jobs no longer persist credentials.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012U8wmr3AQGVrmQLp2zNBwa
The tag is read from the environment and must match a release-tag pattern
before anything uses it; the checkout targets refs/tags/<tag> and the run
stops unless the triggering build was of that tag's commit. workflow_run
runs are accepted only from this repository's push or dispatch builds.
The job runs under the release environment with contents: write, the
workflow default drops to read, the key sits in a keyring of its own that
is removed at the end, and the passphrase is read from stdin.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012U8wmr3AQGVrmQLp2zNBwa
…ple VII.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012U8wmr3AQGVrmQLp2zNBwa
wrangler-action installs the newest wrangler 4.x unless told otherwise;
both deploy steps now name 4.147.0.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012U8wmr3AQGVrmQLp2zNBwa
@ten9876
ten9876 marked this pull request as ready for review October 18, 2026 19:23
@ten9876
ten9876 requested review from a team as code owners October 18, 2026 19:23

@ten9876 ten9876 left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Reviewed revision 313b3dffa6554916f7ce87c84df32cafaac91f24.

Follow-up changes are requested. The detailed report is being supplied directly to the maintainer.

ten9876 and others added 2 commits October 18, 2026 12:39
…XII.

release-pdf renders the tag's manual with a read-only token and no
environment, for v* releases only, and uploads the PDF. A new
sign-release-pdf job, under the release environment, checks the tag,
downloads the artifact, requires it to be exactly the one expected
regular file, then signs, verifies and attaches it. It checks out and
runs nothing from the repository.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012U8wmr3AQGVrmQLp2zNBwa
… on its own. Principle XII.

sign-dmg unpacks the unsigned bundle into input/ and accepts only the
four directories build-dmg stages, with no symlink resolving outside
them. Every step runs in input/; the DMG and symbol archive are written
to out/, checked to be the expected regular files, and only out/ is
uploaded and attached. The DAX PKG version must be a version number.

sign-dmg runs unless cancelled, so a failed build leg no longer skips
the other leg's signing.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012U8wmr3AQGVrmQLp2zNBwa
@ten9876
ten9876 merged commit cedd50f into main Oct 18, 2026
12 checks passed
@ten9876
ten9876 deleted the ci/release-environments branch October 18, 2026 20:04
NF0T added a commit to NF0T/AetherSDR that referenced this pull request Oct 18, 2026
…. Principle XII.

Addresses the review on aethersdr#6558, and restores what the rebase onto aethersdr#6571
dropped from the deploy job.

- resolve-target (read-only token, no checkout) accepts only an empty
  ref, main, or a published non-pre-release v* release tag, and pins it
  to a commit SHA. Any other branch or tag is refused.
- build-site builds that SHA with a read-only token, no secrets and
  npm ci --ignore-scripts, writes the PDF redirect, checks the 25 MiB
  limit and uploads the site.
- deploy runs in the cloudflare-pages environment with no permissions
  and no checkout, and passes the token only to the pinned
  wrangler-action, with wrangler pinned to 4.147.0 again. A missing
  token or account ID fails the job instead of skipping it.
- A release deploys after sign-release-pdf, the job that now attaches
  the PDF, not after release-pdf.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.

1 participant