name: Quality on: workflow_call: inputs: ref: description: Exact commit SHA to validate required: true type: string run_backend: description: Run backend quality checks required: false default: true type: boolean run_frontend: description: Run frontend quality checks required: false default: true type: boolean permissions: contents: read jobs: backend: if: inputs.run_backend runs-on: ubuntu-latest timeout-minutes: 30 env: CARGO_TERM_COLOR: always # 编译 AWS SDK 调试信息需要更大的 codegen 线程栈 RUST_MIN_STACK: 16777216 CPR_TEST_DATABASE_URL: postgres://codex_proxy:codex_proxy@127.0.0.1:5432/codex_proxy CPR_TEST_REDIS_URL: redis://127.0.0.1:6379 services: postgres: image: postgres:18-bookworm@sha256:d9c83446333daec3f0588cc709adb80c26090b7f9f0f7ec8d43c243385d79818 env: POSTGRES_DB: codex_proxy POSTGRES_USER: codex_proxy POSTGRES_PASSWORD: codex_proxy POSTGRES_INITDB_ARGS: --auth-local=scram-sha-256 --auth-host=scram-sha-256 ports: - 5432:5432 options: >- --health-cmd "pg_isready -U codex_proxy -d codex_proxy" --health-interval 5s --health-timeout 5s --health-retries 10 redis: image: redis:8-alpine@sha256:9d317178eceac8454a2284a9e6df2466b93c745529947f0cd42a0fa9609d7005 ports: - 6379:6379 options: >- --health-cmd "redis-cli ping" --health-interval 5s --health-timeout 5s --health-retries 10 steps: - name: Checkout uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: ref: ${{ inputs.ref }} # PR 场景需要 base 提交做冻结清单的 append-only 比对。 fetch-depth: ${{ github.event_name == 'pull_request' && 0 || 1 }} # 已应用迁移的字节被 sqlx 启动校验钉死:改动已发布迁移会让所有 # 存量部署重启即失败。冻结清单保证旧文件不被改、新文件必须入册。 - name: Migration freeze check working-directory: backend/migrations shell: bash env: BASE_SHA: ${{ github.event.pull_request.base.sha }} run: | set -euo pipefail sha256sum --check --strict .frozen-sha256 for file in *.sql; do if ! awk '{print $2}' .frozen-sha256 | grep -Fxq "$file"; then echo "::error file=backend/migrations/$file::migration $file is not frozen; append its sha256 to backend/migrations/.frozen-sha256" exit 1 fi done # 清单自证不足以防「改迁移 + 同 PR 刷新清单行」:校验对新哈希通过。 # 对 PR,断言 .frozen-sha256 相对 base 只增不改——base 里的每一行 # 必须原样保留(改哈希=删行,删迁移=删行,都会被挡)。 if [ -n "${BASE_SHA:-}" ]; then base_manifest="$(git show "${BASE_SHA}:backend/migrations/.frozen-sha256" 2>/dev/null || true)" if [ -n "$base_manifest" ]; then while IFS= read -r line; do [ -z "$line" ] && continue if ! grep -Fxq "$line" .frozen-sha256; then file="$(printf '%s' "$line" | awk '{print $2}')" echo "::error file=backend/migrations/.frozen-sha256::frozen migration $file was modified or removed; released migrations are append-only — add a new numbered migration instead" exit 1 fi done <<< "$base_manifest" fi fi - name: Read build toolchains id: toolchains run: ruby .github/scripts/toolchains.rb >> "$GITHUB_OUTPUT" - name: Setup Rust uses: dtolnay/rust-toolchain@683b588c487a69d01f6c6ddf40f036ff682cc2c8 # master with: toolchain: ${{ steps.toolchains.outputs.rust }} components: rustfmt,clippy - name: Cache Rust uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2 with: workspaces: backend -> target save-if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }} - name: Rust format working-directory: backend run: cargo fmt --check - name: Rust clippy working-directory: backend run: cargo clippy --all-targets --all-features --locked -- -D warnings - name: Setup nextest uses: taiki-e/install-action@8cca0918f8471c33515202b73f56ab27f2bed283 # v2.87.29 with: tool: cargo-nextest@0.9.143 fallback: none - name: Build Rust tests once working-directory: backend run: | cargo metadata --format-version 1 --locked > "$RUNNER_TEMP/nextest-cargo-metadata.json" cargo nextest list --test main --locked --list-type binaries-only --message-format json \ > "$RUNNER_TEMP/nextest-binaries.json" - name: Configure PostgreSQL and Redis test authentication shell: bash run: | set -euo pipefail postgres_password="$(openssl rand -hex 24)" redis_password="$(openssl rand -hex 24)" echo "::add-mask::$postgres_password" echo "::add-mask::$redis_password" docker exec --env PGPASSWORD=codex_proxy "${{ job.services.postgres.id }}" psql \ --username=codex_proxy --dbname=codex_proxy \ --set=ON_ERROR_STOP=1 \ --command="ALTER USER codex_proxy WITH PASSWORD '${postgres_password}'" >/dev/null docker exec --env PGPASSWORD="$postgres_password" "${{ job.services.postgres.id }}" psql \ --username=codex_proxy --dbname=codex_proxy \ --set=ON_ERROR_STOP=1 --command='SELECT 1' >/dev/null docker exec "${{ job.services.redis.id }}" redis-cli CONFIG SET requirepass "$redis_password" >/dev/null docker exec "${{ job.services.redis.id }}" redis-cli --no-auth-warning -a "$redis_password" ping >/dev/null { echo "CPR_TEST_DATABASE_URL=postgres://codex_proxy:${postgres_password}@127.0.0.1:5432/codex_proxy" echo "CPR_TEST_REDIS_URL=redis://:${redis_password}@127.0.0.1:6379" } >> "$GITHUB_ENV" - name: Expose matching PostgreSQL backup and restore tools shell: bash env: CPR_TEST_POSTGRES_CONTAINER: ${{ job.services.postgres.id }} run: | set -euo pipefail client_dir="$RUNNER_TEMP/pg-client" mkdir -p "$client_dir" for tool in pg_dump psql; do printf '#!/bin/sh\nexec docker exec -i --env PGPASSWORD "%s" %s "$@"\n' "$CPR_TEST_POSTGRES_CONTAINER" "$tool" > "$client_dir/$tool" chmod +x "$client_dir/$tool" done echo "$client_dir" >> "$GITHUB_PATH" - name: Rust tests working-directory: backend run: | cargo nextest run \ --cargo-metadata "$RUNNER_TEMP/nextest-cargo-metadata.json" \ --binaries-metadata "$RUNNER_TEMP/nextest-binaries.json" \ --no-tests fail - name: Store tests with Redis ACL authentication working-directory: backend shell: bash run: | set -euo pipefail redis_acl_password="$(openssl rand -hex 24)" echo "::add-mask::$redis_acl_password" docker exec "${{ job.services.redis.id }}" redis-cli -e --no-auth-warning \ -u "$CPR_TEST_REDIS_URL" --user default ACL SETUSER cpr_acl reset on \ ">$redis_acl_password" '~*' '&*' '+@all' >/dev/null docker exec --env REDISCLI_AUTH="$redis_acl_password" "${{ job.services.redis.id }}" \ redis-cli -e --user cpr_acl ACL SETUSER default off >/dev/null export CPR_TEST_REDIS_URL="redis://cpr_acl:${redis_acl_password}@127.0.0.1:6379" # 从同一份构建清单选择 Redis 测试,保留 workspace 特性图且不重复运行 PostgreSQL cargo nextest run \ --cargo-metadata "$RUNNER_TEMP/nextest-cargo-metadata.json" \ --binaries-metadata "$RUNNER_TEMP/nextest-binaries.json" \ --no-tests fail -E 'package(=gateway-store) & test(/^redis::/)' frontend: if: inputs.run_frontend runs-on: ubuntu-latest timeout-minutes: 15 env: CI: 'true' steps: - name: Checkout uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: ref: ${{ inputs.ref }} - name: Validate and build frontend uses: ./.github/actions/build-frontend workflow-lint: runs-on: ubuntu-latest timeout-minutes: 5 steps: - name: Checkout uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: ref: ${{ inputs.ref }} # GitHub 已支持 queue: max,actionlint 尚未支持该字段 # https://github.com/rhysd/actionlint/issues/657 - name: Lint GitHub Actions workflows run: >- docker run --rm --volume "$PWD:/repo" --workdir /repo rhysd/actionlint:1.7.7@sha256:887a259a5a534f3c4f36cb02dca341673c6089431057242cdc931e9f133147e9 -color -ignore '^unexpected key "queue" for "concurrency" section\. expected one of "cancel-in-progress", "group"$' .github/workflows/*.yml - name: Check workflow script syntax run: | bash -n release/publish shopt -s globstar nullglob for script in .github/**/*.sh; do bash -n "$script"; done for script in .github/scripts/*.rb; do ruby -c "$script"; done ruby .github/scripts/toolchains.rb ruby -c backend/crates/gateway-plugin/runtime/plugin-api-lifecycle.rb