Repository navigation
docs(research): keep the sector crowding research from #301 #1380
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: | |
| branches: [main, master] | |
| push: | |
| branches: [main, master] | |
| permissions: | |
| contents: read | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} | |
| cancel-in-progress: ${{ github.event_name == 'pull_request' }} | |
| jobs: | |
| python-static-unit: | |
| name: lint + unit | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Install uv | |
| uses: astral-sh/setup-uv@v3 | |
| with: | |
| version: "latest" | |
| enable-cache: true | |
| cache-dependency-glob: uv.lock | |
| # Runs BEFORE `uv sync` on purpose: `uv sync` auto-repairs uv.lock's editable | |
| # self-version to match pyproject, which would mask a stale committed lock. We | |
| # want CI to fail on the committed state. version_sync_check is stdlib-only, so | |
| # system python3 (no uv) reads the lock exactly as committed. | |
| - name: Version sync check (committed lock, pre-sync) | |
| run: python3 scripts/release/version_sync_check.py | |
| - name: Sync deps | |
| run: uv sync --extra postgres | |
| - name: Ruff | |
| run: uv run ruff check src/ tests/ scripts/ | |
| - name: AST except handler check (Guardrail 2) | |
| run: uv run python scripts/_lint_except.py src | |
| - name: No Yahoo Finance (CLAUDE.md standing rule) | |
| run: uv run python scripts/check_no_yahoo.py | |
| - name: Runtime assets ship in the package | |
| run: uv run python scripts/check_runtime_assets.py | |
| - name: Guardrail greps (3, 5, 9) | |
| run: | | |
| set -e | |
| # Guardrail 5: no fake cursor/connection in INTEGRATION tests (unit tests may stub freely) | |
| ! grep -rE 'class _Fake(Cursor|Connection)' tests/integration/ || (echo "Guardrail 5 violation: integration test uses fake cursor/connection"; exit 1) | |
| # Guardrail 9: no pipe-joined strings near SQL execute calls | |
| ! grep -rE '"\|".join\(' src/ || (echo "Guardrail 9 violation"; exit 1) | |
| # Guardrail 3: no production code importing from tests/ or removed fixtures module | |
| ! grep -rE 'from tests' src/ || (echo "Guardrail 3 violation"; exit 1) | |
| ! grep -rE 'from uw_scan\.fixtures' src/ || (echo "Guardrail 3 violation"; exit 1) | |
| echo "guardrail greps clean" | |
| - name: Migration prefix guard | |
| run: uv run python scripts/check_migration_prefixes.py | |
| - name: Unit tests | |
| run: uv run pytest tests/unit/ -v | |
| python-integration: | |
| name: integration (${{ matrix.shard }}/${{ matrix.total-shards }}) | |
| runs-on: ubuntu-latest | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| shard: [1, 2, 3, 4] | |
| total-shards: [4] | |
| services: | |
| postgres: | |
| image: postgres:15 | |
| env: | |
| POSTGRES_USER: postgres | |
| POSTGRES_PASSWORD: postgres | |
| POSTGRES_DB: postgres | |
| ports: | |
| - 5432:5432 | |
| options: >- | |
| --health-cmd "pg_isready -U postgres" | |
| --health-interval 5s | |
| --health-timeout 5s | |
| --health-retries 10 | |
| env: | |
| UW_SCAN_DB_HOST: 127.0.0.1 | |
| UW_SCAN_DB_PORT: 5432 | |
| # CI uses option_wizard_local as the "working" DB to satisfy the host/db | |
| # isolation tripwire in uw_scan.config._enforce_db_isolation (the bare | |
| # name option_wizard is reserved for the Mac mini Tailscale host). | |
| UW_SCAN_DB_NAME: option_wizard_local | |
| UW_SCAN_DB_SCHEMA: uw_scan | |
| UW_SCAN_DB_USER: postgres | |
| UW_SCAN_DB_PASSWORD: postgres | |
| # Integration test fixtures refuse to run against the working DB | |
| # (UW_SCAN_DB_NAME). They point psycopg at UW_SCAN_TEST_DB_NAME, which | |
| # must exist before the tests start. | |
| UW_SCAN_TEST_DB_NAME: option_wizard_test | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Install uv | |
| uses: astral-sh/setup-uv@v3 | |
| with: | |
| version: "latest" | |
| enable-cache: true | |
| cache-dependency-glob: uv.lock | |
| - name: Sync deps | |
| run: uv sync --extra postgres | |
| - name: Create option_wizard_local + option_wizard_test DBs | |
| run: | | |
| PGPASSWORD=postgres psql -h 127.0.0.1 -U postgres -d postgres -c "CREATE DATABASE option_wizard_local" | |
| PGPASSWORD=postgres psql -h 127.0.0.1 -U postgres -d postgres -c "CREATE DATABASE option_wizard_test" | |
| - name: Integration tests (duration-sharded, parallel within shard) | |
| # pytest-split distributes tests across the 4 shards by recorded per-test | |
| # duration (.test_durations); pytest-xdist (-n auto) parallelises each shard | |
| # across the runner's cores. Every test still runs — coverage is unchanged, | |
| # the split is purely about balancing wall-clock. Each xdist worker gets its | |
| # own option_wizard_test_gwN database (created on demand by the integration | |
| # conftest's pytest_configure). The live E2E (tests/integration/test_pipeline_e2e.py) | |
| # is auto-skipped because UW_SCAN_API_KEY is not set on CI. | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| uv run pytest tests/integration/ \ | |
| --splits "${{ matrix.total-shards }}" --group "${{ matrix.shard }}" \ | |
| --durations-path .test_durations \ | |
| -n auto | |
| python: | |
| name: lint + unit + integration | |
| runs-on: ubuntu-latest | |
| needs: | |
| - python-static-unit | |
| - python-integration | |
| if: ${{ always() }} | |
| steps: | |
| - name: Check Python quality gates | |
| run: | | |
| if [ "${{ needs.python-static-unit.result }}" != "success" ]; then | |
| echo "python-static-unit failed: ${{ needs.python-static-unit.result }}" | |
| exit 1 | |
| fi | |
| if [ "${{ needs.python-integration.result }}" != "success" ]; then | |
| echo "python-integration failed: ${{ needs.python-integration.result }}" | |
| exit 1 | |
| fi | |
| echo "Python lint, unit, and integration gates passed" | |
| web: | |
| name: web typecheck + test + lint + build | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-node@v4 | |
| with: | |
| node-version: "22" | |
| cache: npm | |
| cache-dependency-path: web/package-lock.json | |
| - name: Install web deps | |
| working-directory: web | |
| run: npm ci | |
| - name: Typecheck | |
| working-directory: web | |
| run: npm run typecheck | |
| - name: Unit tests | |
| working-directory: web | |
| run: npm run test | |
| - name: Lint | |
| working-directory: web | |
| run: npm run lint | |
| - name: Build | |
| working-directory: web | |
| run: npm run build | |
| - name: Install Playwright Chromium | |
| working-directory: web | |
| run: npx playwright install --with-deps chromium | |
| - name: Technicals browser regression | |
| working-directory: web | |
| run: npm run test:e2e:technicals |