The CI tier is whatever the workload actually needs. Build it from marketplace actions, version-pin every dependency, and treat releases as permanent.The policy on this page applies to every workflow under
JacobPEvans/* and dryvist/*. The companion page git-signing covers identity and signed-commit attribution; this page covers everything else.
Prefer native CI/CD constructs
| Task | Use this | Not this |
|---|---|---|
| CI/CD automation | Marketplace actions, reusable workflows, composite actions | Custom shell script |
| GitHub Actions logic | Expressions, fromJSON(), matrix strategies | Python or bash in a run: step |
.github/scripts/*.sh, scripts/*.py), not inlined into YAML — see No scripts in non-script files for the full rule.
GitHub Releases
Treat published releases as permanent. Once a release is promoted from draft to published, do not modify or delete it — ever. GitHub technically allows edits and deletions; the policy here forbids it. If a correction is needed, create a new release.- Open releases as drafts until fully complete (all assets uploaded, notes finalized).
- Promote draft to published only when everything is ready.
- Every repo uses release-please for automated version bumps:
- Patch bumps come from
fix:commits - Minor bumps come from
feat:commits - Major bumps are human-initiated only — edit
.release-please-manifest.jsonmanually. Automated major bumps (including fromBREAKING CHANGE:footers) are blocked by the release workflow
- Patch bumps come from
- Conventional-commit style preference:
fix:for config tweaks, small improvements, incremental adjustments, dependency updatesfeat:reserved for genuinely new capabilities, integrations, or significant behavioral changes
- Reusable workflows are homed in
dryvist/.github(the canonical org home) — see Reusable workflow ownership for the few still being relocated
Dependency versioning
See Dependency automation for the full trust-tier, scheduling, automerge, and version-pinning policy — including which action sources ride@main, which take SemVer tags, and which are SHA-pinned.
Scanner posture for self-references
@main on a self-owned workflow is deliberate — consumers ride upstream fixes the moment they land, with no per-repo Renovate PR. SemVer tags still exist and release-please keeps bumping them for the changelog and the per-run resolved-SHA audit trail, but callers ride @main. Every org scanner is configured to allow it for dryvist/* action sources:
- Renovate —
pinDigests: falsefordryvist/**overrides the global digest-pinning, so@mainis never rewritten to a SHA. - zizmor —
unpinned-usespolicydryvist/*: ref-pin. - CodeQL — code scanning default setup on public repos only (free; private repos excluded to avoid the paid Code Security charge), managed as IaC in
dryvist/tofu-github(per-repo, pending the provider resource). No committed query-filter config; same-org@mainfalse positives (github/codeql#18316) are dismissed in the code scanning UI. - OSV-Scanner — reports dependency vulnerabilities, not ref pinning, so
@mainis never flagged.
dryvist/.github → SECURITY.md.
Reusable workflow ownership
Reusable GitHub Actions workflows are referenced by a literalowner/repo in uses: — GitHub allows no variables there and does not follow repository move or rename redirects for those references. So when a shared-CI repository changes ownership, every consumer’s uses: must be updated at the same time.
dryvist is the canonical home for everything dryvist uses — JacobPEvans-personal/* may depend on dryvist/*, never the reverse. Reference each workflow by its literal current owner below until a pending relocation lands:
| Workflow set | Current home | Status |
|---|---|---|
ai-workflows reusable workflows | dryvist/ai-workflows | canonical |
| Nix + release-please reusable workflows | dryvist/.github | canonical |
Other shared .github workflows (_markdown-lint, _ci-gate, …) | JacobPEvans-personal/.github | pending relocation to dryvist/.github |
Runner choice
RunsOn (AWS EC2 spot viatofu-runs-on) is the default for Linux jobs. The control plane carries a small fixed monthly cost regardless of job volume; once it is paid for, workflows that stay on ubuntu-latest spend GitHub Actions minutes that do not need to be spent.
On-prem self-hosted is the documented exception for jobs that genuinely need local hardware — not a routine choice.
| Workload | Runner |
|---|---|
| Linux job (lint, validate, build, test) | RunsOn — runs-on=${{ github.run_id }}/runner=2cpu-linux-x64 |
Nix flake check (Linux) | RunsOn with more RAM — runs-on=${{ github.run_id }}/cpu=4/ram=16/family=m7+c7/extras=s3-cache |
macos-latest | GitHub-hosted — RunsOn EC2 Mac has a 24-hour minimum allocation; for short jobs macos-latest is cheaper despite the 10× billing multiplier |
windows-latest | RunsOn — supports Windows; case-by-case |
*.lock.yml from gh-aw compile | GitHub-hosted — lock files are regenerated; runner label must flow through the .md companion (gh-aw does not expose this yet) |
Disabled-schedule workflow (manual workflow_dispatch only) | GitHub-hosted — migration saves nothing |
| Job requiring local hardware (OrbStack cluster, Proxmox LAN access, dev-loop instrumentation that cannot be replicated on a fresh EC2 spot instance) | on-prem self-hosted — [self-hosted, Linux, ARM64] (orbstack-kubernetes) or [self-hosted, Linux] (ansible-proxmox-apps); see “On-prem runner requirements” below |
runs-on=${{ github.run_id }} segment is required so the RunsOn control plane can correlate the GitHub Actions workflow_job webhook back to the originating run — without it, the job hangs in queued. The shared reusable workflows accept a runner_label input (default ubuntu-latest); callers opt in by passing the RunsOn label string.
Full RunsOn label catalog, prereqs (GitHub App allowlist), rollout playbook, and verification steps live in tofu-runs-on/docs/migration-guide.md.
On-prem runner requirements
On-prem self-hosted runners are a single point of failure when the job has E2E coverage and the runner is broken or stale. Every on-prem runner must:- Authenticate via a GitHub App (
APP_ID+APP_PRIVATE_KEY) — never a personal access token. App installation tokens auto-refresh; PATs silently expire and break the runner with no upstream visibility. - Pin the runner image (or VM template) by digest, with Renovate tracking the digest. No floating tags.
- Expose a process-level healthcheck (Docker
healthcheck:, systemdWatchdogSec, or equivalent) so failed state surfaces in standard inspection tools. - Emit a periodic heartbeat to a dead-man’s-switch endpoint (healthchecks.io or equivalent) so silent death pages someone.
- Pre-flight check the secret-injection step so it fails loud when required secrets are missing.
orbstack-kubernetes/docker/actions-runner/ — see that repo’s Makefile runner-* targets and TESTING.md.
Where to go next
Git signing
Identity per execution context and the App-token pattern that signs every AI-authored commit.
tofu-runs-on
The RunsOn runner pool itself — provisioning, OIDC trust, migration guide.
OpenTofu check placement
Static checks in pre-commit, credentialed operations in CI via OIDC.
CI/CD overview
Runner tiers in context, plan/apply gating, branch rulesets.