GitHub Workflows and CI/CD Pipeline¶
This guide describes the current public-alpha CI/CD workflows for Wright. Pull requests validate source, docs, dependencies, leak scanning, and exact container behavior. Release candidates are built once by a reusable workflow and promoted by digest. Public package, image, documentation, and GitHub Release publication happens only from release tags, with GHCR as the canonical registry path and Docker Hub as a required byte-identical distribution target.
Workflow Overview¶
| Workflow | Trigger | What it checks or publishes |
|---|---|---|
python-quality.yml |
Push or pull request to main or dev |
Python 3.13, uv sync --all-packages --all-groups, Ruff lint/format, warning-mode mypy, and uv run pytest. |
frontend-quality.yml |
Push or pull request to main or dev |
Node.js 22, npm ci, ESLint, Prettier, TypeScript, npm run test --workspace=apps/web, and npm run build --workspace=apps/web. |
test-windows.yml |
Push or pull request to main or dev, or manual run |
Runs backend pytest and frontend Vitest on windows-latest; live Playwright remains in the Linux frontend workflow. |
public-alpha-safety.yml |
Push, pull request, or manual run | Repo-native public-alpha leak scan, Gitleaks history scan, and TruffleHog history scan. |
codeql.yml |
Push or pull request to main or dev, plus weekly schedule |
Runs CodeQL for Python and JavaScript/TypeScript. |
dependency-review.yml |
Pull request to main or dev |
Blocks high-severity dependency changes and denied licenses except for reviewed allowlisted advisories. |
docker-pr.yml |
Pull request to main or dev when container/application inputs change |
Builds and loads wright:pr-<sha>, runs the exact-image smoke contract, collects a Trivy report, and enforces the blocking vulnerability policy. It does not publish public images. |
docker-build.yml |
Reusable workflow_call from release.yml |
Builds one amd64 OCI candidate, smokes and scans that exact subject, enforces vulnerability policy, records evidence, and optionally pushes and attests the candidate digest. |
docs-deploy.yml |
Push to main or dev, pull request to main or dev, or manual run |
Runs mkdocs build --strict; deploys GitHub Pages only for non-PR main builds. |
sync-hermes-plugin-mirror.yml |
Relevant push to main or dev, or manual run |
Generates and validates the thin Hermes plugin mirror, records provenance, and publishes the selected mirror branch when enabled. |
release-drafter.yml |
Push to main or dev |
Updates the draft release notes from merged PR metadata. |
release.yml |
Push to tag matching v*, or manual rehearsal |
Builds immutable Python and OCI candidates, installs and smokes them, then publishes/promotes/verifies only for a real tag. The PyPI actions run directly here so OIDC and package attestations share the same trusted workflow identity. Manual dispatch is a no-public-mutation rehearsal. |
Pull Request Gates¶
Pull requests to main or dev run source, frontend, Windows, docs, CodeQL,
dependency, and public-alpha safety gates. When container or application inputs
change, docker-pr.yml also builds and validates the exact PR image:
uv run pytest
npm run test --workspace=apps/web
npm run build --workspace=apps/web
mkdocs build --strict
python scripts/check-public-alpha-leaks.py
The frontend workflow also runs ESLint, Prettier, and TypeScript. The Python
workflow runs Ruff and mypy in warning mode. The docs workflow builds strictly on
pull requests and branch pushes but deploys only from main. The Docker PR gate
does not publish public images.
Local Merge Gates¶
Routine development can use targeted tests and make check. Before integrating
branches, use the heavier merge gates so local validation matches CI closely
enough to catch formatting, mocked UI, live Playwright, docs, package metadata,
Docker, and release drift.
Feature branch to dev:
make check-dev-merge
This runs scripts/check-dev-merge.sh, including git diff --check, Ruff lint
and format checks, ESLint, Prettier, TypeScript, mypy warning-mode checks,
Python package metadata validation, pytest, Hermes plugin pytest, Vitest,
frontend build, strict docs build, and Playwright with PLAYWRIGHT_INCLUDE_LIVE=1
against a temporary local API database.
dev to main:
make check-prod-merge
This runs scripts/check-prod-merge.sh, which includes the dev merge gate plus
public-alpha secret scans, alpha release checks, Docker smoke coverage, Hermes
plugin mirror validation, and Hermes plugin root lifecycle validation.
Use environment skip switches only for documented local host limitations, never to hide a failure. If a GitHub Actions job catches a failure that the local merge gate missed, update the corresponding script and documentation in the same fix.
Docker Smoke Contract¶
docker-pr.yml validates local PR images. The reusable docker-build.yml
validates the build-once release candidate and optionally pushes that candidate
for a real release. Both call scripts/docker-smoke-test.sh against the exact
image without rebuilding it:
- Confirm the image runs as the unprivileged
agentuser and run rawuv pip check. Hermes 0.19.0 exactly pins vulnerable cryptography and Pillow versions, soscripts/reconcile_hermes_pip_check.pyaccepts only Wright's exact two security-version overrides; any other conflict fails. - Validate the immutable manifest, entrypoint, basic execution, and ephemeral recovery behavior.
- Start a temporary container with placeholder
LLM_API_URL,LLM_API_KEY, andLLM_API_MODELvalues. - Wait for
http://127.0.0.1:8090/api/healthand require Wright to report the Hermes connection throughhttp://127.0.0.1:8090/api/agent/health. - Require both
wright-apiandhermes-gatewayto beRUNNINGin supervisord. - Probe the Hermes gateway directly on its internal port
8642.
The Trivy action uses exit code 0 so its JSON report is always available to
the next step. scripts.release.vulnerability_policy.evaluate_report then
applies the blocking vulnerability policy and fails on non-exempt, fixable High
or Critical findings. Scanner collection is non-terminal; policy enforcement is
blocking.
The PR workflow never logs in to a registry or publishes an image. The reusable candidate workflow pushes only when called by a real tagged release.
Release Publishing¶
release.yml is the single publishing path for Python packages, public images,
versioned documentation, and GitHub Releases.
- A manual dispatch is a release rehearsal: it builds, installs, smokes, scans, and records evidence without publishing or promoting public artifacts.
- Tags matching
v*trigger the publication path. - The reusable OCI workflow builds one candidate and pushes it to GHCR by digest with max provenance, an SBOM, and a GitHub artifact attestation.
- The release workflow promotes that tested digest to
ghcr.io/<owner>/wright:<tag>using the GitHub token andpackages: writepermission; it never rebuilds during promotion. - The immutable Python candidate is published through TestPyPI and PyPI
protected environments and installed after each publication stage. Those
publishing actions remain directly in
release.ymlbecause PyPI Trusted Publishing cannot use a reusable workflow as its publisher identity. - Docker Hub publishing is required. Missing
DOCKERHUB_USERNAMEorDOCKERHUB_TOKEN, failed authentication, failed copy, or digest divergence blocks every later release job. - Alpha, beta, and release-candidate tags such as
v0.1.0-alpha.1,v0.1.0-beta.1, andv0.1.0-rc.1are marked as GitHub prereleases. - Stable tags update
latest; prerelease tags do not updatelatest. - Post-publish verification, release evidence, versioned docs, and the GitHub Release run after package and image publication; the GitHub Release is last.
Use docs/alpha-release-notes-template.md before publishing a prerelease so the
release notes capture Docker smoke results, skipped MCP validation,
architecture status, and SBOM/provenance status.
Required Secrets¶
GHCR publishing uses the built-in GitHub token. The release workflow needs
packages: write, which is declared in .github/workflows/release.yml.
Every production release requires these Docker Hub credentials:
DOCKERHUB_USERNAMEDOCKERHUB_TOKEN
Store them as GitHub Actions secrets at repository scope or in the protected
dockerhub environment. The token must be the raw Docker Hub access-token
value with read/write permission, not the token's display name.
Pull requests never publish images or sync registry descriptions.
Maintainer Local Checks¶
Before asking for release review, run the same commands locally when practical:
uv run pytest
npm run test --workspace=apps/web
npm run build --workspace=apps/web
uv run --with mkdocs-material mkdocs build --strict
python scripts/check-public-alpha-leaks.py --include-untracked
scripts/security-scan.sh --include-untracked
make alpha-release-check
scripts/alpha-release-check.sh
On Windows PowerShell, run the scanner wrapper directly:
scripts/security-scan.ps1 -IncludeUntracked
scripts/alpha-release-check.ps1
The scanner wrappers use Dockerized ghcr.io/gitleaks/gitleaks:v8.30.1 and
ghcr.io/trufflesecurity/trufflehog:3.95.7, so no global Gitleaks or
TruffleHog install is required.
For Docker release candidates, also run the local smoke helper against the image you plan to publish:
WRIGHT_DOCKER_IMAGE=wright:<tag> WRIGHT_DOCKER_SKIP_BUILD=1 scripts/docker-smoke-test.sh
Follow-Up Gaps¶
- Branch push workflows do not publish public images.
- The supported public appliance is still
linux/amd64;linux/arm64requires a native build-and-smoke contract before multi-architecture publication. - A rehearsal intentionally cannot prove external TestPyPI, PyPI, GHCR tag promotion, required Docker Hub distribution, docs publication, or GitHub Release side effects. Those remain protected real-tag operations.
- A production release is incomplete until PyPI, GHCR, Docker Hub, versioned
docs, and the GitHub Release have all passed. Merging to
mainis not by itself a completed release; the matching unique version tag must finish the entire protected release train.