name: CI on: push: branches: [master, develop] pull_request: branches: [master, develop*] # Logging policy (server-load reduction): # The Forgejo server stores every line a step streams to its console as the # live job log, and that aggregate volume across all controller-driven CI # runs is what overloads it. The controller's CI-log fetcher # (tools/_ci_logs.py fetch_pr_failure_logs) reads ONLY that live job log and # ONLY to diagnose failures — it does not download artifacts. # So each heavy step writes full output to a build/nox-*-output.log file # (still uploaded as an artifact for humans) and streams to the console: # - on success: a single one-line "OK" (the common path → ~zero volume), # - on failure: the COMPLETE log via `cat` (byte-identical to today, so no # error message or diagnostic data is suppressed). # The COVERAGE OK/FAILED summary line and status-check result echoes are # always streamed — the pipeline greps for them. jobs: load-versions: runs-on: docker container: image: python:3.13-slim outputs: uv-version: ${{ steps.versions.outputs.uv-version }} python-version: ${{ steps.versions.outputs.python-version }} helm-version: ${{ steps.versions.outputs.helm-version }} kubeconform-version: ${{ steps.versions.outputs.kubeconform-version }} steps: - name: Install Node.js (required by actions/checkout) run: | apt-get update && apt-get install -y -qq nodejs && rm -rf /var/lib/apt/lists/* - uses: actions/checkout@v4 - name: Load tool versions from .tool-versions id: versions run: | # Source the tool versions file and export as GitHub Actions outputs while IFS='=' read -r key value; do # Skip empty lines and comments [[ -z "$key" || "$key" =~ ^# ]] && continue # Trim whitespace key=$(echo "$key" | xargs) value=$(echo "$value" | xargs) # Convert to lowercase with hyphens for output names output_key=$(echo "$key" | tr '[:upper:]_' '[:lower:]-') echo "${output_key}=${value}" >> $GITHUB_OUTPUT echo "Loaded: ${output_key}=${value}" done < .tool-versions lint: needs: load-versions runs-on: docker container: image: python:3.13-slim steps: - name: Install Node.js (required by actions/checkout) run: | apt-get update -qq && apt-get install -y -qq nodejs && rm -rf /var/lib/apt/lists/* - uses: actions/checkout@v4 - name: Install uv and nox run: | pip install -q uv==${{ needs.load-versions.outputs.uv-version }} nox - name: Cache uv packages uses: actions/cache@v3 with: path: ~/.cache/uv key: uv-${{ hashFiles('pyproject.toml') }} restore-keys: | uv- - name: Run lint and format checks via nox run: | mkdir -p build rc=0 nox -s lint > build/nox-lint-output.log 2>&1 || rc=$? nox -s format -- --check >> build/nox-lint-output.log 2>&1 || rc=$? if [ "$rc" -ne 0 ]; then echo "===== lint/format FAILED (exit $rc) — full output follows; artifact ci-logs-lint =====" cat build/nox-lint-output.log exit "$rc" fi echo "lint + format: OK" env: NOX_DEFAULT_VENV_BACKEND: uv - name: Upload lint log artifact if: always() uses: actions/upload-artifact@v3 with: name: ci-logs-lint path: build/nox-lint-output.log retention-days: 30 typecheck: needs: load-versions runs-on: docker container: image: python:3.13-slim steps: - name: Install Node.js (required by actions/checkout) run: | apt-get update -qq && apt-get install -y -qq nodejs && rm -rf /var/lib/apt/lists/* - uses: actions/checkout@v4 - name: Install uv and nox run: | pip install -q uv==${{ needs.load-versions.outputs.uv-version }} nox - name: Cache uv packages uses: actions/cache@v3 with: path: ~/.cache/uv key: uv-${{ hashFiles('pyproject.toml') }} restore-keys: | uv- - name: Run typecheck via nox run: | mkdir -p build if nox -s typecheck > build/nox-typecheck-output.log 2>&1; then echo "typecheck: OK" else rc=$? echo "===== typecheck FAILED (exit $rc) — full output follows; artifact ci-logs-typecheck =====" cat build/nox-typecheck-output.log exit "$rc" fi env: NOX_DEFAULT_VENV_BACKEND: uv - name: Upload typecheck log artifact if: always() uses: actions/upload-artifact@v3 with: name: ci-logs-typecheck path: build/nox-typecheck-output.log retention-days: 30 security: needs: load-versions runs-on: docker container: image: python:3.13-slim steps: - name: Install Node.js (required by actions/checkout) run: | apt-get update -qq && apt-get install -y -qq nodejs && rm -rf /var/lib/apt/lists/* - uses: actions/checkout@v4 - name: Install uv and nox run: | pip install -q uv==${{ needs.load-versions.outputs.uv-version }} nox - name: Cache uv packages uses: actions/cache@v3 with: path: ~/.cache/uv key: uv-${{ hashFiles('pyproject.toml') }} restore-keys: | uv- - name: Run security scan and dead-code detection via nox run: | mkdir -p build rc=0 nox -s security_scan > build/nox-security-output.log 2>&1 || rc=$? nox -s dead_code >> build/nox-security-output.log 2>&1 || rc=$? if [ "$rc" -ne 0 ]; then echo "===== security/dead_code FAILED (exit $rc) — full output follows; artifact ci-logs-security =====" cat build/nox-security-output.log exit "$rc" fi echo "security + dead_code: OK" env: NOX_DEFAULT_VENV_BACKEND: uv - name: Upload security log artifact if: always() uses: actions/upload-artifact@v3 with: name: ci-logs-security path: build/nox-security-output.log retention-days: 30 quality: needs: load-versions runs-on: docker container: image: python:3.13-slim steps: - name: Install Node.js (required by actions/checkout) run: | apt-get update -qq && apt-get install -y -qq nodejs && rm -rf /var/lib/apt/lists/* - uses: actions/checkout@v4 - name: Install uv and nox run: | pip install -q uv==${{ needs.load-versions.outputs.uv-version }} nox - name: Cache uv packages uses: actions/cache@v3 with: path: ~/.cache/uv key: uv-${{ hashFiles('pyproject.toml') }} restore-keys: | uv- - name: Run complexity check via nox run: | mkdir -p build if nox -s complexity > build/nox-quality-output.log 2>&1; then echo "complexity: OK" else rc=$? echo "===== complexity FAILED (exit $rc) — full output follows; artifact ci-logs-quality =====" cat build/nox-quality-output.log exit "$rc" fi env: NOX_DEFAULT_VENV_BACKEND: uv - name: Upload quality log artifact if: always() uses: actions/upload-artifact@v3 with: name: ci-logs-quality path: build/nox-quality-output.log retention-days: 30 timeout-minutes: 30 unit_tests: needs: load-versions runs-on: docker container: image: python:3.13-slim steps: - name: Install system dependencies (nodejs for checkout, git for merge tests, curl/tar for Helm) run: | apt-get update -qq && apt-get install -y -qq nodejs git curl tar && rm -rf /var/lib/apt/lists/* - name: Cache Helm binary uses: actions/cache@v3 id: helm-cache with: path: /usr/local/bin/helm key: helm-${{ env.HELM_VERSION }}-linux-amd64 restore-keys: | helm- - name: Install Helm CLI if: steps.helm-cache.outputs.cache-hit != 'true' run: | HELM_VERSION="${{ needs.load-versions.outputs.helm-version }}" ARCH="amd64" HELM_TARBALL="helm-${HELM_VERSION}-linux-${ARCH}.tar.gz" curl -fsSL "https://get.helm.sh/${HELM_TARBALL}" -o "/tmp/${HELM_TARBALL}" curl -fsSL "https://get.helm.sh/${HELM_TARBALL}.sha256sum" -o /tmp/helm.sha256sum cd /tmp && sha256sum -c helm.sha256sum tar -xzf "/tmp/${HELM_TARBALL}" -C /tmp mv /tmp/linux-${ARCH}/helm /usr/local/bin/helm chmod +x /usr/local/bin/helm env: HELM_VERSION: ${{ env.HELM_VERSION }} - uses: actions/checkout@v4 - name: Install uv and nox run: | pip install -q uv==${{ needs.load-versions.outputs.uv-version }} nox - name: Cache uv packages uses: actions/cache@v3 with: path: ~/.cache/uv key: uv-${{ hashFiles('pyproject.toml') }} restore-keys: | uv- - name: Run unit tests via nox run: | mkdir -p build if nox -s unit_tests > build/nox-unit-tests-output.log 2>&1; then echo "unit_tests: OK" else rc=$? echo "===== unit_tests FAILED (exit $rc) — full output follows; artifact ci-logs-unit-tests =====" cat build/nox-unit-tests-output.log exit "$rc" fi env: NOX_DEFAULT_VENV_BACKEND: uv # behave-parallel worker cap. An uncapped host CPU # count (e.g. 64) oversubscribes RAM and the run is # OOM-killed mid-suite (SIGKILL / exit 137) — a red # job with no test summary. TEST_PROCESSES: "8" - name: Upload unit tests log artifact if: always() uses: actions/upload-artifact@v3 with: name: ci-logs-unit-tests path: build/nox-unit-tests-output.log retention-days: 30 timeout-minutes: 45 integration_tests: needs: load-versions runs-on: docker container: image: python:3.13-slim steps: - name: Install system dependencies (nodejs for checkout, git for integration tests, curl/tar for Helm) run: | apt-get update -qq && apt-get install -y -qq nodejs git curl tar && rm -rf /var/lib/apt/lists/* - name: Cache Helm binary uses: actions/cache@v3 id: helm-cache with: path: /usr/local/bin/helm key: helm-${{ env.HELM_VERSION }}-linux-amd64 restore-keys: | helm- - name: Install Helm CLI if: steps.helm-cache.outputs.cache-hit != 'true' run: | HELM_VERSION="${{ needs.load-versions.outputs.helm-version }}" ARCH="amd64" HELM_TARBALL="helm-${HELM_VERSION}-linux-${ARCH}.tar.gz" curl -fsSL "https://get.helm.sh/${HELM_TARBALL}" -o "/tmp/${HELM_TARBALL}" curl -fsSL "https://get.helm.sh/${HELM_TARBALL}.sha256sum" -o /tmp/helm.sha256sum cd /tmp && sha256sum -c helm.sha256sum tar -xzf "/tmp/${HELM_TARBALL}" -C /tmp mv /tmp/linux-${ARCH}/helm /usr/local/bin/helm chmod +x /usr/local/bin/helm env: HELM_VERSION: ${{ env.HELM_VERSION }} - uses: actions/checkout@v4 - name: Install uv and nox run: | pip install -q uv==${{ needs.load-versions.outputs.uv-version }} nox - name: Cache uv packages uses: actions/cache@v3 with: path: ~/.cache/uv key: uv-${{ hashFiles('pyproject.toml') }} restore-keys: | uv- - name: Run integration tests via nox run: | mkdir -p build if nox -s integration_tests > build/nox-integration-tests-output.log 2>&1; then echo "integration_tests: OK" else rc=$? echo "===== integration_tests FAILED (exit $rc) — full output follows; artifact ci-logs-integration-tests =====" cat build/nox-integration-tests-output.log exit "$rc" fi env: NOX_DEFAULT_VENV_BACKEND: uv CLEVERAGENTS_REQUIRE_HELM_RENDER_ASSERTIONS: "true" # pabot worker cap. An uncapped host CPU count # oversubscribes RAM and the run is OOM-killed # (SIGKILL / exit 137). TEST_PROCESSES: "8" # LLM API keys required for Robot Framework integration tests. # These secrets must be configured in Forgejo UI: # Repository Settings > Actions > Secrets # See docs/development/ci-cd.md for details. ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} - name: Upload integration tests log artifact if: always() uses: actions/upload-artifact@v3 with: name: ci-logs-integration-tests path: build/nox-integration-tests-output.log retention-days: 30 coverage: needs: [load-versions, lint, typecheck, security, quality, unit_tests] runs-on: docker container: image: python:3.13-slim # Bound the job so a hung run fails cleanly with diagnostics instead of # being externally reaped to "no data". The parallel engine runs in # ~4min; this leaves generous headroom for install + chunk tail. timeout-minutes: 30 steps: # Operator kill switch: repo variable skip_coverage. When set to # "true", every real step below is gated off and the job returns # success without running the (long) coverage work. This guard step # always runs, so the job has a successful step and the status check # sees coverage == success. Lets ops disable coverage fast without a # deploy. - name: Resolve skip_coverage gate id: gate run: | v="$(printf '%s' "${{ vars.skip_coverage }}" | tr '[:upper:]' '[:lower:]')" if [ "$v" = "true" ]; then echo "coverage SKIPPED — repo variable skip_coverage=$v" echo "run=false" >> "$GITHUB_OUTPUT" else echo "run=true" >> "$GITHUB_OUTPUT" fi - name: Install system dependencies (nodejs for checkout, git for merge tests) if: steps.gate.outputs.run == 'true' run: | apt-get update -qq && apt-get install -y -qq nodejs git && rm -rf /var/lib/apt/lists/* - uses: actions/checkout@v4 if: steps.gate.outputs.run == 'true' - name: Install uv and nox if: steps.gate.outputs.run == 'true' run: | pip install -q uv==${{ needs.load-versions.outputs.uv-version }} nox - name: Cache uv packages if: steps.gate.outputs.run == 'true' uses: actions/cache@v3 with: path: ~/.cache/uv key: uv-${{ hashFiles('pyproject.toml') }} restore-keys: | uv- - name: Run coverage report via nox (fail-under from pyproject) id: coverage if: steps.gate.outputs.run == 'true' # The coverage gate is nox's own --fail-under (sourced from # pyproject [tool.coverage.report].fail_under). Propagate nox's # exit EXPLICITLY via PIPESTATUS so gating does not rely on the # runner's implicit `bash -eo pipefail` default and cannot be # silently un-gated. shell: bash run: | set -uo pipefail mkdir -p build # Quiet on success; on nox failure stream the full log to the # live console (the controller reads the streamed job log, not # the artifact). The exit code is still propagated below, so the # gate (nox's own --fail-under) is unchanged. rc=0 nox -s coverage_report > build/nox-coverage-output.log 2>&1 || rc=$? if [ "$rc" -ne 0 ]; then echo "===== coverage_report FAILED (exit $rc) — full output follows; artifact ci-logs-coverage =====" cat build/nox-coverage-output.log fi # Surface the load-bearing single-line CI summary the pipeline greps. grep -E '^(nox > )?COVERAGE (OK|FAILED):' build/nox-coverage-output.log || true exit "$rc" env: NOX_DEFAULT_VENV_BACKEND: uv - name: Upload coverage log artifact if: always() && steps.gate.outputs.run == 'true' uses: actions/upload-artifact@v3 with: name: ci-logs-coverage path: build/nox-coverage-output.log retention-days: 30 - name: Upload coverage artifacts if: always() && steps.gate.outputs.run == 'true' uses: actions/upload-artifact@v3 with: name: coverage-reports path: | build/coverage.xml build/coverage.json build/htmlcov/ retention-days: 30 build: needs: load-versions runs-on: docker container: image: python:3.13-slim steps: - name: Install Node.js (required by actions/checkout) run: | apt-get update -qq && apt-get install -y -qq nodejs && rm -rf /var/lib/apt/lists/* - uses: actions/checkout@v4 - name: Install uv and nox run: | pip install -q uv==${{ needs.load-versions.outputs.uv-version }} nox - name: Cache uv packages uses: actions/cache@v3 with: path: ~/.cache/uv key: uv-${{ hashFiles('pyproject.toml') }} restore-keys: | uv- - name: Build wheel via nox run: | mkdir -p build if nox -s build > build/nox-build-output.log 2>&1; then echo "build: OK" else rc=$? echo "===== build FAILED (exit $rc) — full output follows; artifact ci-logs-build =====" cat build/nox-build-output.log exit "$rc" fi env: NOX_DEFAULT_VENV_BACKEND: uv - name: Upload build log artifact if: always() uses: actions/upload-artifact@v3 with: name: ci-logs-build path: build/nox-build-output.log retention-days: 30 docker: # continue-on-error: true allows the status-check gate to pass even # when the docker:dind runner is unavailable (infrastructure issue). # The docker job still runs; only infrastructure failures are tolerated. needs: [load-versions, lint, typecheck, security, quality, unit_tests] continue-on-error: true runs-on: docker container: image: docker:dind options: --privileged steps: - name: Start Docker daemon and install dependencies run: | # dockerd logs go to a file, not the live job log; they are # cat'd to the console only if a docker build below fails. dockerd > /tmp/dockerd.log 2>&1 & apk add --no-cache -q git nodejs for i in $(seq 1 30); do docker info >/dev/null 2>&1 && break || sleep 1; done - uses: actions/checkout@v4 - name: Build Docker image (CLI) run: | mkdir -p build if docker build -t cleverernie:test . >> build/docker-output.log 2>&1; then echo "docker build (CLI): OK" else rc=$? echo "===== docker build (CLI) FAILED (exit $rc) — full output follows; artifact ci-logs-docker =====" cat build/docker-output.log echo "===== dockerd log =====" cat /tmp/dockerd.log || true exit "$rc" fi - name: Test Docker image (CLI) run: | mkdir -p build if docker run --rm cleverernie:test --version >> build/docker-output.log 2>&1; then echo "docker run (CLI --version): OK" else rc=$? echo "===== docker run (CLI) FAILED (exit $rc) — full output follows; artifact ci-logs-docker =====" cat build/docker-output.log exit "$rc" fi - name: Build Docker image (Server) run: | mkdir -p build if docker build -f Dockerfile.server -t cleveragents-server:test . >> build/docker-output.log 2>&1; then echo "docker build (Server): OK" else rc=$? echo "===== docker build (Server) FAILED (exit $rc) — full output follows; artifact ci-logs-docker =====" cat build/docker-output.log echo "===== dockerd log =====" cat /tmp/dockerd.log || true exit "$rc" fi - name: Collect dockerd daemon log if: always() run: | mkdir -p build { echo "===== dockerd daemon log ====="; cat /tmp/dockerd.log 2>/dev/null || true; } >> build/docker-output.log - name: Upload docker log artifact if: always() uses: actions/upload-artifact@v3 with: name: ci-logs-docker path: build/docker-output.log retention-days: 30 helm: needs: load-versions runs-on: docker container: image: python:3.13-slim steps: - name: Install system dependencies (nodejs for checkout, curl for Helm and kubeconform) run: | apt-get update -qq && apt-get install -y -qq nodejs curl tar && rm -rf /var/lib/apt/lists/* - uses: actions/checkout@v4 - name: Cache Helm binary uses: actions/cache@v3 id: helm-cache with: path: /usr/local/bin/helm key: helm-${{ env.HELM_VERSION }}-linux-amd64 restore-keys: | helm- - name: Install Helm CLI if: steps.helm-cache.outputs.cache-hit != 'true' run: | HELM_VERSION="${{ needs.load-versions.outputs.helm-version }}" ARCH="amd64" HELM_TARBALL="helm-${HELM_VERSION}-linux-${ARCH}.tar.gz" curl -fsSL "https://get.helm.sh/${HELM_TARBALL}" -o "/tmp/${HELM_TARBALL}" curl -fsSL "https://get.helm.sh/${HELM_TARBALL}.sha256sum" -o /tmp/helm.sha256sum cd /tmp && sha256sum -c helm.sha256sum tar -xzf "/tmp/${HELM_TARBALL}" -C /tmp mv /tmp/linux-${ARCH}/helm /usr/local/bin/helm chmod +x /usr/local/bin/helm env: HELM_VERSION: ${{ env.HELM_VERSION }} - name: Install kubeconform run: | KUBECONFORM_VERSION="${{ needs.load-versions.outputs.kubeconform-version }}" ARCH="amd64" KUBECONFORM_TARBALL="kubeconform-linux-${ARCH}.tar.gz" curl -fsSL "https://github.com/yannh/kubeconform/releases/download/${KUBECONFORM_VERSION}/${KUBECONFORM_TARBALL}" \ -o "/tmp/${KUBECONFORM_TARBALL}" tar -xzf "/tmp/${KUBECONFORM_TARBALL}" -C /tmp mv /tmp/kubeconform /usr/local/bin/kubeconform chmod +x /usr/local/bin/kubeconform kubeconform -v - name: Build Helm chart dependencies run: | mkdir -p build if helm dependency build ./k8s >> build/helm-output.log 2>&1; then echo "helm dependency build: OK" else rc=$? echo "===== helm dependency build FAILED (exit $rc) — full output follows; artifact ci-logs-helm =====" cat build/helm-output.log exit "$rc" fi - name: Helm lint chart run: | mkdir -p build if helm lint ./k8s \ --set database.url="postgresql+asyncpg://user:pass@db-host:5432/cleveragents" >> build/helm-output.log 2>&1; then echo "helm lint: OK" else rc=$? echo "===== helm lint FAILED (exit $rc) — full output follows; artifact ci-logs-helm =====" cat build/helm-output.log exit "$rc" fi - name: Helm template smoke render run: | mkdir -p build if helm template cleveragents ./k8s \ --set database.url="postgresql+asyncpg://user:pass@db-host:5432/cleveragents" >/tmp/rendered.yaml 2>> build/helm-output.log \ && test -s /tmp/rendered.yaml; then echo "helm template: OK" else rc=$? echo "===== helm template FAILED (exit $rc) — full output follows; artifact ci-logs-helm =====" cat build/helm-output.log exit "$rc" fi - name: Validate rendered manifests with kubeconform run: | mkdir -p build if kubeconform \ -strict \ -ignore-missing-schemas \ -kubernetes-version 1.29.0 \ -summary \ /tmp/rendered.yaml >> build/helm-output.log 2>&1; then echo "kubeconform: OK" else rc=$? echo "===== kubeconform FAILED (exit $rc) — full output follows; artifact ci-logs-helm =====" cat build/helm-output.log exit "$rc" fi - name: Upload helm log artifact if: always() uses: actions/upload-artifact@v3 with: name: ci-logs-helm path: build/helm-output.log retention-days: 30 push-validation: # Validates that the CI runner can authenticate and push to the repository. # Root cause of the push failure: actions/checkout@v4 was not configured with # explicit push credentials (token + persist-credentials), and no git user # config (name/email) was set — both are required for any push operation. runs-on: docker container: image: python:3.13-slim steps: - name: Install system dependencies (nodejs for checkout, git for push validation) run: | apt-get update -qq && apt-get install -y -qq nodejs git curl && rm -rf /var/lib/apt/lists/* - name: Checkout with explicit write credentials uses: actions/checkout@v4 with: # Pass the Forgejo token explicitly so the credential helper is # configured for HTTPS push operations. Without this, the default # checkout may only have read access and push will fail with a # 403 or authentication error. token: ${{ secrets.FORGEJO_TOKEN }} persist-credentials: true - name: Configure git user for CI operations run: | # Required for any git commit or push operation in CI. # Uses a bot identity to distinguish CI-generated commits from # human commits. Without this, git push fails with: # "Author identity unknown — please tell me who you are." git config user.name "CleverAgents CI" git config user.email "ci-bot@cleverthis.com" - name: Verify HTTPS credential helper is configured run: | # Confirm that the credential helper set up by actions/checkout # is active. This ensures HTTPS push operations will authenticate # correctly without prompting for a password. mkdir -p build { echo "=== Git credential configuration ===" git config --list | grep -E "credential|url" || echo "WARNING: No credential helper found" echo "=== Remote URL ===" git remote get-url origin echo "=== Credential helper check ===" if git config credential.helper > /dev/null 2>&1; then echo "OK: Credential helper is configured: $(git config credential.helper)" else echo "WARNING: No credential helper configured — push may fail" fi } >> build/push-validation-output.log 2>&1 echo "credential-helper check: done" - name: Smoke-test push access via API # Validates write permission using the Forgejo API before attempting # any real push. This catches credential issues early with a clear # error message rather than a cryptic git error. env: FORGEJO_URL: ${{ secrets.FORGEJO_URL }} FORGEJO_TOKEN: ${{ secrets.FORGEJO_TOKEN }} run: | mkdir -p build rc=0 ( REPO="${{ forgejo.repository }}" API_URL="${FORGEJO_URL}/api/v1/repos/${REPO}" echo "=== Testing repository API access ===" HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" \ -H "Authorization: token ${FORGEJO_TOKEN}" \ "${API_URL}") if [ "${HTTP_STATUS}" != "200" ]; then echo "ERROR: FORGEJO_TOKEN cannot access repository API (HTTP ${HTTP_STATUS})." echo "Ensure FORGEJO_TOKEN is set in Repository Settings > Actions > Secrets" echo "and that the token has repository (write) scope." exit 1 fi PUSH_ALLOWED=$(curl -s \ -H "Authorization: token ${FORGEJO_TOKEN}" \ "${API_URL}" | python3 -c "import sys,json; d=json.load(sys.stdin); print(str(d.get('permissions',{}).get('push',False)).lower())") if [ "${PUSH_ALLOWED}" != "true" ]; then echo "ERROR: FORGEJO_TOKEN does not have push (write) permission." echo "Grant the token Contents: Write permission or use a token with full repository scope." exit 1 fi echo "OK: Push access verified -- FORGEJO_TOKEN has write permission on ${REPO}" echo "=== Push access smoke-test passed ===" ) >> build/push-validation-output.log 2>&1 || rc=$? if [ "$rc" -ne 0 ]; then echo "===== push-validation FAILED (exit $rc) — full output follows; artifact ci-logs-push-validation =====" cat build/push-validation-output.log exit "$rc" fi echo "push-validation: OK" - name: Upload push-validation log artifact if: always() uses: actions/upload-artifact@v3 with: name: ci-logs-push-validation path: build/push-validation-output.log retention-days: 30 status-check: if: always() needs: [load-versions, lint, typecheck, security, quality, unit_tests, integration_tests, coverage, build, docker, helm, push-validation] runs-on: docker container: image: python:3.13-slim steps: - name: Check required job results run: | echo "load-versions: ${{ needs.load-versions.result }}" echo "lint: ${{ needs.lint.result }}" echo "typecheck: ${{ needs.typecheck.result }}" echo "security: ${{ needs.security.result }}" echo "quality: ${{ needs.quality.result }}" echo "unit_tests: ${{ needs.unit_tests.result }}" echo "integration_tests: ${{ needs.integration_tests.result }}" echo "coverage: ${{ needs.coverage.result }}" echo "build: ${{ needs.build.result }}" echo "docker: ${{ needs.docker.result }}" echo "helm: ${{ needs.helm.result }}" echo "push-validation: ${{ needs.push-validation.result }}" if [ "${{ needs.load-versions.result }}" != "success" ] || \ [ "${{ needs.lint.result }}" != "success" ] || \ [ "${{ needs.typecheck.result }}" != "success" ] || \ [ "${{ needs.security.result }}" != "success" ] || \ [ "${{ needs.quality.result }}" != "success" ] || \ [ "${{ needs.unit_tests.result }}" != "success" ] || \ [ "${{ needs.integration_tests.result }}" != "success" ] || \ [ "${{ needs.coverage.result }}" != "success" ] || \ [ "${{ needs.build.result }}" != "success" ] || \ [ "${{ needs.docker.result }}" != "success" ] || \ [ "${{ needs.helm.result }}" != "success" ] || \ [ "${{ needs.push-validation.result }}" != "success" ]; then echo "FAILED: One or more required jobs did not succeed" exit 1 fi echo "All required CI checks passed"