prodex 0.5.0

OpenAI profile pooling and safe auto-rotate for Codex CLI and Claude Code
Documentation
name: CI

on:
  push:
    branches:
      - main
  pull_request:

permissions:
  contents: read

concurrency:
  group: ci-${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

jobs:
  fmt:
    runs-on: ubuntu-latest
    timeout-minutes: 30

    steps:
      - name: Check out repository
        uses: actions/checkout@v6

      - name: Install Rust toolchain
        uses: dtolnay/rust-toolchain@stable
        with:
          components: rustfmt

      - name: Cache Rust dependencies
        uses: Swatinem/rust-cache@v2

      - name: Check formatting
        run: cargo fmt --check

  secret-scan:
    runs-on: ubuntu-latest
    timeout-minutes: 30

    steps:
      - name: Check out repository
        uses: actions/checkout@v6

      - name: Scan repository for leaked credentials
        shell: bash
        run: |
          set -euo pipefail
          docker run --rm \
            -v "${PWD}:/repo" \
            ghcr.io/gitleaks/gitleaks:v8.30.1 \
            detect --source /repo --no-git --redact --no-banner --config /repo/.gitleaks.toml

  supply-chain:
    runs-on: ubuntu-latest
    timeout-minutes: 30

    steps:
      - name: Check out repository
        uses: actions/checkout@v6

      - name: Install Rust toolchain
        uses: dtolnay/rust-toolchain@stable
        with:
          components: clippy

      - name: Cache Rust dependencies
        uses: Swatinem/rust-cache@v2

      - name: Check locked compile graph
        run: cargo check --locked --all-targets --all-features

      # Keep the requested -D warnings gate visible without blocking the rest of the
      # supply-chain checks until the existing source warning backlog is cleared.
      - name: Run clippy warning gate
        continue-on-error: true
        run: cargo clippy --locked --all-targets --all-features -- -D warnings

      - name: Install cargo-audit
        run: cargo install cargo-audit --locked --version 0.22.1

      - name: Run cargo audit
        run: cargo audit

      - name: Install cargo-deny
        run: cargo install cargo-deny --locked --version 0.19.0

      - name: Run cargo deny checks
        run: cargo deny check advisories sources

  auto-rotate:
    runs-on: ubuntu-latest
    timeout-minutes: 30

    steps:
      - name: Check out repository
        uses: actions/checkout@v6

      - name: Install Rust toolchain
        uses: dtolnay/rust-toolchain@stable

      - name: Cache Rust dependencies
        uses: Swatinem/rust-cache@v2

      - name: Run auto-rotate integration tests
        run: cargo test --test auto_rotate

  profile-commands-internal:
    runs-on: ubuntu-latest
    timeout-minutes: 30

    steps:
      - name: Check out repository
        uses: actions/checkout@v6

      - name: Install Rust toolchain
        uses: dtolnay/rust-toolchain@stable

      - name: Cache Rust dependencies
        uses: Swatinem/rust-cache@v2

      - name: Run profile command internal tests
        run: |
          cargo test --lib profile_commands_internal_tests:: -- --test-threads=1

  main-internal-core:
    runs-on: ubuntu-latest
    timeout-minutes: 30

    steps:
      - name: Check out repository
        uses: actions/checkout@v6

      - name: Install Rust toolchain
        uses: dtolnay/rust-toolchain@stable

      - name: Cache Rust dependencies
        uses: Swatinem/rust-cache@v2

      - name: Run main internal tests without runtime proxy shard
        run: |
          set -euo pipefail
          cargo test --lib main_internal_tests:: -- --skip runtime_proxy_ --test-threads=1

  main-internal-runtime-proxy:
    runs-on: ubuntu-latest
    timeout-minutes: 30

    steps:
      - name: Check out repository
        uses: actions/checkout@v6

      - name: Install Rust toolchain
        uses: dtolnay/rust-toolchain@stable

      - name: Cache Rust dependencies
        uses: Swatinem/rust-cache@v2

      - name: Run runtime proxy internal test shard
        run: |
          set -euo pipefail
          for attempt in 1 2; do
            echo "runtime proxy shard attempt ${attempt}"
            if cargo test --lib main_internal_tests::runtime_proxy_ -- --test-threads=1; then
              exit 0
            fi
            if [ "${attempt}" -eq 2 ]; then
              exit 1
            fi
            echo "retrying runtime proxy shard after a transient failure"
            sleep 5
          done

  runtime-stress:
    runs-on: ubuntu-latest
    timeout-minutes: 30

    steps:
      - name: Check out repository
        uses: actions/checkout@v6

      - name: Install Rust toolchain
        uses: dtolnay/rust-toolchain@stable

      - name: Cache Rust dependencies
        uses: Swatinem/rust-cache@v2

      - name: Run parallel-safe runtime proxy stress tests
        run: |
          set -euo pipefail
          # The main test job already covers runtime_proxy_ once. Keep one extra focused
          # stress pass here, but leave the slow continuation-heavy cases to the dedicated
          # rerun step below so they are not paid twice in this loop as well. Keep the
          # process-global and serial-sensitive quota/compact followup cases out of the
          # shared process so the rest of runtime_proxy_ can use libtest's default
          # parallelism.
          for iteration in 1; do
            echo "runtime_proxy_ stress iteration ${iteration}"
            cargo test --lib main_internal_tests::runtime_proxy_ -- \
              --skip runtime_proxy_persists_previous_response_affinity_across_restart \
              --skip runtime_proxy_persists_session_affinity_across_restart_for_compact \
              --skip runtime_proxy_claude_launch_env_uses_foundry_compat_with_profile_config_dir \
              --skip runtime_proxy_claude_launch_env_honors_model_override \
              --skip runtime_proxy_claude_launch_env_keeps_custom_picker_entry_for_unknown_override \
              --skip runtime_proxy_claude_launch_env_uses_codex_config_model_by_default \
              --skip runtime_proxy_claude_launch_env_maps_alias_backed_override_to_builtin_picker_value \
              --skip runtime_proxy_claude_target_model_maps_builtin_aliases_to_pinned_gpt_models \
              --skip runtime_proxy_claude_reasoning_effort_override_normalizes_env \
              --skip runtime_proxy_claude_reasoning_effort_override_ignores_invalid_env \
              --skip runtime_proxy_active_request_wait_recovers_after_short_burst \
              --skip runtime_proxy_anthropic_admission_wait_recovers_after_short_burst \
              --skip runtime_proxy_retries_after_websocket_reuse_silent_hang \
              --skip runtime_proxy_retries_after_websocket_reuse_precommit_hold_timeout \
              --skip runtime_proxy_does_not_rotate_after_multi_chunk_sse_stall \
              --skip runtime_proxy_sheds_long_lived_queue_overload_fast \
              --skip runtime_proxy_absorbs_brief_anthropic_queue_burst \
              --skip runtime_proxy_absorbs_brief_long_lived_queue_burst \
              --skip runtime_proxy_does_not_rotate_after_first_sse_chunk_reset \
              --skip runtime_proxy_retries_same_compact_owner_after_websocket_reuse_watchdog \
              --skip runtime_proxy_bound_previous_response_without_turn_state_fails_as_transport_after_websocket_reuse_watchdog \
              --skip runtime_proxy_stale_websocket_previous_response_reuse_fails_as_transport \
              --skip runtime_proxy_logs_local_writer_disconnect_after_first_chunk \
              --skip runtime_proxy_websocket_surfaces_mid_turn_close_without_post_commit_rotate \
              --skip runtime_proxy_passes_through_unauthorized_response_and_quarantines_profile_for_next_fresh_request \
              --skip runtime_proxy_broker_health_endpoint_reports_registered_metadata \
              --skip runtime_proxy_uses_current_profile_without_extra_runtime_quota_probe \
              --skip runtime_proxy_reuses_rotated_profile_without_reprobing_quota \
              --skip runtime_proxy_waits_for_anthropic_inflight_relief_then_succeeds \
              --skip runtime_proxy_waits_for_responses_inflight_relief_then_succeeds \
              --skip runtime_proxy_responses_inflight_relief_times_out_without_relief \
              --skip runtime_proxy_wait_scopes_to_session_owner_relief \
              --skip runtime_proxy_keeps_previous_response_affinity_for_http_requests \
              --skip runtime_proxy_keeps_previous_response_affinity_for_websocket_requests \
              --skip runtime_proxy_restores_compact_followup_owner_across_restart \
              --skip runtime_proxy_retries_overloaded_compact_on_another_profile \
              --skip runtime_proxy_retries_quota_blocked_response_on_another_profile \
              --skip runtime_proxy_reuses_compact_owner_for_followup_until_response_commits \
              --skip runtime_proxy_retries_usage_limited_response_on_another_profile \
              --skip runtime_proxy_websocket_releases_quota_blocked_previous_response_affinity_before_fresh_fallback \
              --skip runtime_proxy_websocket_reuse_rotates_on_delayed_quota_before_commit \
              --skip runtime_proxy_websocket_rotates_on_upstream_websocket_quota_error
          done

      - name: Run serialized runtime-sensitive stress tests
        run: |
          set -euo pipefail
          for test_name in \
            runtime_proxy_claude_launch_env_uses_foundry_compat_with_profile_config_dir \
            runtime_proxy_claude_launch_env_honors_model_override \
            runtime_proxy_claude_launch_env_keeps_custom_picker_entry_for_unknown_override \
            runtime_proxy_claude_launch_env_uses_codex_config_model_by_default \
            runtime_proxy_claude_launch_env_maps_alias_backed_override_to_builtin_picker_value \
            runtime_proxy_claude_target_model_maps_builtin_aliases_to_pinned_gpt_models \
            runtime_proxy_claude_reasoning_effort_override_normalizes_env \
            runtime_proxy_claude_reasoning_effort_override_ignores_invalid_env \
            runtime_proxy_active_request_wait_recovers_after_short_burst \
            runtime_proxy_anthropic_admission_wait_recovers_after_short_burst \
            runtime_proxy_sheds_long_lived_queue_overload_fast \
            runtime_proxy_retries_after_websocket_reuse_precommit_hold_timeout \
            runtime_proxy_absorbs_brief_anthropic_queue_burst \
            runtime_proxy_absorbs_brief_long_lived_queue_burst \
            runtime_proxy_does_not_rotate_after_first_sse_chunk_reset \
            runtime_proxy_retries_same_compact_owner_after_websocket_reuse_watchdog \
            runtime_proxy_bound_previous_response_without_turn_state_fails_as_transport_after_websocket_reuse_watchdog \
            runtime_proxy_stale_websocket_previous_response_reuse_fails_as_transport \
            runtime_proxy_logs_local_writer_disconnect_after_first_chunk \
            runtime_proxy_websocket_surfaces_mid_turn_close_without_post_commit_rotate \
            runtime_proxy_passes_through_unauthorized_response_and_quarantines_profile_for_next_fresh_request \
            runtime_proxy_broker_health_endpoint_reports_registered_metadata \
            runtime_proxy_uses_current_profile_without_extra_runtime_quota_probe \
            runtime_proxy_reuses_rotated_profile_without_reprobing_quota \
            runtime_proxy_waits_for_anthropic_inflight_relief_then_succeeds \
            runtime_proxy_waits_for_responses_inflight_relief_then_succeeds \
            runtime_proxy_responses_inflight_relief_times_out_without_relief \
            runtime_proxy_wait_scopes_to_session_owner_relief \
            runtime_proxy_keeps_previous_response_affinity_for_http_requests \
            runtime_proxy_keeps_previous_response_affinity_for_websocket_requests \
            runtime_proxy_restores_compact_followup_owner_across_restart \
            runtime_proxy_retries_overloaded_compact_on_another_profile \
            runtime_proxy_retries_quota_blocked_response_on_another_profile \
            runtime_proxy_reuses_compact_owner_for_followup_until_response_commits \
            runtime_proxy_retries_usage_limited_response_on_another_profile \
            runtime_proxy_websocket_releases_quota_blocked_previous_response_affinity_before_fresh_fallback \
            runtime_proxy_websocket_reuse_rotates_on_delayed_quota_before_commit \
            runtime_proxy_websocket_rotates_on_upstream_websocket_quota_error
          do
            cargo test --lib "main_internal_tests::${test_name}" -- --test-threads=1
          done

      - name: Rerun continuation-heavy tests
        run: |
          set -euo pipefail
          for iteration in 1 2; do
            echo "continuation-heavy iteration ${iteration}"
            cargo test --lib main_internal_tests::runtime_proxy_persists_previous_response_affinity_across_restart -- --test-threads=1
            cargo test --lib main_internal_tests::runtime_proxy_persists_session_affinity_across_restart_for_compact -- --test-threads=1
            cargo test --lib main_internal_tests::runtime_proxy_retries_after_websocket_reuse_silent_hang -- --test-threads=1
            cargo test --lib main_internal_tests::runtime_proxy_does_not_rotate_after_multi_chunk_sse_stall -- --test-threads=1
          done