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
9 changes: 9 additions & 0 deletions .github/actions/release/generate-changelog/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,15 @@ runs:
echo "::notice::Using existing CHANGELOG section for ${tag}"
body="${existing}"
else
# Refuse rather than bury a hand-curated section under an auto-generated one-liner and
# commit that back. The tag-resolves gate spares a tagless shallow checkout a false alarm.
if git rev-parse -q --verify "refs/tags/${tag}" >/dev/null 2>&1; then
newest="$(grep -m1 -oE '^## v?[0-9]+\.[0-9]+\.[0-9]+([-+][0-9A-Za-z.-]+)*' CHANGELOG.md 2>/dev/null | sed -E 's/^## v?//')"
if [ -n "${newest}" ] && [ "${newest}" != "${tag}" ] && ! git rev-parse -q --verify "refs/tags/${newest}" >/dev/null 2>&1; then
echo "::error::CHANGELOG.md's newest section is '## v${newest}', but you are releasing '${tag}' and '${newest}' was never tagged. A hand-curated section's version was likely not bumped to the release tag. Rename it to '## v${tag}' (or delete it to auto-generate from commits), then re-tag."
exit 1
fi
fi
echo "::notice::Auto-generating CHANGELOG section for ${tag}"
prev_tag="$(git describe --tags --abbrev=0 HEAD^ 2>/dev/null || true)"
commits_range="${prev_tag:+${prev_tag}..}HEAD"
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/security-gate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ permissions:
contents: read

jobs:
supply-chain:
scan-supply-chain:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
Expand All @@ -35,7 +35,7 @@ jobs:
- if: ${{ steps.detect.outputs.ecosystem == 'other' }}
uses: coroboros/ci/.github/actions/security/osv-scanner@v0

secret-scan:
scan-secrets:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/security.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ permissions:
contents: read

jobs:
dependency-review:
review-dependencies:
if: ${{ github.event_name == 'pull_request' }}
runs-on: ubuntu-latest
steps:
Expand All @@ -18,7 +18,7 @@ jobs:
fail-on-severity: high
comment-summary-in-pr: never

licenses:
check-licenses:
runs-on: ubuntu-latest
# Advisory — reports a non-allowed license, never blocks the release.
continue-on-error: true
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/self-lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ env:
YAMLLINT_VERSION: "1.38.0"

jobs:
actionlint:
check-actions:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
Expand All @@ -38,7 +38,7 @@ jobs:
shell: bash
run: actionlint -color

yamllint:
check-yaml:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
Expand All @@ -51,7 +51,7 @@ jobs:
shell: bash
run: yamllint -f colored .

shellcheck:
check-shell:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/self-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ permissions:
contents: read

jobs:
rolling-tag:
move-rolling-tag:
runs-on: ubuntu-latest
permissions:
contents: write # force-push the rolling major tag
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/self-security.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,15 @@ permissions:
contents: read

jobs:
gitleaks:
scan-secrets:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
fetch-depth: 0
- uses: ./.github/actions/security/gitleaks

osv-scanner:
scan-deps:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
Expand Down
18 changes: 9 additions & 9 deletions .github/workflows/self-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ permissions:
contents: read

jobs:
verify-tag:
test-verify-tag:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
Expand All @@ -39,7 +39,7 @@ jobs:
[ "${{ steps.moved.outcome }}" = "failure" ] || { echo "::error::verify-tag must fail when HEAD != GITHUB_SHA"; exit 1; }
echo "::notice::verify-tag passes on match, fails on divergence"

generate-changelog:
test-generate-changelog:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
Expand All @@ -54,7 +54,7 @@ jobs:
[ "${{ steps.gate.outcome }}" = "failure" ] || { echo "::error::SemVer gate must reject the non-SemVer ref '${GITHUB_REF_NAME}'"; exit 1; }
echo "::notice::generate-changelog SemVer gate rejects non-tag refs"

