Author SHA1 Message Date
Tirsvad ccb1b88b1d Resolve the pending commit link in MIL-007
TirSystem/github-action: Publish wiki / publish-wiki (push) Skipped
TirSystem/github-action: Sync GitHub mirror metadata / sync-metadata (push) Skipped
2026-10-11 12:31:25 +08:00
Tirsvad 7bcc67a065 Use current tool versions only for MIL-007: .NET SDK 10 or later 2026-10-11 12:31:24 +08:00
Tirsvad 92c1795379 Resolve pending commit links in the MIL-007 use case documents
TirSystem/github-action: Publish wiki / publish-wiki (push) Skipped
TirSystem/github-action: Sync GitHub mirror metadata / sync-metadata (push) Skipped
Validate workflows / validate (pull_request) Successful in 19s
2026-10-11 12:26:59 +08:00
Tirsvad 32b5ab62ab Extend UC-005 and US-001 to C++ and C#, and record the runner tools of MIL-007 2026-10-11 12:26:58 +08:00
Tirsvad 2172939527 Merge pull request 'Plan MIL-007: quality checks for C++ and C#' (#94) from plan-mil-007-cpp-csharp into main
TirSystem/github-action: Publish wiki / publish-wiki (push) Skipped
Validate workflows / validate (push) Successful in 20s
TirSystem/github-action: Sync GitHub mirror metadata / sync-metadata (push) Successful in 5s
Reviewed-on: #94
2026-10-11 06:23:15 +02:00
Tirsvad 4c51435b16 Link the MIL-007 milestone in the project plan
TirSystem/github-action: Publish wiki / publish-wiki (push) Skipped
TirSystem/github-action: Sync GitHub mirror metadata / sync-metadata (push) Skipped
Validate workflows / validate (pull_request) Successful in 17s
2026-10-11 12:22:06 +08:00
Tirsvad bdf89d38d9 Resolve pending commit links in the MIL-007 acceptance
TirSystem/github-action: Sync GitHub mirror metadata / sync-metadata (push) Skipped
Validate workflows / validate (pull_request) Successful in 19s
TirSystem/github-action: Publish wiki / publish-wiki (push) Skipped
2026-10-11 12:21:35 +08:00
Tirsvad e3ad1fa7e7 Review and accept MIL-007 and the PP-001 amendment 2026-10-11 12:21:33 +08:00
Tirsvad 8ad19fca8d Resolve pending commit links in the MIL-007 plan
TirSystem/github-action: Sync GitHub mirror metadata / sync-metadata (push) Skipped
Validate workflows / validate (pull_request) Successful in 20s
TirSystem/github-action: Publish wiki / publish-wiki (push) Skipped
2026-10-11 12:19:57 +08:00
Tirsvad b74ee0e5cf Plan MIL-007: quality checks for C++ and C# 2026-10-11 12:19:56 +08:00
Tirsvad 07a5f9b084 Merge pull request 'Record the verification of pinned consumers in MIL-004' (#93) from mil-004-pin-verified into main
TirSystem/github-action: Publish wiki / publish-wiki (push) Skipped
TirSystem/github-action: Sync GitHub mirror metadata / sync-metadata (push) Successful in 6s
Validate workflows / validate (push) Successful in 21s
2026-10-11 06:18:37 +02:00
Tirsvad fd62602c40 Resolve pending commit links in the MIL-004 pin verification
TirSystem/github-action: Publish wiki / publish-wiki (push) Skipped
TirSystem/github-action: Sync GitHub mirror metadata / sync-metadata (push) Skipped
Validate workflows / validate (pull_request) Successful in 21s
Task: MIL-004#1
2026-10-11 12:17:58 +08:00
Tirsvad 05b635c1b3 Record the verification of pinned consumers in MIL-004
Task: MIL-004#1
2026-10-11 12:17:57 +08:00
Tirsvad 9cd723fb48 Merge pull request 'Add a coverage gate to validate-workflows' (#92) from coverage-gate into main
TirSystem/github-action: Publish wiki / publish-wiki (push) Successful in 9s
Validate workflows / validate (push) Successful in 21s
TirSystem/github-action: Sync GitHub mirror metadata / sync-metadata (push) Successful in 5s
2026-10-11 06:07:53 +02:00
Tirsvad 075fae2e45 Add a coverage gate to validate-workflows and stop tracking .coverage
Validate workflows / validate (pull_request) Successful in 22s
TirSystem/github-action: Publish wiki / publish-wiki (push) Skipped
TirSystem/github-action: Sync GitHub mirror metadata / sync-metadata (push) Skipped
Task: MIL-003#3
2026-10-11 12:05:47 +08:00
Tirsvad 80fb0cfa82 Merge pull request 'Test the HTTP and command-line paths of the tools' (#91) from test-tools-coverage into main
Validate workflows / validate (push) Successful in 15s
TirSystem/github-action: Publish wiki / publish-wiki (push) Skipped
TirSystem/github-action: Sync GitHub mirror metadata / sync-metadata (push) Successful in 5s
2026-10-11 05:27:08 +02:00
Tirsvad eb3e1395db Test the HTTP and command-line paths of the tools
TirSystem/github-action: Publish wiki / publish-wiki (push) Skipped
TirSystem/github-action: Sync GitHub mirror metadata / sync-metadata (push) Skipped
Validate workflows / validate (pull_request) Successful in 17s
Task: MIL-003#3
2026-10-11 11:26:45 +08:00
Tirsvad 2817ecdef5 Merge pull request 'Run the quality checks from a script file' (#90) from quality-checks-script-file into main
Validate workflows / validate (push) Successful in 15s
TirSystem/github-action: Publish wiki / publish-wiki (push) Successful in 11s
TirSystem/github-action: Sync GitHub mirror metadata / sync-metadata (push) Successful in 5s
2026-10-11 05:15:53 +02:00
Tirsvad 0025c58e7b Add the changelog entry of release 1.1.0
TirSystem/github-action: Publish wiki / publish-wiki (push) Skipped
TirSystem/github-action: Sync GitHub mirror metadata / sync-metadata (push) Skipped
Validate workflows / validate (pull_request) Successful in 14s
Task: MIL-004#5
2026-10-11 11:15:13 +08:00
Tirsvad ae0360350a Resolve pending commit links in the quality-checks script documents
TirSystem/github-action: Publish wiki / publish-wiki (push) Skipped
TirSystem/github-action: Sync GitHub mirror metadata / sync-metadata (push) Skipped
Task: MIL-003#3
2026-10-11 11:14:04 +08:00
Tirsvad 670f0ac469 Run the quality checks from a script file of this repository
Task: MIL-003#3
2026-10-11 11:14:03 +08:00
34 changed files with 1004 additions and 161 deletions
+12
View File
@@ -0,0 +1,12 @@
# Coverage of the scripts in tools/ by the offline tests. validate-workflows.yml
# runs `coverage run -m pytest -q tests` and `coverage report`, which fails
# below fail_under. Measured at 97% on 2026-10-11; the gate leaves room for a
# few lines and fails when a tool grows without tests.
[run]
branch = True
source = tools
[report]
fail_under = 95
show_missing = True
skip_empty = True
+16 -95
View File
@@ -35,6 +35,10 @@ on:
description: "Version of the shfmt-py package (shfmt 3.11.0)" description: "Version of the shfmt-py package (shfmt 3.11.0)"
type: string type: string
default: "3.11.0.2" default: "3.11.0.2"
tools-ref:
description: "Tag or branch of TirSystem/github-action whose tools/quality-checks.sh is run; pin it to the exact tag together with the workflow"
type: string
default: "v1"
jobs: jobs:
quality-checks: quality-checks:
@@ -50,104 +54,21 @@ jobs:
SHFMT_PY_VERSION: ${{ inputs.shfmt-py-version }} SHFMT_PY_VERSION: ${{ inputs.shfmt-py-version }}
QC_VENV: /tmp/quality-tools QC_VENV: /tmp/quality-tools
steps: steps:
- name: Validate the inputs
run: |
set -u
IFS=',' read -ra langs <<< "${LANGUAGES// /}"
if [ "${#langs[@]}" -eq 0 ]; then
echo "::error::[configuration] languages is empty; use python, shell or both, comma separated."
exit 1
fi
for lang in "${langs[@]}"; do
case "$lang" in
python | shell) ;;
*)
echo "::error::[configuration] Unknown language '$lang'; use python and shell, comma separated."
exit 1
;;
esac
done
case "$WORKING_DIRECTORY" in
"" | /* | .. | ../* | */.. | */../*)
echo "::error::[configuration] working-directory must be a relative path inside the repository."
exit 1
;;
esac
echo "Checking ${LANGUAGES} in ${WORKING_DIRECTORY}"
- name: Check out the repository - name: Check out the repository
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Fetch the shared script
uses: actions/checkout@v4
with:
repository: TirSystem/github-action
ref: ${{ inputs.tools-ref }}
path: shared-scripts
- name: Validate the inputs
run: bash shared-scripts/tools/quality-checks.sh validate
- name: Install the tools - name: Install the tools
run: | run: bash shared-scripts/tools/quality-checks.sh install
set -eu
IFS=',' read -ra langs <<< "${LANGUAGES// /}"
packages=()
for lang in "${langs[@]}"; do
case "$lang" in
python) packages+=("ruff==${RUFF_VERSION}" "mypy==${MYPY_VERSION}") ;;
shell) packages+=("shellcheck-py==${SHELLCHECK_PY_VERSION}" "shfmt-py==${SHFMT_PY_VERSION}") ;;
esac
done
python3 -m venv "$QC_VENV"
"$QC_VENV/bin/pip" install --quiet "${packages[@]}"
- name: Run the checks - name: Run the checks
run: | run: bash shared-scripts/tools/quality-checks.sh run
set -u
QC_BIN="${QC_BIN:-$QC_VENV/bin}"
if [ ! -d "$WORKING_DIRECTORY" ]; then
echo "::error::[configuration] working-directory '$WORKING_DIRECTORY' does not exist."
exit 1
fi
cd "$WORKING_DIRECTORY"
failed=()
run_tool() {
local name="$1"
shift
echo "::group::$name"
if "$@"; then
echo "::endgroup::"
echo "PASS $name"
else
echo "::endgroup::"
echo "FAIL $name"
failed+=("$name")
fi
}
wanted() {
local lang
IFS=',' read -ra langs <<< "${LANGUAGES// /}"
for lang in "${langs[@]}"; do
[ "$lang" = "$1" ] && return 0
done
return 1
}
if wanted python; then
mapfile -t files < <(git ls-files -- '*.py')
if [ "${#files[@]}" -eq 0 ]; then
echo "::notice::No python files in '$WORKING_DIRECTORY'; nothing was checked."
else
run_tool "ruff check" "$QC_BIN/ruff" check "${files[@]}"
run_tool "ruff format --check" "$QC_BIN/ruff" format --check "${files[@]}"
run_tool "mypy --strict" "$QC_BIN/mypy" --strict "${files[@]}"
fi
fi
if wanted shell; then
mapfile -t files < <(git ls-files -- '*.sh')
if [ "${#files[@]}" -eq 0 ]; then
echo "::notice::No shell files in '$WORKING_DIRECTORY'; nothing was checked."
else
run_tool "shellcheck" "$QC_BIN/shellcheck" "${files[@]}"
run_tool "shfmt -d" "$QC_BIN/shfmt" -d "${files[@]}"
fi
fi
if [ "${#failed[@]}" -gt 0 ]; then
echo "::error::Quality checks failed: ${failed[*]}"
exit 1
fi
echo "All selected checks passed."
+5 -2
View File
@@ -20,5 +20,8 @@ jobs:
python3 -m venv .venv python3 -m venv .venv
.venv/bin/python -m pip install --quiet -r tests/requirements.txt .venv/bin/python -m pip install --quiet -r tests/requirements.txt
- name: Validate workflow files and run the offline tests - name: Validate workflow files and run the offline tests under coverage
run: .venv/bin/python -m pytest -q tests run: .venv/bin/python -m coverage run -m pytest -q tests
- name: Check the coverage of the tools
run: .venv/bin/python -m coverage report
+3
View File
@@ -14,3 +14,6 @@
.venv/ .venv/
__pycache__/ __pycache__/
.pytest_cache/ .pytest_cache/
# Local coverage data (python -m coverage run)
.coverage
+13
View File
@@ -20,6 +20,19 @@ Each release lists its changes in these groups, in this order:
## [Unreleased] ## [Unreleased]
## [1.1.0] - 2026-10-11
### Added
- Input `tools-ref` of `quality-checks.yml` (default `v1`): the tag or branch from
which the workflow fetches its script `tools/quality-checks.sh`.
### Changed
- `quality-checks.yml` runs the script file `tools/quality-checks.sh` instead of
embedding about 100 lines of shell; the checks, messages and results are the
same.
## [1.0.1] - 2026-10-11 ## [1.0.1] - 2026-10-11
### Changed ### Changed
+4 -3
View File
@@ -255,7 +255,7 @@ Trigger -> Workflow: Start workflow
activate Workflow activate Workflow
Workflow -> Workflow: Check out the repository Workflow -> Workflow: Check out the repository
Workflow -> Workflow: Create a virtual environment and\ninstall tests/requirements.txt Workflow -> Workflow: Create a virtual environment and\ninstall tests/requirements.txt
Workflow -> Workflow: Run pytest on tests/ (offline, no secrets):\nworkflows parse as YAML, no embedded Python,\nscripts compile, wiki pages and translations are current,\nsync, publish and release flows against a fake API Workflow -> Workflow: Run pytest on tests/ under coverage (offline, no secrets):\nworkflows parse as YAML, no embedded Python,\nscripts compile, wiki pages and translations are current,\nsync, publish and release flows against a fake API
alt a test fails alt a test fails
Workflow --> Trigger: Workflow fails (the pull request is not ready) Workflow --> Trigger: Workflow fails (the pull request is not ready)
@@ -298,10 +298,11 @@ The workflow files and the scripts in `tools/` are validated by offline tests th
python3 -m venv .venv python3 -m venv .venv
source .venv/bin/activate # Windows PowerShell: .venv\Scripts\Activate.ps1 source .venv/bin/activate # Windows PowerShell: .venv\Scripts\Activate.ps1
python -m pip install -r tests/requirements.txt python -m pip install -r tests/requirements.txt
python -m pytest -q tests python -m coverage run -m pytest -q tests
python -m coverage report
``` ```
The same checks run on every pull request and push to `main` through [`validate-workflows.yml`](.gitea/workflows/validate-workflows.yml). They parse every workflow as YAML, check that none embeds Python, compile the scripts in `tools/`, require explicit job permissions, and test credential parsing, push mirror address parsing and the sync and publish flows against a fake API. The same checks run on every pull request and push to `main` through [`validate-workflows.yml`](.gitea/workflows/validate-workflows.yml). They parse every workflow as YAML, check that none embeds Python, compile the scripts in `tools/`, require explicit job permissions, and test credential parsing, push mirror address parsing and the sync and publish flows against a fake API. The run fails when the line and branch coverage of `tools/` falls below 95% (`.coveragerc`).
After a change to the sync workflow, also trigger **Sync GitHub mirror metadata** manually in Gitea and compare the description and topics on the GitHub mirror. After a change to the sync workflow, also trigger **Sync GitHub mirror metadata** manually in Gitea and compare the description and topics on the GitHub mirror.
+9 -5
View File
@@ -9,8 +9,8 @@
## Version History ## Version History
| Date | Status | Author | Reviewer | Change | Commit | | Date | Status | Author | Reviewer | Change | Commit |
| --- | --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- | --- |
| 2026-10-10 | Deprecated | Jens Tirsvad Nielsen | S01 | Initial version | [385d2fb] | | 2026-10-10 | Deprecated | Jens Tirsvad Nielsen | S01 | Reviewed (RC-038): the security impact of installing the tools from PyPI named | [385d2fb] |
| 2026-10-10 | Accepted | 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`. Allowed Status values: `Proposed`, `Accepted`, `Rejected`, `Deprecated`, `Superseded by ADR-NNNN`.
@@ -55,9 +55,12 @@ Options for the shape of the workflow:
The shared workflow `quality-checks.yml` is one reusable workflow with the The shared workflow `quality-checks.yml` is one reusable workflow with the
inputs `languages` (a comma list of `python` and `shell`, default both), inputs `languages` (a comma list of `python` and `shell`, default both),
`working-directory` (default `.`) and one version input per tool whose `working-directory` (default `.`), one version input per tool whose
defaults are the pinned versions above, and it installs the tools from PyPI defaults are the pinned versions above, and `tools-ref` (default `v1`, the
into a virtual environment. A run passes when every selected tool exits 0 and 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 fails when any exits non-zero, and a language with no files is skipped with a
notice and counts as a pass. notice and counts as a pass.
@@ -122,3 +125,4 @@ The rules that follow from it:
[US-001]: ../user-stories.md [US-001]: ../user-stories.md
[ADR-0004]: ./adr-0004-shared-wiki-publishing-opt-in.md [ADR-0004]: ./adr-0004-shared-wiki-publishing-opt-in.md
[385d2fb]: https://git.tirsystem.com/TirSystem/github-action/commit/385d2fb7cb9be1fdc8fd3503d0af624fa38eafc7 [385d2fb]: https://git.tirsystem.com/TirSystem/github-action/commit/385d2fb7cb9be1fdc8fd3503d0af624fa38eafc7
[670f0ac]: https://git.tirsystem.com/TirSystem/github-action/commit/670f0ac469043bb166c84290c0dbf62b4908e3cc
+2 -2
View File
@@ -10,8 +10,8 @@ each document lives in *this* project and the next version to use.
| BC | Business Case | docs/business-case.md | 002 | | BC | Business Case | docs/business-case.md | 002 |
| SA | Stakeholder Analysis | docs/stakeholder-analysis.md | 002 | | SA | Stakeholder Analysis | docs/stakeholder-analysis.md | 002 |
| PP | Project Plan | docs/project-plan.md | 002 | | PP | Project Plan | docs/project-plan.md | 002 |
| MIL | Milestone / Gateway | docs/milestones/*.md | 007 | | MIL | Milestone / Gateway | docs/milestones/*.md | 008 |
| RC | SQA Review Record | docs/sqa/reviews/rc-*.md | 051 | | RC | SQA Review Record | docs/sqa/reviews/rc-*.md | 054 |
| TM | Traceability Matrix | docs/sqa/traceability-matrix.md | 002 | | TM | Traceability Matrix | docs/sqa/traceability-matrix.md | 002 |
| ADR | Architecture Decision Record | docs/adr/adr-NNNN-*.md | 0008 | | ADR | Architecture Decision Record | docs/adr/adr-NNNN-*.md | 0008 |
| UCD | Use Case Diagram | docs/use-case-diagram.md | 002 | | UCD | Use Case Diagram | docs/use-case-diagram.md | 002 |
+4 -4
View File
@@ -9,8 +9,8 @@
## Version History ## Version History
| Date | Status | Author | Reviewer | Change | Commit | | Date | Status | Author | Reviewer | Change | Commit |
| --- | --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- | --- |
| 2026-10-10 | Deprecated | Jens Tirsvad Nielsen | S01 | Added task 6 (embedded scripts to files) and Go/No-Go criterion 5; traceability adds O2 (RC-034) | [616c5d2] | | 2026-10-11 | Deprecated | Jens Tirsvad Nielsen | S01 | Added the verification results of the first release (v1.0.0) | [6933e91] |
| 2026-10-11 | Accepted | Jens Tirsvad Nielsen | S01 | Added the verification results of the first release (v1.0.0) | [6933e91] | | 2026-10-11 | Accepted | Jens Tirsvad Nielsen | S01 | Criterion 3 verified with pinned consumers after v1.0.1 and v1.1.0 (RC-048) | [05b635c] |
--- ---
@@ -81,7 +81,7 @@ and the release page were created, and the job token was enough (ADR-0007).
| --- | --- | --- | | --- | --- | --- |
| 1 | A release is validated before a tag is created | Go: `tools/release.py` refuses unless the status `Validate workflows / validate (push)` of the tip of `main` is success; tested for failed and missing runs | | 1 | A release is validated before a tag is created | Go: `tools/release.py` refuses unless the status `Validate workflows / validate (push)` of the tip of `main` is success; tested for failed and missing runs |
| 2 | Version tag and changelog entry that marks breaking changes | Go: `v1.0.0` and `CHANGELOG.md` `[1.0.0]`; `tools/changelog.py` checks the convention | | 2 | Version tag and changelog entry that marks breaking changes | Go: `v1.0.0` and `CHANGELOG.md` `[1.0.0]`; `tools/changelog.py` checks the convention |
| 3 | A consumer pinned to the previous version keeps working | Go: tags are never moved or deleted; the upgrade guide describes the way back; the first release has no previous version, so this is by construction until `v1.0.1` | | 3 | A consumer pinned to the previous version keeps working | Go, verified on 2026-10-11 after `v1.0.1`: a caller in `TirSystem-BashScript/RepoFoundry` at `@v1.0.0` and one at `@v1` both passed (run 660); after `v1.1.0` a caller at `@v1.1.0` with `tools-ref: v1.1.0` and one at `@v1` passed (run 672). `v1.0.0` still points to `97ffa49` |
| 4 | Publish destination decided, token scope documented | Go: ADR-0007 | | 4 | Publish destination decided, token scope documented | Go: ADR-0007 |
| 5 | No embedded Python in the scoped workflows | Go: both run files of this repository, with offline tests that exercise the files (MIL-004 task 6) | | 5 | No embedded Python in the scoped workflows | Go: both run files of this repository, with offline tests that exercise the files (MIL-004 task 6) |
@@ -92,5 +92,5 @@ After the release the two scoped workflows fetch their scripts at `ref: v1`
[BC-001]: ../business-case.md [BC-001]: ../business-case.md
[ADR-0002]: ../adr/adr-0002-s01-approves-as-stand-in.md [ADR-0002]: ../adr/adr-0002-s01-approves-as-stand-in.md
[616c5d2]: https://git.tirsystem.com/TirSystem/github-action/commit/616c5d2e48f0d76399a6f78923891fc7f50c8e22
[6933e91]: https://git.tirsystem.com/TirSystem/github-action/commit/6933e91c83075fb39b2dfbe41683cb2a0a0bce28 [6933e91]: https://git.tirsystem.com/TirSystem/github-action/commit/6933e91c83075fb39b2dfbe41683cb2a0a0bce28
[05b635c]: https://git.tirsystem.com/TirSystem/github-action/commit/05b635c1b3d79390c794e62e781595fbe5f1a4ea
@@ -0,0 +1,125 @@
# MIL-007: Quality checks for C++ and C#
## Metadata
| Key | Value |
| --- | --- |
| ID | MIL-007 |
| CrossReference | [BC-001], [US-001], [UC-005], [MIL-003], [MIL-004], [ADR-0005], [ADR-0006] |
## Version History
| Date | Status | Author | Reviewer | Change | Commit |
| --- | --- | --- | --- | --- | --- |
| 2026-10-11 | Deprecated | Jens Tirsvad Nielsen | S01 | Added the verification results of task 2 (runner tools) | [32b5ab6] |
| 2026-10-11 | Proposed | Jens Tirsvad Nielsen | S01 | Current tool versions only, .NET SDK 10 or later: results of run 699 replace the SDK 8 result; tasks 3 and criterion 1 amended | [7bcc67a] |
---
## Purpose
Decide whether a C++ or C# project can run the same kind of quality checks
as a Python or shell project, through a shared workflow of this repository,
and whether the runners of the instance can run them.
## Deliverable
Reusable quality-check workflows for C++ and for C#, each with a documented
set of inputs and pinned tool versions, a guide for reading its results on the
wiki in English, Danish and Bahasa Malaysia, a use of each in at least one
other repository, and a release that makes them available at the major tag.
The existing workflow `quality-checks.yml` for Python and shell keeps its
name and contract. The workflows run current tools only: a .NET SDK of version
10 or later, and the latest stable release of each clang tool; a tool that
is only available as an old version is not used.
## Go / No-Go Criteria
| # | Criterion (objectively checkable) | Go | No-Go |
| --- | --- | --- | --- |
| 1 | The runner of the instance can run the tools of each language (installed in the run or provided by the image), verified and recorded | Verified for both, at current versions (.NET SDK 10 or later) | A tool cannot be had without changing the runner, or only as an old version |
| 2 | The contract (tools, versions, inputs, what a pass and a fail mean) is decided in an ADR, and a language that cannot be checked without a build system says what it needs | ADR accepted | Open |
| 3 | A failing check shows the tool, file and line in the log, verified with a seeded failure for each workflow | Verified | Cause not visible |
| 4 | One other repository runs each workflow and its status appears on pull requests | Appears for both | Does not run |
| 5 | The workflows embed no script: each runs a file of this repository, and the offline tests exercise the file | Count of embedded scripts is 0 | Embedded script remains |
| 6 | A release (a minor version) makes both available, and the changelog and the wiki name them | Released | Unreleased |
## Dependencies
| Depends on | Reason |
| --- | --- |
| MIL-003 | The contract, the reusable workflow mechanism and the guide for reading results |
| MIL-004 | The release process and the release workflow publish the result |
## Traceability
| Business Case objective / KPI / user story | Reference |
| --- | --- |
| O4 | [BC-001] |
| US-001.09, US-001.10 (extended to C++ and C#) | [US-001] |
## Ownership
| Role | Stakeholder ID (SA) |
| --- | --- |
| Owner | S05 |
| Approving reviewer | S01 |
## Target Date
2027-01-16 — confirmed on 2026-10-11 by S01, standing in for S05 (ADR-0002); it follows the last decision of the plan, 2026-12-26, with two weeks from 2027-01-04. The Business Case sets no deadline.
## Tasks
| # | Task | Summary | Needs its own Use Case/User Story? | Reference |
| --- | --- | --- | --- | --- |
| 1 | Write the use case and stories for C++ and C# checks | Extend UC-005 (Run Project Quality Checks) so that its Brief names C++ and C#, and add stories to US-001 for a C++ project and a C# project, each with a pass and a fail acceptance criterion. Review them with the checklists of the `artifact` skill. | Yes | UC-005, US-001.09, US-001.10 |
| 2 | Verify what the runner provides | On a throwaway branch, run a job on `ubuntu-latest` that prints which of `clang-format`, `clang-tidy`, `cppcheck` and the .NET SDK (`dotnet --version`) exist, and whether each can be installed in the run (for example from PyPI, apt or the dotnet install script) and how long that takes. Record the result in the milestone like MIL-003 task 1. | No | |
| 3 | Decide the contract | Record an ADR that decides: one workflow for both languages or one per language; the tools and pinned versions, current releases only and .NET SDK 10 or later (C++: `clang-format --dry-run -Werror`, `clang-tidy` and compiler warnings; `cppcheck` only if a current release can be had; C#: `dotnet format --verify-no-changes`, a build with analyzers and warnings as errors); the inputs (languages or paths, working directory, build command or compile database); what a build-dependent check needs from the caller; whether C is included with C++; and the names of the workflows and wiki pages (they follow `QualityChecksPythonShell`). Must be accepted before tasks 4 and 5. | No | ADR-0005 (refined) |
| 4 | Implement the C++ workflow | Add the reusable workflow and its script file `tools/quality-checks-cpp.sh` (or as the ADR decides), pinned tool versions, least-privilege permissions and offline tests with stub tools, as `quality-checks.yml` does. | No | |
| 5 | Implement the C# workflow | The same for C#, with its script file and tests. | No | |
| 6 | Write the guides and the wiki pages | One guide per workflow in `guides/` for reading its results, the wiki pages in English, Danish and Bahasa Malaysia, the Home workflow list and diagrams (a test requires every workflow to be listed), and an update of the upgrade guide. | No | |
| 7 | Adopt each workflow in one other repository | Onboard a real C++ project and a real C# project, or a throwaway repository when none exists, and fix the gaps in the guides that appear. Verify a seeded failure. | No | |
| 8 | Release | Add the changelog entry (a new workflow is a minor change, ADR-0006) and run the Release workflow; record the results in this milestone and review it. | No | |
## Verification results
Task 2 was run on 2026-10-11 with a throwaway branch, `verify-runner-tools`,
in `TirSystem/github-action` (run 692, deleted afterwards). One job on
`ubuntu-latest` printed what the image has and tried each way of installing a
tool.
| Fact | Result |
| --- | --- |
| Image | Ubuntu 24.04, `x86_64`, the job runs as root, `sudo`, `curl`, `python3` 3.12 and `pip` present |
| Present in the image | `gcc` and `g++` 13.3, `make`; missing: `clang-format`, `clang-tidy`, `cppcheck`, `cmake`, `dotnet` |
| `clang-format` | from PyPI in 2 s (23.1.3); from apt in 9 s |
| `clang-tidy` | from PyPI in 5 s (22.1.8); from apt in 25 s |
| `cppcheck` | apt gives 2.13.0 in 6 s, which is old (the latest release is 2.22.0); the PyPI package named `cppcheck` is a different project (1.5.3) and is not used; a current release needs a build from source |
| .NET SDK | the official script `dot.net/v1/dotnet-install.sh` with `--channel 10.0` gives 10.0.401 in 20 s; apt `dotnet-sdk-10.0` gives 10.0.112 in 23 s, an older feature band; the same script also offers 11.0 as a release candidate, which is not used |
A first run (run 692) looked at .NET SDK 8, which is not used: the requirement
is a .NET SDK of version 10 or later, and old tools are not run. A second run
(run 699, also deleted afterwards) checked current versions. The current
clang tools come from PyPI in a few seconds with an exact version, as
`quality-checks.yml` already does for its tools, and the .NET SDK 10 comes from
the official script in 20 s, with no change to the runner. `cppcheck` is the
exception: only an old version is a package, so the contract either drops it
(`clang-tidy` does the static analysis) or builds a current release from
source, which costs minutes per run.
Not covered by this task: running a tool against a real C++ project (a
`clang-tidy` run needs a compile database or flags) or a real C# solution
(`dotnet format` and a build need the project's restore of packages from the
network), a private runner with no internet, and an image that differs from
`ubuntu-latest`. Those belong to the contract (task 3) and to the adoption
(task 7).
---
[BC-001]: ../business-case.md
[US-001]: ../user-stories.md
[UC-005]: ../uc-005/uc.md
[MIL-003]: ./mil-003-quality-checks.md
[MIL-004]: ./mil-004-versioned-release.md
[ADR-0005]: ../adr/adr-0005-quality-check-contract.md
[ADR-0006]: ../adr/adr-0006-release-process-and-version-pinning.md
[32b5ab6]: https://git.tirsystem.com/TirSystem/github-action/commit/32b5ab62ab5a82638b71b8fcfb43a819e92ab346
[7bcc67a]: https://git.tirsystem.com/TirSystem/github-action/commit/7bcc67a065ac5f67b4e13ebf243a4bbbe2af47d9
+15 -6
View File
@@ -4,25 +4,25 @@
| Key | Value | | Key | Value |
| --- | --- | | --- | --- |
| ID | PP-001 | | ID | PP-001 |
| CrossReference | [BC-001], [SA-001], [ADR-0002], [US-001], [MIL-001], [MIL-002], [MIL-003], [MIL-004], [MIL-005], [MIL-006] | | CrossReference | [BC-001], [SA-001], [ADR-0002], [US-001], [MIL-001], [MIL-002], [MIL-003], [MIL-004], [MIL-005], [MIL-006], [MIL-007] |
## Version History ## Version History
| Date | Status | Author | Reviewer | Change | Commit | | Date | Status | Author | Reviewer | Change | Commit |
| --- | --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- | --- |
| 2026-10-10 | Deprecated | Jens Tirsvad Nielsen | S01 | P1 use cases written and accepted (UCD-001, US-001, UC-001 to UC-004): Open Issues updated | [a2bf2d4] | | 2026-10-10 | Deprecated | Jens Tirsvad Nielsen | S01 | Dates confirmed by S01 (stand-in for S05); the confirmation item is removed from Open Issues | [ea06eaa] |
| 2026-10-10 | Accepted | Jens Tirsvad Nielsen | S01 | Dates confirmed by S01 (stand-in for S05); the confirmation item is removed from Open Issues | [ea06eaa] | | 2026-10-11 | Accepted | Jens Tirsvad Nielsen | S01 | Added MIL-007 (quality checks for C++ and C#); reviewed (RC-053) | [e3ad1fa] |
--- ---
## Purpose ## Purpose
This plan schedules six milestones that move the repository from one This plan schedules seven milestones that move the repository from one
workflow with known defects to a documented, versioned collection of shared workflow with known defects to a documented, versioned collection of shared
workflows. The Business Case sets no deadline, so the dates are the plan's own; they were confirmed on 2026-10-10 by S01, standing in for S05 (ADR-0002). workflows. The Business Case sets no deadline, so the dates are the plan's own; they were confirmed on 2026-10-10 by S01, standing in for S05 (ADR-0002).
## Planning Assumptions ## Planning Assumptions
- Week 1 starts 2026-10-05; phases are two weeks long; the last decision is 2026-12-26. - Week 1 starts 2026-10-05; phases are two weeks long; the last decision is 2026-12-26; MIL-007 follows in January 2027 (2027-01-04 to 2027-01-16, confirmed on 2026-10-11 by S01).
- Work on a milestone starts only after its `MIL-*` document is `Accepted`. - Work on a milestone starts only after its `MIL-*` document is `Accepted`.
- Dates were confirmed on 2026-10-10 by S01, standing in for S05 (ADR-0002). - Dates were confirmed on 2026-10-10 by S01, standing in for S05 (ADR-0002).
@@ -36,6 +36,7 @@ workflows. The Business Case sets no deadline, so the dates are the plan's own;
| Versioned release and publishing | [MIL-004] | 2026-11-16 to 2026-11-28 | 2026-11-28 | S05 | None yet | Release process and reusable release workflow | [Milestone MIL-004] | | Versioned release and publishing | [MIL-004] | 2026-11-16 to 2026-11-28 | 2026-11-28 | S05 | None yet | Release process and reusable release workflow | [Milestone MIL-004] |
| Wiki and translations | [MIL-005] | 2026-11-30 to 2026-12-12 | 2026-12-12 | S05 | None yet | Wiki pages in English, Danish and Bahasa Malaysia, kept current by every pull request | [Milestone MIL-005] | | Wiki and translations | [MIL-005] | 2026-11-30 to 2026-12-12 | 2026-12-12 | S05 | None yet | Wiki pages in English, Danish and Bahasa Malaysia, kept current by every pull request | [Milestone MIL-005] |
| Shared wiki publishing | [MIL-006] | 2026-12-14 to 2026-12-26 | 2026-12-26 | S05 | None yet | Scoped publish-wiki workflow with opt-in, piloted in one other repository | [Milestone MIL-006] | | Shared wiki publishing | [MIL-006] | 2026-12-14 to 2026-12-26 | 2026-12-26 | S05 | None yet | Scoped publish-wiki workflow with opt-in, piloted in one other repository | [Milestone MIL-006] |
| Quality checks for C++ and C# | [MIL-007] | 2027-01-04 to 2027-01-16 | 2027-01-16 | S05 | US-001.09, US-001.10 (to extend) | Reusable quality-check workflows for C++ and C#, released | [Milestone MIL-007] |
```plantuml ```plantuml
@startgantt @startgantt
@@ -52,6 +53,8 @@ Project starts 2026-10-05
[MIL-005 Go/No-Go] happens 2026-12-12 [MIL-005 Go/No-Go] happens 2026-12-12
[MIL-006 Shared wiki] starts 2026-12-14 and ends 2026-12-26 [MIL-006 Shared wiki] starts 2026-12-14 and ends 2026-12-26
[MIL-006 Go/No-Go] happens 2026-12-26 [MIL-006 Go/No-Go] happens 2026-12-26
[MIL-007 C++ and C#] starts 2027-01-04 and ends 2027-01-16
[MIL-007 Go/No-Go] happens 2027-01-16
@endgantt @endgantt
``` ```
@@ -65,12 +68,14 @@ Project starts 2026-10-05
| Gitea Actions workflows for software engineers (release and publishing) | [MIL-004] | | Gitea Actions workflows for software engineers (release and publishing) | [MIL-004] |
| Wiki pages in English, Danish and Bahasa Malaysia | [MIL-005] | | Wiki pages in English, Danish and Bahasa Malaysia | [MIL-005] |
| The wiki publisher shared with other repositories through scoped delivery | [MIL-006] | | The wiki publisher shared with other repositories through scoped delivery | [MIL-006] |
| Gitea Actions workflows for software engineers (quality checks for C++ and C#) | [MIL-007] |
## Dependencies ## Dependencies
``` ```
MIL-001 → MIL-002 → MIL-003 → MIL-004 MIL-001 → MIL-002 → MIL-003 → MIL-004
MIL-002 → MIL-005 → MIL-006 MIL-002 → MIL-005 → MIL-006
MIL-003 + MIL-004 → MIL-007
``` ```
A No-Go moves every later window by the rework time of the failed milestone. A No-Go moves every later window by the rework time of the failed milestone.
@@ -79,6 +84,7 @@ A No-Go moves every later window by the rework time of the failed milestone.
| Risk | Impact | Mitigation | | Risk | Impact | Mitigation |
| --- | --- | --- | | --- | --- | --- |
| The runner cannot provide the C++ or C# tools (MIL-007) | The workflows need a custom runner image or a longer install | MIL-007 task 2 verifies it first; the ADR of task 3 says what a build-dependent check needs from the caller. |
| Reusable workflow support is missing on the instance | MIL-003 and MIL-004 change shape | Verify first in each milestone (MIL-002 task 2, MIL-003 task 1); fall back to copied templates. | | Reusable workflow support is missing on the instance | MIL-003 and MIL-004 change shape | Verify first in each milestone (MIL-002 task 2, MIL-003 task 1); fall back to copied templates. |
| Security review by S05 is a bottleneck | Milestone decisions slip | Keep milestones small and plan review time before each decision date. | | Security review by S05 is a bottleneck | Milestone decisions slip | Keep milestones small and plan review time before each decision date. |
@@ -251,11 +257,14 @@ no tests, no release process.
[MIL-004]: ./milestones/mil-004-versioned-release.md [MIL-004]: ./milestones/mil-004-versioned-release.md
[MIL-005]: ./milestones/mil-005-wiki-and-translations.md [MIL-005]: ./milestones/mil-005-wiki-and-translations.md
[MIL-006]: ./milestones/mil-006-shared-wiki-publishing.md [MIL-006]: ./milestones/mil-006-shared-wiki-publishing.md
[MIL-007]: ./milestones/mil-007-cpp-csharp-quality-checks.md
[Milestone MIL-001]: https://git.tirsystem.com/TirSystem/github-action/milestone/18 [Milestone MIL-001]: https://git.tirsystem.com/TirSystem/github-action/milestone/18
[Milestone MIL-002]: https://git.tirsystem.com/TirSystem/github-action/milestone/19 [Milestone MIL-002]: https://git.tirsystem.com/TirSystem/github-action/milestone/19
[Milestone MIL-003]: https://git.tirsystem.com/TirSystem/github-action/milestone/20 [Milestone MIL-003]: https://git.tirsystem.com/TirSystem/github-action/milestone/20
[Milestone MIL-004]: https://git.tirsystem.com/TirSystem/github-action/milestone/21 [Milestone MIL-004]: https://git.tirsystem.com/TirSystem/github-action/milestone/21
[Milestone MIL-005]: https://git.tirsystem.com/TirSystem/github-action/milestone/93 [Milestone MIL-005]: https://git.tirsystem.com/TirSystem/github-action/milestone/93
[Milestone MIL-006]: https://git.tirsystem.com/TirSystem/github-action/milestone/94 [Milestone MIL-006]: https://git.tirsystem.com/TirSystem/github-action/milestone/94
[a2bf2d4]: https://git.tirsystem.com/TirSystem/github-action/commit/a2bf2d420416f7aff2badc6fe32a7690e2e523fd [Milestone MIL-007]: https://git.tirsystem.com/TirSystem/github-action/milestone/95
[ea06eaa]: https://git.tirsystem.com/TirSystem/github-action/commit/ea06eaaa698247d20e4a8e2694d7acbb1de58b63 [ea06eaa]: https://git.tirsystem.com/TirSystem/github-action/commit/ea06eaaa698247d20e4a8e2694d7acbb1de58b63
[b74ee0e]: https://git.tirsystem.com/TirSystem/github-action/commit/b74ee0e5cfd04f25ba5f2c1addc80699147c479a
[e3ad1fa]: https://git.tirsystem.com/TirSystem/github-action/commit/e3ad1fa7e7eef57a6793a479edb41ac6fd2db688
+5 -3
View File
@@ -9,7 +9,8 @@
## Version History ## Version History
| Date | Status | Author | Reviewer | Change | Commit | | Date | Status | Author | Reviewer | Change | Commit |
| --- | --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- | --- |
| 2026-10-11 | Accepted | Jens Tirsvad Nielsen | S01 | Initial version | [6933e91] | | 2026-10-11 | Deprecated | Jens Tirsvad Nielsen | S01 | Initial version | [6933e91] |
| 2026-10-11 | Accepted | Jens Tirsvad Nielsen | S01 | Criterion 3 verified; the action item is closed | [05b635c] |
--- ---
@@ -31,16 +32,17 @@
## Overall Verdict ## Overall Verdict
Go - all six criteria pass, and the five Go/No-Go criteria of MIL-004 are met. One limit is stated, not hidden: criterion 3 (a consumer pinned to the previous version keeps working) is met by construction until a second release exists, because tags are never moved. S01 gave the Go in chat on 2026-10-11, standing in for the other reviewers as ADR-0002 records; there is no independent reader. Go - all six criteria pass, and the five Go/No-Go criteria of MIL-004 are met. Criterion 3 (a consumer pinned to the previous version keeps working) was met by construction at the first release and verified on 2026-10-11 with pinned consumers after `v1.0.1` and `v1.1.0` (see the Verification results of MIL-004). S01 gave the Go in chat on 2026-10-11, standing in for the other reviewers as ADR-0002 records; there is no independent reader.
## Action Items ## Action Items
| Action | Owner | Due | | Action | Owner | Due |
| --- | --- | --- | | --- | --- | --- |
| Verify criterion 3 with a real consumer when `v1.0.1` or `v1.1.0` is released | S05 | next release | | Done 2026-10-11: criterion 3 verified with pinned consumers after `v1.0.1` (run 660) and `v1.1.0` (run 672) | S05 | done |
--- ---
[MIL-004]: ../../milestones/mil-004-versioned-release.md [MIL-004]: ../../milestones/mil-004-versioned-release.md
[QC-MIL-001]: ../../../framework/qc/qc-milestones-gateways.md [QC-MIL-001]: ../../../framework/qc/qc-milestones-gateways.md
[6933e91]: https://git.tirsystem.com/TirSystem/github-action/commit/6933e91c83075fb39b2dfbe41683cb2a0a0bce28 [6933e91]: https://git.tirsystem.com/TirSystem/github-action/commit/6933e91c83075fb39b2dfbe41683cb2a0a0bce28
[05b635c]: https://git.tirsystem.com/TirSystem/github-action/commit/05b635c1b3d79390c794e62e781595fbe5f1a4ea
@@ -0,0 +1,51 @@
# Review Record: ADR-0005 (tools-ref)
## Metadata
| Key | Value |
| --- | --- |
| ID | RC-051 |
| CrossReference | [ADR-0005], [QC-ADR-001] |
## Version History
| Date | Status | Author | Reviewer | Change | Commit |
| --- | --- | --- | --- | --- | --- |
| 2026-10-11 | Accepted | Jens Tirsvad Nielsen | S01 | Initial version | [670f0ac] |
---
## Artifact Under Review
- Instance reviewed: [ADR-0005] after the addition of the input `tools-ref`
- Checklist used: [QC-ADR-001]
## Checklist Results
| # | Criterion | Status | Evidence/Notes |
| --- | --- | --- | --- |
| 1 | The ID (`ADR-NNNN`, 4-digit) matches the filename number and is unique and sequential | Pass | Unchanged. |
| 2 | Context states the problem, the forces (cost, risk, constraints) and the options evaluated | Pass | Unchanged; the move of the shell steps to a script file is an implementation of the decided contract, not a new option. |
| 3 | At least two options were considered, or the absence of alternatives is justified | Pass | Unchanged. |
| 4 | Decision is stated in one or two unhedged sentences and matches one of the evaluated options | Pass | The decision sentence names the new optional input. |
| 5 | Consequences list both Positive and Negative outcomes | Pass | Finding noted for the next revision, not a blocker: a reusable workflow cannot learn the ref it was called at (the context fields `workflow_ref`, `workflow_sha`, `job_workflow_sha` and `action_ref` are empty on this instance, verified on 2026-10-11), so the script ref is an input and a call at an exact tag is pinned completely only with `tools-ref` set to the same tag. |
| 6 | Quality-attribute impacts (performance, security, portability, etc.) are named where the decision affects them | Pass | Maintainability gains (a script of this repository, linted and tested as a file); the pin limitation above is the reliability cost. |
| 7 | Affected Artifacts are listed as links, and each is also in CrossReference | Pass | Unchanged. |
| 8 | Status changes are recorded as new Version History rows with a Change summary and Commit link; retained rows are never rewritten | Pass | The earlier row is kept with status Deprecated and a new row added. |
| 9 | A superseded or deprecated ADR names its successor, and the successor links back | N-A | Nothing is superseded. |
| 10 | Written in business or technical language of the domain, with no unexplained abbreviations | Pass | Unchanged. |
| 11 | Status is one of `Proposed`, `Accepted`, `Rejected`, `Deprecated` or `Superseded by ADR-NNNN`, and every Version History row uses only these | Pass | Deprecated and Accepted. |
## Overall Verdict
Go - all applicable criteria pass. Adding an optional input is a minor change under ADR-0006. S01 gave the Go in chat on 2026-10-11, standing in for the other reviewers as ADR-0002 records; there is no independent reader.
## Action Items
| Action | Owner | Due |
| --- | --- | --- |
| Run a pinned consumer (`@v1.0.1` with `tools-ref: v1.0.1`) after the next release to confirm the complete pin | S05 | next release |
---
[ADR-0005]: ../../adr/adr-0005-quality-check-contract.md
[QC-ADR-001]: ../../../framework/qc/qc-adr.md
[670f0ac]: https://git.tirsystem.com/TirSystem/github-action/commit/670f0ac469043bb166c84290c0dbf62b4908e3cc
+47
View File
@@ -0,0 +1,47 @@
# Review Record: MIL-007
## Metadata
| Key | Value |
| --- | --- |
| ID | RC-052 |
| CrossReference | [MIL-007], [QC-MIL-001] |
## Version History
| Date | Status | Author | Reviewer | Change | Commit |
| --- | --- | --- | --- | --- | --- |
| 2026-10-11 | Accepted | Jens Tirsvad Nielsen | S01 | Initial version | [e3ad1fa] |
---
## Artifact Under Review
- Instance reviewed: [MIL-007]
- Checklist used: [QC-MIL-001]
## Checklist Results
| # | Criterion | Status | Evidence/Notes |
| --- | --- | --- | --- |
| 1 | A concrete deliverable is defined for every gate | Pass | Two reusable workflows with pinned tools, guides and wiki pages in three languages, a use in another repository and a release. |
| 2 | Explicit Go/No-Go criteria are stated for each gate | Pass | Six criteria, each with a Go and a No-Go: runner verified, ADR accepted, seeded failure visible, a run in another repository, no embedded script, released. |
| 3 | Dependencies on other milestones are explicitly mapped | Pass | MIL-003 for the contract and mechanism, MIL-004 for the release; both are closed. |
| 4 | Each milestone is traceable to a Business Case objective or KPI | Pass | O4, and US-001.09 and US-001.10 which the milestone extends. |
| 5 | Milestone owner and approving reviewer are identified | Pass | Owner S05, approving reviewer S01. |
| 6 | Milestone has a defined target date consistent with project constraints | Pass | 2027-01-16, after the last decision of the plan; the Business Case sets no deadline. |
## Overall Verdict
Go - all six criteria pass; no findings. The plan-first order holds inside the milestone: the use case and stories (task 1), the runner check (task 2) and the contract ADR (task 3) come before any workflow (tasks 4 and 5). The decision between one workflow and one per language is left to the ADR and is not made here. S01 gave the Go in chat on 2026-10-11, standing in for the other reviewers as ADR-0002 records; there is no independent reader.
## Action Items
| Action | Owner | Due |
| --- | --- | --- |
| Run `sync-project.sh --apply` so that the milestone and its issues exist | S05 | 2026-10-11 |
| Decide in the ADR (task 3) one workflow or one per language, and whether C is included | S01 | before tasks 4 and 5 |
---
[MIL-007]: ../../milestones/mil-007-cpp-csharp-quality-checks.md
[QC-MIL-001]: ../../../framework/qc/qc-milestones-gateways.md
[e3ad1fa]: https://git.tirsystem.com/TirSystem/github-action/commit/e3ad1fa7e7eef57a6793a479edb41ac6fd2db688
+47
View File
@@ -0,0 +1,47 @@
# Review Record: PP-001 Project Plan (MIL-007)
## Metadata
| Key | Value |
| --- | --- |
| ID | RC-053 |
| CrossReference | [PP-001], [BC-001] |
## Version History
| Date | Status | Author | Reviewer | Change | Commit |
| --- | --- | --- | --- | --- | --- |
| 2026-10-11 | Accepted | Jens Tirsvad Nielsen | S01 | Initial version | [e3ad1fa] |
---
## Artifact Under Review
- Instance reviewed: [PP-001]
- Checklist used: none: the Project Plan has no QC checklist. Validated against the Business Case [BC-001] and the Go / No-Go criteria of each listed milestone, as `PP.md` prescribes.
## Checklist Results
| # | Criterion | Status | Evidence/Notes |
| --- | --- | --- | --- |
| 1 | Every milestone in the plan has a `MIL-*` document and the plan's dates equal that document's target date | Pass | MIL-001 to MIL-007 exist; the MIL-007 decision date 2027-01-16 equals its Target Date. |
| 2 | Every Business Case scope item is covered by a gateway | Pass | Quality checks for C++ and C# are mapped to MIL-007 in Scope Coverage under the quality-check scope item (O4). |
| 3 | The schedule respects the Business Case constraints | Pass | The Business Case sets no deadline; the dates are the plan's own and say so. |
| 4 | Dependencies between milestones are stated | Pass | The Dependencies diagram shows MIL-003 and MIL-004 to MIL-007, and MIL-007 lists them in its own document. |
| 5 | Every gateway has an owner who is a stakeholder ID | Pass | MIL-007 is owned by S05. |
| 6 | Plan content is internally consistent | Pass | "Seven milestones", the Planning Assumptions, the Gantt entries, the Gateway Schedule and the Scope Coverage agree. |
| 7 | Open Issues are current | Pass | Unchanged items remain open; a plan risk was added for the runner's tools. |
## Overall Verdict
Go - all seven criteria pass; no findings. S01 gave the Go in chat on 2026-10-11, standing in for the other reviewers as ADR-0002 records; there is no independent reader.
## Action Items
| Action | Owner | Due |
| --- | --- | --- |
| Add the link to the Gitea milestone of MIL-007 to the Gateway Schedule after `--apply` | S05 | 2026-10-11 |
---
[PP-001]: ../../project-plan.md
[BC-001]: ../../business-case.md
[e3ad1fa]: https://git.tirsystem.com/TirSystem/github-action/commit/e3ad1fa7e7eef57a6793a479edb41ac6fd2db688
+15 -7
View File
@@ -4,13 +4,13 @@
| Key | Value | | Key | Value |
| --- | --- | | --- | --- |
| ID | TM-001 | | ID | TM-001 |
| CrossReference | [BC-001], [SA-001], [PP-001], [MIL-001], [MIL-002], [MIL-003], [MIL-004], [MIL-005], [MIL-006], [RC-001], [RC-002], [RC-003], [RC-004], [RC-005], [RC-006], [RC-007], [RC-008], [RC-009], [RC-010], [RC-011], [RC-012], [RC-013], [RC-014], [RC-015], [RC-016], [RC-017], [RC-018], [RC-019], [RC-020], [RC-021], [RC-022], [RC-023], [RC-024], [RC-025], [RC-026], [RC-027], [RC-028], [RC-029], [RC-030], [RC-031], [RC-032], [RC-033], [RC-034], [RC-035], [RC-036], [RC-037], [RC-038], [RC-039], [RC-040], [RC-041], [RC-042], [RC-043], [RC-044], [RC-045], [RC-046], [RC-047], [RC-048], [RC-049], [RC-050], [ADR-0007], [UC-007], [UC-006], [ADR-0006], [UC-005], [ADR-0005], [UCD-001], [US-001], [UC-001], [UC-002], [UC-003], [UC-004], [ADR-0001], [ADR-0002], [ADR-0003], [ADR-0004] | | CrossReference | [BC-001], [SA-001], [PP-001], [MIL-001], [MIL-002], [MIL-003], [MIL-004], [MIL-005], [MIL-006], [MIL-007], [RC-001], [RC-002], [RC-003], [RC-004], [RC-005], [RC-006], [RC-007], [RC-008], [RC-009], [RC-010], [RC-011], [RC-012], [RC-013], [RC-014], [RC-015], [RC-016], [RC-017], [RC-018], [RC-019], [RC-020], [RC-021], [RC-022], [RC-023], [RC-024], [RC-025], [RC-026], [RC-027], [RC-028], [RC-029], [RC-030], [RC-031], [RC-032], [RC-033], [RC-034], [RC-035], [RC-036], [RC-037], [RC-038], [RC-039], [RC-040], [RC-041], [RC-042], [RC-043], [RC-044], [RC-045], [RC-046], [RC-047], [RC-048], [RC-049], [RC-050], [RC-051], [RC-052], [RC-053], [ADR-0007], [UC-007], [UC-006], [ADR-0006], [UC-005], [ADR-0005], [UCD-001], [US-001], [UC-001], [UC-002], [UC-003], [UC-004], [ADR-0001], [ADR-0002], [ADR-0003], [ADR-0004] |
## Version History ## Version History
| Date | Status | Author | Reviewer | Change | Commit | | Date | Status | Author | Reviewer | Change | Commit |
| --- | --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- | --- |
| 2026-10-11 | Deprecated | Jens Tirsvad Nielsen | S01 | Added RC-049 (MIL-006 pilot results) | [d575f27] | | 2026-10-11 | Deprecated | Jens Tirsvad Nielsen | S01 | Added RC-051 (ADR-0005 tools-ref) | [670f0ac] |
| 2026-10-11 | Accepted | Jens Tirsvad Nielsen | S01 | Added RC-050 (MIL-005 translations accepted) | [ead3d98] | | 2026-10-11 | Accepted | Jens Tirsvad Nielsen | S01 | Added MIL-007, RC-052 and RC-053 | [e3ad1fa] |
--- ---
@@ -26,7 +26,7 @@ updated whenever an artifact instance is created or reviewed.
| --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- |
| [SA-001] | SA | - | [BC-001], [UCD-001] | [RC-004] | | [SA-001] | SA | - | [BC-001], [UCD-001] | [RC-004] |
| [BC-001] | BC | [SA-001] | [PP-001], [MIL-001], [MIL-002], [MIL-003], [MIL-004], [MIL-005], [MIL-006] | [RC-001], [RC-008] | | [BC-001] | BC | [SA-001] | [PP-001], [MIL-001], [MIL-002], [MIL-003], [MIL-004], [MIL-005], [MIL-006] | [RC-001], [RC-008] |
| [PP-001] | PP | [BC-001], [SA-001] | [MIL-001], [MIL-002], [MIL-003], [MIL-004], [MIL-005], [MIL-006], [US-001] | [RC-002], [RC-009], [RC-015], [RC-017], [RC-025], [RC-032] | | [PP-001] | PP | [BC-001], [SA-001] | [MIL-001], [MIL-002], [MIL-003], [MIL-004], [MIL-005], [MIL-006], [MIL-007], [US-001] | [RC-002], [RC-009], [RC-015], [RC-017], [RC-025], [RC-032], [RC-053] |
| [MIL-001] | MIL | [BC-001], [PP-001] | [MIL-002] | [RC-003], [RC-026] | | [MIL-001] | MIL | [BC-001], [PP-001] | [MIL-002] | [RC-003], [RC-026] |
| [MIL-002] | MIL | [BC-001], [PP-001], [MIL-001] | [MIL-003], [MIL-005] | [RC-005], [RC-027] | | [MIL-002] | MIL | [BC-001], [PP-001], [MIL-001] | [MIL-003], [MIL-005] | [RC-005], [RC-027] |
| [MIL-003] | MIL | [BC-001], [PP-001], [MIL-002] | [MIL-004] | [RC-006], [RC-028], [RC-033], [RC-039] | | [MIL-003] | MIL | [BC-001], [PP-001], [MIL-002] | [MIL-004] | [RC-006], [RC-028], [RC-033], [RC-039] |
@@ -39,6 +39,7 @@ updated whenever an artifact instance is created or reviewed.
| [RC-006] | RC | [MIL-003] | - | - | | [RC-006] | RC | [MIL-003] | - | - |
| [MIL-005] | MIL | [BC-001], [PP-001], [MIL-002] | [MIL-006] | [RC-010], [RC-011], [RC-030], [RC-050] | | [MIL-005] | MIL | [BC-001], [PP-001], [MIL-002] | [MIL-006] | [RC-010], [RC-011], [RC-030], [RC-050] |
| [MIL-006] | MIL | [BC-001], [PP-001], [MIL-002], [MIL-005] | - | [RC-016], [RC-031], [RC-049] | | [MIL-006] | MIL | [BC-001], [PP-001], [MIL-002], [MIL-005] | - | [RC-016], [RC-031], [RC-049] |
| [MIL-007] | MIL | [BC-001], [PP-001], [MIL-003], [MIL-004] | - | [RC-052] |
| [RC-007] | RC | [MIL-004] | - | - | | [RC-007] | RC | [MIL-004] | - | - |
| [RC-008] | RC | [BC-001] | - | - | | [RC-008] | RC | [BC-001] | - | - |
| [RC-009] | RC | [PP-001] | - | - | | [RC-009] | RC | [PP-001] | - | - |
@@ -62,7 +63,7 @@ updated whenever an artifact instance is created or reviewed.
| [UC-003] | UC | [UCD-001], [US-001], [SA-001] | - | [RC-023] | | [UC-003] | UC | [UCD-001], [US-001], [SA-001] | - | [RC-023] |
| [UC-004] | UC | [UCD-001], [US-001], [SA-001] | - | [RC-024] | | [UC-004] | UC | [UCD-001], [US-001], [SA-001] | - | [RC-024] |
| [UC-005] | UC | [UCD-001], [US-001], [SA-001] | [ADR-0005] | [RC-037] | | [UC-005] | UC | [UCD-001], [US-001], [SA-001] | [ADR-0005] | [RC-037] |
| [ADR-0005] | ADR | [MIL-003], [UC-005], [US-001], [ADR-0004] | - | [RC-038] | | [ADR-0005] | ADR | [MIL-003], [UC-005], [US-001], [ADR-0004] | - | [RC-038], [RC-051] |
| [UC-006] | UC | [UCD-001], [US-001], [SA-001] | [ADR-0006] | [RC-042] | | [UC-006] | UC | [UCD-001], [US-001], [SA-001] | [ADR-0006] | [RC-042] |
| [ADR-0006] | ADR | [MIL-004], [UC-006], [US-001], [ADR-0004], [ADR-0005] | - | [RC-043] | | [ADR-0006] | ADR | [MIL-004], [UC-006], [US-001], [ADR-0004], [ADR-0005] | - | [RC-043] |
| [ADR-0007] | ADR | [MIL-004], [UC-006], [ADR-0006] | - | [RC-047] | | [ADR-0007] | ADR | [MIL-004], [UC-006], [ADR-0006] | - | [RC-047] |
@@ -99,6 +100,9 @@ updated whenever an artifact instance is created or reviewed.
| [RC-048] | RC | [MIL-004] | - | - | | [RC-048] | RC | [MIL-004] | - | - |
| [RC-049] | RC | [MIL-006] | - | - | | [RC-049] | RC | [MIL-006] | - | - |
| [RC-050] | RC | [MIL-005] | - | - | | [RC-050] | RC | [MIL-005] | - | - |
| [RC-051] | RC | [ADR-0005] | - | - |
| [RC-052] | RC | [MIL-007] | - | - |
| [RC-053] | RC | [PP-001] | - | - |
## Coverage Notes ## Coverage Notes
@@ -118,6 +122,7 @@ updated whenever an artifact instance is created or reviewed.
[MIL-004]: ../milestones/mil-004-versioned-release.md [MIL-004]: ../milestones/mil-004-versioned-release.md
[MIL-005]: ../milestones/mil-005-wiki-and-translations.md [MIL-005]: ../milestones/mil-005-wiki-and-translations.md
[MIL-006]: ../milestones/mil-006-shared-wiki-publishing.md [MIL-006]: ../milestones/mil-006-shared-wiki-publishing.md
[MIL-007]: ../milestones/mil-007-cpp-csharp-quality-checks.md
[RC-001]: ./reviews/rc-001-business-case.md [RC-001]: ./reviews/rc-001-business-case.md
[RC-002]: ./reviews/rc-002-project-plan.md [RC-002]: ./reviews/rc-002-project-plan.md
[RC-003]: ./reviews/rc-003-mil-001.md [RC-003]: ./reviews/rc-003-mil-001.md
@@ -178,11 +183,14 @@ updated whenever an artifact instance is created or reviewed.
[RC-048]: ./reviews/rc-048-mil-004-outcome.md [RC-048]: ./reviews/rc-048-mil-004-outcome.md
[RC-049]: ./reviews/rc-049-mil-006-outcome.md [RC-049]: ./reviews/rc-049-mil-006-outcome.md
[RC-050]: ./reviews/rc-050-mil-005-outcome.md [RC-050]: ./reviews/rc-050-mil-005-outcome.md
[RC-051]: ./reviews/rc-051-adr-0005-tools-ref.md
[RC-052]: ./reviews/rc-052-mil-007.md
[RC-053]: ./reviews/rc-053-pp-001-mil-007.md
[ADR-0007]: ../adr/adr-0007-release-publish-destination.md [ADR-0007]: ../adr/adr-0007-release-publish-destination.md
[UC-007]: ../uc-007/uc.md [UC-007]: ../uc-007/uc.md
[UC-006]: ../uc-006/uc.md [UC-006]: ../uc-006/uc.md
[ADR-0006]: ../adr/adr-0006-release-process-and-version-pinning.md [ADR-0006]: ../adr/adr-0006-release-process-and-version-pinning.md
[UC-005]: ../uc-005/uc.md [UC-005]: ../uc-005/uc.md
[ADR-0005]: ../adr/adr-0005-quality-check-contract.md [ADR-0005]: ../adr/adr-0005-quality-check-contract.md
[d575f27]: https://git.tirsystem.com/TirSystem/github-action/commit/d575f272fcb044e3165951013464177a6e659ea6 [670f0ac]: https://git.tirsystem.com/TirSystem/github-action/commit/670f0ac469043bb166c84290c0dbf62b4908e3cc
[ead3d98]: https://git.tirsystem.com/TirSystem/github-action/commit/ead3d98d73fcab95fd57b0eb005a02aff9983256 [e3ad1fa]: https://git.tirsystem.com/TirSystem/github-action/commit/e3ad1fa7e7eef57a6793a479edb41ac6fd2db688
+8 -5
View File
@@ -4,12 +4,13 @@
| Key | Value | | Key | Value |
| --- | --- | | --- | --- |
| ID | UC-005 | | ID | UC-005 |
| CrossReference | [UCD-001], [US-001], [SA-001], [ADR-0005] | | CrossReference | [UCD-001], [US-001], [SA-001], [ADR-0005], [MIL-007] |
## Version History ## Version History
| Date | Status | Author | Reviewer | Change | Commit | | Date | Status | Author | Reviewer | Change | Commit |
| --- | --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- | --- |
| 2026-10-10 | Accepted | Jens Tirsvad Nielsen | S01 | Initial version | [385d2fb] | | 2026-10-10 | Deprecated | Jens Tirsvad Nielsen | S01 | Initial version | [385d2fb] |
| 2026-10-11 | Proposed | Jens Tirsvad Nielsen | S01 | Brief extended to C++ and C# (MIL-007 task 1) | [32b5ab6] |
--- ---
@@ -20,18 +21,18 @@
**Level:** user-goal **Level:** user-goal
**Primary Actor:** Software Engineer **Primary Actor:** Software Engineer
**Stakeholders:** S02, S05 (interests below) **Stakeholders:** S02, S05 (interests below)
**Stories:** US-001.09, US-001.10 **Stories:** US-001.09, US-001.10, US-001.16, US-001.17
## Brief ## Brief
A Software Engineer opens a pull request in a repository that calls the shared quality-check workflow. The workflow installs the pinned tools, runs the checks for each selected language on the files of the repository (lint, format and types for Python; lint and format for shell), and reports one status for the pull request. When every selected tool passes, the status is green. A Software Engineer opens a pull request in a repository that calls the shared quality-check workflow. The workflow installs the pinned tools, runs the checks for each selected language on the files of the repository (lint, format and types for Python; lint and format for shell; format and static checks for C++; format and analyzer checks for C#), and reports one status for the pull request. A language that needs a build or a project file says what is missing instead of passing. When every selected tool passes, the status is green.
**Stakeholder interests** **Stakeholder interests**
- S02 — checks run the same way in every project without writing a pipeline, and a failure says what to fix - S02 — checks run the same way in every project without writing a pipeline, and a failure says what to fix
- S05 — the checks and their tool versions are defined once and change through a reviewed release - S05 — the checks and their tool versions are defined once and change through a reviewed release
**Preconditions:** The repository is onboarded, the shared workflow is available through reusable workflow delivery, and the repository has files of at least one supported language. **Preconditions:** The repository is onboarded, the shared workflow is available through reusable workflow delivery, and the repository has files of at least one supported language (Python, shell, C++ or C#).
**Postconditions (success guarantee):** The pull request has a pass or fail status that comes from every selected tool, and no file of the repository was changed. **Postconditions (success guarantee):** The pull request has a pass or fail status that comes from every selected tool, and no file of the repository was changed.
@@ -41,4 +42,6 @@ A Software Engineer opens a pull request in a repository that calls the shared q
[US-001]: ../user-stories.md [US-001]: ../user-stories.md
[SA-001]: ../stakeholder-analysis.md [SA-001]: ../stakeholder-analysis.md
[ADR-0005]: ../adr/adr-0005-quality-check-contract.md [ADR-0005]: ../adr/adr-0005-quality-check-contract.md
[MIL-007]: ../milestones/mil-007-cpp-csharp-quality-checks.md
[385d2fb]: https://git.tirsystem.com/TirSystem/github-action/commit/385d2fb7cb9be1fdc8fd3503d0af624fa38eafc7 [385d2fb]: https://git.tirsystem.com/TirSystem/github-action/commit/385d2fb7cb9be1fdc8fd3503d0af624fa38eafc7
[32b5ab6]: https://git.tirsystem.com/TirSystem/github-action/commit/32b5ab62ab5a82638b71b8fcfb43a819e92ab346
+34 -5
View File
@@ -4,13 +4,13 @@
| Key | Value | | Key | Value |
| --- | --- | | --- | --- |
| ID | US-001 | | ID | US-001 |
| CrossReference | [UCD-001], [BC-001], [MIL-001], [MIL-002], [MIL-003], [MIL-004], [UC-001], [UC-002], [UC-003], [UC-004], [UC-005], [UC-006], [UC-007] | | CrossReference | [UCD-001], [BC-001], [MIL-001], [MIL-002], [MIL-003], [MIL-004], [UC-001], [UC-002], [UC-003], [UC-004], [UC-005], [UC-006], [UC-007], [MIL-007] |
## Version History ## Version History
| Date | Status | Author | Reviewer | Change | Commit | | Date | Status | Author | Reviewer | Change | Commit |
| --- | --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- | --- |
| 2026-10-10 | Deprecated | Jens Tirsvad Nielsen | S01 | Added US-001.11 to US-001.13 (releases, UC-006); US-001.13 traced to the epic (RC-041) | [044d8ba] | | 2026-10-10 | Deprecated | Jens Tirsvad Nielsen | S01 | Added US-001.14 and US-001.15 (UC-007); US-001.13 traced to UC-007 (RC-046) | [4cf52af] |
| 2026-10-10 | Accepted | Jens Tirsvad Nielsen | S01 | Added US-001.14 and US-001.15 (UC-007); US-001.13 traced to UC-007 (RC-046) | [4cf52af] | | 2026-10-11 | Proposed | Jens Tirsvad Nielsen | S01 | Added US-001.16 and US-001.17 (C++ and C# checks, MIL-007 task 1) | [32b5ab6] |
--- ---
@@ -218,9 +218,37 @@ actors of [UCD-001]. Stories for quality checks and releases (MIL-004) and for t
| --- | --- | --- | | --- | --- | --- |
| [UC-007], [MIL-004] | fits one iteration | none | | [UC-007], [MIL-004] | fits one iteration | none |
### US-001.16 — A C++ project gets the same checks
**As a** Software Engineer, **I want** a pull request in a C++ repository to run format and static checks, **so that** a defect is found before review without me writing a pipeline.
**Acceptance Criteria**
- Given a repository that calls the shared C++ quality-check workflow and has C++ files, when a pull request is opened, then the format and static checks run and the status is pass or fail.
- Given a file with a finding, when the check runs, then the job fails and the log shows the tool name with `path:line` and the tool's message.
- Given a repository that lacks what a check needs, when the check runs, then it fails with a `[configuration]` message that names what is missing instead of passing.
| Traces to | Size | INVEST exceptions |
| --- | --- | --- |
| [UC-005], [MIL-007] | fits one iteration | none |
### US-001.17 — A C# project gets the same checks
**As a** Software Engineer, **I want** a pull request in a C# repository to run format and analyzer checks, **so that** a defect is found before review without me writing a pipeline.
**Acceptance Criteria**
- Given a repository that calls the shared C# quality-check workflow and has a project or solution, when a pull request is opened, then the format and analyzer checks run and the status is pass or fail.
- Given a file with a finding, when the check runs, then the job fails and the log shows the tool name with `path:line` and the tool's message.
- Given a repository without a project or solution, when the check runs, then it fails with a `[configuration]` message that names what is missing instead of passing.
| Traces to | Size | INVEST exceptions |
| --- | --- | --- |
| [UC-005], [MIL-007] | fits one iteration | none |
## INVEST Check ## INVEST Check
All fifteen stories are independent of each other except that US-001.05 and All seventeen stories are independent of each other except that US-001.05 and
US-001.06 build on US-001.04 (the secrets must exist before a repository can US-001.06 build on US-001.04 (the secrets must exist before a repository can
run the workflow). Each states one goal and can be negotiated, delivers value run the workflow). Each states one goal and can be negotiated, delivers value
to the named role, is small enough for one iteration, and has acceptance to the named role, is small enough for one iteration, and has acceptance
@@ -236,12 +264,13 @@ behaviour is tested rather than built.
[MIL-002]: ./milestones/mil-002-onboarding-and-operations.md [MIL-002]: ./milestones/mil-002-onboarding-and-operations.md
[MIL-003]: ./milestones/mil-003-quality-checks.md [MIL-003]: ./milestones/mil-003-quality-checks.md
[MIL-004]: ./milestones/mil-004-versioned-release.md [MIL-004]: ./milestones/mil-004-versioned-release.md
[MIL-007]: ./milestones/mil-007-cpp-csharp-quality-checks.md
[UC-001]: ./uc-001/uc.md [UC-001]: ./uc-001/uc.md
[UC-002]: ./uc-002/uc.md [UC-002]: ./uc-002/uc.md
[UC-003]: ./uc-003/uc.md [UC-003]: ./uc-003/uc.md
[UC-004]: ./uc-004/uc.md [UC-004]: ./uc-004/uc.md
[UC-005]: ./uc-005/uc.md [UC-005]: ./uc-005/uc.md
[UC-006]: ./uc-006/uc.md [UC-006]: ./uc-006/uc.md
[044d8ba]: https://git.tirsystem.com/TirSystem/github-action/commit/044d8ba21f96f4aceb807d62addc35cdcbb56051
[UC-007]: ./uc-007/uc.md [UC-007]: ./uc-007/uc.md
[4cf52af]: https://git.tirsystem.com/TirSystem/github-action/commit/4cf52afe00f546c6b20424fb8dfc12796ffd9296 [4cf52af]: https://git.tirsystem.com/TirSystem/github-action/commit/4cf52afe00f546c6b20424fb8dfc12796ffd9296
[32b5ab6]: https://git.tirsystem.com/TirSystem/github-action/commit/32b5ab62ab5a82638b71b8fcfb43a819e92ab346
+6
View File
@@ -32,12 +32,18 @@ jobs:
| `mypy-version` | `1.11.2` | Version of mypy | | `mypy-version` | `1.11.2` | Version of mypy |
| `shellcheck-py-version` | `0.10.0.1` | Version of the shellcheck-py package (ShellCheck 0.10.0) | | `shellcheck-py-version` | `0.10.0.1` | Version of the shellcheck-py package (ShellCheck 0.10.0) |
| `shfmt-py-version` | `3.11.0.2` | Version of the shfmt-py package (shfmt 3.11.0) | | `shfmt-py-version` | `3.11.0.2` | Version of the shfmt-py package (shfmt 3.11.0) |
| `tools-ref` | `v1` | Tag or branch of `TirSystem/github-action` from which the script `tools/quality-checks.sh` is fetched |
Reference the workflow at the major tag (`@v1`) or at an exact tag Reference the workflow at the major tag (`@v1`) or at an exact tag
(`@v1.0.0`), never at `main` and never at the branch of a pull request: that (`@v1.0.0`), never at `main` and never at the branch of a pull request: that
branch is deleted when the pull request is merged and the reference stops branch is deleted when the pull request is merged and the reference stops
working. See the upgrade guide for what each choice receives. working. See the upgrade guide for what each choice receives.
The workflow file calls a script of this repository, fetched at `tools-ref`. A
call at an exact tag is pinned completely only when `tools-ref` names the same
tag, for example `@v1.0.1` with `tools-ref: v1.0.1`; with the default the
script follows `v1`.
## What is checked ## What is checked
| Language | Files | Tools | | Language | Files | Tools |
+4
View File
@@ -16,6 +16,10 @@ repository, tags `vMAJOR.MINOR.PATCH` and a moving major tag `vMAJOR`.
A breaking release gets a new major version (`v2`), so a pipeline on `@v1` A breaking release gets a new major version (`v2`), so a pipeline on `@v1`
never receives it until you change the line yourself. never receives it until you change the line yourself.
The quality-checks workflow fetches its script at its input `tools-ref`, which
defaults to `v1`. To pin it completely, call it at an exact tag and set
`tools-ref` to the same tag; otherwise the script follows the major tag.
The two scoped workflows (**Sync GitHub mirror metadata** and **Publish The two scoped workflows (**Sync GitHub mirror metadata** and **Publish
wiki**) are delivered by the instance and are not in your `uses:` lines; you wiki**) are delivered by the instance and are not in your `uses:` lines; you
cannot pin them, but they fetch their scripts at the major tag. cannot pin them, but they fetch their scripts at the major tag.
+1
View File
@@ -1,2 +1,3 @@
pytest>=8,<10 pytest>=8,<10
PyYAML>=6,<7 PyYAML>=6,<7
coverage>=7,<8
+31 -9
View File
@@ -15,6 +15,8 @@ import yaml
from workflow_source import REPO_ROOT from workflow_source import REPO_ROOT
WORKFLOW = REPO_ROOT / ".gitea" / "workflows" / "quality-checks.yml" WORKFLOW = REPO_ROOT / ".gitea" / "workflows" / "quality-checks.yml"
SCRIPT = REPO_ROOT / "tools" / "quality-checks.sh"
COMMANDS = {"Validate the inputs": "validate", "Install the tools": "install", "Run the checks": "run"}
BASH = shutil.which("bash") BASH = shutil.which("bash")
pytestmark = pytest.mark.skipif(BASH is None, reason="bash is needed to run the workflow steps") pytestmark = pytest.mark.skipif(BASH is None, reason="bash is needed to run the workflow steps")
@@ -25,7 +27,7 @@ def document() -> dict:
return yaml.safe_load(WORKFLOW.read_text(encoding="utf-8")) return yaml.safe_load(WORKFLOW.read_text(encoding="utf-8"))
def step_script(document: dict, name: str) -> str: def step_command(document: dict, name: str) -> str:
steps = document["jobs"]["quality-checks"]["steps"] steps = document["jobs"]["quality-checks"]["steps"]
return next(step["run"] for step in steps if step.get("name") == name) return next(step["run"] for step in steps if step.get("name") == name)
@@ -40,9 +42,11 @@ class TestDefinition:
"mypy-version", "mypy-version",
"shellcheck-py-version", "shellcheck-py-version",
"shfmt-py-version", "shfmt-py-version",
"tools-ref",
} }
assert inputs["languages"]["default"] == "python,shell" assert inputs["languages"]["default"] == "python,shell"
assert inputs["working-directory"]["default"] == "." assert inputs["working-directory"]["default"] == "."
assert inputs["tools-ref"]["default"] == "v1"
def test_tool_versions_are_pinned_to_the_adr(self, document): def test_tool_versions_are_pinned_to_the_adr(self, document):
inputs = document[True]["workflow_call"]["inputs"] inputs = document[True]["workflow_call"]["inputs"]
@@ -60,14 +64,32 @@ class TestDefinition:
for step in document["jobs"]["quality-checks"]["steps"]: for step in document["jobs"]["quality-checks"]["steps"]:
assert "${{" not in step.get("run", ""), f"{step.get('name')} interpolates an expression" assert "${{" not in step.get("run", ""), f"{step.get('name')} interpolates an expression"
@pytest.mark.parametrize( def test_the_script_parses(self):
"name", ["Validate the inputs", "Install the tools", "Run the checks"] result = subprocess.run([BASH, "-n", str(SCRIPT)], text=True, capture_output=True)
)
def test_every_script_parses(self, document, name):
script = step_script(document, name)
result = subprocess.run([BASH, "-n"], input=script, text=True, capture_output=True)
assert result.returncode == 0, result.stderr assert result.returncode == 0, result.stderr
def test_the_workflow_embeds_no_script_and_runs_the_file(self, document):
steps = document["jobs"]["quality-checks"]["steps"]
commands = [step["run"] for step in steps if "run" in step]
assert commands == [
"bash shared-scripts/tools/quality-checks.sh validate",
"bash shared-scripts/tools/quality-checks.sh install",
"bash shared-scripts/tools/quality-checks.sh run",
]
def test_the_script_is_fetched_at_the_tools_ref_input(self, document):
steps = document["jobs"]["quality-checks"]["steps"]
fetch = next(step for step in steps if step.get("name") == "Fetch the shared script")
assert fetch["with"] == {
"repository": "TirSystem/github-action",
"ref": "${{ inputs.tools-ref }}",
"path": "shared-scripts",
}
def test_an_unknown_command_is_refused(self):
result = subprocess.run([BASH, str(SCRIPT), "bogus"], text=True, capture_output=True)
assert result.returncode == 2
STUB = """#!/usr/bin/env bash STUB = """#!/usr/bin/env bash
name="$(basename "$0")" name="$(basename "$0")"
@@ -111,7 +133,7 @@ def add_files(root: Path, *relative: str) -> None:
def run_step(document, name, repo, bin_dir, tmp_path, **env): def run_step(document, name, repo, bin_dir, tmp_path, **env):
script = step_script(document, name) command = COMMANDS[name]
log = tmp_path / "tools.log" log = tmp_path / "tools.log"
environment = dict(os.environ) environment = dict(os.environ)
environment.update( environment.update(
@@ -126,7 +148,7 @@ def run_step(document, name, repo, bin_dir, tmp_path, **env):
) )
environment.update(env) environment.update(env)
result = subprocess.run( result = subprocess.run(
[BASH, "-c", script], cwd=repo, env=environment, text=True, capture_output=True [BASH, str(SCRIPT), command], cwd=repo, env=environment, text=True, capture_output=True
) )
calls = log.read_text(encoding="utf-8").splitlines() if log.exists() else [] calls = log.read_text(encoding="utf-8").splitlines() if log.exists() else []
return result, calls return result, calls
+371
View File
@@ -0,0 +1,371 @@
"""Offline tests for the parts of tools/ that the flow tests do not reach.
The flow tests of each tool replace its HTTP function with a fake, so the real
`request_json` of the three API tools and the command-line entry point of each
tool are tested here: the reply that is parsed, the HTTP error that is named by
category, and the exit code and message of a run as a process.
"""
import io
import json
import subprocess
import sys
import urllib.error
import pytest
from workflow_source import REPO_ROOT, load_script
TOOLS = REPO_ROOT / "tools"
changelog = load_script(TOOLS / "changelog.py", "changelog_entry")
build_wiki = load_script(TOOLS / "build_wiki.py", "build_wiki_entry")
release = load_script(TOOLS / "release.py", "release_entry")
publish = load_script(TOOLS / "publish_wiki.py", "publish_wiki_entry")
sync = load_script(TOOLS / "sync_github_metadata.py", "sync_entry")
API_TOOLS = pytest.mark.parametrize(
"tool", [release, publish, sync], ids=["release", "publish_wiki", "sync_github_metadata"]
)
class FakeResponse:
def __init__(self, body: bytes) -> None:
self._body = body
def __enter__(self):
return self
def __exit__(self, *exc):
return False
def read(self) -> bytes:
return self._body
def http_error(code: int) -> urllib.error.HTTPError:
return urllib.error.HTTPError("https://git.example/api", code, "reason", {}, io.BytesIO(b""))
@pytest.fixture()
def urlopen(monkeypatch):
"""Replace urllib.request.urlopen; set `.reply` to a body or an exception."""
class Opener:
reply: object = b"{}"
requests: list = []
def __call__(self, request, timeout=None):
self.requests.append(request)
if isinstance(self.reply, BaseException):
raise self.reply
return FakeResponse(self.reply)
opener = Opener()
opener.requests = []
monkeypatch.setattr("urllib.request.urlopen", opener)
return opener
class TestRequestJson:
@API_TOOLS
def test_parses_a_json_reply(self, tool, urlopen):
urlopen.reply = b'{"name": "x"}'
assert tool["request_json"]("https://git.example/api", headers={"A": "b"}) == {"name": "x"}
@API_TOOLS
def test_an_empty_reply_is_none(self, tool, urlopen):
urlopen.reply = b""
assert tool["request_json"]("https://git.example/api", method="DELETE") is None
@API_TOOLS
def test_a_body_is_sent_as_json(self, tool, urlopen):
urlopen.reply = b"{}"
tool["request_json"]("https://git.example/api", method="POST", body={"k": 1})
assert json.loads(urlopen.requests[0].data) == {"k": 1}
assert urlopen.requests[0].get_method() == "POST"
@API_TOOLS
@pytest.mark.parametrize(
"code, category",
[(401, "credentials"), (403, "permissions"), (500, "api")],
)
def test_an_http_error_is_named_by_category(self, tool, urlopen, code, category):
urlopen.reply = http_error(code)
with pytest.raises(tool["WorkflowError"]) as error:
tool["request_json"]("https://git.example/api")
assert error.value.category == category
assert str(code) in str(error.value)
@pytest.mark.parametrize("tool", [publish, sync], ids=["publish_wiki", "sync_github_metadata"])
def test_a_missing_repository_is_a_configuration_error(self, tool, urlopen):
urlopen.reply = http_error(404)
with pytest.raises(tool["WorkflowError"]) as error:
tool["request_json"]("https://git.example/api")
assert error.value.category == "configuration"
@API_TOOLS
def test_an_unreachable_host_is_an_api_error(self, tool, urlopen):
urlopen.reply = urllib.error.URLError("no route")
with pytest.raises(tool["WorkflowError"]) as error:
tool["request_json"]("https://git.example/api")
assert error.value.category == "api"
assert "unreachable" in str(error.value)
def test_the_sync_tool_names_the_service_in_its_message(self, urlopen):
urlopen.reply = http_error(500)
with pytest.raises(sync["WorkflowError"]) as github:
sync["request_json"]("https://api.github.com/repos/o/r")
assert "GitHub API" in str(github.value)
with pytest.raises(sync["WorkflowError"]) as gitea:
sync["request_json"]("https://git.example/api/v1/repos/o/r")
assert "Gitea API" in str(gitea.value)
class TestPagination:
def test_release_reads_every_page_of_tags(self):
pages = {1: [{"name": f"v{n}"} for n in range(50)], 2: [{"name": "v50"}], 3: []}
def request(url, headers=None):
page = int(url.split("page=")[1])
return pages[page]
names = release["list_tag_names"]("https://git.example/api", {}, request)
assert len(names) == 51 and names[-1] == "v50"
def test_release_stops_on_an_empty_first_page(self):
assert release["list_tag_names"]("https://git.example/api", {}, lambda *a, **k: []) == []
def test_publish_reads_every_page_of_wiki_titles(self):
full = [{"sub_url": f"Page-{n}"} for n in range(publish["PAGE_LIMIT"])]
calls = []
def send(url, headers=None):
calls.append(url)
return full if "page=1&" in url else [{"sub_url": "Last"}]
titles = publish["list_wiki_titles"]("https://git.example/wiki", {}, send)
assert len(calls) == 2
assert "Last" in titles and "Page-0" in titles
def test_publish_refuses_a_list_that_is_not_a_list(self):
with pytest.raises(publish["WorkflowError"]) as error:
publish["list_wiki_titles"](
"https://git.example/wiki", {}, lambda *a, **k: {"message": "x"}
)
assert error.value.category == "api"
class TestReleaseInputs:
@pytest.fixture()
def env(self, tmp_path, monkeypatch):
changelog_file = tmp_path / "CHANGELOG.md"
changelog_file.write_text(
"## [Unreleased]\n\n## [1.0.0] - 2026-10-10\n\n### Added\n\n- First.\n",
encoding="utf-8",
)
for key, value in {
"GITEA_API_URL": "https://git.example/api/v1",
"SOURCE_REPOSITORY": "o/r",
"RELEASE_CREDENTIALS": "t" * 40,
"RELEASE_VERSION": "1.0.0",
"RELEASE_SHA": "a" * 40,
"RELEASE_DRY_RUN": "true",
"CHANGELOG_FILE": str(changelog_file),
}.items():
monkeypatch.setenv(key, value)
return monkeypatch
@pytest.mark.parametrize("repository", ["", "owner", "/repo", "owner/"])
def test_a_malformed_repository_is_refused(self, env, repository):
env.setenv("SOURCE_REPOSITORY", repository)
with pytest.raises(release["WorkflowError"]) as error:
release["main"](request=lambda *a, **k: None)
assert error.value.category == "configuration"
@pytest.mark.parametrize("sha", ["", "abc", "g" * 40])
def test_a_malformed_commit_is_refused(self, env, sha):
env.setenv("RELEASE_SHA", sha)
with pytest.raises(release["WorkflowError"]) as error:
release["main"](request=lambda *a, **k: None)
assert "RELEASE_SHA" in str(error.value)
def test_an_unreadable_changelog_is_a_configuration_error(self, env, tmp_path):
env.setenv("CHANGELOG_FILE", str(tmp_path / "missing.md"))
with pytest.raises(release["WorkflowError"]) as error:
release["main"](request=lambda *a, **k: None)
assert error.value.category == "configuration"
def test_a_changelog_that_breaks_its_convention_is_a_validation_error(self, env, tmp_path):
bad = tmp_path / "bad.md"
bad.write_text("## [1.0.0] - 2026-10-10\n\n### Added\n\n- X.\n", encoding="utf-8")
env.setenv("CHANGELOG_FILE", str(bad))
with pytest.raises(release["WorkflowError"]) as error:
release["main"](request=lambda *a, **k: None)
assert error.value.category == "validation"
assert "Unreleased" in str(error.value)
class TestChangelogCommandLine:
@pytest.fixture()
def good(self, tmp_path):
path = tmp_path / "CHANGELOG.md"
path.write_text(
"## [Unreleased]\n\n## [1.1.0] - 2026-10-11\n\n### Added\n\n- A.\n\n"
"## [1.0.0] - 2026-10-10\n\n### Fixed\n\n- B.\n",
encoding="utf-8",
)
return path
def test_check_passes_a_good_file(self, good, capsys):
assert changelog["main"](["check", "--file", str(good)]) == 0
assert "follows the convention" in capsys.readouterr().out
def test_check_names_a_missing_version(self, good, capsys):
assert changelog["main"](["check", "--file", str(good), "--version", "2.0.0"]) == 1
assert "no changelog entry with changes for version 2.0.0" in capsys.readouterr().err
def test_check_reports_each_problem(self, tmp_path, capsys):
path = tmp_path / "CHANGELOG.md"
path.write_text("## [1.0.0]\n\n### Odd\n\n", encoding="utf-8")
assert changelog["main"](["check", "--file", str(path)]) == 1
err = capsys.readouterr().err
assert "first section must be `## [Unreleased]`" in err
assert "not one of Breaking, Added, Changed, Fixed" in err
assert "needs a date" in err
def test_notes_prints_the_release_page(self, good, capsys):
assert changelog["main"](["notes", "1.1.0", "--file", str(good)]) == 0
out = capsys.readouterr().out
assert out.startswith("## 1.1.0 - 2026-10-11") and "- A." in out
def test_notes_needs_a_version(self, good, capsys):
assert changelog["main"](["notes", "--file", str(good)]) == 1
assert "needs a version" in capsys.readouterr().err
def test_notes_names_an_unknown_version(self, good, capsys):
assert changelog["main"](["notes", "9.9.9", "--file", str(good)]) == 1
assert "no changelog entry" in capsys.readouterr().err
def test_a_group_without_an_item_and_a_release_without_a_group_are_problems(self):
text = "## [Unreleased]\n\n### Added\n\n## [1.0.0] - 2026-10-10\n"
problems = changelog["validate"](text)
assert any("has no item" in p for p in problems)
assert any("has no change group" in p for p in problems)
def test_an_unreleased_section_has_no_date_and_versions_descend(self):
text = (
"## [Unreleased] - 2026-10-10\n\n## [1.0.0] - 2026-10-10\n\n### Added\n\n- A.\n\n"
"## [1.1.0] - 2026-10-11\n\n### Added\n\n- B.\n"
)
problems = changelog["validate"](text)
assert any("has no date" in p for p in problems)
assert any("must be lower than the version above it" in p for p in problems)
class TestBuildWikiCommandLine:
def test_hash_prints_twelve_hex_digits_for_a_page(self, capsys):
assert build_wiki["main"](["--hash", "Home"]) == 0
assert len(capsys.readouterr().out.strip()) == build_wiki["HASH_LENGTH"]
def test_hash_of_an_unknown_page_fails(self, capsys):
assert build_wiki["main"](["--hash", "Nope"]) == 1
assert "unknown page" in capsys.readouterr().err
def test_check_passes_on_the_committed_wiki(self, capsys):
assert build_wiki["main"](["--check"]) == 0
assert "up to date" in capsys.readouterr().out
def test_check_lists_every_problem_and_the_command_that_fixes_it(self, monkeypatch, capsys):
monkeypatch.setattr(build_wiki._module, "check_pages", lambda: ["Home.md: missing"])
monkeypatch.setattr(build_wiki._module, "check_translations", lambda: [])
assert build_wiki["main"](["--check"]) == 1
err = capsys.readouterr().err
assert "ERROR: wiki/Home.md: missing" in err
assert "python tools/build_wiki.py" in err
def test_generate_reports_the_changed_pages(self, monkeypatch, capsys):
monkeypatch.setattr(build_wiki._module, "write_pages", lambda: ["Home.md"])
assert build_wiki["main"]([]) == 0
assert "Wrote 1 page(s): Home.md" in capsys.readouterr().out
def test_generate_reports_a_build_error(self, monkeypatch, capsys):
def broken():
raise build_wiki["WikiBuildError"]("README.md is missing")
monkeypatch.setattr(build_wiki._module, "write_pages", broken)
assert build_wiki["main"]([]) == 1
assert "ERROR: README.md is missing" in capsys.readouterr().err
def run_tool(name: str, **env: str) -> subprocess.CompletedProcess:
"""Run a tool as `python tools/<name>` with only the given environment."""
environment = {"PATH": "", "SYSTEMROOT": "", **env}
environment = {k: v for k, v in environment.items() if v != "" or k in env}
return subprocess.run(
[sys.executable, str(TOOLS / name)],
env=environment,
capture_output=True,
text=True,
cwd=REPO_ROOT,
)
class TestRunAsAProcess:
"""The `if __name__ == "__main__"` block: exit code 1 and ERROR: on stderr."""
def test_release_without_a_version_exits_1(self):
result = run_tool("release.py")
assert result.returncode == 1
assert result.stderr.startswith("ERROR: [configuration]")
def test_publish_wiki_without_a_repository_exits_1(self):
result = run_tool("publish_wiki.py", WIKI_DIR=str(REPO_ROOT / "wiki"), GITEA_API_URL="https://x")
assert result.returncode == 1
assert "ERROR:" in result.stderr
def test_sync_without_a_token_exits_1(self):
result = run_tool("sync_github_metadata.py", GITEA_API_URL="https://x", SOURCE_REPOSITORY="o/r")
assert result.returncode == 1
assert "ERROR:" in result.stderr and "TOKEN_FOR_GITEA" in result.stderr
def test_changelog_checks_the_committed_file(self):
result = run_tool("changelog.py", **{})
assert result.returncode != 0 # the command is required
result = subprocess.run(
[sys.executable, str(TOOLS / "changelog.py"), "check"],
capture_output=True,
text=True,
cwd=REPO_ROOT,
)
assert result.returncode == 0, result.stderr
class TestRemainingBranches:
def test_notes_of_a_release_without_changes_is_not_a_page(self):
text = "## [Unreleased]\n\n## [1.0.0] - 2026-10-10\n"
with pytest.raises(LookupError):
changelog["notes"](text, "1.0.0")
@pytest.mark.parametrize("repository", [None, "", "owner", "/repo", "owner/"])
def test_publish_refuses_a_malformed_repository(self, repository):
with pytest.raises(publish["WorkflowError"]) as error:
publish["split_repository"](repository)
assert error.value.category == "configuration"
def test_publish_treats_a_wiki_without_pages_as_empty(self):
def send(url, headers=None):
raise publish["WorkflowError"]("configuration", "not found")
assert publish["list_wiki_titles"]("https://git.example/wiki", {}, send) == set()
def test_publish_does_not_hide_a_failure_on_a_later_page(self):
full = [{"sub_url": f"P-{n}"} for n in range(publish["PAGE_LIMIT"])]
def send(url, headers=None):
if "page=1&" in url:
return full
raise publish["WorkflowError"]("configuration", "gone")
with pytest.raises(publish["WorkflowError"]):
publish["list_wiki_titles"]("https://git.example/wiki", {}, send)
+16
View File
@@ -73,3 +73,19 @@ def test_no_workflow_copy_outside_the_workflow_directories():
if path.name == SYNC_WORKFLOW.name if path.name == SYNC_WORKFLOW.name
] ]
assert not stray, f"duplicate workflow copies: {stray}" assert not stray, f"duplicate workflow copies: {stray}"
def test_validation_runs_the_tests_under_a_coverage_gate():
"""validate-workflows.yml measures tools/ and fails below the .coveragerc limit."""
text = (REPO_ROOT / ".gitea" / "workflows" / "validate-workflows.yml").read_text(
encoding="utf-8"
)
assert "-m coverage run -m pytest -q tests" in text
assert "-m coverage report" in text
config = (REPO_ROOT / ".coveragerc").read_text(encoding="utf-8")
assert "source = tools" in config and "branch = True" in config
assert any(
line.replace(" ", "") == "fail_under=95" for line in config.splitlines()
), "the gate is 95 percent"
requirements = (REPO_ROOT / "tests" / "requirements.txt").read_text(encoding="utf-8")
assert "coverage" in requirements
+122
View File
@@ -0,0 +1,122 @@
#!/usr/bin/env bash
# Steps of the reusable workflow .gitea/workflows/quality-checks.yml (ADR-0005).
# The workflow checks out the calling repository, fetches this file into
# shared-scripts/ and runs one command per step:
#
# quality-checks.sh validate check the inputs, before anything is installed
# quality-checks.sh install install the pinned tools into a virtual environment
# quality-checks.sh run run every selected tool on the tracked files
#
# Settings come from the environment, never from interpolated expressions:
# LANGUAGES, WORKING_DIRECTORY, RUFF_VERSION, MYPY_VERSION,
# SHELLCHECK_PY_VERSION, SHFMT_PY_VERSION, QC_VENV (and QC_BIN for tests).
# Every failure is printed as ::error::[category] message.
validate_inputs() {
set -u
IFS=',' read -ra langs <<< "${LANGUAGES// /}"
if [ "${#langs[@]}" -eq 0 ]; then
echo "::error::[configuration] languages is empty; use python, shell or both, comma separated."
exit 1
fi
for lang in "${langs[@]}"; do
case "$lang" in
python | shell) ;;
*)
echo "::error::[configuration] Unknown language '$lang'; use python and shell, comma separated."
exit 1
;;
esac
done
case "$WORKING_DIRECTORY" in
"" | /* | .. | ../* | */.. | */../*)
echo "::error::[configuration] working-directory must be a relative path inside the repository."
exit 1
;;
esac
echo "Checking ${LANGUAGES} in ${WORKING_DIRECTORY}"
}
install_tools() {
set -eu
IFS=',' read -ra langs <<< "${LANGUAGES// /}"
packages=()
for lang in "${langs[@]}"; do
case "$lang" in
python) packages+=("ruff==${RUFF_VERSION}" "mypy==${MYPY_VERSION}") ;;
shell) packages+=("shellcheck-py==${SHELLCHECK_PY_VERSION}" "shfmt-py==${SHFMT_PY_VERSION}") ;;
esac
done
python3 -m venv "$QC_VENV"
"$QC_VENV/bin/pip" install --quiet "${packages[@]}"
}
run_checks() {
set -u
QC_BIN="${QC_BIN:-$QC_VENV/bin}"
if [ ! -d "$WORKING_DIRECTORY" ]; then
echo "::error::[configuration] working-directory '$WORKING_DIRECTORY' does not exist."
exit 1
fi
cd "$WORKING_DIRECTORY"
failed=()
run_tool() {
local name="$1"
shift
echo "::group::$name"
if "$@"; then
echo "::endgroup::"
echo "PASS $name"
else
echo "::endgroup::"
echo "FAIL $name"
failed+=("$name")
fi
}
wanted() {
local lang
IFS=',' read -ra langs <<< "${LANGUAGES// /}"
for lang in "${langs[@]}"; do
[ "$lang" = "$1" ] && return 0
done
return 1
}
if wanted python; then
mapfile -t files < <(git ls-files -- '*.py')
if [ "${#files[@]}" -eq 0 ]; then
echo "::notice::No python files in '$WORKING_DIRECTORY'; nothing was checked."
else
run_tool "ruff check" "$QC_BIN/ruff" check "${files[@]}"
run_tool "ruff format --check" "$QC_BIN/ruff" format --check "${files[@]}"
run_tool "mypy --strict" "$QC_BIN/mypy" --strict "${files[@]}"
fi
fi
if wanted shell; then
mapfile -t files < <(git ls-files -- '*.sh')
if [ "${#files[@]}" -eq 0 ]; then
echo "::notice::No shell files in '$WORKING_DIRECTORY'; nothing was checked."
else
run_tool "shellcheck" "$QC_BIN/shellcheck" "${files[@]}"
run_tool "shfmt -d" "$QC_BIN/shfmt" -d "${files[@]}"
fi
fi
if [ "${#failed[@]}" -gt 0 ]; then
echo "::error::Quality checks failed: ${failed[*]}"
exit 1
fi
echo "All selected checks passed."
}
case "${1:-}" in
validate) validate_inputs ;;
install) install_tools ;;
run) run_checks ;;
*)
echo "::error::[configuration] usage: quality-checks.sh validate|install|run" >&2
exit 2
;;
esac
+5 -4
View File
@@ -1,4 +1,4 @@
<!-- translation of Home.md, source sha256 8d1f5ed6df38 --> <!-- translation of Home.md, source sha256 510b243ec5f2 -->
> Dette er en oversættelse af [`Home`](Home). Den engelske side er autoritativ, og ændringer her bliver overskrevet, næste gang wikien publiceres. > Dette er en oversættelse af [`Home`](Home). Den engelske side er autoritativ, og ændringer her bliver overskrevet, næste gang wikien publiceres.
@@ -252,7 +252,7 @@ Trigger -> Workflow: Start workflow
activate Workflow activate Workflow
Workflow -> Workflow: Check out the repository Workflow -> Workflow: Check out the repository
Workflow -> Workflow: Create a virtual environment and\ninstall tests/requirements.txt Workflow -> Workflow: Create a virtual environment and\ninstall tests/requirements.txt
Workflow -> Workflow: Run pytest on tests/ (offline, no secrets):\nworkflows parse as YAML, no embedded Python,\nscripts compile, wiki pages and translations are current,\nsync, publish and release flows against a fake API Workflow -> Workflow: Run pytest on tests/ under coverage (offline, no secrets):\nworkflows parse as YAML, no embedded Python,\nscripts compile, wiki pages and translations are current,\nsync, publish and release flows against a fake API
alt a test fails alt a test fails
Workflow --> Trigger: Workflow fails (the pull request is not ready) Workflow --> Trigger: Workflow fails (the pull request is not ready)
@@ -299,9 +299,10 @@ Workflow-filerne og scripts i `tools/` valideres af offline-tests, som hverken k
python3 -m venv .venv python3 -m venv .venv
source .venv/bin/activate # Windows PowerShell: .venv\Scripts\Activate.ps1 source .venv/bin/activate # Windows PowerShell: .venv\Scripts\Activate.ps1
python -m pip install -r tests/requirements.txt python -m pip install -r tests/requirements.txt
python -m pytest -q tests python -m coverage run -m pytest -q tests
python -m coverage report
``` ```
De samme kontroller kører ved hver pull request og hvert push til `main` gennem [`validate-workflows.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/workflows/validate-workflows.yml). De parser alle workflows som YAML, kontrollerer at ingen indlejrer Python, kompilerer scripts i `tools/`, kræver eksplicitte jobtilladelser og tester parsing af credentials, parsing af push-spejlets adresse og synkroniserings- og publiceringsforløbene mod et falsk API. De samme kontroller kører ved hver pull request og hvert push til `main` gennem [`validate-workflows.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/workflows/validate-workflows.yml). De parser alle workflows som YAML, kontrollerer at ingen indlejrer Python, kompilerer scripts i `tools/`, kræver eksplicitte jobtilladelser og tester parsing af credentials, parsing af push-spejlets adresse og synkroniserings- og publiceringsforløbene mod et falsk API. Kørslen fejler, når linje- og forgreningsdækningen af `tools/` er under 95 % (`.coveragerc`).
Efter en ændring af synkroniseringsworkflowet skal du også starte **Sync GitHub mirror metadata** manuelt i Gitea og sammenligne beskrivelsen og emnerne på GitHub-spejlet. Efter en ændring af synkroniseringsworkflowet skal du også starte **Sync GitHub mirror metadata** manuelt i Gitea og sammenligne beskrivelsen og emnerne på GitHub-spejlet.
+4 -3
View File
@@ -252,7 +252,7 @@ Trigger -> Workflow: Start workflow
activate Workflow activate Workflow
Workflow -> Workflow: Check out the repository Workflow -> Workflow: Check out the repository
Workflow -> Workflow: Create a virtual environment and\ninstall tests/requirements.txt Workflow -> Workflow: Create a virtual environment and\ninstall tests/requirements.txt
Workflow -> Workflow: Run pytest on tests/ (offline, no secrets):\nworkflows parse as YAML, no embedded Python,\nscripts compile, wiki pages and translations are current,\nsync, publish and release flows against a fake API Workflow -> Workflow: Run pytest on tests/ under coverage (offline, no secrets):\nworkflows parse as YAML, no embedded Python,\nscripts compile, wiki pages and translations are current,\nsync, publish and release flows against a fake API
alt a test fails alt a test fails
Workflow --> Trigger: Workflow fails (the pull request is not ready) Workflow --> Trigger: Workflow fails (the pull request is not ready)
@@ -299,9 +299,10 @@ The workflow files and the scripts in `tools/` are validated by offline tests th
python3 -m venv .venv python3 -m venv .venv
source .venv/bin/activate # Windows PowerShell: .venv\Scripts\Activate.ps1 source .venv/bin/activate # Windows PowerShell: .venv\Scripts\Activate.ps1
python -m pip install -r tests/requirements.txt python -m pip install -r tests/requirements.txt
python -m pytest -q tests python -m coverage run -m pytest -q tests
python -m coverage report
``` ```
The same checks run on every pull request and push to `main` through [`validate-workflows.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/workflows/validate-workflows.yml). They parse every workflow as YAML, check that none embeds Python, compile the scripts in `tools/`, require explicit job permissions, and test credential parsing, push mirror address parsing and the sync and publish flows against a fake API. The same checks run on every pull request and push to `main` through [`validate-workflows.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/workflows/validate-workflows.yml). They parse every workflow as YAML, check that none embeds Python, compile the scripts in `tools/`, require explicit job permissions, and test credential parsing, push mirror address parsing and the sync and publish flows against a fake API. The run fails when the line and branch coverage of `tools/` falls below 95% (`.coveragerc`).
After a change to the sync workflow, also trigger **Sync GitHub mirror metadata** manually in Gitea and compare the description and topics on the GitHub mirror. After a change to the sync workflow, also trigger **Sync GitHub mirror metadata** manually in Gitea and compare the description and topics on the GitHub mirror.
+5 -4
View File
@@ -1,4 +1,4 @@
<!-- translation of Home.md, source sha256 8d1f5ed6df38 --> <!-- translation of Home.md, source sha256 510b243ec5f2 -->
> Ini ialah terjemahan [`Home`](Home). Halaman bahasa Inggeris ialah teks yang sah, dan perubahan di sini akan ditimpa pada penerbitan wiki yang seterusnya. > Ini ialah terjemahan [`Home`](Home). Halaman bahasa Inggeris ialah teks yang sah, dan perubahan di sini akan ditimpa pada penerbitan wiki yang seterusnya.
@@ -252,7 +252,7 @@ Trigger -> Workflow: Start workflow
activate Workflow activate Workflow
Workflow -> Workflow: Check out the repository Workflow -> Workflow: Check out the repository
Workflow -> Workflow: Create a virtual environment and\ninstall tests/requirements.txt Workflow -> Workflow: Create a virtual environment and\ninstall tests/requirements.txt
Workflow -> Workflow: Run pytest on tests/ (offline, no secrets):\nworkflows parse as YAML, no embedded Python,\nscripts compile, wiki pages and translations are current,\nsync, publish and release flows against a fake API Workflow -> Workflow: Run pytest on tests/ under coverage (offline, no secrets):\nworkflows parse as YAML, no embedded Python,\nscripts compile, wiki pages and translations are current,\nsync, publish and release flows against a fake API
alt a test fails alt a test fails
Workflow --> Trigger: Workflow fails (the pull request is not ready) Workflow --> Trigger: Workflow fails (the pull request is not ready)
@@ -299,9 +299,10 @@ Fail aliran kerja dan skrip dalam `tools/` disahkan oleh ujian luar talian yang
python3 -m venv .venv python3 -m venv .venv
source .venv/bin/activate # Windows PowerShell: .venv\Scripts\Activate.ps1 source .venv/bin/activate # Windows PowerShell: .venv\Scripts\Activate.ps1
python -m pip install -r tests/requirements.txt python -m pip install -r tests/requirements.txt
python -m pytest -q tests python -m coverage run -m pytest -q tests
python -m coverage report
``` ```
Semakan yang sama dijalankan pada setiap pull request dan setiap push ke `main` melalui [`validate-workflows.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/workflows/validate-workflows.yml). Semakan itu menghurai setiap aliran kerja sebagai YAML, memastikan tiada yang menanam Python, mengkompil skrip dalam `tools/`, memerlukan kebenaran kerja yang jelas, dan menguji penghuraian kelayakan (credentials), penghuraian alamat push mirror serta aliran penyegerakan dan penerbitan terhadap API palsu. Semakan yang sama dijalankan pada setiap pull request dan setiap push ke `main` melalui [`validate-workflows.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/workflows/validate-workflows.yml). Semakan itu menghurai setiap aliran kerja sebagai YAML, memastikan tiada yang menanam Python, mengkompil skrip dalam `tools/`, memerlukan kebenaran kerja yang jelas, dan menguji penghuraian kelayakan (credentials), penghuraian alamat push mirror serta aliran penyegerakan dan penerbitan terhadap API palsu. Larian gagal apabila liputan baris dan cabang `tools/` di bawah 95% (`.coveragerc`).
Selepas perubahan pada aliran kerja penyegerakan, cetuskan juga **Sync GitHub mirror metadata** secara manual dalam Gitea dan bandingkan penerangan dan topik pada cermin GitHub. Selepas perubahan pada aliran kerja penyegerakan, cetuskan juga **Sync GitHub mirror metadata** secara manual dalam Gitea dan bandingkan penerangan dan topik pada cermin GitHub.
+4 -1
View File
@@ -1,4 +1,4 @@
<!-- translation of QualityChecksPythonShell.md, source sha256 9b63f6eb0c8c --> <!-- translation of QualityChecksPythonShell.md, source sha256 2a0c9d43c77d -->
> Dette er en oversættelse af [`QualityChecksPythonShell`](QualityChecksPythonShell). Den engelske side er autoritativ, og ændringer her bliver overskrevet, næste gang wikien publiceres. > Dette er en oversættelse af [`QualityChecksPythonShell`](QualityChecksPythonShell). Den engelske side er autoritativ, og ændringer her bliver overskrevet, næste gang wikien publiceres.
@@ -35,9 +35,12 @@ jobs:
| `mypy-version` | `1.11.2` | Version af mypy | | `mypy-version` | `1.11.2` | Version af mypy |
| `shellcheck-py-version` | `0.10.0.1` | Version af pakken shellcheck-py (ShellCheck 0.10.0) | | `shellcheck-py-version` | `0.10.0.1` | Version af pakken shellcheck-py (ShellCheck 0.10.0) |
| `shfmt-py-version` | `3.11.0.2` | Version af pakken shfmt-py (shfmt 3.11.0) | | `shfmt-py-version` | `3.11.0.2` | Version af pakken shfmt-py (shfmt 3.11.0) |
| `tools-ref` | `v1` | Tag eller gren i `TirSystem/github-action`, hvorfra scriptet `tools/quality-checks.sh` hentes |
Henvis til workflowet ved major-tagget (`@v1`) eller et præcist tag (`@v1.0.0`), aldrig ved `main` og aldrig ved en pull requests gren: den gren slettes, når pull requesten flettes, og henvisningen holder op med at virke. Se opgraderingsvejledningen for, hvad hvert valg modtager. Henvis til workflowet ved major-tagget (`@v1`) eller et præcist tag (`@v1.0.0`), aldrig ved `main` og aldrig ved en pull requests gren: den gren slettes, når pull requesten flettes, og henvisningen holder op med at virke. Se opgraderingsvejledningen for, hvad hvert valg modtager.
Workflowfilen kalder et script i dette repository, hentet ved `tools-ref`. Et kald ved et præcist tag er kun fuldt fastlåst, når `tools-ref` angiver samme tag, for eksempel `@v1.0.1` med `tools-ref: v1.0.1`; med standardværdien følger scriptet `v1`.
## Hvad der kontrolleres ## Hvad der kontrolleres
| Sprog | Filer | Værktøjer | | Sprog | Filer | Værktøjer |
+6
View File
@@ -38,12 +38,18 @@ jobs:
| `mypy-version` | `1.11.2` | Version of mypy | | `mypy-version` | `1.11.2` | Version of mypy |
| `shellcheck-py-version` | `0.10.0.1` | Version of the shellcheck-py package (ShellCheck 0.10.0) | | `shellcheck-py-version` | `0.10.0.1` | Version of the shellcheck-py package (ShellCheck 0.10.0) |
| `shfmt-py-version` | `3.11.0.2` | Version of the shfmt-py package (shfmt 3.11.0) | | `shfmt-py-version` | `3.11.0.2` | Version of the shfmt-py package (shfmt 3.11.0) |
| `tools-ref` | `v1` | Tag or branch of `TirSystem/github-action` from which the script `tools/quality-checks.sh` is fetched |
Reference the workflow at the major tag (`@v1`) or at an exact tag Reference the workflow at the major tag (`@v1`) or at an exact tag
(`@v1.0.0`), never at `main` and never at the branch of a pull request: that (`@v1.0.0`), never at `main` and never at the branch of a pull request: that
branch is deleted when the pull request is merged and the reference stops branch is deleted when the pull request is merged and the reference stops
working. See the upgrade guide for what each choice receives. working. See the upgrade guide for what each choice receives.
The workflow file calls a script of this repository, fetched at `tools-ref`. A
call at an exact tag is pinned completely only when `tools-ref` names the same
tag, for example `@v1.0.1` with `tools-ref: v1.0.1`; with the default the
script follows `v1`.
## What is checked ## What is checked
| Language | Files | Tools | | Language | Files | Tools |
+4 -1
View File
@@ -1,4 +1,4 @@
<!-- translation of QualityChecksPythonShell.md, source sha256 9b63f6eb0c8c --> <!-- translation of QualityChecksPythonShell.md, source sha256 2a0c9d43c77d -->
> Ini ialah terjemahan [`QualityChecksPythonShell`](QualityChecksPythonShell). Halaman bahasa Inggeris ialah teks yang sah, dan perubahan di sini akan ditimpa pada penerbitan wiki yang seterusnya. > Ini ialah terjemahan [`QualityChecksPythonShell`](QualityChecksPythonShell). Halaman bahasa Inggeris ialah teks yang sah, dan perubahan di sini akan ditimpa pada penerbitan wiki yang seterusnya.
@@ -35,9 +35,12 @@ jobs:
| `mypy-version` | `1.11.2` | Versi mypy | | `mypy-version` | `1.11.2` | Versi mypy |
| `shellcheck-py-version` | `0.10.0.1` | Versi pakej shellcheck-py (ShellCheck 0.10.0) | | `shellcheck-py-version` | `0.10.0.1` | Versi pakej shellcheck-py (ShellCheck 0.10.0) |
| `shfmt-py-version` | `3.11.0.2` | Versi pakej shfmt-py (shfmt 3.11.0) | | `shfmt-py-version` | `3.11.0.2` | Versi pakej shfmt-py (shfmt 3.11.0) |
| `tools-ref` | `v1` | Tag atau cabang `TirSystem/github-action` tempat skrip `tools/quality-checks.sh` diambil |
Rujuk aliran kerja pada tag major (`@v1`) atau pada tag tepat (`@v1.0.0`), jangan sekali-kali pada `main` dan jangan pada cabang pull request: cabang itu dipadam apabila pull request digabungkan dan rujukan itu berhenti berfungsi. Lihat panduan naik taraf untuk apa yang diterima oleh setiap pilihan. Rujuk aliran kerja pada tag major (`@v1`) atau pada tag tepat (`@v1.0.0`), jangan sekali-kali pada `main` dan jangan pada cabang pull request: cabang itu dipadam apabila pull request digabungkan dan rujukan itu berhenti berfungsi. Lihat panduan naik taraf untuk apa yang diterima oleh setiap pilihan.
Fail aliran kerja memanggil skrip dalam repositori ini, yang diambil pada `tools-ref`. Panggilan pada tag tepat hanya disematkan sepenuhnya apabila `tools-ref` menamakan tag yang sama, contohnya `@v1.0.1` dengan `tools-ref: v1.0.1`; dengan nilai lalai skrip mengikuti `v1`.
## Perkara yang disemak ## Perkara yang disemak
| Bahasa | Fail | Alat | | Bahasa | Fail | Alat |
+3 -1
View File
@@ -1,4 +1,4 @@
<!-- translation of Upgrading.md, source sha256 ff8a06c4a03f --> <!-- translation of Upgrading.md, source sha256 3f119a1ab12d -->
> Dette er en oversættelse af [`Upgrading`](Upgrading). Den engelske side er autoritativ, og ændringer her bliver overskrevet, næste gang wikien publiceres. > Dette er en oversættelse af [`Upgrading`](Upgrading). Den engelske side er autoritativ, og ændringer her bliver overskrevet, næste gang wikien publiceres.
@@ -18,6 +18,8 @@ Sådan tager en softwareudvikler en ny udgivelse af de fælles workflows i brug
En udgivelse, der bryder kompatibiliteten, får en ny major-version (`v2`), så en pipeline på `@v1` aldrig modtager den, før du selv ændrer linjen. En udgivelse, der bryder kompatibiliteten, får en ny major-version (`v2`), så en pipeline på `@v1` aldrig modtager den, før du selv ændrer linjen.
Kvalitetskontrol-workflowet henter sit script ved sit input `tools-ref`, som som standard er `v1`. For at fastlåse det helt skal du kalde det ved et præcist tag og sætte `tools-ref` til samme tag; ellers følger scriptet major-tagget.
De to scoped workflows (**Sync GitHub mirror metadata** og **Publish wiki**) leveres af instansen og står ikke i dine `uses:`-linjer; du kan ikke fastlåse dem, men de henter deres scripts ved major-tagget. De to scoped workflows (**Sync GitHub mirror metadata** og **Publish wiki**) leveres af instansen og står ikke i dine `uses:`-linjer; du kan ikke fastlåse dem, men de henter deres scripts ved major-tagget.
## Aflæsning af en changelog-post ## Aflæsning af en changelog-post
+4
View File
@@ -22,6 +22,10 @@ repository, tags `vMAJOR.MINOR.PATCH` and a moving major tag `vMAJOR`.
A breaking release gets a new major version (`v2`), so a pipeline on `@v1` A breaking release gets a new major version (`v2`), so a pipeline on `@v1`
never receives it until you change the line yourself. never receives it until you change the line yourself.
The quality-checks workflow fetches its script at its input `tools-ref`, which
defaults to `v1`. To pin it completely, call it at an exact tag and set
`tools-ref` to the same tag; otherwise the script follows the major tag.
The two scoped workflows (**Sync GitHub mirror metadata** and **Publish The two scoped workflows (**Sync GitHub mirror metadata** and **Publish
wiki**) are delivered by the instance and are not in your `uses:` lines; you wiki**) are delivered by the instance and are not in your `uses:` lines; you
cannot pin them, but they fetch their scripts at the major tag. cannot pin them, but they fetch their scripts at the major tag.
+3 -1
View File
@@ -1,4 +1,4 @@
<!-- translation of Upgrading.md, source sha256 ff8a06c4a03f --> <!-- translation of Upgrading.md, source sha256 3f119a1ab12d -->
> Ini ialah terjemahan [`Upgrading`](Upgrading). Halaman bahasa Inggeris ialah teks yang sah, dan perubahan di sini akan ditimpa pada penerbitan wiki yang seterusnya. > Ini ialah terjemahan [`Upgrading`](Upgrading). Halaman bahasa Inggeris ialah teks yang sah, dan perubahan di sini akan ditimpa pada penerbitan wiki yang seterusnya.
@@ -18,6 +18,8 @@ Cara seorang jurutera perisian menerima keluaran baharu aliran kerja bersama den
Keluaran yang memecahkan keserasian mendapat versi major baharu (`v2`), jadi saluran paip pada `@v1` tidak menerimanya sehingga anda sendiri menukar baris itu. Keluaran yang memecahkan keserasian mendapat versi major baharu (`v2`), jadi saluran paip pada `@v1` tidak menerimanya sehingga anda sendiri menukar baris itu.
Aliran kerja semakan kualiti mengambil skripnya pada inputnya `tools-ref`, yang lalai ialah `v1`. Untuk menyematkannya sepenuhnya, panggil pada tag tepat dan tetapkan `tools-ref` kepada tag yang sama; jika tidak, skrip mengikuti tag major.
Dua aliran kerja scoped (**Sync GitHub mirror metadata** dan **Publish wiki**) dihantar oleh instans dan tidak terdapat dalam baris `uses:` anda; anda tidak boleh menyematkannya, tetapi ia mengambil skripnya pada tag major. Dua aliran kerja scoped (**Sync GitHub mirror metadata** dan **Publish wiki**) dihantar oleh instans dan tidak terdapat dalam baris `uses:` anda; anda tidak boleh menyematkannya, tetapi ia mengambil skripnya pada tag major.
## Membaca satu catatan changelog ## Membaca satu catatan changelog