From d2b63fbc770e86ea7eec6e1d6115ba118fdc7f60 Mon Sep 17 00:00:00 2001 From: Marcel Date: Thu, 14 May 2026 08:46:29 +0200 Subject: [PATCH 1/5] ci(unit-tests): add grep guard for (upload|download)-artifact@v4+ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a repo-invariant check in the same 'Assert' block as the ADR-012 birpc guard. Anchored to YAML `uses:` lines so the inline self-test fixture does not false-positive. Fails with an actionable error referencing ADR-014 / #557. Guard is intentionally RED at this commit — the three v4 call sites are downgraded in the next commit. Co-Authored-By: Claude Sonnet 4.6 --- .gitea/workflows/ci.yml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index 5aa5d748..bfd1ae7a 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -56,6 +56,23 @@ jobs: exit 1 fi + - name: Assert no (upload|download)-artifact past v3 + shell: bash + run: | + # Self-test: verify the regex catches v4+ (anchored to YAML `uses:` lines). + tmp=$(mktemp) + printf ' uses: actions/upload-artifact@v5\n' > "$tmp" + grep -qP '^\s+uses:\s+actions/(upload|download)-artifact@v[4-9]' "$tmp" \ + || { echo "FAIL: guard self-test — regex missed upload-artifact@v5"; rm "$tmp"; exit 1; } + rm "$tmp" + # Guard: Gitea Actions (act_runner) does not implement the v4 artifact protocol. + # Both upload-artifact and download-artifact share the same incompatibility. + # Pin to @v3. See ADR-014 / #557. + if grep -RPn '^\s+uses:\s+actions/(upload|download)-artifact@v[4-9]' .gitea/workflows/; then + echo "::error::actions/(upload|download)-artifact@v4+ is unsupported on Gitea Actions (act_runner). Pin to @v3. See ADR-014 / #557." + exit 1 + fi + - name: Run unit and component tests with coverage shell: bash run: | -- 2.49.1 From 596d03cee9a12a7036c6b27cc883e3fa56a6bf8e Mon Sep 17 00:00:00 2001 From: Marcel Date: Thu, 14 May 2026 08:47:39 +0200 Subject: [PATCH 2/5] =?UTF-8?q?ci(workflows):=20downgrade=20upload-artifac?= =?UTF-8?q?t=20v4=20=E2=86=92=20v3=20=E2=80=94=20Gitea=20act=5Frunner=20li?= =?UTF-8?q?mitation=20(ADR-014)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reverts the re-regression introduced in 410b91e2. Gitea Actions (act_runner) does not implement the v4 artifact protocol — jobs report failure even when all tests pass. Pins all three call sites back to @v3 and adds load-bearing inline comments pointing to ADR-014 / #557. This commit makes the grep guard added in the previous commit GREEN. Co-Authored-By: Claude Sonnet 4.6 --- .gitea/workflows/ci.yml | 6 ++++-- .gitea/workflows/coverage-flake-probe.yml | 3 ++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index bfd1ae7a..7418145d 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -94,9 +94,10 @@ jobs: exit 1 fi + # Gitea Actions (act_runner) does not implement upload-artifact v4 protocol — pinned per ADR-014. Do NOT upgrade. See #557. - name: Upload coverage reports if: always() - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v3 with: name: coverage-reports path: | @@ -130,9 +131,10 @@ jobs: || { echo "FAIL: /hilfe/transkription.html missing from prerender output"; exit 1; } echo "PASS: only /hilfe/transkription.html prerendered." + # Gitea Actions (act_runner) does not implement upload-artifact v4 protocol — pinned per ADR-014. Do NOT upgrade. See #557. - name: Upload screenshots if: always() - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v3 with: name: unit-test-screenshots path: frontend/test-results/screenshots/ diff --git a/.gitea/workflows/coverage-flake-probe.yml b/.gitea/workflows/coverage-flake-probe.yml index 0258ba7a..0af5a805 100644 --- a/.gitea/workflows/coverage-flake-probe.yml +++ b/.gitea/workflows/coverage-flake-probe.yml @@ -56,9 +56,10 @@ jobs: exit 1 fi + # Gitea Actions (act_runner) does not implement upload-artifact v4 protocol — pinned per ADR-014. Do NOT upgrade. See #557. - name: Upload coverage log on failure if: failure() - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v3 with: name: coverage-log-run-${{ matrix.run }} path: /tmp/coverage-test-${{ github.run_id }}-${{ matrix.run }}.log -- 2.49.1 From 7997de3fa02b348e53a57527d4e792f7a68bd041 Mon Sep 17 00:00:00 2001 From: Marcel Date: Thu, 14 May 2026 08:48:43 +0200 Subject: [PATCH 3/5] docs(adr-014): record upload-artifact v3 pin and Gitea act_runner v4 limitation Documents the three-incident history, the enforcement layers (inline comments + grep guard + ADR), how to spot the symptom, and the explicit upgrade trigger (act_runner v4 protocol support OR v3 CVE). Cross-references ADR-011 (single-tenant Gitea runner) and #557. Co-Authored-By: Claude Sonnet 4.6 --- docs/adr/014-upload-artifact-v3-pin.md | 122 +++++++++++++++++++++++++ 1 file changed, 122 insertions(+) create mode 100644 docs/adr/014-upload-artifact-v3-pin.md diff --git a/docs/adr/014-upload-artifact-v3-pin.md b/docs/adr/014-upload-artifact-v3-pin.md new file mode 100644 index 00000000..cfb2c11c --- /dev/null +++ b/docs/adr/014-upload-artifact-v3-pin.md @@ -0,0 +1,122 @@ +# ADR 014 — Pin actions/upload-artifact to v3 (Gitea act_runner v4 protocol incompatibility) + +**Status:** Accepted +**Date:** 2026-05-14 +**Issues:** [#557 — re-regression](https://git.raddatz.cloud/marcel/familienarchiv/issues/557) · [#14 — original incident](https://git.raddatz.cloud/marcel/familienarchiv/issues/14) + +--- + +## Context + +`actions/upload-artifact` is available in two incompatible major versions. The v4 client +uploads via a GitHub-specific artifact API that is **not implemented** in Gitea's +`act_runner` (the self-hosted CI substrate established by ADR-011). When a workflow step +uses `actions/upload-artifact@v4` on this runner, `act_runner` returns a non-zero exit +code from the v4 client even when all tests pass, producing: + +> green test suite — red job status — no artifact uploaded + +The failure lands in the upload step, _after_ the test output, making it hard to diagnose +from the build log. + +### Incident history + +| Date | Commit | Event | +|---|---|---| +| 2026-03-19 | `9f3f022e` | Original downgrade: `upload-artifact@v4 → v3` | +| 2026-03-19 | `4142c7cd` | Rationale committed; closes #14 | +| 2026-05-05 | `410b91e2` | Re-regression: upgraded back to v4 without referencing #14 | +| 2026-05-14 | this PR | Second downgrade + ADR + grep guard | + +The root cause of the re-regression was institutional-memory failure: the original +rationale was captured only in a commit body, invisible at the point of change (the +`uses:` line). This ADR, the inline comments, and the grep guard are the three +defence layers that replace that missing breadcrumb. + +--- + +## Decision + +**Pin all `actions/upload-artifact` and `actions/download-artifact` call sites to `@v3`.** + +Both action families share the same v4 protocol incompatibility with `act_runner`. +Pinning to the major tag (`@v3`) keeps us on the latest v3 patch without Renovate noise. + +Three call sites are pinned: +- `.gitea/workflows/ci.yml` — "Upload coverage reports" step +- `.gitea/workflows/ci.yml` — "Upload screenshots" step +- `.gitea/workflows/coverage-flake-probe.yml` — "Upload coverage log on failure" step + +Each pinned `uses:` line carries a load-bearing inline comment: + +```yaml +# Gitea Actions (act_runner) does not implement upload-artifact v4 protocol — pinned per ADR-014. Do NOT upgrade. See #557. +- uses: actions/upload-artifact@v3 +``` + +A CI grep guard enforces the constraint automatically (see below). + +--- + +## Consequences + +### Enforcement layers (defence in depth) + +1. **Inline comments** on every `uses:` line — visible at the point of change. +2. **CI grep guard** in `.gitea/workflows/ci.yml` ("Assert no (upload|download)-artifact + past v3") — fails the build if a future commit re-introduces `@v4` or higher on any + workflow file. Anchored to YAML `uses:` lines to avoid false positives on embedded + shell strings. Includes a self-test that proves the regex catches v4+ before scanning + the repo. +3. **This ADR** — canonical rationale; cross-referenced by comments and guard message. + +### How to spot the symptom + +- Test suite output shows green (vitest, surefire, pytest all exit 0) +- CI job status shows red +- Artifacts section of the run is empty +- Build log shows a non-zero exit from the `Upload …` step immediately after green tests + +### `@v3` maintenance-mode status + +GitHub placed `actions/upload-artifact@v3` in maintenance mode (no new features) but it +has not been removed and carries no known unpatched CVE as of this writing. If GitHub +publishes a v3-specific security advisory, that is an additional trigger to re-evaluate +(see upgrade conditions below). + +### When to remove this pin + +Re-evaluate pinning **when either condition is met:** + +1. `gitea/act_runner` ships a release with v4 artifact protocol support. Track upstream: + +2. `actions/upload-artifact@v3` acquires an unpatched CVE that cannot be mitigated + at the runner level. + +When upgrading: remove the grep guard step, update all three `uses:` lines, remove the +inline comments, and update this ADR's status to Superseded. + +--- + +## Alternatives + +### SHA pinning (`uses: actions/upload-artifact@`) + +More secure against action repository compromise, but adds Renovate update friction +and is disproportionate for a self-hosted, single-tenant Gitea instance with one +trusted contributor (ADR-011). Rejected. + +### Minor/patch pinning (`@v3.4.0`) + +Avoids Renovate PRs but freezes us on a specific patch. The v3 major track is in +maintenance mode — minor pinning has no benefit and would require manual updates +for any v3 security patches. Rejected. + +### Renovate `packageRules` bypass + +Would prevent automated PRs from proposing v4. Not needed while Renovate is not +configured for this repository. Revisit if Renovate is introduced. + +### Migrating the runner to a v4-compatible Gitea release + +Out of scope for this issue. A separate decision; tracked in #557's non-goals. -- 2.49.1 From 37b2ed6a906e405f6a4c60c77f3c5a5bd23d72e9 Mon Sep 17 00:00:00 2001 From: Marcel Date: Thu, 14 May 2026 08:49:32 +0200 Subject: [PATCH 4/5] =?UTF-8?q?docs(ci-gitea):=20replace=20'=E2=86=90=20up?= =?UTF-8?q?graded=20from=20v3'=20with=20ADR-014=20pin=20comment?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Lines 203, 230, and 332 carried comments that actively encouraged the regression (they read as if v4 is the canonical target). Replaced with the correct pinned-at-v3 comment referencing ADR-014. Co-Authored-By: Claude Sonnet 4.6 --- docs/infrastructure/ci-gitea.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/infrastructure/ci-gitea.md b/docs/infrastructure/ci-gitea.md index 017d6ba2..b57a719e 100644 --- a/docs/infrastructure/ci-gitea.md +++ b/docs/infrastructure/ci-gitea.md @@ -200,7 +200,7 @@ jobs: working-directory: frontend - name: Upload screenshots if: always() - uses: actions/upload-artifact@v4 # ← upgraded from v3 + uses: actions/upload-artifact@v3 # pinned per ADR-014 — Gitea Actions does not implement v4 protocol. Do NOT upgrade. with: name: unit-test-screenshots path: frontend/test-results/screenshots/ @@ -227,7 +227,7 @@ jobs: working-directory: backend - name: Upload test results if: always() - uses: actions/upload-artifact@v4 # ← upgraded from v3 + uses: actions/upload-artifact@v3 # pinned per ADR-014 — Gitea Actions does not implement v4 protocol. Do NOT upgrade. with: name: backend-test-results path: backend/target/surefire-reports/ @@ -329,7 +329,7 @@ jobs: E2E_BACKEND_URL: http://localhost:8080 - name: Upload E2E results if: always() - uses: actions/upload-artifact@v4 # ← upgraded from v3 + uses: actions/upload-artifact@v3 # pinned per ADR-014 — Gitea Actions does not implement v4 protocol. Do NOT upgrade. with: name: e2e-results path: frontend/test-results/e2e/ -- 2.49.1 From 22538e32a7d6b3c26174d95cae8c1fcdbd424652 Mon Sep 17 00:00:00 2001 From: Marcel Date: Thu, 14 May 2026 10:18:05 +0200 Subject: [PATCH 5/5] ci(unit-tests): add negative self-test case to upload-artifact guard MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous self-test proved the regex catches @v5 (positive case). This adds a negative case proving @v3 is NOT flagged — guards against a false-positive that would break every CI run permanently. Suggested by Sara Holt in review of PR #558. Co-Authored-By: Claude Sonnet 4.6 --- .gitea/workflows/ci.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index 7418145d..2cc1deae 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -59,11 +59,14 @@ jobs: - name: Assert no (upload|download)-artifact past v3 shell: bash run: | - # Self-test: verify the regex catches v4+ (anchored to YAML `uses:` lines). + # Self-test: verify the regex catches v4+ and does not catch v3. tmp=$(mktemp) printf ' uses: actions/upload-artifact@v5\n' > "$tmp" grep -qP '^\s+uses:\s+actions/(upload|download)-artifact@v[4-9]' "$tmp" \ || { echo "FAIL: guard self-test — regex missed upload-artifact@v5"; rm "$tmp"; exit 1; } + printf ' uses: actions/upload-artifact@v3\n' > "$tmp" + grep -qvP '^\s+uses:\s+actions/(upload|download)-artifact@v[4-9]' "$tmp" \ + || { echo "FAIL: guard self-test — regex incorrectly flagged upload-artifact@v3"; rm "$tmp"; exit 1; } rm "$tmp" # Guard: Gitea Actions (act_runner) does not implement the v4 artifact protocol. # Both upload-artifact and download-artifact share the same incompatibility. -- 2.49.1