Skip to content

docs(research): keep the sector crowding research from #301 #1380

docs(research): keep the sector crowding research from #301

docs(research): keep the sector crowding research from #301 #1380

Workflow file for this run

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