name: CI

on:
    push:
        branches: [master, develop]
    pull_request:
        branches: [master, develop*]

env:
    UV_VERSION: "0.8.0"
    PYTHON_VERSION: "3.13"
    NOX_DEFAULT_VENV_BACKEND: "uv"
    HELM_VERSION: "v3.16.4"

# 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:
    lint:
        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==${{ env.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:
        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==${{ env.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:
        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==${{ env.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:
        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==${{ env.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:
        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: |
                  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==${{ env.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:
        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: |
                  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==${{ env.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:
        runs-on: docker
        container:
            image: python:3.13-slim
        needs: [lint, typecheck, security, quality, unit_tests]
        # 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==${{ env.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:
        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==${{ env.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: [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:
        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: |
                  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="v0.7.0"
                  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: [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 "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.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"
