Harden agent file exports and owner reports #5898
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: CI | |
| on: | |
| pull_request: | |
| push: | |
| branches: [main] | |
| # Auto-cancel previous runs on the same PR/branch | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} | |
| cancel-in-progress: true | |
| # Needed for posting test summaries/annotations | |
| permissions: | |
| contents: read | |
| checks: write | |
| pull-requests: write | |
| jobs: | |
| tag-guard: | |
| name: Tag Guard | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| - name: Set up Python | |
| uses: actions/setup-python@v6 | |
| with: | |
| python-version: "3.12" | |
| - name: Verify all tests are tagged and tags are in CI | |
| run: | | |
| python3 scripts/check_test_tags.py | |
| complexity-guardrails: | |
| name: Complexity Guardrails | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| env: | |
| UV_PYTHON_DOWNLOADS: "never" | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| - name: Check source LoC budget | |
| run: | | |
| python3 scripts/check_complexity_budgets.py --loc-only | |
| - name: Set up Python | |
| uses: actions/setup-python@v6 | |
| with: | |
| python-version: "3.12" | |
| - name: Set up uv (with cache) | |
| uses: astral-sh/setup-uv@v7 | |
| with: | |
| enable-cache: true | |
| ignore-nothing-to-cache: true | |
| - name: Sync deps (locked) | |
| if: ${{ hashFiles('uv.lock') != '' }} | |
| run: | | |
| uv sync --frozen --all-extras --dev | |
| - name: Sync deps | |
| if: ${{ hashFiles('uv.lock') == '' }} | |
| run: | | |
| uv sync --all-extras --dev | |
| - name: Check prompt-size budgets | |
| run: | | |
| uv run python scripts/check_complexity_budgets.py --prompt-only | |
| frontend-tests: | |
| name: Frontend Tests | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| needs: [tag-guard, complexity-guardrails] | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| - name: Set up Node | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: "22" | |
| cache: npm | |
| cache-dependency-path: frontend/package-lock.json | |
| - name: Install frontend dependencies | |
| working-directory: frontend | |
| run: npm ci | |
| - name: Run frontend tests | |
| working-directory: frontend | |
| run: npm test | |
| sandbox-server-tests: | |
| name: Sandbox Server Tests | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| needs: [tag-guard, complexity-guardrails] | |
| env: | |
| TEST_OUTPUT_DIR: test-results | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| - name: Set up Python | |
| uses: actions/setup-python@v6 | |
| with: | |
| python-version: "3.13" | |
| - name: Set up uv (with cache) | |
| uses: astral-sh/setup-uv@v7 | |
| with: | |
| enable-cache: true | |
| ignore-nothing-to-cache: true | |
| - name: Create sandbox venv | |
| run: uv venv .venv-sandbox | |
| - name: Install sandbox test dependencies | |
| run: | | |
| uv pip install --python .venv-sandbox/bin/python -q -e ./sandbox_server unittest-xml-reporting | |
| - name: Run sandbox server tests | |
| run: | | |
| set -euo pipefail | |
| mkdir -p "$TEST_OUTPUT_DIR" | |
| .venv-sandbox/bin/python -m xmlrunner discover \ | |
| -s sandbox_server/tests \ | |
| -p 'test_*.py' \ | |
| -o "$TEST_OUTPUT_DIR" | |
| - name: Upload test results artifact | |
| if: always() | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: test-results-sandbox-server | |
| path: test-results/** | |
| if-no-files-found: warn | |
| retention-days: 7 | |
| tests: | |
| name: tests (${{ matrix.batch }}) | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 25 | |
| needs: [tag-guard, complexity-guardrails] | |
| strategy: | |
| fail-fast: false | |
| max-parallel: 10 | |
| matrix: | |
| include: | |
| - batch: shard_01 | |
| tag: batch_pages batch_sms batch_console_email_oauth batch_token_usage batch_event_parallel batch_allowlist_direction batch_tool_costs batch_output_schema batch_soft_expiration batch_console_api_keys batch_console_allowlist batch_pa_shutdown_triggers complexity_guardrails_batch | |
| - batch: shard_02 | |
| tag: batch_mcp_tools batch_human_input batch_eval_fingerprint batch_user_flags batch_api_agents batch_api_tasks pipedream_jit_connect batch_browser_task_db batch_agent_transfer batch_periodic batch_mcp_admin mcp_org_assignment_batch | |
| - batch: shard_03 | |
| tag: batch_pages_signals batch_console_agents batch_organizations referral_batch batch_subscription batch_email_body batch_support_turnstile batch_billing_rollup batch_agent_collaborators batch_api_org_keys batch_spawn_depth batch_compaction batch_browser_agent_max_tokens | |
| - batch: shard_04 | |
| tag: batch_agent_chat batch_console_api batch_agent_webhooks batch_usage_api batch_forward_detection pipedream_connect http_request_batch batch_system_settings batch_schedule batch_plan_versioning smtp batch_browser_config batch_org_billing | |
| - batch: shard_05 | |
| tag: batch_event_processing batch_result_analysis batch_promptree llm_routing_profiles_batch global_secrets_batch batch_agent_invite batch_api_tasks_reads batch_secrets batch_celery_redbeat batch_setup_cookies batch_redis_budget_cleanup batch_dedicated_proxy_models batch_charter_tools | |
| - batch: shard_06 | |
| tag: batch_event_processing_credits batch_marketing_events batch_email batch_video_generation batch_proxy_selection batch_public_templates batch_email_blocklist batch_global_skill_evals batch_xvfb batch_websocket batch_stripe_config batch_peer_intro | |
| - batch: shard_07 | |
| tag: batch_sqlite batch_agent_lifecycle batch_console_mcp_servers batch_agent_short_description batch_api_tasks_mutations batch_email_verification batch_peer_dm agent_credit_soft_target_batch batch_console_mcp_oauth batch_native_integrations batch_agent_tags eval_sim batch_recruitment_sourcing batch_image_generation_skill | |
| - batch: shard_08 | |
| tag: batch_agent_tools sqlite_analysis batch_event_llm batch_agent_secrets_ctx batch_task_credits batch_allowlist_rules batch_api_decodo batch_llm_intelligence batch_llm_admin_cleanup batch_dedicated_proxy_service batch_pa_step_credits oss_readiness_batch batch_api_serializer batch_fbp_middleware | |
| - batch: shard_09 | |
| tag: context_hints_batch batch_text_sanitization batch_billing batch_outbound_delivery batch_console_context batch_agent_limits batch_email_sender_db batch_secrets_profiles batch_contact_requests batch_outbound_dedupe batch_email_allowlist batch_redis_leaks | |
| - batch: shard_10 | |
| tag: batch_agent_filesystem batch_api_persistent_agents batch_console_agents_management batch_tool_results batch_owner_billing batch_attachment_guidance batch_link_shortener batch_step_compaction batch_browser_profile batch_email_footer batch_outbound_email batch_web_task_followup | |
| env: | |
| # Ensure Django loads test settings; manage.py still imports config.settings first | |
| DJANGO_SETTINGS_MODULE: config.test_settings | |
| PYTHONUNBUFFERED: "1" | |
| TEST_OUTPUT_DIR: test-results | |
| UV_PYTHON_DOWNLOADS: "never" # use setup-python managed interpreter | |
| # Satisfy config.settings import before test settings override DB to SQLite | |
| DJANGO_SECRET_KEY: test-secret | |
| GOBII_ENCRYPTION_KEY: dummy-encryption-key-for-testing | |
| POSTGRES_DB: test | |
| POSTGRES_USER: test | |
| POSTGRES_PASSWORD: test | |
| POSTGRES_HOST: localhost | |
| POSTGRES_PORT: "5432" | |
| REDIS_URL: redis://localhost:6379/0 | |
| # Satisfy LLM fail-fast check; any one provider key is enough | |
| OPENAI_API_KEY: dummy | |
| # Disable Celery worker self-shutdown logic in tests to avoid interfering with CI runner | |
| BROWSER_USE_TASK_MAX_COUNT: "0" | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| - name: Set up Python | |
| uses: actions/setup-python@v6 | |
| with: | |
| python-version: "3.12" | |
| - name: Set up uv (with cache) | |
| uses: astral-sh/setup-uv@v7 | |
| with: | |
| enable-cache: true | |
| ignore-nothing-to-cache: true | |
| - name: Restore venv cache | |
| id: restore-venv | |
| uses: actions/cache/restore@v5 | |
| with: | |
| path: .venv | |
| key: venv-${{ runner.os }}-py3.12-${{ hashFiles('uv.lock') }} | |
| restore-keys: | | |
| venv-${{ runner.os }}-py3.12- | |
| # Install dependencies into a project venv using uv, leveraging cache. | |
| # Prefer lockfile if present; otherwise resolve from pyproject. | |
| - name: Sync deps (locked) | |
| if: ${{ hashFiles('uv.lock') != '' }} | |
| run: | | |
| uv sync --frozen --all-extras --dev | |
| - name: Sync deps | |
| if: ${{ hashFiles('uv.lock') == '' }} | |
| run: | | |
| uv sync --all-extras --dev | |
| - name: Add JUnit test runner to venv | |
| run: | | |
| uv pip install -q unittest-xml-reporting | |
| - name: Save venv cache | |
| if: steps.restore-venv.outputs.cache-hit != 'true' | |
| uses: actions/cache/save@v5 | |
| with: | |
| path: .venv | |
| key: venv-${{ runner.os }}-py3.12-${{ hashFiles('uv.lock') }} | |
| - name: Run batch tests (${{ matrix.batch }}) | |
| run: | | |
| set -euo pipefail | |
| BATCH_OUTPUT_DIR="$TEST_OUTPUT_DIR/${{ matrix.batch }}" | |
| mkdir -p "$BATCH_OUTPUT_DIR" | |
| TAGS="${{ matrix.tag }}" | |
| TAG_ARGS=() | |
| for TAG in $TAGS; do | |
| TAG_ARGS+=(--tag "$TAG") | |
| done | |
| echo "Running tests with tags: $TAGS" | |
| uv run python manage.py test "${TAG_ARGS[@]}" \ | |
| --settings=config.test_settings \ | |
| --parallel 1 \ | |
| --testrunner=xmlrunner.extra.djangotestrunner.XMLTestRunner | |
| # xmlrunner uses suite name + second-level timestamp for filenames, so | |
| # batch-specific subdirs avoid collisions when multiple matrix jobs | |
| # emit the same TEST-*.xml basename. | |
| shopt -s nullglob || true | |
| for f in TEST-*.xml; do mv "$f" "$BATCH_OUTPUT_DIR/"; done || true | |
| - name: Upload test results artifact | |
| if: always() | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: test-results-${{ matrix.batch }} | |
| path: test-results/** | |
| if-no-files-found: warn | |
| retention-days: 7 | |
| # No per-batch PR checks to reduce noise; artifacts are uploaded above. | |
| report: | |
| name: Combined Test Results | |
| runs-on: ubuntu-latest | |
| if: ${{ always() }} | |
| needs: [tests, sandbox-server-tests] | |
| steps: | |
| - name: Download all test result artifacts | |
| uses: actions/download-artifact@v8 | |
| with: | |
| pattern: test-results-* | |
| merge-multiple: true | |
| path: all-results | |
| - name: Publish combined test results | |
| uses: EnricoMi/publish-unit-test-result-action@v2 | |
| with: | |
| files: all-results/**/*.xml | |
| check_name: "Combined Test Results" | |
| comment_mode: off | |
| publish-static-assets: | |
| name: Publish Static Assets | |
| runs-on: ubuntu-latest | |
| # Test jobs report independently; staging deploy only needs deploy inputs. | |
| needs: [tag-guard, complexity-guardrails] | |
| if: github.event_name == 'push' && github.ref == 'refs/heads/main' | |
| permissions: | |
| contents: read | |
| env: | |
| STATIC_BUCKET: gobii-platform-static | |
| STATIC_ASSET_BASE_URL: https://static.gobii.ai/frontend/releases | |
| VITE_BUILD_BASE_URL: https://static.gobii.ai/frontend/releases/${{ github.sha }}/ | |
| AWS_ACCESS_KEY_ID: ${{ secrets.CLOUDFLARE_R2_ACCESS_KEY_ID }} | |
| AWS_SECRET_ACCESS_KEY: ${{ secrets.CLOUDFLARE_R2_SECRET_ACCESS_KEY }} | |
| AWS_DEFAULT_REGION: auto | |
| R2_ENDPOINT: https://${{ secrets.CLOUDFLARE_ACCOUNT_ID }}.r2.cloudflarestorage.com | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| - name: Set up Node | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: "22" | |
| cache: npm | |
| cache-dependency-path: frontend/package-lock.json | |
| - name: Install AWS CLI | |
| run: | | |
| python3 -m pip install --user awscli | |
| - name: Build frontend assets | |
| run: | | |
| set -euo pipefail | |
| export PATH="$HOME/.local/bin:$PATH" | |
| npm ci --prefix frontend | |
| npm run build --prefix frontend | |
| test -f static/frontend/manifest.json | |
| python3 scripts/vite_release_manifest.py \ | |
| --manifest static/frontend/manifest.json \ | |
| --root static/frontend \ | |
| --check-local > vite_release_paths.txt | |
| - name: Upload immutable release assets | |
| env: | |
| RELEASE_PREFIX: s3://${{ env.STATIC_BUCKET }}/frontend/releases/${{ github.sha }} | |
| run: | | |
| set -euo pipefail | |
| export PATH="$HOME/.local/bin:$PATH" | |
| aws s3 sync static/frontend/assets "${RELEASE_PREFIX}/assets" \ | |
| --endpoint-url "${R2_ENDPOINT}" \ | |
| --cache-control "public, max-age=31536000, immutable" | |
| aws s3 cp static/frontend/manifest.json "${RELEASE_PREFIX}/manifest.json" \ | |
| --endpoint-url "${R2_ENDPOINT}" \ | |
| --cache-control "no-cache, max-age=0, must-revalidate" \ | |
| --content-type "application/json" | |
| - name: Verify uploaded release assets | |
| env: | |
| RELEASE_PREFIX: frontend/releases/${{ github.sha }} | |
| run: | | |
| set -euo pipefail | |
| export PATH="$HOME/.local/bin:$PATH" | |
| aws s3api head-object \ | |
| --bucket "${STATIC_BUCKET}" \ | |
| --key "${RELEASE_PREFIX}/manifest.json" \ | |
| --endpoint-url "${R2_ENDPOINT}" >/dev/null | |
| while IFS= read -r relative_path; do | |
| aws s3api head-object \ | |
| --bucket "${STATIC_BUCKET}" \ | |
| --key "${RELEASE_PREFIX}/${relative_path}" \ | |
| --endpoint-url "${R2_ENDPOINT}" >/dev/null | |
| done < vite_release_paths.txt | |
| - name: Verify public asset URL | |
| run: | | |
| set -euo pipefail | |
| curl -fsSI "${STATIC_ASSET_BASE_URL}/${GITHUB_SHA}/manifest.json" >/dev/null | |
| trigger-preview: | |
| name: Dispatch Preview Deployment | |
| runs-on: ubuntu-latest | |
| # Failed tests should not prevent preview environments from updating. | |
| needs: [tag-guard, complexity-guardrails] | |
| if: > | |
| github.event_name == 'pull_request' && | |
| github.event.pull_request.head.repo.full_name == github.repository | |
| steps: | |
| - name: Generate GitHub App token | |
| id: app-token | |
| uses: actions/create-github-app-token@v3 | |
| with: | |
| app-id: ${{ secrets.GH_APP_ID }} | |
| private-key: ${{ secrets.GH_APP_PRIVATE_KEY }} | |
| owner: gobii-ai | |
| repositories: gobii | |
| - name: Notify infra repo | |
| uses: peter-evans/repository-dispatch@v4 | |
| with: | |
| token: ${{ steps.app-token.outputs.token }} | |
| repository: gobii-ai/gobii | |
| event-type: preview-ready | |
| client-payload: | | |
| { | |
| "owner": "${{ github.repository_owner }}", | |
| "repo": "${{ github.event.repository.name }}", | |
| "pr_number": ${{ github.event.pull_request.number }}, | |
| "head_sha": "${{ github.event.pull_request.head.sha }}", | |
| "head_ref": "${{ github.event.pull_request.head.ref }}", | |
| "html_url": "${{ github.event.pull_request.html_url }}", | |
| "trigger_run_id": ${{ github.run_id }}, | |
| "trigger_run_attempt": ${{ github.run_attempt }}, | |
| "actor": "${{ github.actor }}" | |
| } | |
| trigger-staging: | |
| name: Dispatch Staging Deployment | |
| runs-on: ubuntu-latest | |
| # Failed tests should not prevent staging from deploying a published asset set. | |
| needs: [tag-guard, complexity-guardrails, publish-static-assets] | |
| if: github.event_name == 'push' && github.ref == 'refs/heads/main' | |
| steps: | |
| - name: Generate GitHub App token | |
| id: app-token | |
| uses: actions/create-github-app-token@v3 | |
| with: | |
| app-id: ${{ secrets.GH_APP_ID }} | |
| private-key: ${{ secrets.GH_APP_PRIVATE_KEY }} | |
| owner: gobii-ai | |
| repositories: gobii | |
| - name: Notify infra repo | |
| uses: peter-evans/repository-dispatch@v4 | |
| with: | |
| token: ${{ steps.app-token.outputs.token }} | |
| repository: gobii-ai/gobii | |
| event-type: staging-ready | |
| client-payload: | | |
| { | |
| "owner": "${{ github.repository_owner }}", | |
| "repo": "${{ github.event.repository.name }}", | |
| "ref": "${{ github.ref }}", | |
| "head_sha": "${{ github.sha }}", | |
| "trigger_run_id": ${{ github.run_id }}, | |
| "trigger_run_attempt": ${{ github.run_attempt }}, | |
| "pusher": "${{ github.actor }}", | |
| "compare_url": "${{ github.event.compare || '' }}" | |
| } |