Define and accept the contract of the shared quality-check workflow

Add the brief use case UC-005 (Run Project Quality Checks), the actor and
use case in UCD-001, stories US-001.09 and US-001.10, and ADR-0005 with the
inputs, the pinned tools and what a pass and a fail mean, based on a probe
of the runner image. Review and accept them (RC-035 to RC-038) and update
TM-001 and the registry.

Task: MIL-003#2
This commit is contained in:
2026-10-10 16:06:30 +08:00
parent bf077b1910
commit 385d2fb7cb
10 changed files with 424 additions and 18 deletions
+123
View File
@@ -0,0 +1,123 @@
# 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 | Initial version | pending |
| 2026-10-10 | Accepted | Jens Tirsvad Nielsen | S01 | Reviewed (RC-038): the security impact of installing the tools from PyPI named | pending |
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 `.`) and one version input per tool whose
defaults are the pinned versions above, and it installs the tools from PyPI
into a virtual environment. 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