Skip to content

Release automation and v0.2.0 - #78

Merged
samtalki merged 7 commits into
claude/powerdiff-powerio-migration-sulux7from
release/v0.2.0-automation
Sep 5, 2026
Merged

samtalki merged 7 commits into
claude/powerdiff-powerio-migration-sulux7from
release/v0.2.0-automation

Conversation

@samtalki

@samtalki samtalki commented Sep 5, 2026

Copy link
Copy Markdown
Member

Summary

Release automation brought to current Julia practice, plus the v0.2.0 version bump
and changelog. Stacked on #76 — the diff shown here is only the release work.

Registration stops mutating main

register.yml bumped the version, committed it, pushed to main, and posted the
JuliaRegistrator trigger. Reading it turned up six defects, each confirmed:

  1. It registered a commit CI had never run. A push made with GITHUB_TOKEN
    starts no workflow, so the Set version to X.Y.Z commit it created was the one
    commit on main no CI ever saw. There was no Pkg.test() anywhere in the file.
  2. It could register the wrong version and stay green. git commit -am … || echo
    swallowed any failure; git push then no-opped, git rev-parse HEAD returned the
    pre-bump tip, and the workflow registered the previous version.
  3. It wrote to main from CI, with no concurrency group.
  4. No pre-flight against General for a duplicate or out-of-order version.
  5. The awk range printed the ## X.Y.Z heading into the release-notes body.
  6. $GITHUB_OUTPUT used a fixed heredoc delimiter a changelog line could terminate.

Register.yml replaces it and writes nothing. The version bump and the changelog
section land through an ordinary reviewed pull request, so CI runs on the exact tree
that gets registered. The workflow then takes that commit's SHA and refuses unless it
is the current tip of main; validates and extracts the top changelog section;
requires the notes to mention "breaking" or "changelog", because General runs
guideline_breaking_explanation and every pre-1.0 minor bump is breaking; checks the
version against General's Versions.toml for duplicates and ordering; checks the tag
does not exist; re-runs Pkg.test() on that tree; refuses to post twice for the same
commit; and offers a dry run. The notes travel as a file path and the comment body is
built with jq -Rs, so the changelog text never passes through a shell expansion.