commit-artifacts:
test-commit-artifacts:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
Expand Down Expand Up @@ -102,7 +102,7 @@ jobs:
|| { echo "::error::no-op branch pushed unexpectedly"; exit 1; }
echo "::notice::commit-artifacts no-op left main untouched"

cargo-deny-guard:
test-cargo-deny:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
Expand All @@ -122,7 +122,7 @@ jobs:
[ "${{ steps.reject.outcome }}" = "failure" ] || { echo "::error::cargo-deny must reject a consumer deny.exceptions.toml"; exit 1; }
echo "::notice::cargo-deny rejects consumer deny.exceptions.toml"

install-dist:
test-install-dist:
# cargo-dist packages the Windows zip flat (dist.exe at root) but the Linux/macOS
# tarballs nested — extraction differs per OS, so the smoke covers all three.
strategy:
Expand All @@ -140,7 +140,7 @@ jobs:
dist --version || { echo "::error::dist not on PATH after install-dist"; exit 1; }
echo "::notice::install-dist OK — $(dist --version)"

native-deps-target:
test-native-deps:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
Expand Down Expand Up @@ -174,7 +174,7 @@ jobs:
[ "${got}" = "aarch64-unknown-linux-gnu" ] || { echo "::error::ci/setup.sh saw '${got}', expected the exported target"; exit 1; }
echo "::notice::native-deps passes CARGO_DIST_TARGET through to ci/setup.sh"

test-deps:
test-test-deps:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
Expand All @@ -200,7 +200,7 @@ jobs:
[ "${FOO:-}" = "1" ] || { echo "::error::ci/test.env did not propagate FOO to the job env"; exit 1; }
echo "::notice::test-deps runs test-setup.sh and propagates test.env"

javascript-base:
test-javascript-base:
runs-on: ubuntu-latest
env:
NPM_CONFIG_FILE: "registry=https://registry.npmjs.org/"
Expand All @@ -221,7 +221,7 @@ jobs:
done
echo "::notice::javascript/base ran install (sfw + frozen lockfile), lint, build, test on the fixture"

rust-base:
test-rust-base:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
Expand Down
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# Changelog

## v0.2.6 - 09/06/2026

### Fixes
- `generate-changelog` — fail loud when the newest `CHANGELOG.md` section's version was never tagged and ≠ the release tag, instead of auto-generating a degraded one-liner over a mis-bumped hand-curated section and committing it back. Gated on the release tag resolving locally, so a shallow checkout can't false-positive.

### Refactor
- All workflows — align every remaining job id to the `verb-noun` convention: `self-lint` → `check-actions`/`check-yaml`/`check-shell`; `self-security` → `scan-secrets`/`scan-deps`; `self-release` → `move-rolling-tag`; `self-test` → `test-<composite>`; `security-gate` → `scan-supply-chain`/`scan-secrets`; `security` → `review-dependencies`/`check-licenses`. Job ids only — consumers `uses:` the workflows, so `@v0` references are unaffected.

## v0.2.5 - 09/06/2026

### Refactor
Expand Down
9 changes: 5 additions & 4 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ Reusable GitHub Actions workflows + composite actions for the Coroboros stack.

## Important files

