Releases: NDDev-it-com/ci-workflows
Release list
0.13.2
Fixed
-
zizmor-sarif.ymlnever declared theactions: readits SARIF upload
needs.github/codeql-action/upload-sarifreads the workflow run, so it
requiresactions: read;public-codeql.yml— the other SARIF uploader here —
declares it, and this one did not. The incomplete set had propagated into
catalog/capabilities.ymland all four examples that call it. Consumers saw
the scan succeed and the upload fail withResource not accessible by integration; observed ongithub-device-sync, red since adoption. Reusable,
catalog, examples and this repository's ownci.ymlcaller now declare the
same set.A caller cannot grant a reusable less than the reusable declares, so the token
comes from the called workflow: a consumer cannot work around this by adding
the permission to its own caller — it needs a release carrying the fix.
Added
public-scorecard.ymlwas the only reusable without a copy-paste example.
examples/public-oss/scorecard.ymlcovered the JSON variant only, so the
SARIF-uploading variant — the one that needssecurity-events: writeand
turns Scorecard findings into persistent code-scanning alerts — had no
validated caller. Addsexamples/public-oss/scorecard-sarif.yml, including
the reason to pick one variant over the other and the shared trigger
constraint. Every reusable now has an example.
0.13.1
Fixed
-
Five reusables were self-applied but still recorded as
unverified. When
codeql.yml,gitleaks.yml,dependency-review.yml, andscorecard.ymlwere
added as self workflows calling their reusables through relative refs, the
runtime-coverage ledger was never updated to match — so the repository was
proving these workflows on every push while the ledger claimed no observed run
existed. Promoted toruntime-provenagainst runs that executed the current
bytes:public-codeql.yml,secret-scan.yml,public-scorecard-json.yml,
zizmor-sarif.yml,public-dependency-review.yml. Runtime-proven entries go
from one to six.Nothing was promoted on estate evidence, although consumer repositories did run
several of these today: every such run executed an older pinned revision, and a
proof is only valid for the bytes it exercised. That tension is structural — a
dependency bump invalidates a proof — which is why sustainable coverage comes
from self-calling fixtures rather than from opportunistically harvesting
consumer runs.
Added
- New skill
ci-consumer-adoption. The skill set covered authoring and
operating the library but nothing about wiring a consumer repository onto
it, which is where the two observed failure shapes live: a repository that
looks configured but still bills, and a repository routed to hardware its jobs
cannot use. Covers tier selection from entitlements rather than visibility
alone, pinning to a released tag by full SHA, runner routing by visibility,
the two managed scans no workflow file can reach, and proving the routing from
a completed run'srunner_nameinstead of a saved setting.
Changed
ci-cost-performancegains the visibility-routing rule and its corollaries:
self-hosting pays only where minutes are metered, moving public work onto own
hardware buys a fork-PR execution path rather than a saving, platform-scheduled
scans carry their own runner control, there is no spillover from a busy
self-hosted label to a hosted runner, and an isolated self-hosted runner is
unprivileged so system-path installs fail there.
0.13.0
Added
-
GitHub Code Quality is now a modelled tier instead of an unattached price
tag. The product existed in the ledger asgithub-code-quality-transition
and nowhere else: no capability, no tier doc, no mention in the tier tables —
so the catalog priced a product it never told adopters how to place. It gets
catalog/capabilities.ymlentrygithub-code-qualityand a tier doc,
docs/16-code-quality.md.It is deliberately not folded into the public tier. Re-verification
against the billing docs on 2026-08-01 established that visibility does not
gate the licence: a public repository is billed the same per-active-committer
rate as a private one, and being public only removes the Actions-minutes
component. That contradicteddocs/01-public-oss-free.md, which promised the
"entire security and supply-chain suite for free" — a claim that would have
been false for any adopter who enabled Code Quality on a public repo. The
three-tier model sorts by visibility and plan; this product obeys neither, so
it is documented as an orthogonal fourth tier and excluded from both free
tiers, with the free maintainability substitutes (coverage-gate.yml,
docs-quality.yml,pr-hygiene.yml, zizmor, the language packs) named
explicitly.Two further billing facts are now recorded because they invert the usual
cost-control instinct: committers are counted once per organization, so
enabling one repository already bills the whole active-committer set and a
"few paid repos, many free repos" split saves nothing unless the committer
sets actually differ; and the licence is independent of Code Security and
Secret Protection, so GHAS does not include it and holding both means paying
two products to drive one CodeQL engine.The capability carries
workflow: nullandexample: null, which is the
honest shape rather than a gap: Code Quality has no Action, noworkflow_call
entrypoint, and no REST or GraphQL API, so enablement is UI-only and cannot be
pinned by SHA, asserted, or drift-checked from CI. For the same reason the
merge gate — ruleset rule "Require code quality results", severity threshold,
checkCodeQL - Code Quality— is documented as a UI procedure and not
encoded in.github/rulesets/: those specs are shaped for
POST /repos/{owner}/{repo}/rulesetsand the rule-type identifier for this
rule is undocumented.AGENTS.mdrecords the gate so the next contributor does
not re-derive it, replacing a stale instruction to "refresh that fact" on a
date that has passed. -
The library described GitHub's price list, not this estate's receipts.
Every tier doc reasoned from what GitHub charges a hypothetical adopter, so a
private NDDev repository was configured as if it were on the free plan while
the organization was already paying for Enterprise Cloud, Code Security,
Secret Protection, and Code Quality.docs/17-nddev-tier.mdrecords the
verified entitlements and the advice that consequently does not apply.The concrete loss this closes:
docs/02-private-free.mdroutes private
releases torelease-supply-chain-free.ymlbecause Artifact Attestations
require Enterprise Cloud on private repos. This estate has Enterprise
Cloud, so all 26 private repositories were emittingslsa_build_level: null
and discarding provenance that was already bought. They can use the attested
release-supply-chain.yml.Also recorded, because it inverts the usual cost instinct: these products bill
per active committer counted once per organization, so with one committer
the estate pays the same whether one repository or fifty are enabled — partial
coverage would have cost identically and protected less.examples/nddev/
joins the aggregate-example allowlist invalidate_catalog.py, alongside the
three existing per-tier security suites.The doc is explicit about what is absent too: Copilot Autofix is unavailable
(Copilot Business provisioned, zero seats assigned), and SHA pinning is
unenforced at both org and enterprise (sha_pinning_required: false) — flagged
rather than recommended blindly, since enabling it org-wide would break any
repository still pinning actions by tag.
Fixed
-
A grouped action bump could not land on its own. Dependabot updates the
uses:pins inside the workflows but knows nothing aboutcatalog/tools.yml,
sovalidate_catalog.pyfailed on seven tools whose catalog pin no longer
matched the workflow that used it, andvalidate_runtime_coverage.pyfailed
because two workflow files changed after the run that proved them. Catalog
pins andcurrent_versionsynced forattest,checkout,setup-python,
setup-go,setup-dotnet,labeler, andmarkdownlint-cli2-action.
actionlint.ymlis re-proven against the CI run that executed the new bytes —
this repository's ownci.ymlcalls it through a relative ref, so the proof is
real.release-supply-chain.ymlis downgraded tostatic-only: nothing in
this repository's CI calls it, so no run has executed the new bytes, and the
ledger's rule is to downgrade rather than carry a stale proof. The next real
release re-proves it. -
The catalog recorded four action pins that no workflow used.
setup-node,
setup-java,setup-swift, andcheckov-actionhad drifted a version behind
the SHA theirused_byworkflows actually reference —setup-swiftby a full
major (v2.4.0recorded,v3shipped).validate_catalog.pychecked only the
pin's shape and thatused_bypaths existed, never that the pin matched
reality, so the gate stayed green while the declared source of truth was wrong
in four places. Pins synced andvalidate_catalog.pynow fails when a
catalog pin does not appear verbatim in each of itsused_byworkflows. -
sql-ci.ymldeclared apython_versioninput that nothing read. It was
the only never-read input across all reusables; the workflow has no
setup-pythonstep at all and provisions Python throughsetup-uv. A caller
passing it got a silently ignored value. Input removed and the header comment
corrected from "pinned setup-python" to "pinned setup-uv". -
docs/12-community-dx.mdlisted five community-health files as still missing;
all five have shipped. Only the optional.github/FUNDING.ymlremains absent.
Added
- Self-application of the public OSS security suite. This repository shipped
CodeQL, OSSF Scorecard, Dependency Review, and gitleaks to the estate while
consuming none of them itself; it self-applied onlyactionlint,zizmor,
andrelease-supply-chain. New self workflowscodeql.yml,gitleaks.yml,
dependency-review.yml, andscorecard.ymlcall the matching reusables
through relative refs, with triggers taken from this repository's own
examples/public-oss/shapes.
Fixed
-
SELF_WORKFLOWSwas duplicated as a literal in three places.
_workflow_yaml.pyheld the named constant whilevalidate_catalog.pyand
generate_docs.pyeach hardcoded{"ci.yml", "release.yml"}again. Both now
import it. With the constant honoured in only one of the three,
validate_all.pypassed whiledocs/generated/workflow-inventory.mdlisted
the new self workflows asMISSINGinstead ofinternal— a green gate over
wrong generated output. -
Pinned tools installed into
/usr/local/bin, which no correctly isolated
self-hosted runner allows.actionlint.ymlandosv-scan.ymlplaced their
checksum-verified binaries in a system path. That works on GitHub-hosted
runners, where the job user may write there, and fails outright on a
self-hosted runner whose account is unprivileged:
install: cannot create regular file '/usr/local/bin/actionlint': Permission denied.
Found by routing a real private-repository job to a self-hosted runner. The
privilege is not incidental — a runner that can write to system paths shares
mutable state between jobs — so the destination moved rather than the runner's
permissions: both now install into"${RUNNER_TEMP}/bin"and prepend it to
GITHUB_PATH, which is writable on hosted and self-hosted alike and is torn
down with the job. Checksum verification is unchanged.cpp-ci.ymlstill uses
sudo apt-get; that is a different class (system packages, not one pinned
binary) and remains hosted-only.
Documentation
- No documented rule for which repositories run where. Private-repository
minutes are metered and public ones are free, so the cost-optimal routing is
private → self-hosted, public → GitHub-hosted.docs/05-runners.mdgains
Routing by visibilitywith that rule, the fork-PR reasoning that makes a
self-hosted runner on a public repository a security defect rather than a
saving, and an explicit statement that this repository is public and must
never route itself to self-hosted or ship a self-hosted default in
examples/. - Two runner settings are invisible to workflow files. CodeQL default
setup and Code Quality scans are scheduled by GitHub, not by a workflow,
and each carries its own runner control. Missing either leaves a repository
consuming metered minutes while every caller says otherwise. Both are now
documented with the exact API call and UI path. - AI findings were undocumented.
docs/16-code-quality.mdgains an
AI findingssection: they are metered separately from the per-committer
licence with no included allowance (discountAmount: 0.00on every
billing line, $0.01/credit), and one repository burned 774.9 credits in about
twelve days — roughly twice the licence that covers a whole organization. Also
records why a product budget cannot fence this off, and that the switch is
absent entirely where CodeQL finds no supported language. docs/17-nddev-tier.mdgains the verified per-line cost envelope and a runner
routing summary, and...
0.12.0
Changed
- Repository renamed
nddev-ci-workflows→ci-workflows. Every active
and identity surface now uses the new coordinate: releasepackage_name,
allexamples/**reusableuses:refs, README/docs/skills, and the two
gate-coupled slugs (check_examples.pyUSES_RE,validate_runtime_coverage.py
REPO_SLUG+runtime-coverage.ymlrun URLs). Consumers must repin reusable
uses:references toNDDev-it-com/ci-workflows/…(GitHub does not redirect
uses:across a rename). - Pin previously-mutable uv/bun tool versions:
semgrep-cisemgrep_version
default1.170.0,sql-cisqlfluff_versiondefault4.2.2, and the
web-cidefaultlint_commandnow pinsstylelint@17.14.1+
htmlhint@1.9.2. Empty/bare versions previously floated to the latest
release at run time.
Added
check_tool_pinningvalidator (wired intovalidate_all): rejects empty
uv/bun tool-version inputs and bareuvx/bunx <tool>invocations, keeping
tool versions as reproducible as the full-SHA action pins.
0.11.3 — Dependabot action bumps
Bump pinned GitHub Actions (setup-node v7, setup-java v5.6, checkov-action v12.3114, setup-swift v3); iac-scan runtime-coverage downgraded to unverified pending re-run.
0.11.2 — uv 0.11.30 + fact refresh
Bump pinned uv 0.11.29->0.11.30 across setup-uv workflows + examples; refresh the github-code-quality-transition product fact (GA/paid transition completed 2026-07-20).
0.11.1 — uv/bun-only reusable CI
Breaking: all reusable workflows + examples migrated to uv/bun (RVR-P1-001). SHA-pinned setup-uv v8.3.2 + setup-bun v2.2.0, frozen locks, no pip/npm. Module renamed to ci-workflows. (Supersedes burned 0.11.0 tag.)
0.10.0
Added
- Three repository-operation skills for agents under
.agents/skills/
(mirrored to.claude/skills/):nddev-repo-orientation(instant mental
model, file map, contract index, and a task router),nddev-change-flow(the
complete golden-path checklist, including paired-variant mirroring and the
runtime-coverage "static-only dance"), andnddev-release-flow(version prep,
the signed tag, the immutable-release verification checklist, and the
post-release ledger re-promotion). They complement the eight portable
CI/GitHub-Actions doctrine skills and route to the catalog and agent
instructions as the source of truth rather than duplicating volatile facts.
EXPECTED_SKILLSand theAGENTS.md/.claude/CLAUDE.mdskill sections now
describe the two groups.
Changed
- Re-promote the
release-supply-chain.ymlruntime-coverage record to
runtime-provenafter the 0.9.0 release run
(…/actions/runs/29173277373ate27d4e3) re-executed the current workflow
live, recording the fresh run URL andproven_digest. Completes the honesty
cycle opened when RVR-P2-011 edited the workflow and correctly dropped it to
static-only.
0.9.0
Fixed
-
Sync the attested-release permission + Syft contract into every
consumer-facing surface. 0.8.1 addedartifact-metadata: writeto the
attesting jobs and bumped Syft to 1.46.0 in the workflows and release
validator, but the caller-facing docs, examples, and catalog still showed
the old three-scope set and Syft 1.42.3. Because a caller'sGITHUB_TOKEN
permissions are the ceiling for a reusable workflow, a consumer copying the
stale three-scope example under-scopes the attest step. Update the
release-supply-chain.ymlcaller permissions inREADME.md,
examples/public-oss/release.yml, anddocs/04/docs/09; add
artifact-metadata: writeto theartifact-attestations,
slsa-build-provenance, and attestedreleases-packagescapabilities in
catalog/capabilities.yml; correct the Syft version inREADME.md,
docs/07, anddocs/13; and add a 0.8.1 caller migration note. Contract-
truth synchronization only — no workflow behavior change. -
Remove volatile GitHub tariffs from the
ci-free-tier-plannerskill and
guard against regression..agents/skills/ci-free-tier-planner/SKILL.md
hard-coded plan quotas (Actions minute allowances, storage/cache sizes, and
the attestation / dependency-review / secret-protection / environment plan
gates), contradicting the project rule that volatile plan/price/quota facts
live only in the freshness-enforcedcatalog/product-facts.yml. A product
fact could expire and correctly redden the ledger while the skill still
served the stale number. Rewrite section 4 to hold the durable procedure and
reference the facts by id (github-actions-*,github-attestations-*,
github-dependency-review-*,github-secret-scanning-*,
github-environments-*,github-code-quality-transition) with fail-closed
resolution, and add acheck_skills.pyguard (with self-test) that fails CI
if anySKILL.mdreintroduces a comma-grouped allowance,<n> minutes, or a
storage-size figure. Regenerate the.claude/skillsmirror. Found by an
independent forensic review (RVR-P2-010). -
Harden the runtime-coverage honesty gate against weak and stale proofs.
validate_runtime_coverage.pyaccepted anyhttpsURL for a
runtime-provenrecord (its own fixture usedexample.invalid), so a docs
page or a foreign-repo run would have passed, and it never noticed that the
release-supply-chain.ymlrecord still pointed at a 0.7.0 run
(…/29165402032ateda8ff7) taken two edits before the current file.
Requirelast_runto be a
github.com/NDDev-it-com/nddev-ci-workflows/actions/runs/<id>URL and add a
proven_digest(sha256 of the workflow file at the proving run) that the
validator recomputes and matches, so any later edit to a proven workflow
reddens CI until it is re-run and re-recorded (or downgraded) instead of
silently keeping the label. Repoint all three runtime-proven records to runs
that provably executed the current bytes (actionlint / zizmor-sarif → the
ci.ymlrun…/29172553315; release-supply-chain → the 0.8.1 release run
…/29167958787at8b8e3ea) and record each digest. Fixtures now cover
foreign-URL, non-run-URL, missing-digest, and stale-digest. Found by an
independent forensic review (RVR-P2-009). -
Bound the optional runtime bundle to the SBOM-covered source archive
(RVR-P2-011).release-supply-chain.ymland its byte-parallel free twin
attach an optional secondruntime_pathsbundle that received a
build-provenance attestation but no SBOM — and becauseruntime_pathswas
validated independently ofarchive_paths, it could ship tracked files the
Syft scan of the source payload never saw. Enforceruntime_paths ⊆ archive_pathsinside the deterministic-bundle program (both variants,
byte-identically) so every file in the runtime bundle is also in the source
archive thatsbom.spdx.jsoninventories — the source SBOM is now provably a
superset of everything the release ships. Add acheck_release_supply_chain.py
fixture (the runtime-bundle program was previously the one embedded program
with no hermetic test) covering subset-accepted and outside-archive / absolute
/ unmatched / empty-archive-refused. Therelease-supply-chain.yml
runtime-coverage record drops tostatic-only(its contract validator stands
in) until the next release re-proves it live. Found by an independent forensic
review.
Changed
- Catalog tool inventory + currency (
catalog/tools.yml). Add the seven
in-use actions that were absent from the catalog —actions/setup-node,
actions/setup-go,actions-rust-lang/setup-rust-toolchain,
actions/setup-dotnet,actions/setup-java,hashicorp/setup-terraform,
andaquasecurity/trivy-action— and correctgithub/codeql-action's
used_by(it also runs inzizmor-sarif.ymlandpublic-scorecard.yml).
Bump drifted pins to latest across the workflows and catalog in lockstep:
codeql-action v4.36.3→v4.37.0, lychee-action v2.8.0→v2.9.0, labeler
v6.1.0→v6.2.0, stale v10.3.0→v10.4.0, setup-java v5.4.0→v5.5.0, and the
documented semgrep CLI version v1.168.0→v1.169.0. All remain full-SHA pinned
with# vX.Y.Zcomments; no workflow behavior change.
0.8.1
Fixed
- Grant
artifact-metadata: writeto the attesting release jobs.
actions/attest@v4.1.1documentsid-token: write+attestations: writeartifact-metadata: writeas its required permission set; the release
jobs inrelease-supply-chain.ymlandrelease.ymlwere missing
artifact-metadata: write, so the action's artifact-storage-record step
ran without its scope (the Sigstore signing/attestation itself still
succeeds, which is why past releases passed). Add the scope to both jobs and
to the release validator's exact attested-permission assertion. The
attestation-free variant is unaffected (it has no attest steps). Found by a
deep review against theactions/attestREADME at the pinned tag.
Changed
- Relabel the release provenance claim from "SLSA v1.0" to "SLSA v1" in the
workflow header, README, and SECURITY.md. slsa.dev has retired v1.0 (v1.2 is
current); the reusable-workflow → Build L3 mechanism is unchanged across
v1.1/v1.2, so only the version label needed correcting. - Bump the checksum-pinned Syft SBOM generator from 1.42.3 to 1.46.0 (latest)
in bothrelease-supply-chain.ymlandrelease-supply-chain-free.yml, with
the release validator'sSYFT_PINSandcatalog/tools.ymlupdated in
lockstep (new Linux amd64/arm64 archive sizes and SHA-256s verified against
the upstreamsyft_1.46.0_checksums.txt). Syft is a manually pinned binary
outside Dependabot's reach, so it had drifted four minor versions behind;
this is a currency/best-practice pass, not a contract change — the SPDX-JSON
output and every archive/SBOM/manifest/checksum invariant are unchanged.