Automatic changelog, in two layers

  • GitHub release notes: TagBot's default template already renders the changelog
    section carried by the Registrator trigger, a diff link, and the pull requests
    merged since the last release. Only changelog_ignore is added, filtering
    housekeeping by label. Overriding the template would mean owning it forever for no
    gain.
  • CHANGELOG.md: now Keep a Changelog, rendered into the docs by Changelog.jl
    (registered, used by Documenter.jl itself), which linkifies [#123] and generates
    docs/src/changelog.md at build time. That page is generated, never tracked.

CI hardening

Explicit permissions: on every workflow — there were none outside Benchmark.yml,
so everything inherited the repository default. Concurrency groups that supersede
in-flight pull request runs but never cancel a run on main or a scheduled one.
Every third-party action pinned to a commit SHA with a # vX.Y.Z comment, including
julia-actions/*, which execute arbitrary Julia in the job; Dependabot already
updates SHA pins and rewrites the version comment. Coverage through Codecov with
fail_ci_if_error: false.

Documentation.yml gains caching — it had none, so every docs build re-downloaded
the PowerIO native artifact — and drops the Pkg.develop made redundant by
[sources]. Its tag trigger narrows to v*. actions: write is dropped; Documenter
does not need the Actions API, and julia-actions/cache skips old-cache pruning on
the default branch and tolerates a 403 elsewhere.

DeployDocsSite.yml stops splicing its PAT into a git clone URL, where the secret
landed on the command line and persisted in _website/.git/config for the rest of the
job. A second actions/checkout writes the credential as an http.extraheader and
strips it in post-job cleanup.

Benchmark.yml skips pull requests from forks. This is what put the red X on #77
through no fault of the contributor
: a fork gets a read-only GITHUB_TOKEN
regardless of the workflow's pull-requests: write, so the step that posts the
benchmark comment 403s. Not pull_request_target, which would run a fork's script
with a write-scoped token.

test-full also runs on a pull request labelled full-matrix, so a compat bump gets
matrix coverage before it lands rather than after.

v0.2.0

version = "0.2.0", and the changelog promoted to ## [0.2.0] - 2026-09-05.

Worth stating plainly: v0.1.0 was registered against PowerIO 0.6, and main is 13
commits ahead of it.
This release is not just the PowerIO 0.11 port and the
restoration example — it is everything unreleased since July, including the public
KKT layout (#72), the energized-branch angle gates (#71) and the pull request
benchmarks (#70).

Compat gains LinearAlgebra and SparseArrays, which had none. Not required today,
since RegistryCI's stdlib check is off, but the upstream comment says they intend to
turn it on. Install instructions use Pkg.add("PowerDiff"); the package has been in
General since 0.0.1.

New files

RELEASING.md (seven steps, one screen), CONTRIBUTING.md, and a short pull request
template whose one load-bearing item is the changelog checkbox, since Register.yml
now hard-fails on a missing section.

Verification

  • All six workflows parse; actionlint 1.7.7 with shellcheck is clean.
  • All ten action SHA pins resolve.
  • Register.yml's changelog step was extracted from the YAML and run against 12
    fixtures, including a hostile body containing $(touch /tmp/pwned), quotes, and a
    literal RELEASE_NOTES_EOF. No injection fired and the heading is excluded from the
    notes. It was then run against this pull request's actual CHANGELOG.md: accepted,
    heading excluded, "breaking" matched.
  • Docs build clean with the changelog page; Changelog UUID verified against General.
  • Full test suite passes.

To release v0.2.0 once this merges

RELEASING.md has it, and steps 1 and 2 are this stack:

  1. Merge Port to PowerIO 0.11 #76, then this pull request. main is then the release commit.
  2. git fetch origin && git rev-parse origin/main
  3. Actions → Register → Run workflow, paste that SHA. Tick dry_run first to see
    the trigger comment without posting it.
  4. AutoMerge merges the General pull request in about ten minutes; TagBot tags
    v0.2.0 and publishes the release within the hour.

One thing to do by hand, before the release: run TagBot once via workflow_dispatch
to backfill the missing v0.0.1 GitHub release, and create the changelog skip,
full-matrix and release labels.

🤖 Generated with Claude Code

samtalki and others added 6 commits September 5, 2026 15:19
Pin every action to a commit SHA with the tag in a trailing comment, add
`persist-credentials: false` to each checkout, and declare
`permissions: contents: read` at the top of CI.

CI gains a concurrency group that cancels only pull request runs; a run on
main or a scheduled run covers a commit or a point in time nothing else
re-covers. The `test` job now processes coverage and uploads it to Codecov
with `fail_ci_if_error: false`. `test-full` runs on a pull request carrying
the `full-matrix` label, so a `[compat]` bump can get macOS/arm64 and
multi-version coverage before it lands, and it no longer instruments
coverage, which the `test` job already does.

The benchmark job is restricted to pull requests from a branch of this
repository. A fork pull request receives a read-only GITHUB_TOKEN whatever
the `permissions` block requests, so the comparison comment gets a 403 and
the check fails for a reason unrelated to the change. Its AirspeedVelocity
pin moves to v0.6.5.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Both docs workflows now use `julia-actions/cache`, so the PowerIO native
library artifact and the PGLib_opf archive are restored instead of
re-downloaded on every build. `[sources] PowerDiff = {path = ".."}` in
docs/Project.toml already resolves PowerDiff to the checkout, so the
`Pkg.develop` call is gone from both and `Pkg.instantiate()` is the whole
install.

Documentation.yml builds through `julia-actions/julia-docdeploy` with
`install-package: false`, narrows its tag trigger to `v*` (Documenter maps
only `vX.Y.Z` to a versioned docs directory), and gains a docs concurrency
group. Its permission set drops `actions: write`: Documenter needs
`contents: write` to push gh-pages, `statuses: write` to attach the preview
link and `pull-requests: read` to resolve the preview path, and none of it
touches the Actions API.

DeployDocsSite.yml declares `permissions: contents: read` (it publishes to
another repository through a PAT), triggers on CHANGELOG.md as well, and
serializes on a concurrency group. The website repository is now obtained
with a second `actions/checkout`, which supplies WEBSITE_DEPLOY_TOKEN as an
`http.extraheader` and strips it in post-job cleanup, rather than splicing
it into a clone URL where it lands on a command line and persists in
_website/.git/config. That checkout runs after the docs build because it
cleans only its own path, while the root checkout cleans the workspace.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Register.yml takes a full 40-character `expected_sha` and a `dry_run` flag,
checks out that exact commit, and posts one JuliaRegistrator trigger comment
on it. It writes no tracked file, pushes no branch and creates no tag; the
version bump in Project.toml and the CHANGELOG promotion land through a
reviewed pull request. That ordering matters because a commit pushed by a
workflow with GITHUB_TOKEN starts no further workflow, so a bump written
from CI is the one commit on main that CI never runs against.

The gate, in order: `expected_sha` is well-formed and is the current tip of
main; Project.toml carries an X.Y.Z version; the changelog has a section for
that version, skipping an empty `## [Unreleased]` and rejecting a non-empty
one, whose body holds at least one bullet and mentions "breaking" or
"changelog" as General's `guideline_breaking_explanation` check requires;
the version is absent from General's Versions.toml and higher than every
version in it; the tag does not exist; and the full test suite passes on the
tree being registered. The release notes travel by file path through
`$GITHUB_OUTPUT` and are encoded with `jq -Rs`, so changelog text never
reaches a shell expansion or a heredoc delimiter. Posting is skipped when
the commit already carries a trigger comment, making a re-run safe.

TagBot keeps its trigger, actor gate and token/ssh fallbacks, and gains a
serializing concurrency group and a `changelog_ignore` list for
housekeeping and bot labels. Its `changelog` template stays at TagBot's
default, which already renders the heading, the diff link, the notes carried
by the Registrator trigger, and the merged pull requests and closed issues.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
docs/make.jl renders CHANGELOG.md into docs/src/changelog.md with
Changelog.jl before makedocs runs, so issue, pull request and version
references become links, and adds the page after the API reference.
CHANGELOG.md at the repository root stays the single source; the rendered
page is generated on every build and is gitignored.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
RELEASING.md gives the seven steps of a release: the release pull request
that bumps Project.toml and promotes the changelog section, the merge and
CI-green wait, reading the tip of main, dispatching Register with that SHA,
AutoMerge, TagBot, and the checks that confirm the release landed. Every
constraint it states is one the Register workflow enforces.

CONTRIBUTING.md gives the test, docs and benchmark commands, records that
the benchmark check is skipped on fork pull requests by design, and
describes the `full-matrix` and `changelog skip` labels that the pull
request template refers to.

The pull request template prompts for a description, a changelog bullet or
the `changelog skip` label, and the three checks a reviewer would otherwise
ask about.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Everything on main since v0.1.0 goes out in this release: v0.1.0 was registered
against PowerIO 0.6, so the 0.7 through 0.11 work, the public KKT layout, the
energized-branch angle gates and the pull request benchmarks have all been
unreleased until now.

The changelog moves to Keep a Changelog form, which is what Changelog.jl renders
into the documentation and what the registration workflow validates. Compat gains
the two stdlibs that had none, and the install instructions use the registered
package, which has been in General since 0.0.1.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@samtalki samtalki added full-matrix Run the full Julia and OS matrix on this pull request release Version bump and release paperwork labels Sep 5, 2026
`on: pull_request` defaults to `opened, synchronize, reopened`, so applying a
label starts no run and the matrix job the label gates stays skipped until the
next push. Listening for `labeled` is what makes the gate reachable.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

Benchmark Results (Julia v1)

Time benchmarks
main ed24e85... main / ed24e85...
ac_opf/kkt_jacobian/pglib_opf_case300_ieee.m 0.0325 ± 0.002 s 0.0327 ± 0.00067 s 0.993 ± 0.063
ac_opf/kkt_param/pglib_opf_case300_ieee.m/switching 2.63 ± 0.39 ms 2.65 ± 0.7 ms 0.991 ± 0.3
dc_opf/kkt_jacobian/pglib_opf_case300_ieee.m/cost_linear 0.962 ± 0.03 μs 0.961 ± 0.041 μs 1 ± 0.053
dc_opf/kkt_jacobian/pglib_opf_case300_ieee.m/cost_quadratic 0.401 ± 0.041 μs 0.39 ± 0.07 μs 1.03 ± 0.21
dc_opf/kkt_jacobian/pglib_opf_case300_ieee.m/demand 2.64 ± 0.21 μs 2.64 ± 0.22 μs 1 ± 0.12
dc_opf/kkt_jacobian/pglib_opf_case300_ieee.m/flowlimit 8.71 ± 4.6 μs 8.77 ± 0.87 μs 0.993 ± 0.53
dc_opf/kkt_jacobian/pglib_opf_case300_ieee.m/full 0.0972 ± 0.012 ms 0.106 ± 0.0061 ms 0.919 ± 0.13
dc_opf/kkt_jacobian/pglib_opf_case300_ieee.m/susceptance 5.41 ± 0.36 ms 6.39 ± 0.43 ms 0.846 ± 0.079
parser/pglib_opf_case300_ieee.m 0.719 ± 0.011 ms 1.56 ± 0.036 ms 0.461 ± 0.013
time_to_load 1.88 ± 0.018 s 2.01 ± 0.054 s 0.935 ± 0.027
Memory benchmarks
main ed24e85... main / ed24e85...
ac_opf/kkt_jacobian/pglib_opf_case300_ieee.m 0.373 M allocs: 12.3 MB 0.373 M allocs: 12.3 MB 1
ac_opf/kkt_param/pglib_opf_case300_ieee.m/switching 14.8 k allocs: 26.2 MB 14.8 k allocs: 26.2 MB 1
dc_opf/kkt_jacobian/pglib_opf_case300_ieee.m/cost_linear 6 allocs: 1.83 kB 6 allocs: 1.83 kB 1
dc_opf/kkt_jacobian/pglib_opf_case300_ieee.m/cost_quadratic 6 allocs: 1.83 kB 6 allocs: 1.83 kB 1
dc_opf/kkt_jacobian/pglib_opf_case300_ieee.m/demand 9 allocs: 12 kB 9 allocs: 12 kB 1
dc_opf/kkt_jacobian/pglib_opf_case300_ieee.m/flowlimit 9 allocs: 16.3 kB 9 allocs: 16.3 kB 1
dc_opf/kkt_jacobian/pglib_opf_case300_ieee.m/full 0.111 k allocs: 0.754 MB 0.111 k allocs: 0.754 MB 1
dc_opf/kkt_jacobian/pglib_opf_case300_ieee.m/susceptance 27.4 k allocs: 21.5 MB 27.4 k allocs: 21.5 MB 1
parser/pglib_opf_case300_ieee.m 0.082 k allocs: 4.7 kB 0.033 k allocs: 1.22 kB 3.86
time_to_load 0.149 k allocs: 11.1 kB 0.149 k allocs: 11.1 kB 1

@samtalki
samtalki merged commit 9f1b0e2 into main Sep 5, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

full-matrix Run the full Julia and OS matrix on this pull request release Version bump and release paperwork

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant