From 50a912221491c7041fc18d9b84b272a341dcc20d Mon Sep 17 00:00:00 2001 From: soeren Date: Mon, 17 Aug 2026 20:16:21 +0200 Subject: [PATCH 1/2] Streamline CI validation gates --- .github/workflows/change-classification.yml | 90 +++++++++++++++++++++ .github/workflows/ci.yml | 63 +++++++++++++-- .github/workflows/security-audit.yml | 43 +++++++++- AGENTS.md | 3 +- docs/releasing.md | 4 +- 5 files changed, 194 insertions(+), 9 deletions(-) create mode 100644 .github/workflows/change-classification.yml diff --git a/.github/workflows/change-classification.yml b/.github/workflows/change-classification.yml new file mode 100644 index 0000000..b321b70 --- /dev/null +++ b/.github/workflows/change-classification.yml @@ -0,0 +1,90 @@ +name: Change classification + +on: + workflow_call: + outputs: + automation: + description: GitHub Actions workflows changed + value: ${{ jobs.classify.outputs.automation }} + backend: + description: Laravel runtime or tests changed + value: ${{ jobs.classify.outputs.backend }} + dependencies: + description: Composer or npm dependencies changed + value: ${{ jobs.classify.outputs.dependencies }} + frontend: + description: Frontend sources or build configuration changed + value: ${{ jobs.classify.outputs.frontend }} + infrastructure: + description: Container or deployment configuration changed + value: ${{ jobs.classify.outputs.infrastructure }} + source: + description: Application source code changed + value: ${{ jobs.classify.outputs.source }} + +permissions: + contents: read + pull-requests: read + +jobs: + classify: + name: Classify changed files + runs-on: ubuntu-latest + outputs: + automation: ${{ steps.filter.outputs.automation }} + backend: ${{ steps.filter.outputs.backend }} + dependencies: ${{ steps.filter.outputs.dependencies }} + frontend: ${{ steps.filter.outputs.frontend }} + infrastructure: ${{ steps.filter.outputs.infrastructure }} + source: ${{ steps.filter.outputs.source }} + steps: + - name: Checkout + uses: actions/checkout@v7 + + - name: Classify changes + id: filter + uses: dorny/paths-filter@v3 + with: + filters: | + automation: + - '.github/workflows/**' + backend: + - 'app/**' + - 'artisan' + - 'bootstrap/**' + - 'composer.json' + - 'composer.lock' + - 'config/**' + - 'database/**' + - 'phpunit.xml' + - 'resources/lang/**' + - 'resources/views/**' + - 'routes/**' + - 'tests/**' + dependencies: + - 'composer.json' + - 'composer.lock' + - 'package.json' + - 'package-lock.json' + frontend: + - 'package.json' + - 'package-lock.json' + - 'resources/css/**' + - 'resources/js/**' + - 'vite.config.*' + infrastructure: + - '.env.docker.example' + - 'Caddyfile' + - 'Dockerfile' + - 'compose*.yml' + - 'install.sh' + source: + - 'app/**' + - 'bootstrap/**' + - 'config/**' + - 'database/**' + - 'resources/css/**' + - 'resources/js/**' + - 'resources/lang/**' + - 'resources/views/**' + - 'routes/**' diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2e3bf02..3c57da6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,20 +9,27 @@ on: permissions: contents: read + pull-requests: read concurrency: group: ci-${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: - release_validation: - name: Release-ready validation - uses: ./.github/workflows/release-validation.yml + changes: + name: Change classification + uses: ./.github/workflows/change-classification.yml php: name: PHP quality and tests runs-on: ubuntu-latest - needs: frontend + needs: + - changes + - frontend + if: >- + github.event_name == 'workflow_dispatch' || + needs.changes.outputs.backend == 'true' || + needs.changes.outputs.automation == 'true' steps: - name: Checkout uses: actions/checkout@v7 @@ -63,6 +70,12 @@ jobs: frontend: name: Frontend build runs-on: ubuntu-latest + needs: changes + if: >- + github.event_name == 'workflow_dispatch' || + needs.changes.outputs.backend == 'true' || + needs.changes.outputs.frontend == 'true' || + needs.changes.outputs.automation == 'true' steps: - name: Checkout uses: actions/checkout@v7 @@ -97,7 +110,13 @@ jobs: mariadb: name: MariaDB integration tests runs-on: ubuntu-latest - needs: frontend + needs: + - changes + - frontend + if: >- + github.event_name == 'workflow_dispatch' || + needs.changes.outputs.backend == 'true' || + needs.changes.outputs.automation == 'true' services: db: image: mariadb:11 @@ -147,6 +166,11 @@ jobs: container: name: Container integration test runs-on: ubuntu-latest + needs: changes + if: >- + github.event_name == 'workflow_dispatch' || + needs.changes.outputs.infrastructure == 'true' || + needs.changes.outputs.automation == 'true' steps: - name: Checkout uses: actions/checkout@v7 @@ -244,6 +268,11 @@ jobs: compose: name: Compose configuration runs-on: ubuntu-latest + needs: changes + if: >- + github.event_name == 'workflow_dispatch' || + needs.changes.outputs.infrastructure == 'true' || + needs.changes.outputs.automation == 'true' steps: - name: Checkout uses: actions/checkout@v7 @@ -261,6 +290,11 @@ jobs: shell: name: Installer script runs-on: ubuntu-latest + needs: changes + if: >- + github.event_name == 'workflow_dispatch' || + needs.changes.outputs.infrastructure == 'true' || + needs.changes.outputs.automation == 'true' steps: - name: Checkout uses: actions/checkout@v7 @@ -272,3 +306,22 @@ jobs: run: | test -x install.sh shellcheck install.sh + + gate: + name: CI gate + runs-on: ubuntu-latest + if: always() + needs: + - changes + - php + - frontend + - mariadb + - container + - compose + - shell + steps: + - name: Require successful executed checks + if: >- + contains(needs.*.result, 'failure') || + contains(needs.*.result, 'cancelled') + run: exit 1 diff --git a/.github/workflows/security-audit.yml b/.github/workflows/security-audit.yml index 953aed2..c048bc8 100644 --- a/.github/workflows/security-audit.yml +++ b/.github/workflows/security-audit.yml @@ -4,15 +4,34 @@ on: pull_request: push: branches: - - main - master schedule: - cron: "0 5 * * 1" +permissions: + contents: read + pull-requests: read + +concurrency: + group: security-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: + changes: + name: Change classification + if: github.event_name != 'schedule' + uses: ./.github/workflows/change-classification.yml + dependency-audit: name: Dependency Audit runs-on: ubuntu-latest + needs: changes + if: >- + always() && ( + github.event_name == 'schedule' || + needs.changes.outputs.dependencies == 'true' || + needs.changes.outputs.automation == 'true' + ) steps: - name: Checkout uses: actions/checkout@v7 @@ -59,6 +78,12 @@ jobs: sast: name: SAST runs-on: ubuntu-latest + needs: changes + if: >- + always() && ( + github.event_name == 'schedule' || + (github.ref == 'refs/heads/master' && needs.changes.outputs.source == 'true') + ) steps: - name: Checkout uses: actions/checkout@v7 @@ -70,3 +95,19 @@ jobs: p/php p/owasp-top-ten p/secrets + + gate: + name: Security gate + runs-on: ubuntu-latest + if: always() + needs: + - changes + - dependency-audit + - secret-scan + - sast + steps: + - name: Require successful executed checks + if: >- + contains(needs.*.result, 'failure') || + contains(needs.*.result, 'cancelled') + run: exit 1 diff --git a/AGENTS.md b/AGENTS.md index 75e1798..7e106fd 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -259,7 +259,8 @@ protected function isAccessible(User $user, ?string $path = null): bool ## GitHub Actions CI -- Continuous Integration runs on pull requests and pushes to `master`; it checks PHP formatting, PHPUnit, the Vite production build, MariaDB compatibility, Compose-based container initialization, deployment configuration, and the installer shell script. +- Continuous Integration runs on pull requests and pushes to `master`. It classifies changed files and runs only the relevant checks: backend changes receive the Vite build plus PHP and MariaDB tests; frontend changes receive the Vite build; infrastructure changes receive Compose, installer, and container backup/restore checks. Workflow changes run the full CI suite. +- Security Audit runs a secret scan on every pull request and `master` push. Dependency audits run for dependency or workflow changes; SAST runs for source changes on `master` and in the scheduled weekly audit. The required merge checks are `CI gate` and `Security gate`. - CI is validation-only: do not add deployment steps, repository write permissions, or secrets without explicit approval. - The frontend workflows use Node.js 24; local frontend checks require Node.js 22.18 or later. - Tags matching `v0.*.*` validate the release again, publish a GHCR container image with provenance and an SBOM, smoke-test its digest, and generate GitHub release notes; they must not deploy the application. diff --git a/docs/releasing.md b/docs/releasing.md index 7df89e6..dc02d4f 100644 --- a/docs/releasing.md +++ b/docs/releasing.md @@ -1,6 +1,6 @@ # Releasing -Releases are deliberately tag-driven. A maintainer creates a concrete `v0.x.y` tag only after the `master` branch CI is green. +Releases are deliberately tag-driven. A maintainer creates a concrete `v0.x.y` tag only after the `master` branch `CI gate` and `Security gate` are green. ## Publish a release @@ -13,7 +13,7 @@ Releases are deliberately tag-driven. A maintainer creates a concrete `v0.x.y` t git push origin v0.1.0 ``` -4. Verify the Release workflow. It repeats application validation, builds and publishes the container with provenance and an SBOM, smoke-tests the published image by digest, and creates the GitHub Release. +4. Verify the Release workflow. It repeats the complete release validation (frontend build, application tests, and dependency audits) for the immutable tag, builds and publishes the container with provenance and an SBOM, smoke-tests the published image by digest, and creates the GitHub Release. 5. Check the generated release notes. Add a concise **Upgrade notes** section that calls out migrations, changed environment variables, deprecations, and any manual operator action. ## Published images From b4a5696b3eecb229b15aca70845484a9df6b4100 Mon Sep 17 00:00:00 2001 From: soeren Date: Mon, 17 Aug 2026 20:17:26 +0200 Subject: [PATCH 2/2] Use current change filter runtime --- .github/workflows/change-classification.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/change-classification.yml b/.github/workflows/change-classification.yml index b321b70..de45c0b 100644 --- a/.github/workflows/change-classification.yml +++ b/.github/workflows/change-classification.yml @@ -43,7 +43,7 @@ jobs: - name: Classify changes id: filter - uses: dorny/paths-filter@v3 + uses: dorny/paths-filter@v4 with: filters: | automation: