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 corrects a stale claim:sha_pinning_requiredis now
true at both org and enterprise level, not false.- New caller example
examples/nddev/security-private-selfhosted.yml— the
nddev suite with every job pinned to a self-hosted label.