StageFreight — Feature Matrix
A capability-by-capability comparison of what StageFreight gives you declaratively versus what
the same outcome costs on raw CI platforms, with the closest dedicated tool and an honest "how hard
is this to build yourself" rating.
The point of this document: most of these capabilities are things you can do anywhere — but on a
raw CI platform you assemble them from actions, plugins, and hand-written API calls against each
backend. StageFreight collapses that into one .stagefreight.yml and talks to the backends for you.
Where StageFreight is behind or not yet GA, this document says so plainly.
One config, many CI hosts. stagefreight ci render <forge> emits a native pipeline for
GitLab, GitHub Actions, Gitea, Forgejo, and Azure DevOps from the same .stagefreight.yml. Your
pipeline isn't married to a vendor — switching CI hosts is a re-render, not a rewrite. Nothing else
in this matrix offers that, because every other tool is one of those vendors.
This is a living document. Capability coverage is complete; per-feature commit provenance
(the "Since" lines) is seeded and being filled in over time. See Maintaining this doc.
How to read it
Columns are the tools; rows are capabilities. StageFreight is first.
| Mark |
Meaning |
| ✅ |
First-class & declarative — you configure it, the tool does it |
| ➕ |
Available, but via a marketplace action / plugin / extra service you wire up |
| 🔧 |
Possible only by writing it yourself (scripts + direct backend API calls) |
| 🚧 |
Designed but gated / experimental in StageFreight — not yet GA |
| — |
Not meaningfully a thing on that platform |
DIY effort = how much work it is to pull off yourself / talk to the backend directly:
Low (a few lines) · Med (a real script + auth) · High (a subsystem of its own).
The Specialized tool column names the best-in-class single-purpose tool, so you can see what
StageFreight is consolidating — and where that dedicated tool is still the stronger standalone choice.
1. Build
| Capability |
StageFreight |
GitHub Actions |
GitLab CI |
Specialized tool |
DIY effort |
| Multi-platform container build |
✅ |
➕ buildx action |
🔧 buildx by hand |
➕ buildx / depot |
Med |
| Go binary build (full multi-OS/arch cross-compile) |
✅ |
➕ setup-go + matrix |
🔧 matrix by hand |
➕ goreleaser |
Med |
| Rust (cargo) binary build — host-only (no cross-compile / no crucible) |
✅ |
➕ setup-rust + matrix |
🔧 matrix by hand |
➕ cargo-dist |
Med |
| 7 more languages (containerized): Node/Electron · .NET (per-RID) · JVM (Java/Kotlin/Scala) · Python (wheel/PyInstaller) · C/C++ (CMake/Meson/Make) · Elixir · Android (signed APK) |
✅ |
➕ per-language setup + matrix |
🔧 per-language |
➕ per-language toolchain |
Med |
kind: command build type (escape hatch — any image/command, capture declared outputs) |
✅ |
✅ run: |
✅ script: |
— |
Low |
| Reproducible build self-proof (crucible) |
✅ |
🔧 |
🔧 |
— (niche: rebuilderd) |
High |
| Build cache (local + registry-backed) |
✅ |
➕ cache action |
➕ cache: |
➕ buildx cache |
Med |
| Build ordering / dependency graph |
✅ |
➕ needs: |
✅ needs: |
— |
Low |
Typed test suites — go/rust native-flag projections (-race, -tags, features), script escape hatch, auto-synthesized from builds |
✅ |
➕ setup + run steps |
🔧 |
➕ per-tool |
Med |
Coverage floor gate (coverage_min fails the suite) |
✅ |
➕ codecov |
➕ coverage regex |
✅ codecov |
Med |
| UPX binary compression |
✅ |
🔧 |
🔧 |
➕ goreleaser |
Low |
| Docker-compose drift detection |
✅ |
🔧 |
🔧 |
➕ ansible/terraform |
High |
Backends · how you'd otherwise do it · tutorials · since
- **Backends:** Docker **buildx/BuildKit** (`docker buildx build`, OCI-layout export, digest capture), native **Go** + **Rust (cargo)** toolchains, and **7 containerized language builders** — Node/Electron, .NET (per-RID self-contained), JVM (Java/Kotlin/Scala/Groovy/Clojure → jar via Maven/Gradle), Python (wheel/sdist or PyInstaller), C/C++ (CMake/Meson/Make), Elixir (mix release), Android (signed APK) — plus a `kind: command` **escape-hatch builder** (any image/command, declared `outputs[]`). Governed Go SDK download — see §11. buildkitd or DinD host. **Cross-compile:** free-form GOOS/GOARCH for Go; per-RID/image for container builders where the toolchain supports it; **Rust is host-only**.
- **Crucible** = two passes: pass-1 gestation (`--output type=oci`, never pushed), pass-2 re-runs the candidate natively and **diffs the bytes** to prove the image is reproducible. Almost no CI platform does this for you — by hand you'd build twice, extract artifacts, and compare digests across a coherent backend.
- **Otherwise:** hand-write `docker buildx` invocations, parse `--metadata-file`/digest output, manage `--cache-from`/`--cache-to`, and (for binaries) a build matrix per OS/arch with ldflags version injection.
- **Tutorials:** [docker/build-push-action](https://github.com/docker/build-push-action) · [buildx cache](https://docs.docker.com/build/cache/backends/) · [goreleaser builds](https://goreleaser.com/customization/builds/) · [reproducible-builds.org](https://reproducible-builds.org/)
- **Since:** binary build engine + caching shipped through v0.6.1 (Go caches on the `/stagefreight` mount, commit `12b8806`; cache-state row `67d916f`). Crucible: ≤ v0.6.0 *(TODO: pin commit)*.
2. Publish to Registries
| Capability |
StageFreight |
GitHub Actions |
GitLab CI |
Specialized tool |
DIY effort |
| Push to one registry |
✅ |
✅ login+push |
✅ |
✅ docker |
Low |
| Push to many registries (8 providers) |
✅ |
🔧 per-registry |
🔧 per-registry |
➕ crane/regctl |
Med |
Tag templating ({version}, {major}.{minor}, {sha:8}) |
✅ |
🔧 |
🔧 |
➕ docker/metadata-action |
Med |
| Sync README to registry (Docker Hub, Harbor, Quay…) |
✅ |
➕ dockerhub-description |
🔧 |
— |
Med |
| Publish GitLab CI component to Catalog |
✅ |
— |
➕ manual |
— |
Med |
| Native registry scan trigger (Harbor Trivy) |
✅ |
🔧 |
🔧 |
— |
Med |
Backends · how you'd otherwise do it · tutorials · since
- **Backends:** Docker Hub (v2 + token-auth), GHCR (packages API), GitLab registry, **Harbor** (v2.0 — push, scan trigger, project ensure, description), JFrog Artifactory/JCR, Quay (v1), Gitea/Forgejo packages, local Docker daemon, generic OCI Distribution.
- **Otherwise:** for each registry, hand-write the token-auth dance, push, and (for description sync) the provider-specific description API — Docker Hub caps short desc at 100 chars / full at 25 KB; Harbor/Quay each differ.
- **Tutorials:** [GHA: push to multiple registries](https://docs.docker.com/build/ci/github-actions/multi-registry/) · [docker/metadata-action](https://github.com/docker/metadata-action) · [peter-evans/dockerhub-description](https://github.com/peter-evans/dockerhub-description) · [GitLab Catalog](https://docs.gitlab.com/ci/components/)
- **Since:** ≤ v0.6.0 *(TODO: pin commits per provider)*.
3. Retention & Cleanup
| Capability |
StageFreight |
GitHub Actions |
GitLab CI |
Specialized tool |
DIY effort |
| Restic-style tag retention (keep_last/daily/weekly/monthly/yearly) |
✅ |
🔧 |
➕ cleanup policy (GitLab-only) |
— |
High |
| Retention across any registry |
✅ |
🔧 |
— |
➕ regctl scripts |
High |
| Protect tag patterns from deletion |
✅ |
🔧 |
➕ regex keep |
— |
Med |
Per-series retention grouping — series by template+identity, keep_branches, rolling tags auto-exempt |
✅ |
— |
— |
— |
High |
Local daemon image pruning (--load dev builds) |
✅ |
🔧 |
🔧 |
🔧 docker rmi |
Low |
| BuildKit cache prune + retention |
✅ |
➕ gha cache evicts |
🔧 |
🔧 |
Med |
| Host hygiene (dangling images, exited containers, networks) |
✅ |
— |
— |
🔧 docker system prune |
Med |
Backends · how you'd otherwise do it · tutorials · since
- **Backend:** each registry's delete API + shared-digest protection (won't delete a tag whose digest another kept tag still points at).
- **Otherwise:** GitLab has a built-in tag cleanup policy, but it's GitLab-only; everywhere else you hand-write the "list tags → apply keep policy → DELETE the losers" engine per registry, plus the digest-sharing safety. This is a genuine subsystem to build well.
- **Tutorials:** [GitLab cleanup policy](https://docs.gitlab.com/user/packages/container_registry/reduce_container_registry_storage/) · [restic forget policy](https://restic.readthedocs.io/en/stable/060_forget.html) (the model SF borrows) · [regctl](https://github.com/regclient/regclient)
- **Since:** ≤ v0.6.0 *(TODO)*.
4. Versioning & Tagging
| Capability |
StageFreight |
GitHub Actions |
GitLab CI |
Specialized tool |
DIY effort |
| Version derived from git (no version file) |
✅ |
➕ git-describe action |
🔧 |
➕ semantic-release |
Med |
| Tag-lineage sources (stable vs prerelease channels) |
✅ |
🔧 |
🔧 |
➕ semantic-release |
High |
Per-branch version formats (e.g. dev-{sha}) |
✅ |
🔧 |
🔧 |
— |
Med |
| Policy-enforced tag planner + approval |
✅ |
🔧 |
🔧 |
➕ semantic-release |
Med |
| Generated tag annotation / highlights |
✅ |
➕ changelog action |
➕ |
✅ git-cliff |
Med |
Backends · how you'd otherwise do it · tutorials · since
- **Backend:** git (tags, lineage), the glossary/change-language engine (§12) for highlights.
- **Otherwise:** semantic-release/git-cliff cover changelog + version bump well, but channel lineage ("this prerelease descends from which stable line") and per-branch version formats are usually bespoke bash around `git describe`.
- **Tutorials:** [semantic-release](https://semantic-release.gitbook.io/) · [git-cliff](https://git-cliff.org/) · [GHA git describe](https://github.com/marketplace/actions/git-describe)
- **Since:** `stagefreight tag` planner ≤ v0.6.0; v0.6.1 cut with it *(TODO: pin commit)*.
5. Releases & Artifacts
| Capability |
StageFreight |
GitHub Actions |
GitLab CI |
Specialized tool |
DIY effort |
| Create a forge release |
✅ |
➕ gh-release action |
✅ release-cli |
✅ goreleaser |
Low |
| Binary archives (tar.gz/zip) + SHA256SUMS |
✅ |
🔧 |
🔧 |
✅ goreleaser |
Med |
Rolling git-tag aliases (v1, v1.2) |
✅ |
🔧 |
🔧 |
➕ |
Med |
| Cross-forge release sync (GitLab→GitHub mirror) |
✅ |
🔧 |
🔧 |
— |
High |
Mirror reconciliation engine — provenance-bounded (only SF-authored releases), retention-driven desired state, upsert + destroy |
✅ |
— |
— |
— |
High |
| Safe mirror refs plane (keep-divergent, foreign-sacred prune) + per-repo sync facets × scopes (exact / current / drafts) with topology view |
✅ |
— |
— |
— |
High |
Prerelease / Latest classification, per-forge (type: → GitHub make_latest) |
✅ |
➕ (release flags) |
🔧 |
➕ goreleaser |
Med |
| Security summary embedded in release notes |
✅ |
🔧 |
🔧 |
— |
Med |
Release notes as a stencil — the body owns the language; targets reference it (notes:); AI embeds are the author's choice |
✅ |
— |
— |
➕ goreleaser templates |
High |
Backends · how you'd otherwise do it · tutorials · since
- **Backends:** GitHub / GitLab / Gitea / Forgejo release APIs (asset upload, release links, rolling tags). **Azure DevOps: releases honestly return `ErrNotSupported`** (no native git-release object) — see [honest status](#honest-status).
- **Otherwise:** goreleaser is the strongest standalone here (archives, checksums, release, on GitHub/GitLab). What it doesn't do is mirror a release to a *second* forge with its own identity, or thread your scan/advisory summary into the notes — those stay bespoke.
- **Tutorials:** [goreleaser](https://goreleaser.com/) · [softprops/action-gh-release](https://github.com/softprops/action-gh-release) · [GitLab release-cli](https://docs.gitlab.com/ci/yaml/#release)
- **Since:** release-notes-as-stencil v0.8.0 (byte-parity gated migration); binary archives + SHA256SUMS v0.6.1; release core ≤ v0.6.0.
6. Lint / Code Quality
| Capability |
StageFreight |
GitHub Actions |
GitLab CI |
Specialized tool |
DIY effort |
| Delta-only (changed-files) linting |
✅ |
➕ paths-filter |
➕ rules:changes |
➕ pre-commit |
Med |
| Cache-aware lint with TTL eviction |
✅ |
➕ cache action |
➕ cache: |
➕ pre-commit |
Med |
| Built-in modules (secrets, tabs, line endings, freshness…) |
✅ |
➕ many actions |
➕ many |
✅ pre-commit/megalinter |
Med |
| ansible-lint from the execution image, over declared plays only (graduated severities) 🚧 |
✅ |
➕ action |
➕ |
✅ ansible-lint |
Low |
Backends · how you'd otherwise do it · tutorials · since
- **Backend:** local filesystem + git (diff vs target branch); embeds gitleaks-style secret scanning.
- **Otherwise:** pre-commit / MegaLinter are the standalone analogs and have a larger rule ecosystem; StageFreight's value is that lint is one phase of the same lifecycle with shared caching, not a separate tool to wire.
- **Tutorials:** [pre-commit](https://pre-commit.com/) · [MegaLinter](https://megalinter.io/) · [GHA paths-filter](https://github.com/dorny/paths-filter)
- **Since:** ≤ v0.6.0 *(TODO)*.
7. Security & Supply Chain
| Capability |
StageFreight |
GitHub Actions |
GitLab CI |
Specialized tool |
DIY effort |
| Image vuln scan (Trivy + Grype) |
✅ |
➕ trivy-action |
➕ template |
✅ trivy/grype |
Med |
| SBOM (SPDX + CycloneDX) |
✅ |
➕ sbom-action |
➕ |
✅ syft |
Med |
| Scan the exact built bytes (OCI layout, no registry round-trip) |
✅ |
🔧 |
🔧 |
🔧 |
High |
| Cross-pipeline advisory bridge |
✅ |
🔧 |
🔧 |
— |
High |
| Image / artifact signing (key · keyless/OIDC · KMS · hardware) |
🚧 |
➕ cosign-installer |
➕ |
✅ cosign |
Med |
| Provenance / OCI labels |
✅ |
➕ provenance attest |
➕ |
✅ cosign attest |
Med |
Backends · how you'd otherwise do it · tutorials · since
- **Backends:** Trivy, Grype, Syft, osv-scanner, govulncheck, cosign — all **governed/verified downloads** (§11). Scans can read the content-store **OCI layout directly** (`trivy --input` / `grype oci-dir:`), so you scan the bytes you built and review, not a re-pulled copy.
- **Signing is implemented but not yet verified (🚧 experimental).** The mechanism is selected from a declared **signing profile** via a cosign class solver (`src/sign/cosign/render.go`): `key`, **keyless/OIDC** (Fulcio/Rekor, `SF_SIGSTORE_*` deployment env), **KMS** (logical key ref), and **hardware** (PKCS#11/YubiKey, FIDO2 `--sk`) with hardware-class assurance (physical-presence, non-exportable) enforced at validation; Tier-0 auto-provision anchors an ephemeral keyless identity when no key/profile resolves. So it is far past "hardcoded `--key`" — the code path exists for every class — **but it has not been exercised/verified end-to-end**, so treat it as experimental until proven. Signed `SHA256SUMS` bundles remain on the roadmap. See `docs/design/plans/signing-trust-model.md`.
- **Tutorials:** [aquasecurity/trivy-action](https://github.com/aquasecurity/trivy-action) · [anchore/sbom-action](https://github.com/anchore/sbom-action) · [sigstore/cosign](https://docs.sigstore.dev/cosign/signing/signing_with_containers/) · [SLSA provenance](https://slsa.dev/)
- **Since:** scan/SBOM/advisory ≤ v0.6.0; signing profile/class-solver (keyless/KMS/hardware) **coded** in the v0.6→v0.7 line but **unverified** *(TODO: pin commit; verify end-to-end)*.
8. Dependency Updates
| Capability |
StageFreight |
GitHub Actions |
GitLab CI |
Specialized tool |
DIY effort |
| Go module updates + verify |
✅ |
➕ dependabot |
➕ renovate |
✅ renovate |
Med |
Dockerfile base-image FROM updates |
✅ |
➕ dependabot |
➕ renovate |
✅ renovate |
Med |
| Post-update vuln check (govulncheck) |
✅ |
🔧 |
🔧 |
➕ |
Med |
| Direct-commit or MR promotion mode |
✅ |
➕ (PR only) |
➕ (MR only) |
➕ renovate |
Med |
| Toolchain metadata recorded for SBOM |
✅ |
— |
— |
— |
High |
Backends · how you'd otherwise do it · tutorials · since
- **Backends:** Go toolchain (`go get`/`go mod tidy` via governed Go SDK), Dockerfile parsing, GitHub releases (for `FROM` tag bumps), the forge (for MR mode).
- **Honest scope:** **Renovate** covers far more ecosystems (npm, pip, gradle, helm, …) and is the stronger standalone updater. StageFreight's edge is that updates are part of *its* lifecycle (verify → vuln-check → its commit/MR conventions → its release notes), Go- and Dockerfile-focused for now.
- **Tutorials:** [Renovate](https://docs.renovatebot.com/) · [Dependabot](https://docs.github.com/code-security/dependabot)
- **Since:** direct/MR promotion present; this session confirmed `promotion: direct` is the StageFreight-repo default. ≤ v0.6.0 *(TODO)*.
9. Docs, Badges & READMEs
| Capability |
StageFreight |
GitHub Actions |
GitLab CI |
Specialized tool |
DIY effort |
| Own SVG badges (no shields.io dependency) |
✅ |
🔧 |
🔧 |
➕ shields.io |
Med |
| Marker-section injection into README/any file |
✅ |
➕ readme actions |
🔧 |
➕ markdown-magic |
Med |
| Sync README to Docker Hub / registries |
✅ |
➕ dockerhub-description |
🔧 |
— |
Med |
Cross-destination project identity (kind: metadata) — descriptions, topics, website, logo fanned to registries + forges |
✅ |
🔧 |
🔧 |
— |
High |
| Generate CLI/config reference docs from code |
✅ |
🔧 |
🔧 |
➕ cobra docs |
Med |
Render build manifest contents into docs (build-contents) |
✅ |
🔧 |
🔧 |
— |
High |
| Auto-commit generated docs (with skip-ci classification) |
✅ |
➕ git-auto-commit |
🔧 |
— |
Med |
Builds and scribe items in one dependency graph — typed depends_on, a build can consume rendered scribe output |
✅ |
— |
— |
— |
Med |
Backends · how you'd otherwise do it · tutorials · since
- **Backends:** local files + git (auto-commit), registry description APIs, shields.io (only for the external `kind: props` badges; native `kind: badge` SVGs are self-owned).
- **Otherwise:** stitch together shields.io URLs, a README-injection action, a Docker Hub description action, and cobra-doc generation — each separate. The **`build-contents`** renderer (image inventory → README table) and the generated-commit **skip-ci classification** (a docs commit is synchronization output, not source intent — so it shouldn't re-trigger the lifecycle) are SF-specific.
- **Tutorials:** [shields.io](https://shields.io/) · [peter-evans/dockerhub-description](https://github.com/peter-evans/dockerhub-description) · [cobra doc gen](https://github.com/spf13/cobra/blob/main/site/content/docs/generating_documentation.md)
- **Since:** stencils extracted as the shared text-composition library v0.8.0; scribe `build:` ownership selector + docs `skip_ci` classification both v0.6.1 (`7d0feb1`, `bfca092`); scribe/badges core ≤ v0.6.0 (as "narrator").
10. Transport & Trust (the differentiator)
| Capability |
StageFreight |
GitHub Actions |
GitLab CI |
Specialized tool |
DIY effort |
| Content-addressed store carrying bytes across phases |
✅ |
🔧 artifacts |
🔧 artifacts |
— |
High |
| Reviewed bytes == published bytes guarantee |
✅ |
🔧 |
🔧 |
— |
High |
| Verify-on-write (digest re-hash between phases) |
✅ |
🔧 |
🔧 |
— |
High |
| Perform / Review / Publish phase split |
✅ |
🔧 stage gates |
🔧 stage gates |
— |
High |
Build/publish manifest (outputs.json/published.json) |
✅ |
🔧 |
🔧 |
— |
Med |
Backends · how you'd otherwise do it · tutorials · since
- **Backend:** a CAS (content-addressed store, FSStore today) carrying an OCI layout perform→review→publish; publish is the *sole* distributor.
- **Why it matters:** on raw CI, "build in one job, scan in another, push in a third" usually means each job **re-pulls or re-builds**, so what you scanned isn't provably what you shipped. SF's transport keeps the exact reviewed bytes and re-verifies the digest before distribution. This is the architectural heart of the project and has no off-the-shelf equivalent.
- **Tutorials:** conceptually adjacent: [SLSA build provenance](https://slsa.dev/spec/v1.0/provenance) · [in-toto attestations](https://in-toto.io/).
- **Since:** the perform/review/publish + manifest split is the v0.5→v0.6 line of work (domain-spine refactor + manifest `outputs.json`/`published.json` split, shipped through v0.6.x) *(TODO: pin commits)*.
| Capability |
StageFreight |
GitHub Actions |
GitLab CI |
Specialized tool |
DIY effort |
| Resolve + checksum-verify + cache build tools |
✅ |
➕ setup-* (per tool) |
🔧 |
➕ asdf/mise |
Med |
| One substrate for Go, Trivy, Syft, Grype, cosign, flux, kubectl, osv |
✅ |
🔧 (8 separate actions) |
🔧 |
➕ mise |
High |
| Pin versions in config, hard-fail if unresolvable |
✅ |
➕ |
🔧 |
➕ mise |
Med |
| No host-PATH fallback, no DinD requirement |
✅ |
— |
— |
— |
High |
Backends · how you'd otherwise do it · tutorials · since
- **Backend:** official download URLs + published checksums for each tool; immutable cache with `.metadata.json` provenance, file-locked installs, persistent on the `/stagefreight` mount.
- **Otherwise:** a `setup-go` + a trivy-installer + a cosign-installer + … per tool, none of which give you a single governed, checksum-verified, provenance-recorded cache. mise/asdf are the closest standalone idea but don't verify against official checksums the same way.
- **Tutorials:** [mise](https://mise.jdx.dev/) · [actions/setup-go](https://github.com/actions/setup-go)
- **Since:** ≤ v0.6.0; persistent Go-cache path on the mount confirmed v0.6.1 (`12b8806`).
12. Orchestration, Conventions & Infra Modes
| Capability |
StageFreight |
GitHub Actions |
GitLab CI |
Specialized tool |
DIY effort |
| CI-vendor portability — one config → GitLab · GitHub · Gitea · Forgejo · Azure 🚧 |
✅ |
— |
— |
— |
High |
| Render the CI file itself from one config |
✅ |
— |
— |
— |
High |
| Conventional-commit planner + change language (glossary) |
✅ |
➕ commitlint |
➕ |
✅ commitizen |
Med |
Templated commit / tag / release message rendering (commit.render · tagging.render · release.render) |
✅ |
🔧 |
🔧 |
➕ git-cliff |
Med |
| Commit backend with converge push — fetch/fast-forward/object-replay before pushing; forge-API commit mode |
✅ |
🔧 |
🔧 |
— |
High |
Config presets with provenance-aware resolution (config resolve shows where every value came from) |
✅ |
— |
➕ includes |
— |
High |
Unified when: gating grammar — events/branches/git_tags/outcomes as OR-lists, shared by publish targets and notifications |
✅ |
🔧 if: expressions |
➕ rules: |
— |
Med |
| Repository reconciliation — the repo converges to its encoded intent (builder⇄go-floor, generated files) |
✅ |
— |
— |
— |
High |
| Generated-commit skip-ci policy (no self-triggering loops) |
✅ |
🔧 |
🔧 |
— |
Med |
| GitOps reconcile (Flux / [Argo 🚧]) + change-impact |
✅ |
🔧 flux scripts |
🔧 |
✅ flux/argo |
Med |
| K8s endpoint exposure classification |
✅ |
— |
— |
— |
High |
| Control-repo / multi-repo governance mode |
✅ |
🔧 |
🔧 |
— |
High |
| Ansible host convergence — declared playbook library, containerized runtime, converge-on-commit 🚧 |
✅ |
🔧 |
🔧 |
➕ AWX/Semaphore |
High |
| Cross-pipeline reconcile serialization (perform mutual exclusion) |
✅ |
➕ concurrency |
✅ resource_group |
— |
Low |
Backends · how you'd otherwise do it · tutorials · since
- **Backends:** the forge as a **render target** — five wired emitters (`gitlab`, `github`, `gitea`, `forgejo`, `azuredevops`), each forge-native with golden tests; git (commits), Flux/Argo + Kubernetes (gitops), Docker (compose drift), ansible-in-a-container (host convergence — the execution image owns the runtime + collections).
- **Ansible vs AWX/Semaphore:** those give you a web UI, run history, and schedules — but as another stateful service to operate. SF ties convergence to the commit that changed the intent (Renovate bumps a pin → merge → nodes roll → phone ping), with a trust posture (pinned execution image, committed known_hosts, forge-protected key) neither ships. Runbooks (`converge: false`) are declared-but-CI-unreachable: lint + `--check` preview, human-run only.
- **Portability / no lock-in:** `render.Emit(forge, …)` dispatches to all five from one forge-neutral pipeline model, so the *same* `.stagefreight.yml` renders `.gitlab-ci.yml`, GitHub Actions workflows, Gitea/Forgejo, or Azure pipelines. To migrate CI hosts by hand you'd rewrite every pipeline in the new vendor's YAML dialect; here it's `stagefreight ci render --write`. (Azure DevOps is experimental.)
- **`ci render` is unusual:** instead of you maintaining `.gitlab-ci.yml`/workflows by hand, SF generates them from `.stagefreight.yml`. The thing other tools assume you write, SF treats as output. (Caveat from real use: because it's generated + dogfooded, a CI-skeleton or config-schema change can require regeneration — see KnownIssues.)
- **Tutorials:** [commitlint](https://commitlint.js.org/) · [Flux](https://fluxcd.io/) · [Argo CD](https://argo-cd.readthedocs.io/)
- **Since:** ansible host convergence + perform serialization on main post-v0.8.0 (🚧 unreleased — ships with the next release); generated-commit skip-ci classification v0.6.1 (`bfca092`/`124f3ba`); glossary + ci-render ≤ v0.6.0.
13. Static-Site / Pages Deployment
| Capability |
StageFreight |
GitHub Actions |
GitLab CI |
Specialized tool |
DIY effort |
| Deploy to Cloudflare Pages (native Direct Upload — no wrangler/npm) |
✅ |
➕ wrangler action |
🔧 |
➕ wrangler |
Med |
| Deploy to GitHub Pages (gh-pages force-push + CNAME) |
✅ |
✅ pages action |
🔧 |
➕ |
Low |
| Multiple custom domains per project (Cloudflare) |
✅ |
🔧 |
🔧 |
🔧 |
Med |
| DNS-aware attach guidance (authoritative-NS classification) |
✅ |
— |
— |
— |
High |
| Same content → container image AND Pages from one config |
✅ |
🔧 |
🔧 |
— |
Med |
Backends · how you'd otherwise do it · tutorials · since
- **Backends:** Cloudflare Pages **Direct Upload API** — a faithful port of wrangler's protocol (no wrangler, no npm, no Docker): SF makes the CF API calls itself. GitHub Pages via go-git force-push to `gh-pages` (+ `.nojekyll`/`CNAME`, no git binary). The Cloudflare path idempotently ensures the project, attaches **each** listed custom domain, and classifies the domain's authoritative nameservers to tailor DNS guidance — the attach is **non-fatal**, reported as data so a domain hiccup never fails a deploy.
- **Otherwise:** wire a `wrangler` action (Node toolchain + API token) or the GitHub Pages action per repo; multi-domain attach and DNS-provider-aware messaging you'd script by hand.
- **Tutorials:** [Cloudflare Pages Direct Upload](https://developers.cloudflare.com/pages/get-started/direct-upload/) · [actions/deploy-pages](https://github.com/actions/deploy-pages)
- **Since:** Cloudflare/GitHub pages providers ≤ v0.6.x; multi-domain (`domain:` list) + non-fatal DomainOutcome + the `8000018` idempotent-attach fix landed in the v0.7 line *(TODO: pin commit)*.
14. Narration, Notifications & AI
| Capability |
StageFreight |
GitHub Actions |
GitLab CI |
Specialized tool |
DIY effort |
| Run summaries as stencils — union bodies of facts, per-line elision, override by shadowing |
✅ |
— |
— |
— |
High |
Phone/webhook notifications with outcome gating (when: outcomes:) |
✅ |
➕ slack actions |
➕ integrations |
✅ ntfy/apprise |
Med |
| Full ntfy vocabulary (priority, tags, click, attach, actions, email) |
✅ |
— |
— |
✅ ntfy |
Low |
AI narration — type: llm stencils over an llms: endpoint library (failure triage, success recap) |
✅ |
— |
— |
— |
High |
| Documented + ratcheted fact vocabulary (docs test fails undocumented facts) |
✅ |
— |
— |
— |
High |
Backends · how you'd otherwise do it · tutorials · since
- **Backends:** ntfy (full header vocabulary) and webhooks for dispatch; ollama for AI narration (openai/anthropic/claude-agent reserved behind the same `llms:` shape).
- **Otherwise:** a slack-notify action with a hand-rolled message template per repo, no shared fact vocabulary, no elision (empty sections render as noise), and no story arc — SF renders ONE union body per outcome arc that any modality mix composes into. AI-wise: nothing comparable is integrated in CI tooling; you'd script a model call and paste output into a webhook.
- **Contracts that make AI safe here:** degrade-to-empty (a down model never fails the pipeline or sends a broken ping), per-run memoization, `` stripping, and the dispatch-only boundary — AI text reaches notifications, stdout cards, and (author's choice) release bodies, never the committed record.
- **Tutorials:** [ntfy](https://ntfy.sh/) · [ollama](https://ollama.com/)
- **Since:** the whole family v0.8.0 — stencil engine (`type: text/ci/llm`), cistate facts, notifications with `when: outcomes:`, llms library, union-body summaries; fact-vocabulary ratchet post-v0.8.0.
Appendix A — Backends StageFreight speaks for you
Every row below is a system you'd otherwise authenticate to and call by hand.
| Category |
Backends |
What SF does against them |
| Container registries |
Docker Hub, GHCR, GitLab, Harbor, JFrog, Quay, Gitea/Forgejo, local daemon, generic OCI |
push, tag list, retention/delete, README sync, native scan trigger (Harbor), referrer discovery, digest verify |
| Git forges |
GitHub/GHES, GitLab, Gitea, Forgejo, Azure DevOps 🚧 |
releases, asset upload, multi-file commits, tags, MRs/PRs, pipeline cancel, artifact download |
| Toolchains |
Go SDK, Trivy, Syft, Grype, osv-scanner, cosign, flux2, kubectl |
resolve → checksum-verify → cache → invoke by absolute path |
| Build |
Docker buildx / BuildKit, buildkitd, DinD, CAS content store |
multi-arch build, OCI export, cache, crucible 2-pass, byte transport |
| Security |
cosign 🚧, Trivy, Grype, Syft, osv-scanner, govulncheck |
sign (key/keyless/KMS/hardware — coded, unverified)/attest, scan, SBOM, advisory bridge, provenance |
| Infra |
Kubernetes, Flux / [Argo 🚧], Docker Compose, Ansible inventory |
gitops reconcile/impact, compose drift, exposure classification |
| Static hosting |
Cloudflare Pages, GitHub Pages |
Direct-Upload deploy, multi-domain attach, authoritative-NS classification, CNAME |
Honest status
Things this matrix marks 🚧 or that deserve a caveat, stated plainly:
- Signing is coded but unverified (experimental). Mechanism selection — key, keyless/OIDC (Fulcio/Rekor), KMS, and hardware (PKCS#11/YubiKey/FIDO2) — exists via signing profiles + a cosign class solver, with hardware-class assurance enforced. It is well past "hardcoded
--key," but has not been exercised/verified end-to-end — don't rely on it in production until proven. Signed SHA256SUMS bundles remain on the roadmap.
- Azure DevOps is experimental and honestly returns
ErrNotSupported for releases (no native git-release object).
- OIDC is wired for keyless signing (Sigstore identity) and gitops; it is not a dead seam. Its main consumer (signing) is itself unverified (above), so exercise the OIDC path when validating signing.
- Multi-arch crucible is deferred (arm64 ships via binaries; the image is single-arch by design for now).
- Dependency updates are Go + Dockerfile focused; Renovate covers more ecosystems.
- Pre-1.0: the config schema isn't frozen — expect breaking changes across versions, and regenerate the CI skeleton after upgrades.
manifest diff is declared but not yet implemented.
- AI narration is plumbing-solid, output-modest: with small local models (the dogfood runs deepseek-r1:1.5b) the triage/recap text is entertainment-grade; the contracts (degrade-to-empty, dispatch-only) are the hardened part. Point
llms: at a stronger backend for serious summaries.
- Ansible host convergence is on main, unreleased — ships with the next release; converge idempotency is the playbook author's contract (SF provides the per-host gate pattern in the docs).
Where the matrix shows StageFreight's real, hard-to-replicate value: crucible reproducibility, multi-registry retention, the perform/review/publish byte-transport trust model, governed toolchains, and rendering the CI file itself — these are the High-DIY-effort rows you'd otherwise build and maintain yourself.
Maintaining this doc
- Coverage (rows) is complete as of v0.8 + the post-v0.8 main line — this pass added §14 Narration/Notifications/AI, the release-notes-stencil row, ansible host convergence + perform serialization (🚧 unreleased), and retired the stale
--help caveat. When a feature lands, add its row.
- Update discipline: coverage is judged against the SHIP LOG, not memory — before editing,
run
git log --pretty=%s $(git log -1 --format=%H -- docs/features.md)..HEAD | grep -E '^feat'
(every feature since the matrix's own last edit) and evaluate every line against the rows.
- Provenance (the "Since" lines) is being filled in. To pin a feature's introducing commit:
git log --oneline --reverse -- <path/to/feature> | head -1, or map to the release tag it first shipped in (git tag --contains <commit>).
- Keep the comparison columns fair — the alternatives are capable; the story is declarative + integrated, not only-SF-can.
- Mark anything not-yet-GA as 🚧 and add it to Honest status. Overstating readiness burns trust the first time an adopter hits the gap.