Skip to content

About

Composite GitHub Actions for estate-wide CI/CD auditing — required files, code hygiene, SPDX, proofs, manifests, secrets. Consumed by main-estate-audit.yml.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

2 stars

Watchers

0 watching

Forks

CI/CD Suite

An atlas of composable repository assurance: security, governance, build hygiene, contracts, language verification and ecosystem integration.

Discover the suite

Start with the CI/CD Capability Atlas: 32 composite actions in 8 clades, organised by both purpose and nature. Each capability has a source link, an assurance boundary and a status: enforcing, tool-dependent, advisory or unfinished. Reusable workflows and caller templates are linked where provided. This is the visitor catalogue, not repository-chora metadata.

Clade Explore

Sentinel

Security scanning & trusted result receipts

Covenant

Governance, attribution & licensing

Forge

Source debt, formats & build hygiene

Schema

Manifest & contract validation

Proof

Language CI, proof execution & interface policy

Bridge

BoJ applicability & external analysis tools

Observatory

Advisory signals, with limitations made explicit

Frontier

Unfinished capabilities, not deployable assurance

Clades are a discovery taxonomy, not new runtime packages or a claim that every gate formally verifies code. Existing action paths and execution semantics are unchanged. The catalogue is generated from a validated inventory; CI rejects uncatalogued actions and stale pages.

See the capability assurance matrix for linked regression controls, explicit evidence gaps and consumer receipts, and the suite-first completion ledger for remaining shared work. These records do not certify every capability.

Estate-wide audit

A subset of the suite is orchestrated by the reusable workflow .github/workflows/main-estate-audit.yml. Each repository calls that workflow from a small caller; it does not copy it (see Consuming it). Dedicated language and scanner workflows are separate entry points, not automatically part of the estate audit.

The audit reports five component checks, one check run each:

Check Gates

governance

required-files · spdx-license · affirmation · referencing

hygiene

code-hygiene · formatting · linguist · secrets · vaulted-tokens

manifests

manifest · recipes-set

languages

idris2-abi · zig-unified-api-adapter · boj-cartridge

advisory

the seven advisory gates below; this check cannot fail, by design

Within a component every gate runs even after an earlier gate fails, so one push reports every failing gate. Before this change one job ran all 26 in sequence, and the first failure hid the rest behind "estate-audit: failing after 8s".

Status

Published 2026-08-07. Defects (1)–(3) are fixed and verified against two live estate repositories; (4) is partly fixed; (5) is closed by removing the stubs from the workflow; (6) is open; (7) is fixed by making an absent tool a not_run/exit 2 instead of a pass.

Before wiring this into a repository, read Known defects and Consuming it.

What actually enforces in the original audit inventory

The following 26-action baseline is not the total suite inventory. The current catalogue includes the four later language gates, bounded secret scanner and official SonarCloud receipt verifier as well.

Of the original 26 actions, 17 can fail a build and 9 cannot — and neither group is one kind of thing: the 17 split into 14 always-in-the-audit enforcing gates and 3 tool-dependent gates that also refuse (not_run, exit 2) when their tool is absent, while the 9 split into 7 advisory gates and 2 unimplemented stubs. Conflating them was the original error.

Class Actions Meaning

Enforcing (14)

affirmation · boj-cartridge · code-hygiene · formatting · idris2-abi · linguist · manifest · recipes-set · referencing · required-files · secrets · spdx-license · vaulted-tokens · zig-unified-api-adapter

Can fail. A green tick means something.

Advisory (7)

badges · gitsea · hosting · metrics · tests-benches · trust-humans · www-compliance

Signal-only checks; some outputs are simulated or heuristic. Warnings are not certification or a successful live-service check. See the catalogue cards for each capability’s actual boundary.

Tool-dependent, not in the audit (3)

contractile-validation · custom-tools · proof-runner

Can fail — and can refuse: with a subject present but its tool (pons/panic-attack, idris2/lake) absent they report status=not_run and exit 2, NO CHECK WAS PERFORMED. main-estate-audit installs none of those tools, so it no longer runs them; callers that install the tools may call them directly. See (7).

Unimplemented (2)

prat · semantic-audit

Stubs. Not referenced by the audit workflow. See (5).

The four language gates added 2026-10-14 (Rust CI, Creusot, SPARK, miniKanren) are enforcing by construction — every check.sh exits 1 on a defect and 2 when no check could be performed — and are classified tier by tier in Language gates below.

formatting and recipes-set moved out of the toothless column; the remaining advisory seven are advisory by design, which is defensible — provided nobody reads their green tick as a guarantee. Renaming them *-advisory would settle that permanently.