- `.github/workflows/javascript-npm-packages.yml` — bundled NPM pipeline (`preflight` / `security-gate` / `publish` / `security`).
- `.github/workflows/rust-packages.yml` — bundled Cargo pipeline (`preflight` matrix / `security-gate` / `package` / `publish` / `security`) + opt-in cargo-dist binary layer (`dist-plan` / `dist-build` / `dist-host` / `dist-publish`, gated on `[package.metadata.dist]` or `[workspace.metadata.dist]`).
- `.github/workflows/security-gate.yml` — blocking gate `publish` `needs:`. `supply-chain` (auto-routed: `Cargo.toml` → `security/rust/cargo-deny` advisories+bans+sources, else `security/osv-scanner`) + `secret-scan` (gitleaks). A separate reusable workflow so the caller's `publish` can `needs:` the whole gate as one job, running each scan once. Imposed via the package workflows, importable standalone by a non-package repo.
- `.github/workflows/security.yml` — advisory layer, never blocks: `dependency-review` (PR-only) + `licenses` (Rust, `continue-on-error`, `security/rust/cargo-deny` `checks: licenses`). License/quality policy lives here, off the gate.
- `.github/workflows/javascript-npm-packages.yml` — bundled NPM pipeline (`preflight` / `security-gate` / `publish-package` / `security`).
- `.github/workflows/rust-packages.yml` — bundled Cargo pipeline (`preflight` matrix / `security-gate` / `verify-package` / `publish-package` / `security`) + opt-in cargo-dist binary layer (`dist-plan` / `dist-build` / `dist-host` / `dist-publish`, gated on `[package.metadata.dist]` or `[workspace.metadata.dist]`).
- `.github/workflows/security-gate.yml` — blocking gate `publish-package` `needs:`. `scan-supply-chain` (auto-routed: `Cargo.toml` → `security/rust/cargo-deny` advisories+bans+sources, else `security/osv-scanner`) + `scan-secrets` (gitleaks). A separate reusable workflow so the caller's `publish` can `needs:` the whole gate as one job, running each scan once. Imposed via the package workflows, importable standalone by a non-package repo.
- `.github/workflows/security.yml` — advisory layer, never blocks: `review-dependencies` (PR-only) + `check-licenses` (Rust, `continue-on-error`, `security/rust/cargo-deny` `checks: licenses`). License/quality policy lives here, off the gate.
- `.github/workflows/{self-lint,self-test,self-security,self-release}.yml` — self-CI: lint, the security composites + `security-gate`/`security` workflows via local `./`, the `v0` rolling-tag move, and `self-test` smoke-testing every composite (plus `javascript/base`/`rust/base` on `test/fixtures/`) every PR. Workflow self-tests resolve their `@v0` composites against the released `v0`, so a brand-new composite is testable only once a release moves `v0` onto it.
- `.github/actions/{check-docs,javascript/base,rust/{base,native-deps,test-deps,install-dist,pin-version},security/{gitleaks,osv-scanner,rust/cargo-deny},release/{verify-tag,generate-changelog,github-release,commit-artifacts}}/action.yml` — composites.
- `.github/dependabot.yml` — auto-PRs for pinned action SHAs. `renovate.json` + `.github/workflows/renovate.yml` — self-hosted Renovate (needs the `RENOVATE_TOKEN` PAT secret, scope `repo` + `workflow`) auto-bumps the version-pinned tooling; `.github/renovate/sync-tool-sha.sh` re-syncs each paired tarball SHA-256 in the same PR.
Expand All @@ -32,6 +32,7 @@ Reusable GitHub Actions workflows + composite actions for the Coroboros stack.
- Env values quoted: `KEY: "value"`.
- GH workflow log commands: `::error::`, `::warning::`, `::notice::`. No ANSI codes.
- Declare env keys only where consumed.
- Job ids: `verb-noun`, kebab-case (imperative verb + object), mirroring the GitLab CI pipelines — `verify-package`, `publish-package`, `generate-changelog`, `commit-artifacts`, `verify-tag`. Phase call-jobs that `uses:` another workflow may stay single-word (`preflight`, `security-gate`, `security`); the cargo-dist `dist-plan`/`dist-build`/`dist-host`/`dist-publish` jobs mirror its subcommands. Reusable-workflow job ids are consumer-visible — rename deliberately.
- **Action and workflow files = implementation only.** Rationale lives in `CLAUDE.md` or `CHANGELOG.md`.

