CI workflow¶
This page documents .github/workflows/ci.yml.
The CI workflow is TopMark's primary source-tree validation workflow. It validates pull requests,
pushes to main, and version-tag pushes before release publication consumes CI-built artifacts.
Note
The canonical vocabulary used throughout the documentation is defined in Terminology and Canonical Vocabulary.
Purpose¶
The CI workflow validates that the repository source tree is healthy before changes are merged or released. It checks formatting, linting, typing, documentation integrity, link integrity, test behavior, and stable public API compatibility.
The workflow also builds release artifacts on version-tag pushes. This is intentional: release artifacts are built in the unprivileged CI workflow and later consumed by the privileged release workflow, so the release workflow does not rebuild the project from repository source code.
Trigger conditions¶
| Trigger | When it runs | Purpose |
|---|---|---|
pull_request |
Pull requests affecting source, tests, tools, documentation, workflow, dependency, or validation files | Validate proposed changes before merge |
push.branches |
Pushes to main |
Validate committed source changes |
push.tags |
Tags matching v* |
Validate the tagged source tree and build release artifacts |
Pull-request runs are path-filtered at the workflow level so unrelated changes do not trigger CI.
Within the workflow, the changes job performs finer-grained path filtering so jobs such as lint,
tests, docs, link checks, pre-commit validation, and API snapshot checks run only when relevant.
Version-tag pushes are not path-filtered by pull-request change groups. They run the release-artifact path after the required validation jobs succeed.
Permissions and trust boundary¶
The workflow uses read-only repository permissions by default:
The release-artifacts job also declares contents: read explicitly. The workflow does not publish
packages, create releases, or request elevated release permissions.
The trust boundary is intentional:
- CI checks out and runs repository code in an unprivileged context.
- CI builds release artifacts from the tagged source tree.
- The privileged release workflow later downloads, verifies, and publishes those artifacts instead of rebuilding them.
This separation keeps artifact production and package publication in separate trust boundaries.
Some setup logic is shared through the local composite action
setup-python-nox, while other jobs keep explicit setup steps where
their caching or environment needs differ. This limited duplication is acceptable because it keeps
job behavior and trust boundaries explicit.
Jobs and validation scope¶
| Job | Purpose | Main tools |
|---|---|---|
changes |
Detect changed file groups for PR job gating | dorny/paths-filter |
python-metadata |
Resolve supported and canonical Python versions for CI jobs | nox, pyproject.toml |
lint |
Validate formatting, linting, typing, and docstring links | nox, ruff, pyright |
pre-commit |
Run configured pre-commit hooks | pre-commit |
docs |
Build the documentation site in strict mode | nox, mkdocs |
tests |
Run the supported Python test matrix | nox, pytest |
coverage |
Generate and publish canonical coverage reports | nox, coverage.py, pytest |
api-snapshot |
Check public API stability for source-changing pull requests | nox, tools/api_snapshot.py |
links |
Validate links in source Markdown files | lycheeverse/lychee-action, lychee.toml |
links-site |
Validate links in the rendered MkDocs site, including generated pages | mkdocs, lycheeverse/lychee-action |
release-artifacts |
Build and upload release artifacts for version tags | uv build, actions/upload-artifact |
Most jobs delegate validation to nox sessions so local development and CI share the same stable
validation contracts and execution semantics. The python-metadata job resolves supported Python
versions and the canonical single-version Python from project metadata through
nox -s print_python_matrix. The test matrix consumes that supported-version list with fail-fast
disabled so failures on one Python version do not hide failures on others.
Coverage reporting runs in a dedicated canonical job on Ubuntu using the resolved canonical Python
version and the existing nox -s coverage session. Coverage intentionally runs outside the full
test matrix to avoid duplicating expensive QA work that is already covered by the compatibility
matrix.
The coverage job depends on the full test matrix succeeding before coverage reports are generated. This keeps the compatibility matrix as the primary validation gate while avoiding additional coverage-processing noise after known test failures.
Canonical single-version jobs such as linting, documentation builds, coverage, API snapshot checks, and release-artifact construction use the same resolved canonical Python value rather than carrying separate hard-coded version literals in the workflow.
Note
Shared Python/bootstrap jobs intentionally use explicit actions/cache ownership while
keeping the setup-uv built-in cache integration disabled. This avoids cache-reservation race
warnings between concurrent jobs using identical bootstrap inputs.
The API snapshot check is pull-request-only and runs when Python-relevant files change. It is a fast guardrail for unexpected stable public API surface changes, not a replacement for the full test matrix.
Documentation integrity is validated at multiple levels:
- the
docsjob runs a strict MkDocs build; - the
linksjob validates links in source Markdown files; - the
links-sitejob validates links in the rendered site, including generated API pages.
Generated API pages are visible only after the site is built, so source-only link checks cannot fully replace the built-site link check.
Artifact handling¶
The CI workflow produces release artifacts only for tag pushes matching v*.
The workflow also publishes coverage artifacts from the dedicated coverage job:
- an HTML coverage report;
- XML and JSON machine-readable coverage reports;
- a short GitHub Step Summary with a coverage overview and artifact notice.
Coverage artifacts are diagnostic CI outputs only. They are not release artifacts and are not consumed by the release workflow.
On a version-tag push, the release-artifacts job:
- builds the source distribution and wheel with
uv build; - derives release metadata from the tag;
- normalizes the tag version with
packaging.version.Version; - records the supported and canonical Python metadata used by the CI run;
- writes checksum metadata with
sha256sum; - uploads
topmark-distandtopmark-release-metaartifacts.
The release workflow consumes those uploaded artifacts after validating the triggering CI run and tag context. CI does not publish the package itself.
This artifact handoff is part of the release trust-boundary model. Artifact creation happens where repository code is already expected to run; publication happens later in a privileged workflow that does not rebuild the project from repository source code.
CI validation model¶
TopMark intentionally separates:
- source-tree validation;
- supported Python-version discovery;
- canonical single-version validation jobs;
- compatibility test-matrix execution;
- diagnostic coverage reporting;
- release artifact construction;
- privileged artifact publication in the downstream release workflow.
This layered CI model keeps repository validation, release artifact creation, and package publication in separate trust boundaries while preserving stable local/CI validation contracts.
Local reproduction¶
The closest local equivalents are the nox sessions used by CI:
nox -s print_python_matrix
nox -s format_check
nox -s lint
nox -s docstring_links
nox -s docs
nox -s qa -p 3.13
nox -s coverage -p 3.13
The concrete 3.13 commands shown here reflect the current canonical Python version. That value is
resolved from project metadata and is expected to move when the supported Python range moves.
Run link checks with:
Run the API snapshot check with:
CI uses the resolved canonical Python value for this session rather than hard-coding the version in
.github/workflows/ci.yml.
Build release artifacts locally with:
Local commands can reproduce most validation behavior, but they do not reproduce GitHub event context, pull-request path filtering, artifact-upload behavior, or downstream release-workflow handoff semantics.
Maintenance notes¶
Keep the CI workflow explicit enough that contributors can determine which validation contract failed from the job name and log output.
When editing this workflow:
- update path filters when adding new source, docs, tooling, or workflow-maintenance files;
- keep nox sessions as the canonical stable validation contracts where practical;
- keep Python-version metadata sourced from
pyproject.tomlthroughnox -s print_python_matrix; - keep the coverage job canonical and lightweight rather than instrumenting the full compatibility matrix;
- keep coverage reporting lightweight and diagnostic rather than turning it into a percentage-driven release gate;
- keep release artifact building in CI unless the release trust model is deliberately redesigned;
- avoid moving package publication into this workflow;
- keep generated-site link validation separate from source Markdown link validation;
- keep action pins synchronized across workflows and local composite actions;
- keep uv cache ownership explicit and centralized rather than mixing
setup-uvcache management with separateactions/cacheownership.
GitHub Actions are pinned to commit SHAs. Use the GitHub Action pin audit to detect drift between workflow files and local composite actions.
Related pages¶
- GitHub workflows:
- CI workflow - source-tree validation and CI orchestration
- Release workflow - release pipeline and package publication
- Published artifact validation workflow - validate published package installation and runtime behavior
- Dependabot workflow - dependency and security-update automation
- GitHub Action pin audit - audit GitHub Action pin consistency across workflows and local composite actions
- GitHub actions:
- Setup Python + nox action - shared Python, uv, cache, and nox bootstrap layer for CI jobs
- CI and validation - overview of the CI documentation family
- Test and validation architecture
- Release process - project-level release workflow and policy
- Contributing to TopMark - contributor workflow and dependency guidance