Files
clean-architecture-frontend…/.gitea/workflows/quality-gates.yml
T

366 lines
14 KiB
YAML

name: frontend-quality-gates
on:
push:
branches: [develop]
tags: ["v*"]
pull_request:
workflow_dispatch:
inputs:
stage:
description: Highest promotion tier to evaluate
required: true
default: merge
type: choice
options:
- merge
- release
- production
- field
- documentation
permissions:
contents: read
env:
CI: "true"
VITE_BUILD_ID: "gitea-${{ gitea.run_id }}-${{ gitea.run_attempt }}"
VITE_COMMIT_SHA: "${{ gitea.sha }}"
RELEASE_ID: "${{ gitea.ref }}-${{ gitea.run_id }}-${{ gitea.run_attempt }}"
CI_RUNNER_IMAGE: "${{ vars.RUNNER_IMAGE_DIGEST }}"
jobs:
merge_gate:
name: ${{ matrix.gate }} / ${{ matrix.name }}
if: ${{ gitea.event_name != 'workflow_dispatch' || inputs.stage != 'documentation' }}
runs-on: ubuntu-latest
timeout-minutes: 45
strategy:
fail-fast: false
matrix:
include:
- { gate: FE-GATE-001, name: manifest-lockfile, browser: false }
- { gate: FE-GATE-002, name: lint, browser: false }
- { gate: FE-GATE-003, name: typecheck, browser: false }
- { gate: FE-GATE-004, name: runtime-schema, browser: false }
- { gate: FE-GATE-005, name: unit, browser: false }
- { gate: FE-GATE-006, name: component, browser: false }
- { gate: FE-GATE-007, name: integration, browser: false }
- { gate: FE-GATE-008, name: e2e, browser: true }
- { gate: FE-GATE-009, name: accessibility, browser: true }
- { gate: FE-GATE-010, name: architecture, browser: false }
- { gate: FE-GATE-011, name: build, browser: false }
- { gate: FE-GATE-013, name: security, browser: false }
- { gate: FE-GATE-020, name: sample-removal, browser: false }
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: .nvmrc
- name: Frozen install
run: |
corepack enable
corepack pnpm install --frozen-lockfile
- name: Install Playwright browsers
if: ${{ matrix.browser }}
run: corepack pnpm exec playwright install --with-deps chromium firefox webkit
- name: Run blocking gate
run: corepack pnpm ci:gate -- ${{ matrix.gate }}
- name: Upload gate evidence
if: always()
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.gate }}-${{ gitea.run_id }}
path: artifacts/
if-no-files-found: error
release_gate:
name: ${{ matrix.gate }} / ${{ matrix.name }}
needs: merge_gate
if: ${{ startsWith(gitea.ref, 'refs/tags/v') || (gitea.event_name == 'workflow_dispatch' && (inputs.stage == 'release' || inputs.stage == 'production' || inputs.stage == 'field')) }}
runs-on: ubuntu-latest
timeout-minutes: 45
env:
HOSTING_BASE_URL: ${{ vars.HOSTING_BASE_URL }}
strategy:
fail-fast: false
matrix:
include:
- { gate: FE-GATE-012, name: bundle, browser: false }
- { gate: FE-GATE-014, name: config-compatibility, browser: false }
- { gate: FE-GATE-019, name: hosting-header, browser: false }
- { gate: FE-GATE-026, name: lab-performance, browser: true }
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: .nvmrc
- name: Frozen install
run: |
corepack enable
corepack pnpm install --frozen-lockfile
- name: Install Playwright browsers
if: ${{ matrix.browser }}
run: corepack pnpm exec playwright install --with-deps chromium firefox webkit
- name: Run blocking gate
run: corepack pnpm ci:gate -- ${{ matrix.gate }}
- name: Upload gate evidence
if: always()
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.gate }}-${{ gitea.run_id }}
path: artifacts/
if-no-files-found: error
immutable_build:
name: FE-GATE-015 / immutable-release-candidate
needs: release_gate
if: ${{ startsWith(gitea.ref, 'refs/tags/v') || (gitea.event_name == 'workflow_dispatch' && (inputs.stage == 'release' || inputs.stage == 'production' || inputs.stage == 'field')) }}
runs-on: ubuntu-latest
timeout-minutes: 45
outputs:
dist_sha256: ${{ steps.candidate.outputs.dist_sha256 }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: .nvmrc
- name: Frozen install
run: |
corepack enable
corepack pnpm install --frozen-lockfile
- name: Build candidate once and verify local evidence
run: corepack pnpm ci:gate -- FE-GATE-015
- name: Archive the exact candidate file set
id: candidate
run: |
mkdir -p .release
tar --sort=name --mtime="@0" --owner=0 --group=0 --numeric-owner -czf ".release/release-candidate-${{ gitea.run_id }}-${{ gitea.run_attempt }}.tar.gz" \
dist \
pnpm-lock.yaml \
artifacts/performance/bundle.json \
artifacts/quality/vite-module-inventory.json \
artifacts/release/build-manifest.json \
artifacts/release/checksums.txt \
artifacts/release/dependency-inventory.json \
artifacts/release/provenance.json \
artifacts/release/release-candidate.json \
artifacts/release/verification.json \
artifacts/release/sbom.cdx.json \
artifacts/security/dependency-diff.json \
artifacts/security/license-report.json \
artifacts/security/scan.sarif \
artifacts/security/supply-chain-coherence.json \
artifacts/security/supply-chain-verification.json \
artifacts/security/vulnerability-report.json
node -e "const m=require('./artifacts/release/release-candidate.json'); process.stdout.write('dist_sha256='+m.distSha256+'\\n')" >> "$GITHUB_OUTPUT"
- name: Upload immutable candidate archive
uses: actions/upload-artifact@v4
with:
name: release-candidate-${{ gitea.run_id }}-${{ gitea.run_attempt }}
path: .release/release-candidate-${{ gitea.run_id }}-${{ gitea.run_attempt }}.tar.gz
if-no-files-found: error
vulnerability_provider:
name: external-vulnerability-provider
needs: immutable_build
runs-on: ubuntu-latest
timeout-minutes: 45
env:
CANDIDATE_DIST_SHA256: ${{ needs.immutable_build.outputs.dist_sha256 }}
CANDIDATE_LOCKFILE_PATH: pnpm-lock.yaml
VULNERABILITY_PROVIDER_COMMAND: ${{ vars.VULNERABILITY_PROVIDER_COMMAND }}
VULNERABILITY_REPORT_PATH: provider-evidence/vulnerability-report.json
steps:
- name: Download immutable candidate archive
uses: actions/download-artifact@v4
with:
name: release-candidate-${{ gitea.run_id }}-${{ gitea.run_attempt }}
path: .release/vulnerability-candidate
- name: Extract the provider input candidate
run: tar -xzf ".release/vulnerability-candidate/release-candidate-${{ gitea.run_id }}-${{ gitea.run_attempt }}.tar.gz"
- name: Run configured external vulnerability provider
run: |
test -n "$VULNERABILITY_PROVIDER_COMMAND"
mkdir -p provider-evidence
sh -eu -c "$VULNERABILITY_PROVIDER_COMMAND"
test -s "$VULNERABILITY_REPORT_PATH"
- name: Upload external vulnerability report
uses: actions/upload-artifact@v4
with:
name: vulnerability-provider-${{ gitea.run_id }}-${{ gitea.run_attempt }}
path: provider-evidence/vulnerability-report.json
if-no-files-found: error
provenance_provider:
name: external-provenance-provider
needs: immutable_build
runs-on: ubuntu-latest
timeout-minutes: 45
env:
CANDIDATE_DIST_SHA256: ${{ needs.immutable_build.outputs.dist_sha256 }}
CANDIDATE_LOCKFILE_PATH: pnpm-lock.yaml
PROVENANCE_PROVIDER_COMMAND: ${{ vars.PROVENANCE_PROVIDER_COMMAND }}
PROVENANCE_ATTESTATION_PATH: provider-evidence/provenance-attestation.json
steps:
- name: Download immutable candidate archive
uses: actions/download-artifact@v4
with:
name: release-candidate-${{ gitea.run_id }}-${{ gitea.run_attempt }}
path: .release/provenance-candidate
- name: Extract the provider input candidate
run: tar -xzf ".release/provenance-candidate/release-candidate-${{ gitea.run_id }}-${{ gitea.run_attempt }}.tar.gz"
- name: Run configured external provenance provider
run: |
test -n "$PROVENANCE_PROVIDER_COMMAND"
mkdir -p provider-evidence
sh -eu -c "$PROVENANCE_PROVIDER_COMMAND"
test -s "$PROVENANCE_ATTESTATION_PATH"
- name: Upload external provenance attestation
uses: actions/upload-artifact@v4
with:
name: provenance-provider-${{ gitea.run_id }}-${{ gitea.run_attempt }}
path: provider-evidence/provenance-attestation.json
if-no-files-found: error
promotion:
name: promote-verified-immutable-candidate
needs: [immutable_build, vulnerability_provider, provenance_provider]
runs-on: ubuntu-latest
timeout-minutes: 45
env:
VULNERABILITY_REPORT_PATH: .release/vulnerability/vulnerability-report.json
PROVENANCE_ATTESTATION_PATH: .release/provenance/provenance-attestation.json
VULNERABILITY_PUBLIC_KEY_PATH: ${{ vars.VULNERABILITY_PUBLIC_KEY_PATH }}
VULNERABILITY_KEY_ID: ${{ vars.VULNERABILITY_KEY_ID }}
PROVENANCE_PUBLIC_KEY_PATH: ${{ vars.PROVENANCE_PUBLIC_KEY_PATH }}
PROVENANCE_KEY_ID: ${{ vars.PROVENANCE_KEY_ID }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: .nvmrc
- name: Frozen install
run: |
corepack enable
corepack pnpm install --frozen-lockfile
- name: Download immutable candidate archive
uses: actions/download-artifact@v4
with:
name: release-candidate-${{ gitea.run_id }}-${{ gitea.run_attempt }}
path: .release/candidate
- name: Download vulnerability provider evidence
uses: actions/download-artifact@v4
with:
name: vulnerability-provider-${{ gitea.run_id }}-${{ gitea.run_attempt }}
path: .release/vulnerability
- name: Download provenance provider evidence
uses: actions/download-artifact@v4
with:
name: provenance-provider-${{ gitea.run_id }}-${{ gitea.run_attempt }}
path: .release/provenance
- name: Extract unchanged candidate
run: tar -xzf ".release/candidate/release-candidate-${{ gitea.run_id }}-${{ gitea.run_attempt }}.tar.gz"
- name: Verify provider signatures and candidate digests
run: |
corepack pnpm verify:provider-evidence
corepack pnpm verify:promotion
- name: Upload promoted unchanged candidate
uses: actions/upload-artifact@v4
with:
name: promoted-release-${{ gitea.run_id }}-${{ gitea.run_attempt }}
path: |
.release/candidate/release-candidate-${{ gitea.run_id }}-${{ gitea.run_attempt }}.tar.gz
.release/vulnerability/vulnerability-report.json
.release/provenance/provenance-attestation.json
artifacts/security/provider-verification.json
artifacts/security/promotion-verification.json
if-no-files-found: error
production_gate:
name: ${{ matrix.gate }} / ${{ matrix.name }}
needs: promotion
if: ${{ gitea.event_name == 'workflow_dispatch' && (inputs.stage == 'production' || inputs.stage == 'field') }}
runs-on: ubuntu-latest
timeout-minutes: 45
strategy:
fail-fast: false
matrix:
include:
- { gate: FE-GATE-016, name: rollback-drill }
- { gate: FE-GATE-021, name: runbook-boot-config }
- { gate: FE-GATE-022, name: runbook-chunk-mismatch }
- { gate: FE-GATE-023, name: runbook-api-degradation }
- { gate: FE-GATE-024, name: runbook-telemetry }
- { gate: FE-GATE-025, name: runbook-release-rollback }
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: .nvmrc
- name: Frozen install
run: |
corepack enable
corepack pnpm install --frozen-lockfile
- name: Run blocking gate
run: corepack pnpm ci:gate -- ${{ matrix.gate }}
- name: Upload gate evidence
if: always()
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.gate }}-${{ gitea.run_id }}
path: artifacts/
if-no-files-found: error
field_gate:
name: FE-GATE-018 / field-web-vitals
needs: production_gate
if: ${{ gitea.event_name == 'workflow_dispatch' && inputs.stage == 'field' }}
runs-on: ubuntu-latest
timeout-minutes: 45
env:
FIELD_WEB_VITALS_INPUT: ${{ vars.FIELD_WEB_VITALS_INPUT }}
MIN_ELIGIBLE_SAMPLES: ${{ vars.MIN_ELIGIBLE_SAMPLES }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: .nvmrc
- name: Frozen install
run: |
corepack enable
corepack pnpm install --frozen-lockfile
- name: Run blocking gate
run: corepack pnpm ci:gate -- FE-GATE-018
- name: Upload gate evidence
if: always()
uses: actions/upload-artifact@v4
with:
name: FE-GATE-018-${{ gitea.run_id }}
path: artifacts/
if-no-files-found: error
documentation_gate:
name: FE-GATE-017 / diagram-review
if: ${{ gitea.event_name == 'workflow_dispatch' && inputs.stage == 'documentation' }}
runs-on: ubuntu-latest
timeout-minutes: 45
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: .nvmrc
- name: Frozen install
run: |
corepack enable
corepack pnpm install --frozen-lockfile
- name: Run documentation gate
run: corepack pnpm ci:gate -- FE-GATE-017
- name: Upload gate evidence
if: always()
uses: actions/upload-artifact@v4
with:
name: FE-GATE-017-${{ gitea.run_id }}
path: artifacts/
if-no-files-found: error