Verify inventory coverage and generated pages at any time:

bash scripts/build-capability-catalogue.sh --check
bash tests/capability-catalogue.sh

Classifications are reviewed metadata, not inferred by grepping for exit 1. That string alone cannot establish applicability, enforcement or real execution. The advisory cards explicitly flag simulated hosting output and heuristic/mock metrics; they must not be read as live health checks or proof certification.

A gate that cannot fail is worse than no gate, because it is credited as assurance. The advisory seven should either grow teeth or be renamed so nobody reads their green tick as a guarantee.

Known defects

1. FIXED — code-hygiene-check scanned for the word, not the marker. It ran one case-insensitive, unanchored git grep over the whole tree, so “admit” matched “admitted”, “sorry” matched ordinary English, any document discussing a marker failed, and believe_me failed repos whose sanctioned axioms are their declared trusted base. Measured 112 matching files in one repo and 313 in another. Now two scans with different semantics — debt markers case-sensitive and whole-word in source only; circumventions in proof languages only, with comments filtered. Repos may exempt paths via .cicd-hygiene-allow. Result: 112 → 2 and 313 → 3, all true positives.

2. FIXED — required-files-check manufactured filler. Presence-only checking meant the cheapest way to pass was template boilerplate, and that is exactly what happened in the estate. It now checks presence, then format, then substance: documents need real content and no placeholders, ARCHITECTURE must name a directory that actually exists, and MAINTAINERS must mention the repository owner — which catches a template shipping its author’s handle. CODEOWNERS is judged on whether it assigns an owner, not on length.

3. FIXED — the suite contradicted itself. required-files-check hard-failed a repo for lacking GOVERNANCE.md while formatting-check warned that same file should be .adoc. Required-files now accepts every policy-legal form, and formatting-check owns the preference. Verified: a repo passes both gates with .adoc (silently) or with .md (with a nudge).

4. PARTLY FIXED — the toothless column is down from 13 to 7, and now triaged (the three tool-dependent gates it also held are defect (7)). recipes-set was a stub that printed “Checking Justfile syntax…” with the command commented out and then declared success; it now parse-checks Justfiles and Makefiles and fails on a file that does not parse. formatting gained teeth in (3). The seven remaining advisories are advisory by design and defensible; the honest fix for them is a rename to *-advisory, not teeth.

5. CLOSED — two actions are unimplemented stubs, and the workflow no longer runs them.

  • semantic-audit is pure echo with “Placeholder for actual invocation”. cicd-squabbler now exists, but is a gate diagnosis and legitimate-remediation engine, not evidence of the imagined audit --semantic interface. Keep this stub out of enforcement; do not confuse pipeline diagnosis with semantic verification.

  • prat claims to validate “PRAT testing”, but PRAT is defined nowhere in the estate — a search of every repository turns up only further copies of the workflow that calls it. Its current check passes any repository that has a tests/ directory or contains the letters “prat” anywhere. It cannot be honestly implemented until somebody says what PRAT is.

A stub that prints success cannot be credited as assurance, so neither step is in main-estate-audit.yml any more. The two directories stay under actions/ for now, for two reasons. Repositories still running a pre-caller copy of the workflow reference them by path. And tests/lock-transitive-closure.sh requires that the SHA the workflow pins serves the whole actions/ tree at HEAD. Move them to draft/ once the estate runs the caller (see Consuming it).

6. OPEN — six actions use bare git grep with no path restriction (idris2-abi, metrics, secrets, spdx-license, vaulted-tokens, zig-unified-api-adapter) and may inherit a milder form of defect (1).

7. FIXED — pass-on-absence: a gate certifying a scan that never ran. custom-tools-check ("Panic Attack & Pons Gate") printed “Custom tools validation passed” when neither pons nor panic-attack was on PATH — the normal case on GitHub runners — so every consumer of main-estate-audit was certifying scans that never ran. It now lives in check.sh (estate convention: exit 1 = ran and failed, exit 2 = NO CHECK WAS PERFORMED) with a status output (passed/failed/not_run), and is out of main-estate-audit, which installs neither tool; panic-attack/pons evidence comes from the standards Hypatia lane’s envelopes.