## Adding a workflow or composite
Expand Down
22 changes: 11 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -223,17 +223,17 @@ Calls the advisory [`security.yml`](#securityyml). Reports, never blocks.

The blocking gate, split from the advisory layer so it can be owned as a black box. Two parallel jobs, both fail the release through the caller's `needs:` graph — a dev can't bypass them:

- **`supply-chain`** — auto-routed by ecosystem: a `Cargo.toml` repo runs [`security/rust/cargo-deny`](#composable-actions) (advisories + bans + sources); any other runs [`security/osv-scanner`](#composable-actions). One tool per repo, never both, so a crate isn't vuln-scanned twice. A repo with no supported manifest skips (osv's no-manifest path).
- **`secret-scan`** — [`security/gitleaks`](#composable-actions), full git history, canonical ruleset.
- **`scan-supply-chain`** — auto-routed by ecosystem: a `Cargo.toml` repo runs [`security/rust/cargo-deny`](#composable-actions) (advisories + bans + sources); any other runs [`security/osv-scanner`](#composable-actions). One tool per repo, never both, so a crate isn't vuln-scanned twice. A repo with no supported manifest skips (osv's no-manifest path).
- **`scan-secrets`** — [`security/gitleaks`](#composable-actions), full git history, canonical ruleset.

Imposed on every package pipeline (a `security-gate` job `needs:`-ed by `publish-package`) and importable directly by a non-package repo. Holds only what *blocks*: a compromised dependency or a leaked secret. License and quality policy live in `security.yml`.

### `security.yml`

The advisory layer — reports, never blocks (parity with GitLab's `allow_failure: true`):

- **`dependency-review`** — PR-only; needs repo's **Dependency graph** enabled. Fails on high-severity CVE introduced by the dep diff. Uses `actions/dependency-review-action@v4`.
- **`licenses`** — Rust-only (`continue-on-error`): [`security/rust/cargo-deny`](#composable-actions) `checks: licenses` against the canonical allow-list. A non-allowed license is surfaced, never blocks the release. Skips a repo with no `Cargo.toml`.
- **`review-dependencies`** — PR-only; needs repo's **Dependency graph** enabled. Fails on high-severity CVE introduced by the dep diff. Uses `actions/dependency-review-action@v4`.
- **`check-licenses`** — Rust-only (`continue-on-error`): [`security/rust/cargo-deny`](#composable-actions) `checks: licenses` against the canonical allow-list. A non-allowed license is surfaced, never blocks the release. Skips a repo with no `Cargo.toml`.

---

Expand All @@ -248,11 +248,11 @@ The advisory layer — reports, never blocks (parity with GitLab's `allow_failur
| `rust/test-deps` | Rust | Loads the optional `ci/test.env` into the job env and runs the optional `ci/test-setup.sh` fixture hook before `cargo test`. Used by `rust/base`. No-op when absent. |
| `rust/install-dist` | Rust | Installs cargo-dist's `dist` binary, prebuilt and SHA-256 verified (Linux/macOS/Windows). Shared by the `dist-plan`, `dist-build`, `dist-host` jobs. |
| `rust/pin-version` | Rust | Installs version-pinned `cargo-set-version` (cargo-edit) and stamps `Cargo.toml` to the release tag. Shared by `publish-package` and the `dist-*` jobs. |
| `security/gitleaks` | transverse | Installs gitleaks (SHA-256 verified), scans with the canonical ruleset, emits SARIF. Behind `security-gate.yml`'s `secret-scan` and self-CI. |
| `security/osv-scanner` | transverse | Scans dependency manifests for known vulnerabilities (OSV.dev); skips a repo with no supported manifest. Behind `security-gate.yml`'s `supply-chain` (non-Rust) and self-CI. |
| `security/rust/cargo-deny` | Rust | Runs cargo-deny against the canonical imposed `security/deny.toml` (sparse-checked from `coroboros/ci`, no consumer override). The `checks` input selects which checks run — `advisories bans sources` for the `security-gate.yml` supply-chain, `licenses` for the `security.yml` advisory layer. |
| `security/gitleaks` | transverse | Installs gitleaks (SHA-256 verified), scans with the canonical ruleset, emits SARIF. Behind `security-gate.yml`'s `scan-secrets` and self-CI. |
| `security/osv-scanner` | transverse | Scans dependency manifests for known vulnerabilities (OSV.dev); skips a repo with no supported manifest. Behind `security-gate.yml`'s `scan-supply-chain` (non-Rust) and self-CI. |
| `security/rust/cargo-deny` | Rust | Runs cargo-deny against the canonical imposed `security/deny.toml` (sparse-checked from `coroboros/ci`, no consumer override). The `checks` input selects which checks run — `advisories bans sources` for the `security-gate.yml` `scan-supply-chain`, `licenses` for the `security.yml` advisory layer. |
| `release/verify-tag` | transverse | Fails the release unless the checked-out `main` HEAD matches the tag SHA. Shared by the npm and Rust `publish-package` jobs — the tag-time jobs that check out `main` to push back; the `dist-*` jobs pin to the tag commit (`github.sha`) instead. |
| `release/generate-changelog` | transverse | SemVer-strict tag guard + generates or reuses the `## vX.Y.Z` section in `CHANGELOG.md` from Conventional Commits. Outputs `body`. Idempotent. |
| `release/generate-changelog` | transverse | SemVer-strict tag guard + generates or reuses the `## vX.Y.Z` section in `CHANGELOG.md` from Conventional Commits. Outputs `body`. Idempotent. Fails the release when the newest section's version was never tagged and ≠ the release tag — a hand-curated section whose version wasn't bumped. |
| `release/github-release` | transverse | Creates the GitHub Release for the current tag, optionally as a `draft`. Body typically chained from `release/generate-changelog`. |
| `release/commit-artifacts` | transverse | Stages the given files and commits them back to `main` as `chore: release ${tag} [skip ci]`. No-op when nothing changed. |

Expand Down Expand Up @@ -295,7 +295,7 @@ Nobody pushes directly to protected branches (`main`, `develop`, `release/x.y.z`
| Other / non-standard | Others |
| `!:` or `BREAKING CHANGE:` | Breaking Changes (always first) |

Section format: `## vX.Y.Z - DD/MM/YYYY`. Idempotent. Reuses an existing hand-curated section for the tag if present.
Section format: `## vX.Y.Z - DD/MM/YYYY`. Idempotent. Reuses an existing hand-curated section for the tag if present. Fails loud on a mis-bumped section — newest `## vX.Y.Z` untagged and ≠ the release tag — instead of auto-generating over it.

</details>

Expand All @@ -305,8 +305,8 @@ Section format: `## vX.Y.Z - DD/MM/YYYY`. Idempotent. Reuses an existing hand-cu

`coroboros/ci` runs a CI on itself — lint, security, and the `v0` release move — plus a test layer that exercises its own composite actions, which are the product:

- **Lint** (`self-lint.yml`) — `actionlint`, `yamllint`, `shellcheck`.
- **Security** (`self-security.yml`) — the `gitleaks` / `osv-scanner` composites and the `security-gate` / `security` workflows, via local `./` refs.
- **Lint** (`self-lint.yml`) — `check-actions` (actionlint), `check-yaml` (yamllint), `check-shell` (shellcheck).
- **Security** (`self-security.yml`) — `scan-secrets` / `scan-deps` (the `gitleaks` / `osv-scanner` composites) and the `security-gate` / `security` workflows, via local `./` refs.
- **Release** (`self-release.yml`) — moves the rolling `v0` tag onto each stable release.
- **Test** (`self-test.yml`) — smoke every composite (`release/*`, `rust/*`, `security/*`) against the real checkout, and run `javascript/base` + `rust/base` end-to-end on a `test/fixtures/` package and crate.

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@coroboros/ci",
"version": "0.2.5",
"version": "0.2.6",
"private": true,
"description": "Reusable GitHub Actions CI for the Coroboros stack.",
"license": "SEE LICENSE IN LICENSE.md",
Expand Down
Loading