Files
github-action/docs/adr/adr-0005-quality-check-contract.md
T
Tirsvad ae0360350a
TirSystem/github-action: Publish wiki / publish-wiki (push) Skipped
TirSystem/github-action: Sync GitHub mirror metadata / sync-metadata (push) Skipped
Resolve pending commit links in the quality-checks script documents
Task: MIL-003#3
2026-10-11 11:14:04 +08:00

129 lines
6.0 KiB
Markdown

# ADR-0005: Contract of the shared quality-check workflow
## Metadata
| Key | Value |
| --- | --- |
| ID | ADR-0005 |
| CrossReference | [MIL-003], [UC-005], [US-001], [ADR-0004] |
## Version History
| Date | Status | Author | Reviewer | Change | Commit |
| --- | --- | --- | --- | --- | --- |
| 2026-10-10 | Deprecated | Jens Tirsvad Nielsen | S01 | Reviewed (RC-038): the security impact of installing the tools from PyPI named | [385d2fb] |
| 2026-10-11 | Accepted | Jens Tirsvad Nielsen | S01 | Added the input tools-ref: the shell steps moved to a script file fetched at that ref (RC-051) | [670f0ac] |
Allowed Status values: `Proposed`, `Accepted`, `Rejected`, `Deprecated`, `Superseded by ADR-NNNN`.
---
## Context
MIL-003 offers one workflow that any repository calls to run lint, format and
type checks. Before it is built (task 3) its inputs and the meaning of a pass
and a fail must be fixed, because consumers in four organizations will depend
on them and MIL-004 will version them. Facts found on 2026-10-10:
- The runner image (Ubuntu 24.04, Python 3.12, `git`, Node.js, `jq`, `make`,
run as `root`) has **none** of ruff, mypy, pytest, shellcheck or shfmt.
- PyPI is reachable, and a virtual environment installs all four tools with
pinned versions: `ruff` 0.6.9, `mypy` 1.11.2, `shellcheck-py` 0.10.0.1
(ShellCheck 0.10.0) and `shfmt-py` 3.11.0.2 (shfmt 3.11.0).
- Each tool exits non-zero on a finding and prints `path:line` and a message.
`shellcheck` also exits 1 on an empty file, so a language with no files must
be skipped before the tool runs.
- Reusable workflows work from the same repository and from other
organizations (issue #15). Secrets passing is untested, and the checks need
none.
- The conventions name the tools: ruff and `mypy --strict` for Python
(QC-PY-001) and shellcheck and shfmt for shell (QC-SH-001).
Options for the shape of the workflow:
1. **One reusable workflow with a `languages` input** that selects the tool
sets, defaults that work with no configuration, and one version input per
tool.
2. **One reusable workflow per language** (`python-checks.yml`,
`shell-checks.yml`). Simple, but a mixed repository calls two workflows and
the contract exists twice.
3. **Detect the languages from the files and take no inputs.** Zero
configuration, but a repository cannot opt out of a tool, and versions
change only with the workflow.
4. **The consumer passes its own commands.** Most flexible, but it is not a
shared check any more and defeats the point of a contract.
## Decision
The shared workflow `quality-checks.yml` is one reusable workflow with the
inputs `languages` (a comma list of `python` and `shell`, default both),
`working-directory` (default `.`), one version input per tool whose
defaults are the pinned versions above, and `tools-ref` (default `v1`, the
tag or branch from which the workflow fetches its script
`tools/quality-checks.sh`), and it installs the tools from PyPI into a virtual
environment. A call at an exact tag is pinned completely only when `tools-ref`
names the same tag; with the default the script follows the major tag. A run passes when every selected tool exits 0 and
fails when any exits non-zero, and a language with no files is skipped with a
notice and counts as a pass.
The rules that follow from it:
- **Python:** `ruff check`, `ruff format --check` and `mypy --strict`, run on
the files under `working-directory`.
- **Shell:** the tracked `*.sh` files are checked with `shellcheck` and
`shfmt -d`.
- **Configuration:** projects configure the tools in the standard way
(`pyproject.toml`, `.shellcheckrc`, `.editorconfig`); the workflow adds none
and changes no file.
- **Reporting:** each tool runs in its own log group named after the tool, and
its own output carries `path:line` and the message; the job status is the
pull request status.
- **Out of scope for the first version:** running the project's tests
(`pytest` needs the project's dependencies), auto-fixing, and other
languages.
## Consequences
**Positive:**
- A repository gets the checks with one `uses:` and no configuration, and a
mixed repository gets both language sets from one call.
- Tool versions are explicit and reviewed, so a new tool release cannot turn
every consumer red overnight.
- A failure is read from the tool's own message, so the workflow needs no
parser of its own.
**Negative:**
- Every run installs the tools from PyPI (about half a minute) and depends on
PyPI being reachable; caching is not part of the first version.
- Projects that need their tests or their own dependencies in the type check
(`mypy` with third-party stubs) must configure that themselves or wait for a
later version.
- `mypy --strict` on an existing codebase is likely to fail at first; a
project that cannot meet it has to configure the tool, which weakens the
shared meaning of "pass".
- Pinned versions need a release (MIL-004) to move; until then they are
updated by changing the workflow on `main`.
- **Security:** the tools are downloaded from PyPI on every run and execute
with the consumer's repository content on a runner that runs as `root`.
Versions are pinned but the packages are not hash-pinned, so a
compromised release of one of the four packages would reach every
consumer; hash pinning is a later hardening step.
## Affected Artifacts
- [MIL-003] — tasks 3 to 5 (issues #17 to #19) implement, adopt and document
this contract.
- [UC-005] — the use case this contract serves.
- [US-001] — stories US-001.09 and US-001.10 state its acceptance criteria.
- [ADR-0004] — the same pattern of a shared scoped or reusable workflow with an
explicit gate; this ADR does not change it.
---
[MIL-003]: ../milestones/mil-003-quality-checks.md
[UC-005]: ../uc-005/uc.md
[US-001]: ../user-stories.md
[ADR-0004]: ./adr-0004-shared-wiki-publishing-opt-in.md
[385d2fb]: https://git.tirsystem.com/TirSystem/github-action/commit/385d2fb7cb9be1fdc8fd3503d0af624fa38eafc7
[670f0ac]: https://git.tirsystem.com/TirSystem/github-action/commit/670f0ac469043bb166c84290c0dbf62b4908e3cc