Skip to content

fix(ci): replace the scaffold release workflow with a working one - #79

Merged
hyperpolymath merged 2 commits into
mainfrom
fix/release-workflow
Oct 9, 2026
Merged

hyperpolymath merged 2 commits into
mainfrom
fix/release-workflow

Conversation

@hyperpolymath

@hyperpolymath hyperpolymath commented Oct 9, 2026 •

Copy link
Copy Markdown
Owner

Summary

Tag v0.1.1 (2cc3088) has no GitHub Release, because release.yml was the unfilled scaffold. Its build step only echoed "Build your artifacts here", so the SLSA job attested an empty subject list. It also downloaded git-cliff from releases/latest/download/git-cliff-x86_64-unknown-linux-gnu.tar.gz, which returns 404 today; git-cliff's asset names now carry the version. This PR replaces it with a workflow that tests the package, packages it, and publishes a Release.

This is the same release-path fix as hyperpolymath/Causals.jl#69 and hyperpolymath/SMTLib.jl#50, adapted to this repo. No issue tracks it.

Changes

  • Build job:

    • Runs on Julia 1.10.
    • Adds General and hyperpolymath/julia-professional-registry (JPR). Causals, ZeroProb, Cliodynamics and their dependency AcceleratorGate are registered in JPR, not in General.
    • Removes any committed Manifest.toml, so the package is resolved fresh from [compat], the way users install it.
    • Runs Pkg.instantiate(); Pkg.build(); Pkg.test().
    • Packages src/, Project.toml, README.adoc and LICENSE.
    • timeout-minutes: 45, matching ci.yml:30, because a cold install of this dependency closure is slow.
  • SBOM: the build job writes a CycloneDX 1.4 sbom.json. It reads the direct dependencies by name from the resolved environment, then loads JSON from a temporary environment, because JSON is not a dependency of this package.

  • Changelog: orhun/git-cliff-action v4.9.1, with git-cliff pinned to v2.14.2 instead of "latest". It uses the repo's existing cliff.toml with --latest --strip header.

  • Provenance: actions/attest-build-provenance v4.2.2 attests the actual release files (release/*, including checksums.txt). It replaces the SLSA generator, whose reusable workflow calls nested actions that cannot be pinned by SHA.

  • A manual run releases an existing tag. workflow_dispatch takes version. Both checkouts use ref: ${{ inputs.version && format('refs/tags/{0}', inputs.version) || '' }}, so the artefacts and changelog come from the tagged tree. The refs/tags/ prefix is needed because checkout tries a bare name as a branch before it tries a tag (src/ref-helper.ts:52-57 at the pinned SHA). It was added in 238d922, after CodeRabbit raised the issue on the twin PR fix(ci): make the release workflow build, attest and publish Causals.jl#69. The release job then checks the version:

    • it is passed through env:, not inlined into the script;
    • it must match ^v[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.]+)?$;
    • it must already exist as a tag (git ls-remote --exit-code).

    So a typo cannot make action-gh-release create a new tag on main. A -pre suffix marks the Release as a prerelease.

  • Least privilege. The top-level permissions are contents: read. Only the release job gets contents: write, id-token: write and attestations: write.

  • The workflow tests itself. A pull_request trigger on this file runs the build and changelog jobs on this PR. The release job is skipped on PRs (if: github.event_name != 'pull_request').

  • The unused full-CHANGELOG.md artefact is dropped, because nothing consumed it.

📌 New pins

Head SHA: 238d922e711577f97b67ee613ee91525432a4351

Each new or changed pin below was resolved from its tag to the same commit with a GraphQL ref(qualifiedName:) lookup:

Action Pin Tag Change
julia-actions/setup-julia fa02766e078afaaf09b14210362cee14137e6a32 v3.0.2 new
julia-actions/cache 9353db09a0f2bbbdec6f41791918c75d37391b36 v3.5.0 new
actions/upload-artifact 043fb46d1a93c77aae656e7c1c64a875d1fc6a0a v7.0.1 was ea165f8d… (v4.6.2)
orhun/git-cliff-action a9a95522b26fe6403f7bb24031f21fb573d0f5ff v4.9.1 new; its version: input pins git-cliff v2.14.2
actions/download-artifact 3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c v8.0.1 new (was only a commented-out v4 line)
actions/attest-build-provenance 4d101475d8b20a2381f78447822ac1eab6504dd8 v4.2.2 new

Unchanged: actions/checkout@8e8c483d… (v6.0.1) and softprops/action-gh-release@a06a81a0… (v2). Removed: slsa-framework/slsa-github-generator/…/generator_generic_slsa3.yml@f7dd8c54… (v2.1.0). This repo has no actions.lock. The workflow stays in block YAML, because this repo's gates do not yet meet the KYAML preconditions.

RSR Quality Checklist

Required

  • Tests pass (just test or equivalent). Pkg.test() passes on the v0.1.1 tree, which is this PR's parent commit; see Testing. This PR changes only release.yml.
  • Code is formatted (just fmt or equivalent). Not applicable: no Julia source changes. actionlint is the check that applies to the workflow.
  • Linter is clean (no new warnings or errors). actionlint .github/workflows/release.yml exits 0.
  • No banned language patterns (no TypeScript, no npm/bun, no Go/Python). The change is YAML with inline bash and Julia.
  • No unsafe blocks without // SAFETY: comments. Not applicable: no Rust or Zig code.
  • No banned functions (believe_me, unsafeCoerce, Obj.magic, Admitted, sorry). None introduced.
  • SPDX license headers present on all new/modified source files. release.yml keeps its MPL-2.0 header; no new files.
  • No secrets, credentials, or .env files included. The workflow reads no secrets; action-gh-release uses the job's GITHUB_TOKEN.

As Applicable

  • .machine_readable/STATE.a2ml updated. Not applicable: no project-state change. (The estate also no longer writes .a2ml files.)
  • .machine_readable/ECOSYSTEM.a2ml updated. Not applicable, for the same reason.
  • .machine_readable/META.a2ml updated. Not applicable, for the same reason.
  • Documentation updated for user-facing changes. Not applicable: no user-facing change. Each Release body is the git-cliff changelog.
  • TOPOLOGY.md updated. Not applicable.
  • CHANGELOG or release notes updated. Not ticked: release notes are generated per tag by git-cliff, and no changelog file is edited.
  • New dependencies reviewed for license compatibility. These are CI actions, not package dependencies: setup-julia, cache, upload-artifact, download-artifact and attest-build-provenance are MIT, and git-cliff-action is Apache-2.0 (GitHub licenseInfo).
  • ABI/FFI changes validated. Not applicable.

Testing

I ran the build job's steps locally, in a throwaway depot, on the v0.1.1 tree (git archive v0.1.1), with Julia 1.10.10 on Linux x64:

  • Registries: General and JPR added.
  • No manifest: the tag tracks no Manifest.toml, and rm -f Manifest.toml ran anyway.
  • Test: Pkg.instantiate(); Pkg.build(); Pkg.test() gave InvestigativeJournalism.jl 174/174, E2E Pipeline Tests 28/28 and Property-Based Tests 870/870. That is 1072 passed, 0 failed, 0 errored, rc=0.
  • SBOM: the SBOM script, taken verbatim from the workflow, gave rc=0. sbom.json parses as CycloneDX 1.4, with metadata InvestigativeJournalism 0.1.1 and 18 direct-dependency components by name (Causals, Cliodynamics, ZeroProb, DataFrames, HTTP, …).

Other checks:

  • actionlint .github/workflows/release.yml: rc=0, before and after 238d922.
  • 238d922 (qualified tag ref): I read the branch-before-tag order from src/ref-helper.ts in actions/checkout at the pinned SHA 8e8c483d. This PR's runs exercise only the empty-ref path. The manual-run path is first exercised by the post-merge gh workflow run. That run publishes a Release, so I did not trigger it from the PR branch.
  • The tag tree contains every path the workflow reads: cliff.toml, README.adoc, LICENSE, Project.toml and src/.
  • git-cliff --latest (local 2.13.1) on this tree produces the changelog, but warns that 15 commits were skipped due to parse errors. One commit message contains a literal \n\n, which shows as-is. Both are cliff.toml/commit-history issues that predate this PR, not workflow defects; see Notes.

Horizon: Linux, Julia 1.10.10 and git-cliff 2.13.1, locally. CI on this PR runs the build and changelog jobs on the hosted runner. The release job, which checks the version, attests and publishes, cannot run on a PR. It is first exercised by the manual run after merge.

Screenshots

Not applicable.

Notes for reviewers

  • Publishing v0.1.1 after merge. A manual run uses the workflow from main and checks out the tag, so the tag's old release.yml does not matter:
    gh workflow run release.yml -R hyperpolymath/InvestigativeJournalism.jl -f version=v0.1.1
  • Two dependency sources. This workflow resolves Causals/ZeroProb/Cliodynamics from JPR, as users do. ci.yml installs them from git. The two can drift; this PR does not change ci.yml.
  • cliff.toml follow-up (not fixed here): run git-cliff -vv --latest to list the 15 skipped commits, then widen commit_parsers or add a catch-all.
  • Deferred red check: governance / Validate Hypatia Baseline. Tracked in governance / Validate Hypatia Baseline is red on main: 28 findings not covered by the baseline #80.
    • It is red on main too (2cc3088, run 37864612160). There, 28 findings are not covered by .hypatia-baseline.json.
    • This PR removes the two findings in release.yml, leaving 26, and adds none (job 113764445362).
    • It is not a required check. The only required context is scan / gitleaks.
  • Nothing is set to auto-merge.

🤖 Generated with Claude Code

https://claude.ai/code/session_016XJoh8YA2sCWQmq93CF8RX

The release workflow was the unfilled template: the build step echoed
"Build your artifacts here", the SLSA provenance job attested an empty
subject list, and git-cliff was fetched from releases/latest by a URL
that no longer resolves. So tag v0.1.1 has no GitHub Release.

The build job now tests the package on Julia 1.10 against General and
the hyperpolymath registry (where Causals, ZeroProb, Cliodynamics and
AcceleratorGate are registered), resolving fresh from [compat] with any
committed manifest removed. It packages the source and a CycloneDX
SBOM. The changelog uses orhun/git-cliff-action v4.9.1 with git-cliff
pinned to v2.14.2. Provenance comes from
actions/attest-build-provenance v4.2.2 on the release files, replacing
the SLSA generator, whose nested actions cannot be pinned by SHA.

A manual run takes an existing tag and checks it out, so the artefacts
match the tagged tree. The version is passed through env, must match
vX.Y.Z or vX.Y.Z-pre, and must already exist as a tag. A pull_request
trigger on this file runs the build and changelog jobs at review; the
release job is skipped there.

The unused full-CHANGELOG.md artifact is dropped: nothing consumed it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016XJoh8YA2sCWQmq93CF8RX
@coderabbitai

coderabbitai Bot commented Oct 9, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: ASSERTIVE
  • Plan: Advanced
  • Run ID: 0770fde4-7e2d-40bd-9666-a5107c328437

📥 Commits

Reviewing files that changed from the base of the PR and between 2cc3088 and cc337d0.


📒 Files selected for processing (1)
  • .github/workflows/release.yml

Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review.


📜 Recent review details
⏰ Context from checks skipped due to timeout. (16)
  • GitHub Check: Dogfooding compliance summary
  • GitHub Check: governance / Trusted-base reduction policy
  • GitHub Check: governance / Code quality + docs
  • GitHub Check: scan / gitleaks
  • GitHub Check: governance / Check Workflow Staleness
  • GitHub Check: governance / Security policy checks
  • GitHub Check: governance / Language / package anti-pattern policy
  • GitHub Check: governance / Guix primary / Nix fallback policy
  • GitHub Check: governance / Workflow security linter
  • GitHub Check: hypatia / Hypatia Neurosymbolic Analysis
  • GitHub Check: Julia 1.11 - ubuntu-latest
  • GitHub Check: analyze (actions, none)
  • GitHub Check: Julia 1.11 - macos-latest
  • GitHub Check: Julia 1.10 - ubuntu-latest
  • GitHub Check: Build Artifacts
  • GitHub Check: semgrep-cloud-platform/scan

⚠️ CI failures not shown inline (9)

GitHub Actions: Governance / 0_governance _ Validate Hypatia Baseline.txt: fix(ci): replace the scaffold release workflow with a working one

Conclusion: failure

View job details

##[group]Run echo "Scanning repository: hyperpolymath/InvestigativeJournalism.jl (checking baseline)"
 �[36;1mecho "Scanning repository: hyperpolymath/InvestigativeJournalism.jl (checking baseline)"�[0m
 �[36;1m# Move the baseline filter OUT of the scanned tree, then delete the�[0m
 �[36;1m# standards checkout, so `hypatia scan .` only ever sees the CALLER's�[0m
 �[36;1m# own files. Without this, `.standards-checkout/` (the tooling we�[0m
 �[36;1m# checked out to get apply-baseline.sh) is itself scanned, and�[0m
 �[36;1m# standards' own files get reported as the caller's findings (a banned�[0m
 �[36;1m# `.ts`, `shell_download` bootstrap.sh scripts, etc.).�[0m
 �[36;1mcp .standards-checkout/scripts/apply-baseline.sh "$RUNNER_TEMP/apply-baseline.sh"�[0m
 �[36;1mrm -rf .standards-checkout�[0m
 �[36;1m# hypatia's `scan` exits non-zero whenever it finds anything — that is�[0m
 �[36;1m# by design, and under `bash -e` it would abort this step at this line,�[0m
 �[36;1m# before the baseline filter (the real gate) ever runs. Tolerate the�[0m
 �[36;1m# scan's own exit code…�[0m
 �[36;1mHYPATIA_FORMAT=json "$HOME/hypatia/hypatia-cli.sh" scan . > hypatia-findings.raw.json || true�[0m
 �[36;1m# …but never swallow a genuine scanner crash into a false pass: require a�[0m
 �[36;1m# valid JSON array before trusting the output as "the findings".�[0m
 �[36;1mif ! jq -e 'type == "array"' hypatia-findings.raw.json >/dev/null 2>&1; then�[0m
 �[36;1m  echo "::error::hypatia scan did not produce a valid JSON findings array (scanner error, not a baseline result)"�[0m

GitHub Actions: Governance / governance _ Validate Hypatia Baseline: fix(ci): replace the scaffold release workflow with a working one

Conclusion: failure

View job details

##[group]Run echo "Scanning repository: hyperpolymath/InvestigativeJournalism.jl (checking baseline)"
 �[36;1mecho "Scanning repository: hyperpolymath/InvestigativeJournalism.jl (checking baseline)"�[0m
 �[36;1m# Move the baseline filter OUT of the scanned tree, then delete the�[0m
 �[36;1m# standards checkout, so `hypatia scan .` only ever sees the CALLER's�[0m
 �[36;1m# own files. Without this, `.standards-checkout/` (the tooling we�[0m
 �[36;1m# checked out to get apply-baseline.sh) is itself scanned, and�[0m
 �[36;1m# standards' own files get reported as the caller's findings (a banned�[0m
 �[36;1m# `.ts`, `shell_download` bootstrap.sh scripts, etc.).�[0m
 �[36;1mcp .standards-checkout/scripts/apply-baseline.sh "$RUNNER_TEMP/apply-baseline.sh"�[0m
 �[36;1mrm -rf .standards-checkout�[0m
 �[36;1m# hypatia's `scan` exits non-zero whenever it finds anything — that is�[0m
 �[36;1m# by design, and under `bash -e` it would abort this step at this line,�[0m
 �[36;1m# before the baseline filter (the real gate) ever runs. Tolerate the�[0m
 �[36;1m# scan's own exit code…�[0m
 �[36;1mHYPATIA_FORMAT=json "$HOME/hypatia/hypatia-cli.sh" scan . > hypatia-findings.raw.json || true�[0m
 �[36;1m# …but never swallow a genuine scanner crash into a false pass: require a�[0m
 �[36;1m# valid JSON array before trusting the output as "the findings".�[0m
 �[36;1mif ! jq -e 'type == "array"' hypatia-findings.raw.json >/dev/null 2>&1; then�[0m
 �[36;1m  echo "::error::hypatia scan did not produce a valid JSON findings array (scanner error, not a baseline result)"�[0m

GitHub Actions: Governance / 1_governance _ Security policy checks.txt: fix(ci): replace the scaffold release workflow with a working one

Conclusion: failure

View job details

##[group]Run set -uo pipefail
 �[36;1mset -uo pipefail�[0m
 �[36;1mDIR=.github/canonical-references�[0m
 �[36;1mif [ ! -d "$DIR" ]; then�[0m
 �[36;1m  echo "ℹ️  [R5] no $DIR/ — skipped (repo has not opted in)"�[0m
 �[36;1m  exit 0�[0m
 �[36;1mfi�[0m
 �[36;1mif ! command -v python3 >/dev/null 2>&1; then�[0m
 �[36;1m  echo "❌ [R5] python3 missing on runner — required for YAML rule parsing"�[0m
 �[36;1m  exit 2�[0m
 �[36;1mfi�[0m
 �[36;1mpython3 - <<'PY'�[0m
 �[36;1mimport os, sys, glob, subprocess�[0m
 �[36;1mtry:�[0m
 �[36;1m    import yaml�[0m
 �[36;1mexcept ImportError:�[0m
 �[36;1m    sys.exit("❌ [R5] PyYAML not installed on runner; install python3-yaml")�[0m
 �[36;1m�[0m
 �[36;1mdir_ = ".github/canonical-references"�[0m
 �[36;1mfiles = sorted(glob.glob(f"{dir_}/*.yml") + glob.glob(f"{dir_}/*.yaml"))�[0m
 �[36;1mif not files:�[0m
 �[36;1m    print(f"ℹ️  [R5] {dir_}/ has no .yml/.yaml rules — skipped")�[0m
 �[36;1m    sys.exit(0)�[0m
 �[36;1m�[0m
 �[36;1mtotal = 0�[0m
 �[36;1mfor rf in files:�[0m
 �[36;1m    with open(rf, encoding="utf-8") as fh:�[0m
 �[36;1m        cfg = yaml.safe_load(fh)�[0m
 �[36;1m    if not isinstance(cfg, dict):�[0m
 �[36;1m        print(f"❌ [R5] {rf}: top-level must be a mapping"); total += 1; continue�[0m
 �[36;1m    rid  = cfg.get("id", os.path.basename(rf))�[0m
 �[36;1m    desc = cfg.get("description", "")�[0m
 �[36;1m    pats = cfg.get("patterns") or []�[0m
 �[36;1m    canon = cfg.get("canonical_pointer", "")�[0m
 �[36;1m    scope = (cfg.get("scope") or {})�[0m
 �[36;1m    includes = scope.get("include") or []�[0m
 �[36;1m    if not pats or not includes:�[0m
 �[36;1m        print(f"❌ [R5:{rid}] missing patterns or scope.include in {rf}")�[0m
 �[36;1m        total += 1; continue�[0m
 �[36;1m    # exclude self-references�[0m
 �[36;1m    skip = set(["CHANGELOG.md", "CHANGELOG.adoc", rf])�[0m
 �[36;1m    if canon: skip.add(canon)�[0m
 �[36;1m    rule_hits = 0�[0m
 �[36;1m    for f_ in includes:�[0m
 �[36;1m        if f_ in skip or not os...

GitHub Actions: Governance / governance _ Security policy checks: fix(ci): replace the scaffold release workflow with a working one

Conclusion: failure

View job details

##[group]Run set -uo pipefail
 �[36;1mset -uo pipefail�[0m
 �[36;1mDIR=.github/canonical-references�[0m
 �[36;1mif [ ! -d "$DIR" ]; then�[0m
 �[36;1m  echo "ℹ️  [R5] no $DIR/ — skipped (repo has not opted in)"�[0m
 �[36;1m  exit 0�[0m
 �[36;1mfi�[0m
 �[36;1mif ! command -v python3 >/dev/null 2>&1; then�[0m
 �[36;1m  echo "❌ [R5] python3 missing on runner — required for YAML rule parsing"�[0m
 �[36;1m  exit 2�[0m
 �[36;1mfi�[0m
 �[36;1mpython3 - <<'PY'�[0m
 �[36;1mimport os, sys, glob, subprocess�[0m
 �[36;1mtry:�[0m
 �[36;1m    import yaml�[0m
 �[36;1mexcept ImportError:�[0m
 �[36;1m    sys.exit("❌ [R5] PyYAML not installed on runner; install python3-yaml")�[0m
 �[36;1m�[0m
 �[36;1mdir_ = ".github/canonical-references"�[0m
 �[36;1mfiles = sorted(glob.glob(f"{dir_}/*.yml") + glob.glob(f"{dir_}/*.yaml"))�[0m
 �[36;1mif not files:�[0m
 �[36;1m    print(f"ℹ️  [R5] {dir_}/ has no .yml/.yaml rules — skipped")�[0m
 �[36;1m    sys.exit(0)�[0m
 �[36;1m�[0m
 �[36;1mtotal = 0�[0m
 �[36;1mfor rf in files:�[0m
 �[36;1m    with open(rf, encoding="utf-8") as fh:�[0m
 �[36;1m        cfg = yaml.safe_load(fh)�[0m
 �[36;1m    if not isinstance(cfg, dict):�[0m
 �[36;1m        print(f"❌ [R5] {rf}: top-level must be a mapping"); total += 1; continue�[0m
 �[36;1m    rid  = cfg.get("id", os.path.basename(rf))�[0m
 �[36;1m    desc = cfg.get("description", "")�[0m
 �[36;1m    pats = cfg.get("patterns") or []�[0m
 �[36;1m    canon = cfg.get("canonical_pointer", "")�[0m
 �[36;1m    scope = (cfg.get("scope") or {})�[0m
 �[36;1m    includes = scope.get("include") or []�[0m
 �[36;1m    if not pats or not includes:�[0m
 �[36;1m        print(f"❌ [R5:{rid}] missing patterns or scope.include in {rf}")�[0m
 �[36;1m        total += 1; continue�[0m
 �[36;1m    # exclude self-references�[0m
 �[36;1m    skip = set(["CHANGELOG.md", "CHANGELOG.adoc", rf])�[0m
 �[36;1m    if canon: skip.add(canon)�[0m
 �[36;1m    rule_hits = 0�[0m
 �[36;1m    for f_ in includes:�[0m
 �[36;1m        if f_ in skip or not os...

GitHub Actions: Governance / 4_governance _ Code quality + docs.txt: fix(ci): replace the scaffold release workflow with a working one

Conclusion: failure

View job details

##[group]Run editorconfig-checker/action-editorconfig-checker@840e866d93b8e032123c23bac69dece044d4d84c
 with:
   github-***REDACTED_SECRET_ASSIGNMENT***
   version: latest
 ##[endgroup]
 Find 'latest' release
 ##[error]Error: The binary 'ec-linux-amd64*' not found

GitHub Actions: Governance / governance _ Code quality + docs: fix(ci): replace the scaffold release workflow with a working one

Conclusion: failure

View job details

##[group]Run editorconfig-checker/action-editorconfig-checker@840e866d93b8e032123c23bac69dece044d4d84c
 with:
   github-***REDACTED_SECRET_ASSIGNMENT***
   version: latest
 ##[endgroup]
 Find 'latest' release
 ##[error]Error: The binary 'ec-linux-amd64*' not found

GitHub Actions: Governance / 8_governance _ Well-Known (RFC 9116 + RSR).txt: fix(ci): replace the scaffold release workflow with a working one

Conclusion: failure

View job details

##[group]Run SECTXT=""
 �[36;1mSECTXT=""�[0m
 �[36;1m[ -f ".well-known/security.txt" ] && SECTXT=".well-known/security.txt"�[0m
 �[36;1m[ -f "security.txt" ] && SECTXT="security.txt"�[0m
 �[36;1mif [ -z "$SECTXT" ]; then�[0m
 �[36;1m  echo "::warning::No security.txt found."�[0m
 �[36;1m  exit 0�[0m
 �[36;1mfi�[0m
 �[36;1mgrep -q "^Contact:" "$SECTXT" || { echo "::error::Missing Contact field"; exit 1; }�[0m

GitHub Actions: Governance / governance _ Well-Known (RFC 9116 + RSR): fix(ci): replace the scaffold release workflow with a working one

Conclusion: failure

View job details

##[group]Run SECTXT=""
 �[36;1mSECTXT=""�[0m
 �[36;1m[ -f ".well-known/security.txt" ] && SECTXT=".well-known/security.txt"�[0m
 �[36;1m[ -f "security.txt" ] && SECTXT="security.txt"�[0m
 �[36;1mif [ -z "$SECTXT" ]; then�[0m
 �[36;1m  echo "::warning::No security.txt found."�[0m
 �[36;1m  exit 0�[0m
 �[36;1mfi�[0m
 �[36;1mgrep -q "^Contact:" "$SECTXT" || { echo "::error::Missing Contact field"; exit 1; }�[0m

GitHub Actions: Governance / governance _ Well-Known (RFC 9116 + RSR): fix(ci): replace the scaffold release workflow with a working one

Conclusion: failure

View job details

##[group]Run MIXED=$(grep -rE 'src="http://|href="http://' --include="*.html" --include="*.htm" . 2>/dev/null | grep -vE 'localhost|127\.0\.0\.1|example\.com|lol/|node_modules/|third-party/|vendor/' | head -5 || true)
 �[36;1mMIXED=$(grep -rE 'src="http://|href="http://' --include="*.html" --include="*.htm" . 2>/dev/null | grep -vE 'localhost|127\.0\.0\.1|example\.com|lol/|node_modules/|third-party/|vendor/' | head -5 || true)�[0m
 �[36;1mif [ -n "$MIXED" ]; then�[0m
 �[36;1m  echo "::error::Mixed content (HTTP in HTML)"�[0m

🔇 Additional comments (1)
.github/workflows/release.yml (1)

4-24: LGTM!

Also applies to: 33-61, 63-109, 120-129, 133-168, 170-184, 189-195





📝 Summary

Summary by CodeRabbit

  • New Features
    • Releases are now generated automatically when a version tag is pushed, or can be triggered manually for an existing tag.
    • Release packages include the source code, a software bill of materials, and SHA-256 checksums. Releases also include build provenance and the latest changelog.
    • Pre-release status is identified automatically from the version tag.
  • Bug Fixes
    • Release creation now validates the version format and checks that the corresponding tag exists.

Walkthrough

The release workflow now supports tag pushes, pull requests that change the workflow, and manual runs with a required version. It builds and tests the Julia package, packages a source archive and SBOM, generates a changelog, then validates and publishes the release with checksums and provenance.

Changes

Release workflow

Layer / File(s) Summary
Build and package release artefacts
.github/workflows/release.yml
The workflow adds pull-request and manual triggers. It checks out the selected ref, builds and tests the Julia package, then uploads a source archive with a CycloneDX SBOM.
Generate the changelog
.github/workflows/release.yml
The changelog job checks out the selected version with full history and uses git-cliff to generate the changelog.
Validate and publish the release
.github/workflows/release.yml
The release job validates the tag, downloads the build artefacts, creates SHA-256 checksums and provenance attestations, then publishes the release files and changelog.

Priority: ⬇️ Low

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Bug fix

Sequence Diagram(s)

sequenceDiagram
  participant GitHubActions
  participant BuildJob
  participant ChangelogJob
  participant ReleaseJob
  participant GitHubRelease
  GitHubActions->>BuildJob: Start package build
  GitHubActions->>ChangelogJob: Generate changelog for selected ref
  BuildJob->>ReleaseJob: Upload source archive and SBOM
  ChangelogJob->>ReleaseJob: Provide generated changelog
  ReleaseJob->>GitHubRelease: Publish validated version with release files
Loading

Merge Risk

Merge Risk: ⚪ Minimal · up to cc337

The release workflow now builds, tests, packages, attests and publishes releases with stricter tag validation. No concrete merge-blocking issue was identified; the release job will first run after merge, so check the first manual release.

Security Architecture Review

Security architecture risk: 🔵 Low · up to cc337

The workflow substantially improves release validation and separates package execution from publishing credentials. A remaining integrity concern is that manual releases select a mutable tag without binding all stages to one commit. Its exposure depends on who can change release tags. Repository protection settings and interrupted-publication behavior were not available for verification.

Retained concerns

  • Medium · security · inferred: Manual release stages are not bound to one immutable source commit. Build and changelog independently checkout inputs.version, while publication validates only that the named tag exists. If an authorized actor moves that tag between stages, the workflow can publish artifacts and release notes from different revisions, or attach artifacts to a tag that now identifies another revision. Checksums and artifact attestation do not provide an explicit workflow-level comparison with the tag's commit. The new manual path and publication of actual package files introduce this integrity exposure beyond the base scaffold; external tag protections could prevent the triggering condition.

Security review details

Security Blast Radius

  • inferred — The visible sensitive outcomes are this repository's release assets and attestations, with downstream exposure to consumers trusting those package files. Package and dependency code executes in the read-only build job but can influence the workspace subsequently packaged. No cloud role assumption or application data-store access is configured in the inspected workflow; external OIDC trust policies were not available.

Security Findings and Attack Paths

  • inferred — The conditional integrity path requires authority to move the selected release tag: a manual build can checkout revision A, the tag can move to revision B before another stage, and the existence check can still pass. Publication can then associate artifacts from A with the tag now naming B. This is not a demonstrated unprivileged exploit, and effective tag-protection policy could eliminate the condition.

Trust Boundaries and Controls

  • observed — Job separation keeps package build/test execution outside the job holding publishing credentials. Pull-request publication is excluded, manual input is passed through an environment variable and shell-quoted, version syntax and remote existence are checked, and actions are SHA-pinned. These controls constrain credential exposure and malformed inputs, but do not explicitly bind a selected tag to a recorded commit throughout the release.

Resilience and Maintainability Implications

  • inferred — The same-run artifact handoff preserves build-to-publication ordering, but fresh dependency resolution means repeated builds of one tag can produce different SBOM contents. Without configured same-tag serialization, concurrent runs can independently reach publication. Whether they replace assets, leave a partial release, or recover idempotently remains an external-action coverage gap rather than an established failure.

Hardening Proposals

  • proposed — Resolve the fully qualified release tag once to an immutable commit, use that commit for both checkouts, and verify its relationship to the publication tag before publishing. For recovery, consider serializing runs by release tag and defining whether retries reuse an existing artifact set or intentionally replace it.



🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff. Analyzed 0 functions across 0…
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Title check Passed The title clearly and concisely describes the main change: replacing the scaffold release workflow with a working CI release workflow.
Description check Passed The description follows the required template and provides a detailed summary, change list, checklist status, testing evidence, and notes. It clearly explains the workflow changes and their validation…


✨ Finishing Touches
🛠️ Fix failing CI checks
  • Commit to this branch
  • Create a new PR


  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

A rabbit checks the tag at dawn,
Then watches Julia build along.
A source bundle and SBOM hop,
While changelog lines fall into place.
Checksums join the release parade,
And provenance trails the files.

Comment @coderabbitai help to get the list of available commands.

actions/checkout resolves an unqualified ref to a branch of that name
before a tag (src/ref-helper.ts:52-57 at the pinned SHA). If a branch
named like the tag existed, a manual run would package that branch,
while the release job only confirms that the tag exists. Both checkouts
now pass refs/tags/<version> on a manual run, and an empty ref (the
triggering ref) on push and pull_request runs, as before.

Raised by CodeRabbit on hyperpolymath/Causals.jl#69.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016XJoh8YA2sCWQmq93CF8RX
@coderabbitai

coderabbitai Bot commented Oct 9, 2026 •

Copy link
Copy Markdown

⚠️ Agent ran but produced no code changes.

The CI failures may require manual intervention.

View agent analysis
I'm making no changes. All three failures come from the shared governance workflow, not from this PR's code, and fixing them would mean editing CI infrastructure, which is out of scope here.

`.github/workflows/governance.yml` doesn't define these checks itself. It calls `hyperpolymath/standards/.github/workflows/governance-reusable.yml@81dbf2dd…`, so every step in those logs lives in that external repo.

1. **Code quality + docs.** The `editorconfig-checker/action-editorconfig-checker` step (with `version: latest`) fails with `The binary 'ec-linux-amd64*' not found`. The action can't find a downloadable file matching that name in editorconfig-checker's latest release. Nothing in this repo affects that. The fix is to pin a `version:` whose assets still match, or update the action, in the shared reusable workflow in `hyperpolymath/standards`.

2. **Well-Known (RFC 9116 + RSR)** and 3. **Security policy checks.** The excerpts only show the start of each job's scripts, not the line that failed. The two scripts shown can't be the cause:
   - This repo has no `security.txt` in either location, so the security.txt step only warns and exits 0.
   - It has no `.github/canonical-references/` directory, so the R5 step skips and exits 0.

   The failing step must come later in those shared jobs, and these logs don't show it. Finding it means reading the full job logs, and the fix would again be in the shared workflow.

The PR itself only changed `release.yml`, and it's already merged: `origin/main` is at `37e120b` (#79), and the checkout shows no diff against it. The PR description also says the governance checks were already red on `main` and aren't required checks.

To get these green, change the reusable workflow in `hyperpolymath/standards` (first, the editorconfig-checker version), then bump the pinned SHA in `governance.yml` here.

@hyperpolymath
hyperpolymath deleted the fix/release-workflow branch October 9, 2026 11:08
hyperpolymath added a commit to hyperpolymath/Causals.jl that referenced this pull request Oct 9, 2026
<!--
SPDX-License-Identifier: CC-BY-SA-4.0
Copyright (c) Jonathan D.A. Jewell <j.d.a.jewell@open.ac.uk>
-->
## Summary

The release workflow has failed on every tag. Run 37866715740 (tag
`v0.2.1`) failed, so Causals.jl has no GitHub Release. This PR fixes the
four defects behind that and hardens the workflow. A second, separate
commit stops tracking `Manifest.toml`; drop it on review if you prefer
to keep the file tracked.

**Commit 1: `fix(ci): make the release workflow build, attest and
publish`**

| Defect | Fix |
|---|---|
| Both upload steps pinned
`actions/upload-artifact@ea165f8d65b6db9a8b71b5c2d1a090c0daf9c8bb`, a
commit that does not exist. It is a corrupted copy of v4.6.2's
`ea165f8d65b6e75b540449e92b4886f43607fa02`. | Pinned to v7.0.1, which
SMTLib.jl already uses. |
| "Package artifacts" had no `shell:`, so `windows-latest` ran `mkdir
-p` and `tar` in pwsh. | The build job defaults to `bash`. |
| The SBOM step ran `using Pkg, JSON`. JSON is not a dependency and the
project was never instantiated. | Instantiate, read the direct
dependencies, then load JSON from a temporary environment. |
| The tracked `Manifest.toml` was written by Julia 1.13.0-alpha2
(manifest_format 2.1, with Causals listed as a path dependency of
itself). On 1.10, `Pkg.build()` fails with "Could not locate the source
code for the StyledStrings package". | Both jobs delete any committed
manifest and resolve from `[compat]`, which is how users install the
package. |

Hardening in the same commit:
- **A manual run builds the tag, not `main`.** In the build and SBOM
jobs, `actions/checkout` takes `ref: ${{ inputs.version &&
format('refs/tags/{0}', inputs.version) || '' }}`, so the tarballs match
the tagged tree that JPR registered.
- The ref is qualified as `refs/tags/` because checkout tries a bare
name as a branch before it tries a tag (`src/ref-helper.ts:52-57` at the
pinned SHA). Without the prefix, a branch with the same name as the tag
would be packaged instead.
  - CodeRabbit raised this; it is fixed in commit 3 (`e536fcf`).
- On tag pushes and PRs, `inputs.version` is empty, so the ref is empty
and checkout uses the triggering ref.
- **The version is validated.** It is passed through `env:` rather than
inline `${{ }}`, must match `^v[0-9]+\.[0-9]+\.[0-9]+$`, and must
already exist as a tag (`git ls-remote --exit-code`). A typo cannot make
`action-gh-release` mint a new tag on `main`.
- **Least-privilege permissions.** `contents: read` at the top level.
The release job alone gets `contents: write`, `id-token: write` and
`attestations: write`.
- **Build provenance.** `actions/attest-build-provenance` v4.2.2 attests
`release/*`, matching SMTLib.jl's workflow.
- **The workflow tests itself at review.** A `pull_request` trigger on
this file runs the build and SBOM jobs. The release job is skipped on
PRs.
- `sha256sum -- *` (shellcheck SC2035, reported by actionlint).

**Commit 2: `chore: stop tracking Manifest.toml`**

`.gitignore:40` already lists `/Manifest.toml`, but the file has been
tracked since the first commit (`7fef2be`). CI on `main` is green only
because its `Pkg.add(url=…AcceleratorGate…)` step re-resolves the
manifest. I measured this: after that step `julia_version` reads 1.10.10
and the self-entry is gone. `STATE.a2ml` and `Dustfile.a2ml` still
describe the manifest drift. I left them as they are, because A2ML is
retired and is not edited.

No issue was open for this. I found it while checking why `v0.2.1` has
no Release.

**Commit 3: `fix(ci): check out refs/tags/<version> on a manual release
run`**

This commit qualifies the checkout ref in the build and SBOM jobs; the
first bullet under "Hardening" above explains why. CodeRabbit raised it
in review. The same fix is in hyperpolymath/SMTLib.jl#50 and
hyperpolymath/InvestigativeJournalism.jl#79.

## Type of change

- [x] 🐛 Bug fix (non-breaking change that fixes an issue)
- [ ] ✨ New feature: none. The `pull_request` trigger and attestation
serve the release path; they add no package functionality.
- [ ] 💥 Breaking change: the package source and `Project.toml` are
untouched. Commit 2 removes `Manifest.toml` from the index only.
- [ ] 🕳️ Soundness fix: not applicable, no checker or proof is involved.
- [ ] 📖 Documentation: no docs changed.
- [ ] 🧹 Refactor / tech debt: the manifest untrack is the nearest fit,
but it changes what Pkg resolves for a fresh clone, so it is not
behaviour-preserving.
- [x] 🔧 Build / CI / tooling

## 📌 New pins

**Head SHA: `e536fcf82a1709c45178547b45d874b2dbd8ed47`**

- **`actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a`**
(v7.0.1), used twice. It replaces the missing
`ea165f8d65b6db9a8b71b5c2d1a090c0daf9c8bb`.
-
**`actions/attest-build-provenance@4d101475d8b20a2381f78447822ac1eab6504dd8`**
(v4.2.2), new.
- Unchanged: checkout `3d3c42e5…`, setup-julia `fa02766e…` (v3.0.2),
julia-actions/cache `9353db09…` (v3.5.0), download-artifact `3e5f45b2…`
(v8.0.1), action-gh-release `efb35369…`.

I resolved all 14 `uses:` SHAs across this workflow and SMTLib.jl's and
InvestigativeJournalism.jl's release workflows through the GraphQL
`object(oid:)` lookup. 13 resolved to Commit objects. The 14th,
`ea165f8d65b6db9a…`, returned null, which is the positive control that
the lookup reports a missing SHA. This repo has no `actions.lock`, and
the workflow stays in block YAML: the KYAML preconditions in the
estate's AGENTS.md §2a are not met.

## How has this been verified?

- **`actionlint .github/workflows/release.yml`** passes (rc=0) after the
SC2035 fix, and again after commit 3.
- **Commit 3 (`e536fcf`, qualified tag ref).** I read the
branch-before-tag order from `src/ref-helper.ts` in `actions/checkout`
at the pinned SHA `3d3c42e5`. This PR's runs exercise only the empty-ref
path. The manual-run path is first exercised by the post-merge `gh
workflow run`, which publishes a Release, so I did not trigger it from
the PR branch.
- **Build and test on Julia 1.10.10, Linux x64**, from a fresh depot
with `Manifest.toml` removed. `Pkg.build(); Pkg.test()` passes,
including the 674 property tests. With the committed manifest, the same
command fails at `Pkg.build()` with the StyledStrings error above.
- **The SBOM script** run locally on 1.10.10 against the same tree
writes a CycloneDX 1.4 `sbom.json` with metadata Causals 0.2.1 and
components Graphs 1.15.0, Statistics 1.10.0, LinearAlgebra "stdlib",
Distributions 0.25.131 and StatsBase 0.34.13. The old `using Pkg, JSON`
form fails on an uninstantiated project.
- **This PR's own `Release` run** is the check for what I could not run
locally: the bash shell on `windows-latest`, macOS, and the pinned
actions resolving. The release job is skipped on it by design.

**Horizon:** I ran the Julia steps on Linux only, on 1.10.10. The
Windows and macOS legs, attestation, and Release creation are verified
only by CI, and Release creation only once a tag push or manual run
happens after merge.

## Checklist

- [x] My commits are **signed** (`git commit -S`). Both show `G`
locally.
- [x] I ran the project's own checks/tests locally and they pass
(1.10.10, Linux, manifest removed; see above).
- [x] New files carry the correct `SPDX-License-Identifier`: there are
no new files. `release.yml` keeps its `MPL-2.0` header.
- [x] Docs are updated, and no public claim now overstates what the code
does. The Release body now gives `gh attestation verify`, which this
workflow supports from this PR on. The stale A2ML notes are described
above.
- [x] I have not introduced a soundness hole.

## Notes for reviewers

- **After merge, publishing v0.2.1** is a manual run: `gh workflow run
release.yml -R hyperpolymath/Causals.jl -f version=v0.2.1`. It builds
the `v0.2.1` tag tree (`3bd2bac`), the same tree JPR registered.
- **Commit 2 is separable.** Without it, the workflow still works,
because it deletes the manifest before resolving. Local work on
1.10/1.11 keeps failing until the file is untracked.
- **Same broken template elsewhere.** The `using Pkg, JSON` SBOM step
appears in six other local repos (SMTLib, PolyglotFormalisms,
KnotTheory, HackenbushGames, Exnovation, BowtieRisk). SMTLib.jl gets its
own PR; the rest are recorded in `dev-notes/inbox/findings.md` for the
owner to triage, not filed as issues.
- Nothing is set to auto-merge.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_016XJoh8YA2sCWQmq93CF8RX

---------

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