The sibling audit of every *-check composite found the same shape twice more, both fixed the same way and both removed from the audit for the same reason: contractile-validation-check echoed “Invoking … runner…” in front of a commented-out command and printed “Contractile Validation Passed.” unconditionally (now: a detection step finds the declarations — with the canonical Intentfile stem, not Intendfile — and the estate DEED validator, the deed-ecosystem/validate-action manifest-check already pins, runs only when they exist; no declarations ⇒ explicit not_applicable, never a pass; there is no deed-lint CLI and an earlier draft of this fix invented one from the placeholder comment), and proof-runner-check had its only invocation commented out and printed “All detected proofs executed successfully.” regardless (now: proof project present + idris2/lake absent ⇒ not_run/exit 2; no project ⇒ not_applicable).

Audited and not the same defect: recipes-set already exits 1 when its tool is missing and a recipe file is present (the model for this fix); the language gates (rust-ci/creusot/spark/kanren) already exit 2 on a missing tool; idris2-abi, zig-unified-api-adapter, boj-cartridge, affirmation and referencing say “Skipping”/::notice:: and exit 0 only when their subject is absent — not applicable, not a scan that failed to run — and manifest delegates to real validator actions. The remaining advisory gates invoke no external tool, so absence-of-tool cannot arise; two of them (vaulted-tokens, www-compliance) did print “Passing…”/“…passed” for the no-subject case and now say not applicable … (this is not a pass) instead. tests/pass-on-absence.sh holds the controls: tool-absent ⇒ exit 2 with no success line, stand-in tools ⇒ invoked for real, failing stand-in ⇒ exit 1.

Blast radius

