Compare commits

...
Author SHA1 Message Date
emilandDevin <158243242+devin-ai-integration[bot]@users.noreply.github.com> b0f912dd70 ci: add notify-failure to auto-merge and cleanup jobs, clean up redundant env
- Add notify-failure composite action to ci/auto-merge and
  build-images/cleanup jobs (previously missing — failures in these
  jobs went unnoticed)
- Remove redundant step-level CI_GITEA_API_TOKEN/CI_GITEA_USERNAME
  env vars now that workflow-level env: block provides them
- Remove redundant step-level PYTHONPATH env (already in workflow env)
- Update ADR-0003 adoption scope to reflect notify-failure coverage
- Update ci-cd-workflow.md with explicit job list for notify-failure
- Update CHANGELOG with bug fix and cleanup entries

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-08-12 21:43:46 +02:00
emilandDevin <158243242+devin-ai-integration[bot]@users.noreply.github.com> 4fc4c5b0d8 docs: add ADR-0003 and update docs for composite actions
CI / validate (pull_request) Successful in 2m35s
CI / auto-merge (pull_request) Successful in 26s
New ADR-0003 documenting composite action design rationale and Gitea
1.27 constraints. Updated AGENTS.md, README.md, architecture.md, and
ci-cd-workflow.md with composite action documentation. CHANGELOG entry.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-08-12 21:27:11 +02:00
emilandDevin <158243242+devin-ai-integration[bot]@users.noreply.github.com> 64ead7b010 ci: convert workflows to use composite actions
Replace inline setup-env, notify-failure, and quality-checks steps in
ci.yml, post-merge.yml, and build-images.yml with composite action
references. Also fix pre-existing make workflow-dryrun failure by
using env context instead of vars/secrets in container.credentials
(act_runner dry-run does not populate vars/secrets contexts).

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-08-12 21:24:22 +02:00
emilandDevin <158243242+devin-ai-integration[bot]@users.noreply.github.com> 9b7c008fdc ci: add composite actions for setup-env, notify-failure, quality-checks
Three Gitea composite actions in .gitea/actions/ that eliminate repeated
multi-step sequences across workflows. Each consumer repo gets its own
copy (no cross-repo references). See ADR-0003 for design rationale.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-08-12 21:21:42 +02:00
12 changed files with 521 additions and 160 deletions
+47
View File
@@ -0,0 +1,47 @@
name: 'Notify on failure'
description: 'Create a Gitea issue when a CI workflow fails (calls devx.ci.notify_failure)'
# Composite action for the common "Notify on failure" step pattern.
# Replaces the repeated inline:
# - name: Notify on failure
# if: failure()
# env:
# CI_GITEA_API_TOKEN: ${{ secrets.CI_GITEA_API_TOKEN }}
# run: |
# . .venv/bin/activate 2>/dev/null || true
# export PATH="$HOME/.local/bin:$PATH"
# python3 -m devx.ci.notify_failure \
# --repo "${{ github.repository }}" \
# --run-id "${{ github.run_id }}" \
# --workflow "ci/validate" \
# --commit "${{ github.sha }}" \
# --auto-login
#
# Gitea 1.27 notes:
# - `if: failure()` is evaluated in the calling workflow's context and
# propagates correctly to composite action steps.
# - `secrets` are not accessible here; the calling workflow's top-level
# `env:` CI_GITEA_API_TOKEN is used via `${{ env.* }}`.
inputs:
workflow:
description: 'Workflow/job name used in the Gitea issue title (e.g., ci/validate)'
required: true
runs:
using: 'composite'
steps:
- name: Notify on failure
if: failure()
shell: bash
env:
CI_GITEA_API_TOKEN: ${{ env.CI_GITEA_API_TOKEN }}
run: |
. .venv/bin/activate 2>/dev/null || true
export PATH="$HOME/.local/bin:$PATH"
python3 -m devx.ci.notify_failure \
--repo "${{ github.repository }}" \
--run-id "${{ github.run_id }}" \
--workflow "${{ inputs.workflow }}" \
--commit "${{ github.sha }}" \
--auto-login
+89
View File
@@ -0,0 +1,89 @@
name: 'Quality checks'
description: 'Run lint, unit tests with coverage, test speed, docs, translations, and security scan'
# Composite action for the 6-step quality check sequence used by the
# devx validate job. Replaces the inline block:
# - Lint all
# - Unit tests with 100% coverage
# - Check unit test speed
# - Documentation gate (coverage + stale refs + lint + version refs + prose)
# - Translation completeness check
# - Dependency security scan
#
# Each step activates the venv defensively (`. .venv/bin/activate 2>/dev/null
# || true`) so the action works whether or not the setup step created a
# venv at the repo root (pre-built CI images symlink /opt/venv to .venv).
#
# Gitea 1.27 notes:
# - Every `run` step needs explicit `shell:`.
# - Inputs are string-typed; numeric thresholds are passed through as
# strings to `devx.tools.check_test_speed`.
inputs:
package:
description: 'Package name for doc version checks (e.g., devx, grm). Empty = no DEVX_DOC_VERSIONS_PKG override.'
required: false
default: ''
test-speed-max:
description: 'Max total test seconds (passed to check_test_speed --max-seconds)'
required: false
default: '15'
test-speed-max-single:
description: 'Max single test seconds (passed to check_test_speed --max-single-seconds)'
required: false
default: '0.5'
translations-file:
description: 'Path to translations.json (empty = default location src/devx/translations.json)'
required: false
default: ''
runs:
using: 'composite'
steps:
- name: Lint all
shell: bash
run: |
. .venv/bin/activate 2>/dev/null || true
export PATH="$HOME/.local/bin:$PATH"
make lint-all
- name: Unit tests with 100% coverage
shell: bash
run: |
. .venv/bin/activate 2>/dev/null || true
make pytest-cov
- name: Check unit test speed
shell: bash
run: |
. .venv/bin/activate 2>/dev/null || true
python3 -m devx.tools.check_test_speed \
--max-seconds "${{ inputs.test-speed-max }}" \
--max-single-seconds "${{ inputs.test-speed-max-single }}"
- name: Documentation gate (coverage + stale refs + lint + version refs + prose)
shell: bash
env:
DEVX_DOC_COVERAGE_STRICT: "1"
DEVX_VALE_LEVEL: warning
run: |
. .venv/bin/activate 2>/dev/null || true
export PATH="$HOME/.local/bin:$PATH"
if [ -n "${{ inputs.package }}" ]; then
export DEVX_DOC_VERSIONS_PKG="${{ inputs.package }}"
fi
make devx-docs-check
- name: Translation completeness check
shell: bash
run: |
. .venv/bin/activate 2>/dev/null || true
if [ -n "${{ inputs.translations-file }}" ]; then
python3 -m devx.ci.check_translations --translations "${{ inputs.translations-file }}"
else
python3 -m devx.ci.check_translations
fi
- name: Dependency security scan
shell: bash
run: |
. .venv/bin/activate 2>/dev/null || true
# Install pip in venv if missing (needed by pip-audit)
.venv/bin/python -m ensurepip 2>/dev/null || true
PIPAPI_PYTHON_LOCATION=$PWD/.venv/bin/python \
pip-audit --desc --skip-editable 2>&1 || true
+37
View File
@@ -0,0 +1,37 @@
name: 'Set up environment'
description: 'Set up CI environment with venv and PATH (calls make setup-image)'
# Composite action for the common "Set up environment" step pattern.
# Replaces the repeated inline:
# - name: Set up environment
# env:
# CI_GITEA_API_TOKEN: ${{ secrets.CI_GITEA_API_TOKEN }}
# run: make setup-image
#
# Gitea 1.27 notes:
# - Every `run` step needs explicit `shell:`.
# - Composite actions cannot access `secrets` directly; they read from
# the `env:` context which the calling workflow must populate.
# - The calling workflow's top-level `env:` block (CI_GITEA_API_TOKEN,
# CI_GITEA_USERNAME) is visible here via `${{ env.* }}`.
inputs:
extras:
description: 'Extra pip install groups passed to make setup-image (e.g., ci,lint,release)'
required: false
default: ''
runs:
using: 'composite'
steps:
- name: Set up environment
shell: bash
env:
CI_GITEA_API_TOKEN: ${{ env.CI_GITEA_API_TOKEN }}
CI_GITEA_USERNAME: ${{ env.CI_GITEA_USERNAME }}
run: |
if [ -n "${{ inputs.extras }}" ]; then
make setup-image EXTRAS="${{ inputs.extras }}"
else
make setup-image
fi
+16 -34
View File
@@ -29,14 +29,20 @@ concurrency:
group: build-images
cancel-in-progress: false
env:
PIP_BREAK_SYSTEM_PACKAGES: "1"
PYTHONPATH: src
CI_GITEA_API_TOKEN: ${{ secrets.CI_GITEA_API_TOKEN }}
CI_GITEA_USERNAME: ${{ vars.CI_GITEA_USERNAME }}
jobs:
build-and-push:
runs-on: docker
container:
image: git.oblachno.oblachno.fyi/oblachno-oss/runner-images/ci-full:latest
credentials:
username: ${{ vars.CI_GITEA_USERNAME }}
password: ${{ secrets.CI_GITEA_API_TOKEN }}
username: ${{ env.CI_GITEA_USERNAME }}
password: ${{ env.CI_GITEA_API_TOKEN }}
timeout-minutes: 30
outputs:
is-release: ${{ steps.check.outputs.is-release }}
@@ -45,13 +51,9 @@ jobs:
with:
fetch-depth: 1
- name: Set up environment
env:
CI_GITEA_API_TOKEN: ${{ secrets.CI_GITEA_API_TOKEN }}
run: make setup-release
- name: Check if this is a release commit
id: check
env:
PYTHONPATH: src
run: |
. .venv/bin/activate
python3 -m devx.ci.detect_release_commit
@@ -59,9 +61,6 @@ jobs:
if: >-
github.event_name == 'workflow_dispatch' ||
(github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'success' && steps.check.outputs.is-release == 'false')
env:
CI_GITEA_API_TOKEN: ${{ secrets.CI_GITEA_API_TOKEN }}
CI_GITEA_USERNAME: ${{ vars.CI_GITEA_USERNAME }}
run: |
. .venv/bin/activate
_TOKEN="$CI_GITEA_API_TOKEN"
@@ -73,10 +72,6 @@ jobs:
if: >-
github.event_name == 'workflow_dispatch' ||
(github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'success' && steps.check.outputs.is-release == 'false')
env:
CI_GITEA_API_TOKEN: ${{ secrets.CI_GITEA_API_TOKEN }}
CI_GITEA_USERNAME: ${{ vars.CI_GITEA_USERNAME }}
PYTHONPATH: src
run: |
. .venv/bin/activate
export PATH="$HOME/.local/bin:$PATH"
@@ -101,20 +96,9 @@ jobs:
--tag latest \
--registry git.oblachno.oblachno.fyi \
--push
- name: Notify on failure
if: failure()
env:
CI_GITEA_API_TOKEN: ${{ secrets.CI_GITEA_API_TOKEN }}
PYTHONPATH: src
run: |
. .venv/bin/activate 2>/dev/null || true
export PATH="$HOME/.local/bin:$PATH"
python3 -m devx.ci.notify_failure \
--repo "${{ github.repository }}" \
--run-id "${{ github.run_id }}" \
--workflow "build-images/build-and-push" \
--commit "${{ github.sha }}" \
--auto-login
- uses: ./.gitea/actions/notify-failure
with:
workflow: "build-images/build-and-push"
cleanup:
needs: [build-and-push]
@@ -123,21 +107,16 @@ jobs:
container:
image: git.oblachno.oblachno.fyi/oblachno-oss/runner-images/ci-base:latest
credentials:
username: ${{ vars.CI_GITEA_USERNAME }}
password: ${{ secrets.CI_GITEA_API_TOKEN }}
username: ${{ env.CI_GITEA_USERNAME }}
password: ${{ env.CI_GITEA_API_TOKEN }}
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 1
- name: Set up environment
env:
CI_GITEA_API_TOKEN: ${{ secrets.CI_GITEA_API_TOKEN }}
run: make setup-ci
- name: Clean up old image versions
env:
CI_GITEA_API_TOKEN: ${{ secrets.CI_GITEA_API_TOKEN }}
PYTHONPATH: src
run: |
. .venv/bin/activate
python3 -m devx.tools.clean_images \
@@ -146,3 +125,6 @@ jobs:
--name oblachno-oss/runner-images/ci-quality \
--name oblachno-oss/runner-images/ci-full \
--keep 2
- uses: ./.gitea/actions/notify-failure
with:
workflow: "build-images/cleanup"
+17 -60
View File
@@ -21,8 +21,8 @@ jobs:
container:
image: git.oblachno.oblachno.fyi/oblachno-oss/runner-images/ci-full:latest
credentials:
username: ${{ vars.CI_GITEA_USERNAME }}
password: ${{ secrets.CI_GITEA_API_TOKEN }}
username: ${{ env.CI_GITEA_USERNAME }}
password: ${{ env.CI_GITEA_API_TOKEN }}
timeout-minutes: 15
defaults:
run:
@@ -33,43 +33,12 @@ jobs:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up environment
env:
CI_GITEA_API_TOKEN: ${{ secrets.CI_GITEA_API_TOKEN }}
run: make setup-image
# --- quality steps ---
- name: Lint all
run: |
. .venv/bin/activate 2>/dev/null || true
export PATH="$HOME/.local/bin:$PATH"
make lint-all
- name: Unit tests with 100% coverage
run: |
. .venv/bin/activate 2>/dev/null || true
make pytest-cov
- name: Check unit test speed
run: |
. .venv/bin/activate 2>/dev/null || true
python3 -m devx.tools.check_test_speed --max-seconds 15 --max-single-seconds 0.5
- name: Documentation gate (coverage + stale refs + lint + version refs + prose)
env:
DEVX_DOC_COVERAGE_STRICT: "1"
DEVX_VALE_LEVEL: warning
run: |
. .venv/bin/activate 2>/dev/null || true
export PATH="$HOME/.local/bin:$PATH"
make devx-docs-check
- name: Translation completeness check
run: |
. .venv/bin/activate 2>/dev/null || true
python3 -m devx.ci.check_translations
- name: Dependency security scan
run: |
. .venv/bin/activate 2>/dev/null || true
# Install pip in venv if missing (needed by pip-audit)
.venv/bin/python -m ensurepip 2>/dev/null || true
PIPAPI_PYTHON_LOCATION=$PWD/.venv/bin/python \
pip-audit --desc --skip-editable 2>&1 || true
- uses: ./.gitea/actions/setup-env
- uses: ./.gitea/actions/quality-checks
with:
package: devx
test-speed-max: "15"
test-speed-max-single: "0.5"
- name: Workflow dry-run validation
run: |
. .venv/bin/activate 2>/dev/null || true
@@ -121,19 +90,9 @@ jobs:
. .venv/bin/activate 2>/dev/null || true
export PATH="$HOME/.local/bin:$PATH"
python3 -m devx.ci.release --dry-run
- name: Notify on failure
if: failure()
env:
CI_GITEA_API_TOKEN: ${{ secrets.CI_GITEA_API_TOKEN }}
run: |
. .venv/bin/activate 2>/dev/null || true
export PATH="$HOME/.local/bin:$PATH"
python3 -m devx.ci.notify_failure \
--repo "${{ github.repository }}" \
--run-id "${{ github.run_id }}" \
--workflow "ci/validate" \
--commit "${{ github.sha }}" \
--auto-login
- uses: ./.gitea/actions/notify-failure
with:
workflow: "ci/validate"
auto-merge:
# Auto-merge runs after validate passes. It reads the task ID
@@ -147,8 +106,8 @@ jobs:
container:
image: git.oblachno.oblachno.fyi/oblachno-oss/runner-images/ci-base:latest
credentials:
username: ${{ vars.CI_GITEA_USERNAME }}
password: ${{ secrets.CI_GITEA_API_TOKEN }}
username: ${{ env.CI_GITEA_USERNAME }}
password: ${{ env.CI_GITEA_API_TOKEN }}
timeout-minutes: 10
defaults:
run:
@@ -158,14 +117,10 @@ jobs:
with:
fetch-depth: 0
token: ${{ secrets.CI_GITEA_API_TOKEN }}
- name: Set up environment
env:
CI_GITEA_API_TOKEN: ${{ secrets.CI_GITEA_API_TOKEN }}
run: make setup-image
- uses: ./.gitea/actions/setup-env
- name: Post approval review
env:
REVIEWER_GITEA_API_TOKEN: ${{ secrets.REVIEWER_GITEA_API_TOKEN }}
CI_GITEA_API_TOKEN: ${{ secrets.CI_GITEA_API_TOKEN }}
PR_NUMBER: ${{ github.event.number }}
REPOSITORY: ${{ github.repository }}
run: |
@@ -179,7 +134,6 @@ jobs:
--body "Auto-approved: all CI checks passed (validate job)."
- name: Squash merge with task ID
env:
CI_GITEA_API_TOKEN: ${{ secrets.CI_GITEA_API_TOKEN }}
VIKUNJA_TOKEN: ${{ secrets.VIKUNJA_TOKEN }}
DEVX_VIKUNJA_PROJECT_ID: "8"
HEAD_REF: ${{ github.head_ref }}
@@ -193,3 +147,6 @@ jobs:
"$PR_TITLE" \
"$REPOSITORY" \
"$PR_NUMBER"
- uses: ./.gitea/actions/notify-failure
with:
workflow: "ci/auto-merge"
+14 -47
View File
@@ -38,8 +38,8 @@ jobs:
container:
image: git.oblachno.oblachno.fyi/oblachno-oss/runner-images/ci-base:latest
credentials:
username: ${{ vars.CI_GITEA_USERNAME }}
password: ${{ secrets.CI_GITEA_API_TOKEN }}
username: ${{ env.CI_GITEA_USERNAME }}
password: ${{ env.CI_GITEA_API_TOKEN }}
timeout-minutes: 10
defaults:
run:
@@ -52,10 +52,7 @@ jobs:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up environment
env:
CI_GITEA_API_TOKEN: ${{ secrets.CI_GITEA_API_TOKEN }}
run: make setup-image
- uses: ./.gitea/actions/setup-env
- name: Ensure branch protection and labels
env:
DEVX_REPO_NAME: devx
@@ -85,19 +82,9 @@ jobs:
--base "HEAD~1" \
--head "HEAD" \
--github-output
- name: Notify on failure
if: failure()
env:
CI_GITEA_API_TOKEN: ${{ secrets.CI_GITEA_API_TOKEN }}
run: |
. .venv/bin/activate 2>/dev/null || true
export PATH="$HOME/.local/bin:$PATH"
python3 -m devx.ci.notify_failure \
--repo "${{ github.repository }}" \
--run-id "${{ github.run_id }}" \
--workflow "post-merge/detect-and-configure" \
--commit "${{ github.sha }}" \
--auto-login
- uses: ./.gitea/actions/notify-failure
with:
workflow: "post-merge/detect-and-configure"
release-and-maintain:
needs: [detect-and-configure]
@@ -106,8 +93,8 @@ jobs:
container:
image: git.oblachno.oblachno.fyi/oblachno-oss/runner-images/ci-full:latest
credentials:
username: ${{ vars.CI_GITEA_USERNAME }}
password: ${{ secrets.CI_GITEA_API_TOKEN }}
username: ${{ env.CI_GITEA_USERNAME }}
password: ${{ env.CI_GITEA_API_TOKEN }}
timeout-minutes: 15
outputs:
tag: ${{ steps.release-tag.outputs.tag }}
@@ -120,13 +107,10 @@ jobs:
fetch-depth: 0
ref: master
token: ${{ secrets.CI_GITEA_API_TOKEN }}
- name: Set up environment
env:
CI_GITEA_API_TOKEN: ${{ secrets.CI_GITEA_API_TOKEN }}
run: make setup-image EXTRAS=release
- uses: ./.gitea/actions/setup-env
with:
extras: "release"
- name: Configure git
env:
CI_GITEA_API_TOKEN: ${{ secrets.CI_GITEA_API_TOKEN }}
run: |
git config user.name "devx-ci-bot"
git config user.email "devx-ci-bot@oblachno.fyi"
@@ -135,16 +119,12 @@ jobs:
- name: Run release
id: release-tag
if: needs.detect-and-configure.outputs.is-release == 'false' && needs.detect-and-configure.outputs.user-facing-changed == 'true'
env:
CI_GITEA_API_TOKEN: ${{ secrets.CI_GITEA_API_TOKEN }}
run: |
. .venv/bin/activate 2>/dev/null || true
export PATH="$HOME/.local/bin:$PATH"
python3 -m devx.ci.release
- name: Build and publish release
if: steps.release-tag.outputs.tag != ''
env:
CI_GITEA_API_TOKEN: ${{ secrets.CI_GITEA_API_TOKEN }}
run: |
. .venv/bin/activate 2>/dev/null || true
export PATH="$HOME/.local/bin:$PATH"
@@ -154,8 +134,6 @@ jobs:
# --- sync-wiki + vikunja (skip on automated/release commits) ---
- name: Sync documentation to wiki
if: needs.detect-and-configure.outputs.is-automated == 'false'
env:
CI_GITEA_API_TOKEN: ${{ secrets.CI_GITEA_API_TOKEN }}
run: |
. .venv/bin/activate 2>/dev/null || true
python3 -m devx.ci.sync_wiki --repo "${{ github.repository }}" --verify
@@ -170,7 +148,6 @@ jobs:
# --- badges (always run — even on release commits) ---
- name: Generate and push badges
env:
CI_GITEA_API_TOKEN: ${{ secrets.CI_GITEA_API_TOKEN }}
PRE_COMMIT_ALLOW_NO_CONFIG: "1"
run: |
. .venv/bin/activate 2>/dev/null || true
@@ -179,16 +156,6 @@ jobs:
git fetch origin master
git reset --hard origin/master
python3 -m devx.ci.push_badges
- name: Notify on failure
if: failure()
env:
CI_GITEA_API_TOKEN: ${{ secrets.CI_GITEA_API_TOKEN }}
run: |
. .venv/bin/activate 2>/dev/null || true
export PATH="$HOME/.local/bin:$PATH"
python3 -m devx.ci.notify_failure \
--repo "${{ github.repository }}" \
--run-id "${{ github.run_id }}" \
--workflow "post-merge/release-and-maintain" \
--commit "${{ github.sha }}" \
--auto-login
- uses: ./.gitea/actions/notify-failure
with:
workflow: "post-merge/release-and-maintain"
+68
View File
@@ -58,6 +58,74 @@ The pre-commit hook runs actionlint automatically when workflow files change.
The CI `validate` job runs `make setup-image` then `make lint-all`.
CI also runs a best-effort `make workflow-dryrun` step (skipped if act_runner is not installed in the CI Docker image).
## Composite Actions (`.gitea/actions/`)
Reusable Gitea composite actions eliminate repeated multi-step sequences
across workflows. Each action lives in its own directory under
`.gitea/actions/<name>/action.yml` and is referenced via
`uses: ./.gitea/actions/<name>`.
### Available Composite Actions
| Action | Purpose | Inputs |
|--------|---------|--------|
| `setup-env` | Run `make setup-image` (with optional `EXTRAS=`) | `extras` (default: `""`) |
| `notify-failure` | Create a Gitea issue on job failure via `devx.ci.notify_failure` | `workflow` (required) |
| `quality-checks` | 6-step quality sequence: lint, tests, speed, docs, translations, security | `package`, `test-speed-max`, `test-speed-max-single`, `translations-file` |
### Gitea 1.27 Constraints
- Every `run` step in a composite action MUST have explicit `shell:`.
- Composite actions CANNOT access `secrets` directly. They read from
the calling workflow's `env:` context (for example, `${{ env.CI_GITEA_API_TOKEN }}`).
The calling workflow's top-level `env:` block must define the required
env vars.
- `if: failure()` in a composite action step is evaluated in the
calling workflow's job-status context.
### Usage Pattern
```yaml
jobs:
validate:
env:
CI_GITEA_API_TOKEN: ${{ secrets.CI_GITEA_API_TOKEN }}
CI_GITEA_USERNAME: ${{ vars.CI_GITEA_USERNAME }}
steps:
- uses: actions/checkout@v4
- uses: ./.gitea/actions/setup-env
- uses: ./.gitea/actions/quality-checks
with:
package: devx
- uses: ./.gitea/actions/notify-failure
with:
workflow: "ci/validate"
```
### Same-Repo Copies (No Cross-Repo References)
Each repo (`devx`, `grm`, `infra`) gets its own copy of the composite
actions under its `.gitea/actions/` directory. There is no
`uses: oblachno-oss/devx/.gitea/actions/...@vX.Y.Z` reference. This
avoids a single-point-of-failure where a bad devx master commit would
break all repos' CI simultaneously. See ADR-0003 for the full
rationale.
### When NOT to Use Composite Actions
- **`make setup-release` / `make setup-ci`**: the `setup-env` action
only wraps `make setup-image`. Workflows that use other setup targets
(for example, `build-images.yml` uses `make setup-release`) keep the inline
setup step.
- **Deploy-specific setup**: infra deploy workflows have additional
steps (`install-collections`, `setup-vault`, `setup_ssh_key`) that
are NOT part of the common setup. The `setup-env` action only
replaces the `make setup-image` step; deploy-specific steps stay
inline.
- **Custom notification**: `security-scan.yml` uses a Mattermost
webhook, not `devx.ci.notify_failure`. The `notify-failure` action
does not apply.
## Architecture
devx is a reusable Python package providing development and CI/CD tools for oblachno-oss projects.
+13
View File
@@ -2,6 +2,19 @@
All notable changes to this project will be documented in this file.
## [Unreleased]
### Ci
- Add composite actions (setup-env, notify-failure, quality-checks) in `.gitea/actions/`
- Convert ci.yml, post-merge.yml, build-images.yml to use composite actions
- Add notify-failure to ci/auto-merge and build-images/cleanup jobs (previously missing)
- Fix `make workflow-dryrun` failure: use `env` context instead of
`vars`/`secrets` in `container.credentials` (act_runner dry-run
does not populate vars/secrets)
- Remove redundant step-level `CI_GITEA_API_TOKEN`/`CI_GITEA_USERNAME`
env vars (now in workflow-level `env:` block)
## [0.50.4] - 2026-08-12
### Bug Fixes
+11
View File
@@ -445,6 +445,17 @@ src/devx/
└── molecule/ # Optional molecule testing helpers (for Ansible projects)
```
### Composite Actions (`.gitea/actions/`)
Reusable Gitea composite actions for CI workflow steps:
- `setup-env` — runs `make setup-image` (with optional `EXTRAS=`)
- `notify-failure` — creates a Gitea issue on job failure
- `quality-checks` — 6-step quality gate (lint, tests, speed, docs, translations, security)
Each consumer repo gets its own copy (no cross-repo references). See
ADR-0003 for the design rationale.
### Design principles
- **Self-contained package** — `src/devx/` never imports from scripts outside the package
+155
View File
@@ -0,0 +1,155 @@
# ADR-0003: Composite Actions for CI Workflow Reuse
Date: 2026-08-12
Status: Accepted
## Context
The devx repository's Gitea Actions workflows (`.gitea/workflows/ci.yml`,
`post-merge.yml`, `build-images.yml`) repeated multi-step
sequences across jobs:
1. **Set up environment**`make setup-image` (optionally with `EXTRAS=`).
Appeared verbatim in 4 jobs across `ci.yml` and `post-merge.yml`, each
with the same `CI_GITEA_API_TOKEN` env wiring.
2. **Notify on failure**`python3 -m devx.ci.notify_failure ... --auto-login`
with venv activation, PATH export, and 5 fixed CLI args. Appeared in
4 jobs (`ci/validate`, `post-merge/detect-and-configure`,
`post-merge/release-and-maintain`, `build-images/build-and-push`),
each differing only in the `--workflow` string.
3. **Quality checks** — a 6-step sequence (lint-all, pytest-cov,
check-test-speed, devx-docs-check, check-translations, pip-audit)
with venv activation boilerplate on every step. Appeared once in
`ci.yml` validate job, but the same sequence is needed by `grm` and
`infra` (Phase 2b/2c of the cross-repo refactoring plan).
This duplication had the following costs:
- **Drift risk**: a fix to notify-failure (for example, new flag,
different env var) had to be applied to 4 places; missing one caused
inconsistent failure notifications.
- **Workflow YAML noise**: the 6-step quality block obscured the
validate job's actual structure (detect-changes, pr-review,
release-dry-run).
- **Cross-repo reuse blocked**: `grm` and `infra` could not adopt the
same quality-checks sequence without copy-pasting the inline steps,
which would amplify the drift problem across 3 repos.
- **Gitea 1.27 constraints**: every `run` step needs explicit `shell:`;
composite actions cannot access `secrets` directly (only `env:`).
These constraints had to be re-discovered and re-applied per step.
## Decision
Introduce three Gitea composite actions in `.gitea/actions/`:
### 1. `setup-env/action.yml`
Wraps the `make setup-image` call. Single input `extras` (default empty)
forwarded to `make setup-image EXTRAS=`. Reads `CI_GITEA_API_TOKEN` and
`CI_GITEA_USERNAME` from the calling workflow's `env:` context.
### 2. `notify-failure/action.yml`
Wraps the `devx.ci.notify_failure` invocation. Single required input
`workflow` (the workflow/job name for the Gitea issue title). Step is
gated by `if: failure()` so it only runs on job failure. Reads
`CI_GITEA_API_TOKEN` from the calling workflow's `env:` context.
### 3. `quality-checks/action.yml`
Wraps the 6-step quality sequence. Inputs:
- `package` (default empty) — sets `DEVX_DOC_VERSIONS_PKG` for doc
version checks (for example, `devx`, `grm`).
- `test-speed-max` (default `15`) — total test seconds threshold.
- `test-speed-max-single` (default `0.5`) — per-test seconds threshold.
- `translations-file` (default empty) — path to `translations.json`
for repos whose translations live outside `src/devx/`.
Each step activates the venv defensively
(`. .venv/bin/activate 2>/dev/null || true`) so the action works with
both pre-built CI images (which symlink `/opt/venv` to `.venv`) and
fresh `make setup-image` runs.
### Adoption Scope
- **`ci.yml` validate job**: `setup-env` + `quality-checks` +
`notify-failure`.
- **`ci.yml` auto-merge job**: `setup-env` + `notify-failure` (no
quality checks — auto-merge only runs after validate passes).
- **`post-merge.yml` detect-and-configure**: `setup-env` +
`notify-failure`.
- **`post-merge.yml` release-and-maintain**: `setup-env` (with
`extras: "release"`) + `notify-failure`.
- **`build-images.yml` build-and-push**: `notify-failure` only. The
setup steps use `make setup-release` (not `make setup-image`), so
`setup-env` does not apply.
- **`build-images.yml` cleanup**: `notify-failure` only. The setup
step uses `make setup-ci` (not `make setup-image`).
### Same-Repo Copies (No Cross-Repo References)
Each consumer repo (`devx`, `grm`, `infra`) gets its own copy of the
composite actions under its `.gitea/actions/` directory. There is no
`uses: oblachno-oss/devx/.gitea/actions/...@vX.Y.Z` reference.
This avoids a single-point-of-failure where a bad `devx` master commit
would break all three repos' CI simultaneously. The cost is three
copies of ~30 lines of YAML each, updated manually when a composite
action changes. Given the stability of these patterns (the inline
versions were unchanged for months), this cost is acceptable.
## Consequences
### Positive
- **Workflow YAML is shorter and clearer**: the validate job's
quality block collapses from 32 lines to 5 lines. The intent
(`uses: ./.gitea/actions/quality-checks`) is more legible than
6 individually wrapped steps.
- **Drift eliminated**: a change to notify-failure (new flag, different
env var) is applied in one file. All 4 calling sites pick it up.
- **Cross-repo reuse enabled**: Phase 2b (`grm`) and Phase 2c (`infra`)
copy the same `action.yml` files and adopt the same `uses:` pattern.
The quality-checks sequence is now portable.
- **Gitea 1.27 constraints centralized**: the `shell: bash` and
`env:` (not `secrets`) patterns are encoded once per action, not
re-derived per step.
- **No release triggered**: changes to `.gitea/**` are classified as
workflow-only by `devx.ci.classify_changes`. Phase 2a does not
produce a new devx version. `grm`/`infra` bump to the Phase 1
release (v0.50.4), not a Phase 2a version.
### Negative
- **Three copies of each action**: when a composite action changes,
the change must be applied to `devx`, `grm`, and `infra`
independently. This is intentional (see Same-Repo Copies preceding)
but is a maintenance cost.
- **Composite action debugging is harder**: Gitea's log output for
composite action steps is nested under the action name. Finding the
failing step requires reading one more level of indentation.
- **`env:` propagation is implicit**: the calling workflow's top-level
`env:` block must define `CI_GITEA_API_TOKEN` for the composite
action to read it. A workflow that omits this will see an empty
token at runtime, not at lint time. actionlint does not catch this.
- **`quality-checks` is devx-shaped**: the `package` and
`translations-file` inputs exist because consumer repos (for example,
`grm`) have translations files outside the default
`src/devx/translations.json` location and need doc version checks
targeting their own package name.
A repo with a different translations path or package layout would need
a new input or a different action. This is acceptable for the current
3-repo scope.
### Neutral
- **`if: failure()` is preserved**: the `notify-failure` composite
action's step has `if: failure()`, which is evaluated in the
calling workflow's job-status context. This is the standard Gitea
Actions pattern for post-failure notification.
- **Venv activation is defensive**: `. .venv/bin/activate 2>/dev/null
|| true` does not fail if the venv is missing (pre-built image path)
or already active. This matches the inline pattern's behavior.
+18
View File
@@ -453,6 +453,24 @@ v2 failures. Supports loading custom platforms from a JSON file.
- **Secrets via environment** — secrets are passed via environment variables,
never on the command line.
## Composite Actions (`.gitea/actions/`)
Reusable Gitea composite actions eliminate repeated multi-step sequences
across workflows. Each action lives in `.gitea/actions/<name>/action.yml`
and is referenced via `uses: ./.gitea/actions/<name>`.
| Action | Purpose |
|--------|---------|
| `setup-env` | Run `make setup-image` (with optional `EXTRAS=`) |
| `notify-failure` | Create a Gitea issue on job failure via `devx.ci.notify_failure` |
| `quality-checks` | 6-step quality sequence: lint, tests, speed, docs, translations, security |
Each consumer repo (`devx`, `grm`, `infra`) gets its own copy — there are
no cross-repo composite action references. This avoids a single-point-of-failure
where a bad devx master commit would break all repos' CI simultaneously.
See ADR-0003 for the full design rationale and Gitea 1.27 constraints.
## Import rules
1. **`src/devx/` is self-contained** — the package never imports from outside `src/`
+36 -19
View File
@@ -38,22 +38,33 @@ The single validation job. Consolidates the former `quality`,
`detect-changes`, `release-dry-run`, `pr-review`, and `pre-merge-check`
jobs into one job to save checkout+setup overhead. Runs on every PR.
**Quality steps**
**Setup and quality steps** (composite actions)
The main quality gate:
The validate job uses three composite actions from `.gitea/actions/`:
1. **Lint all** — ruff check, ruff format check, pyright, bandit, actionlint
(via `make lint-all`)
2. **Unit tests with 100% coverage**`make pytest-cov`
3. **Check unit test speed** — `python -m devx.tools.check_test_speed
--max-seconds 4 --max-single-seconds 0.5`
4. **Documentation coverage check**`python -m devx.ci.doc_coverage
--fail-on-missing`
5. **Translation completeness check**`python -m devx.ci.check_translations`
6. **Dependency security scan**`pip-audit --desc --skip-editable`
(best-effort, non-blocking)
7. **Workflow dry-run validation**`make workflow-dryrun` via act_runner
(best-effort, skipped if act_runner is not installed)
1. **`setup-env`** — runs `make setup-image` to link the pre-built venv
and install the project (no-deps mode)
2. **`quality-checks`** — runs the 6-step quality gate:
- **Lint all** — ruff check, ruff format check, pyright, bandit,
actionlint (via `make lint-all`)
- **Unit tests with 100% coverage**`make pytest-cov`
- **Check unit test speed** — `python -m devx.tools.check_test_speed
--max-seconds 15 --max-single-seconds 0.5`
- **Documentation gate**`make devx-docs-check` (coverage + stale
refs + lint + version refs + prose)
- **Translation completeness check**`python -m devx.ci.check_translations`
- **Dependency security scan**`pip-audit --desc --skip-editable`
(best-effort, non-blocking)
3. **`notify-failure`** — creates a Gitea issue if any step fails
The quality-checks action accepts inputs (`package`, `test-speed-max`,
`test-speed-max-single`, `translations-file`) for cross-repo reuse.
See ADR-0003 for the composite action design rationale.
**Workflow dry-run validation** (inline step, not part of composite action)
`make workflow-dryrun` via act_runner (best-effort, skipped if
act_runner is not installed).
**`detect-changes` step**
@@ -574,8 +585,14 @@ picks up the new version number). This prevents infinite loops.
## Failure handling
Every job in the CI and post-merge workflows has a `notify_failure` step
that runs `if: failure()`. This creates a Gitea issue with the workflow name,
run ID, and commit SHA, ensuring failures that would otherwise go unnoticed
in the Actions tab are surfaced as issues. The issue is created via the tea
CLI with a `bug` label if available.
Every job in the CI, post-merge, and build-images workflows uses the
`notify-failure` composite action (`.gitea/actions/notify-failure`),
which runs `if: failure()`. This creates a Gitea issue with the workflow
name, run ID, and commit SHA, ensuring failures that would otherwise go
unnoticed in the Actions tab are surfaced as issues. The issue is created
via the tea CLI with a `bug` label if available.
Jobs with `notify-failure`:
- `ci/validate`, `ci/auto-merge`
- `post-merge/detect-and-configure`, `post-merge/release-and-maintain`
- `build-images/build-and-push`, `build-images/cleanup`