An earlier rollout_estate.sh copied the whole workflow into 199 repositories. Each copy froze at whatever cicd-suite commit its actions.lock held. That is why a gate fixed here kept failing downstream. The composite-shell fix to required-files (#32, 2026-09-22) had still not reached hyperpolymath/marches on 2026-10-14, because its lock held 5a10b72, the commit before it. The caller replaces those copies.

The workflow now has permissions: contents: read and pins actions/checkout and every gate by full SHA.

Consuming it

A repository calls the audit; it does not copy it. Three pieces:

  1. templates/estate-audit-caller.yml becomes .github/workflows/main-estate-audit.yml, with __CICD_SUITE_SHA__ replaced by a full cicd-suite commit SHA:

    jobs:
      estate:
        uses: hyperpolymath/cicd-suite/.github/workflows/main-estate-audit.yml@<40-hex sha> # cicd-suite main

    Pin a full SHA, never @main or a tag: the account enforces sha_pinning_required, and @main would silently change the gate in every consuming repository at once.

  2. The caller must be de-onboarded from the repository’s actions.lock. Onboarded, a cross-repository reusable-workflow call startup-fails with zero jobs. scripts/deonboard-estate-audit.sh <repo> removes the entry and prunes the dependencies only it reached, comparing owner and repository names case-insensitively as GitHub does. It is the estate-audit entry point to scripts/deonboard-workflow.sh, the one implementation every de-onboard goes through. It needs mikefarah yq v4, and it is idempotent. It refuses any actions.lock schema it has not been tested on; tests/deonboard-lock-schemas.sh holds one fixture per supported schema (v0.0.2, v0.0.3) and runs each through both entry points.

  3. templates/track-estate-audit.yml becomes .github/workflows/track-estate-audit.yml. Weekly, it opens a PR that moves the pin to cicd-suite HEAD; that PR’s own audit run is the check. It needs an AUTOMATION_TOKEN secret (a fine-grained token on that repository with Contents, Pull requests and Workflows set to read/write), because GITHUB_TOKEN may not write under .github/workflows/. Its one uses: is step-security/harden-runner at a full SHA (egress to GitHub only); a repository that carries actions.lock must add that record (gh actions-lock) or the first run dies at Set up job.

rollout_estate.sh does all three for every local estate clone. It writes files and does not commit them.

Language gates (Rust, Creusot, SPARK, miniKanren)

Added 2026-10-14. Four reusable workflows, consumed exactly like the estate audit (a thin caller at a full SHA, de-onboarded from actions.lock), and each built so that its oracle has demonstrably said no before its green is credited.

Reusable Check contexts (caller job id, then job name) Oracle

.github/workflows/rust-ci.yml

rust-ci / Detect Cargo.toml · Cargo check + clippy + fmt · Cargo test · Cargo doc (warnings denied) · MSRV check · cargo-deny (advisories, bans, sources) · llvm-cov line coverage (opt-in) · Rust CI verdict

cargo, every call --locked, every compile -D warnings, doctests run, MSRV builds, cargo-deny. The verdict job turns a skipped mandatory job into red.

.github/workflows/creusot-gate.yml

creusot / Detect Creusot obligations · Creusot theatre gate · Creusot proofs (cargo creusot prove) · Creusot verdict

Creusot at a pinned release. A planted FALSE contract must be refused and a planted TRUE one proved before the caller’s crates are proved; otherwise the verdict is VOID (exit 2), never green.

.github/workflows/spark-gate.yml

spark / SPARK theatre gate (standards) · Detect SPARK units · GNATprove · SPARK verdict

standards' SPARK Theatre Gate (claims side, called at a pin, not copied) plus GNATprove at pinned GNAT/GNATprove versions via Alire, --checks-as-errors=on --warnings=error, with the same planted controls.

.github/workflows/kanren-gate.yml

kanren / miniKanren gate

The relations are RUN by the interpreter their dialect names (guile, Chez, racket); relations with no goal-level test fail; impure operators are listed (enforceable); a claim with no relations is named.

Why. From 2026-09-29 every rust-ci / Cargo check + clippy + fmt calling standards' rust-ci-reusable.yml at a pre-fix SHA died in six seconds: the reusable pinned dtolnay/rust-toolchain by commit and passed no toolchain:, and a SHA ref carries no channel (plasma-parser-writer#105). The fix landed upstream; the callers stayed pinned behind it. actions/rust-ci-check/resolve.sh now computes the channel before any action runs and refuses an empty one, and the previous gate’s silent holes are closed: cargo test --all-targets never ran a doctest, nothing denied rustc warnings outside clippy, cargo doc never ran, the declared rust-version was never built, and no supply-chain check ran unless opted in.

Tiers. Within each gate the theatre tier (a claim with nothing behind it) and the oracle tier (the prover or interpreter run) are enforcing and exit 1; the hollow and purity tiers warn by default and become enforcing through an input (enforce_hollow, enforce_zero_contract, enforce_purity, enforce_claims), mirroring standards' SPARK T2. A tree with nothing to gate gets a ::notice:: that nothing ran — never a tick read as assurance. A missing tool is exit 2, "no check was performed".

How the scripts are pinned. Each reusable job reads the caller’s own uses: line back out of the caller’s checked-out workflow file (GITHUB_WORKFLOW_REF names it; inside a reusable the github context is the caller’s), checks this repository out at that SHA into .cicd-suite/ and runs actions/*-check/check.sh from there, after scripts/verify-suite-checkout.sh has proved the checkout is that commit. A caller pinned at a SHA runs exactly that commit’s scripts, with no second pin to drift and no repin commit after a change to actions/. The caller’s tree is read through git ls-files, so the untracked .cicd-suite/ is invisible to it.

Self-test. .github/workflows/language-gates-self-test.yml runs each gate’s test.sh: a fixture, a planted defect per claim, the exact composite shell, and a mutant where one applies. The prover tiers are exercised against stand-in oracles there; the planted controls in the check scripts exercise the real provers on every live run.

Consuming. templates/rust-ci-caller.yml, creusot-gate-caller.yml, spark-gate-caller.yml and kanren-gate-caller.yml become .github/workflows/<name>.yml with __CICD_SUITE_SHA__ replaced; scripts/deonboard-workflow.sh <repo> <workflow-path> de-onboards each (the estate-audit script now delegates to it); templates/track-estate-audit.yml moves every cicd-suite pin weekly by PR. rollout_language_gates.sh does all of it per clone, by what the tree contains. Keep the caller job ids above: they are the context strings branch rules require.

Not yet witnessed live (2026-10-14). The Creusot and GNATprove install steps and the exit-code contract of cargo creusot prove and gnatprove --checks-as-errors=on were validated against the tools' documentation and stand-ins, not against a live runner. The planted controls are the safeguard: if either prover cannot refuse a false contract on the runner, the gate reports VOID rather than green. The first live subject for SPARK is cicd-squabbler’s spark/squabble_gate.gpr; no repository in the first rollout carries Creusot obligations yet.

What a gate may use, and what a consumer’s lock must hold

Two constraints are evaluated in the caller’s repository, so nothing in this repository’s own CI can witness them; both are now guarded here.

  • Actions policy. The estate’s GitHub Actions policy admits actions that are hyperpolymath-owned, GitHub-made, Marketplace-verified, or on a short pattern list. A reusable that uses anything else startup-fails for every caller with "The action … is not allowed" — which is how EmbarkStudios/cargo-deny-action killed the first live Rust gate caller (plasma-parser-writer#108, 2026-10-19) while the Creusot gate from the same commit ran. The gates now use only actions/, github/, hyperpolymath/*, dtolnay, swatinem, goto-bus-stop and taiki-e, every one at a full SHA, and tests/gate-action-owners.sh (run by the self-test workflow) refuses any other owner or a tag pin. cargo-deny is installed through taiki-e/install-action; Alire is a sha256-pinned release binary rather than alire-project/setup-alire, which also nested floating @v4 tags.

  • Callee lock. Since about 2026-10-15 GitHub validates a called reusable against the callee repository’s actions.lock at the called SHA and rejects a dependency record whose ref: is its own commit SHA ("invalid lockfile pin"). The gate workflows are deliberately not keyed in this repository’s lock, and that resolves (witnessed: the Creusot gate on plasma-parser-writer#108). The SPARK gate’s nested standards theatre gate is pinned at standards 75bd599, the first standards commit whose lock passes that validation; hypatia’s PIN-003 lists the 545 that do not.

  • Consumers with a lock-sync gate. A repository whose CI requires every job-level reusable ref to be keyed in its own actions.lock with a transitively closed dependencies: section (cicd-squabbler’s scripts/check-lock-sync.sh) cannot de-onboard the caller as the templates assume. Such a repository onboards instead: one hyperpolymath/cicd-suite@<sha> record whose uses: is the union of what the called gates use at that SHA, plus a record for each of those copied from a lock GitHub accepts, with the tag — never the SHA — as ref:. cicd-squabbler#135 is the worked example (for its standards callers); gitbot-fleet’s scripts/fix-lock-self-sha-refs.sh and scripts/lib/action-ref-tags.tsv carry the tag map.

Licence

Code MPL-2.0, prose CC-BY-SA-4.0. See LICENSE.

Merge-blocker diagnosis and secret-scan latency

bash scripts/inspect-pr-ci.sh OWNER/REPO PR_NUMBER collects read-only JSON evidence: PR checks, effective branch rules, classic protection, head-SHA workflow runs and jobs, including failed workflows with zero jobs. Requires gh and jq; exit 2 means collection did not complete, including invalid arguments, missing gh or jq, and denied API probes. Exit 0 means collection succeeded, not that CI passed. The report is not merge authorisation, a full check-history export, or an implementation of cicd-squabbler. Use squabbler for attribution and repair. No logs, artifacts, secrets, policy edits, reruns or merges are requested.

Bounded secret scanner (canary, not estate-deployed)

.github/workflows/secret-scanner-reusable.yml and actions/secret-scan-check/ provide a checksum-pinned Gitleaks 8.30.1 implementation with bounded download/scan deadlines, redacted logs, real detector controls using the effective configuration, and separate working-tree/history passes. The vendored estate baseline records its upstream SHA; no renamed standards reusable is needed at runtime.

The direct caller template preserves scan / gitleaks. Full history remains the default. fast-pr: true explicitly opts PRs into scanning all introduced commits, not merely the final diff; non-PR runs remain full. PR scans use the base commit’s configuration and extensions; extensions must stay within that tree. A changed .gitleaksignore fails the scan. PR scans require Python 3.11 or newer to validate configuration paths. A full ancestry checkout is still required: this version reduces scanning work, not checkout history size. No speed claim is established without runner measurements. The job deadline is 15 minutes; each main scan is bounded to 180 seconds plus termination grace. Oversized audits fail visibly rather than wait forever or silently pass.

Do not bulk-replace the old three-job standards bundle yet: this scanner is not claimed to reproduce its specialised Rust/shell heuristic checks. Keep those protections until parity controls or an owner-approved migration exist. The real-binary CI controls and a caller canary must pass before production rollout; see rollout status.

Post-merge startup repair and supplemental detector ports

The follow-up described in Startup and supplemental coverage repins this repository to the published #62 merge commit. The initial pre-merge pin failed resolution after merge; the canonical legacy dependency was then explicitly refused by the caller’s Actions admission policy. Rather than broadening permissions, the suite now carries Rust/shell ports and 26 differential fixtures against frozen legacy run bodies, plus five hardening controls. The former legacy comparison workflow runs these local checks; it no longer invokes standards or repeats full-history Gitleaks.

The new scanner source includes both ports in its composite verdict. Publish that implementation before advancing consumer pins: the currently deployed suite pin still selects the previously published scanner, while separate local supplement jobs preserve this repository’s supplementary checking. No estate-wide rollout or whole-corpus equivalence is claimed by the fixtures.

About

Composite GitHub Actions for estate-wide CI/CD auditing — required files, code hygiene, SPDX, proofs, manifests, secrets. Consumed by main-estate-audit.yml.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

2 stars

Watchers

0 watching

Forks

Releases

Sponsor this project

Packages

Used by

Contributors

Languages