name: CI - Submission Validation on: push: branches: [main] pull_request: branches: [main] env: PYTHONPATH: ${{ github.workspace }} jobs: validate: name: Tests & Validation runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: "3.13" - name: Install dependencies run: | pip install uv uv venv .venv source .venv/bin/activate uv pip install -e ".[dev,eval]" - name: Run unit tests run: | source .venv/bin/activate pytest tests/ -v - name: Validate openenv.yaml run: | source .venv/bin/activate python -c " import yaml with open('openenv.yaml') as f: spec = yaml.safe_load(f) assert spec['name'] and spec['version'] and spec['entrypoint'] assert len(spec['tasks']) >= 3 assert 'observation_space' in spec and 'action_space' in spec print(f'openenv.yaml OK: {spec[\"name\"]} v{spec[\"version\"]}') " - name: Validate Pydantic models run: | source .venv/bin/activate python -c " from app.models import (MarketObservation, TradeAction, StepResult, ResetResult, EnvironmentState, SpecialistVote, CouncilDecision) assert 'side' in TradeAction.model_fields for f in ['ticker','date','price','price_history','cash','position','portfolio_value', 'task_id','step_number','total_steps','chart_path','headlines', 'forum_excerpts','indicators','peers','macro']: assert f in MarketObservation.model_fields, f'Missing: {f}' print('models OK') " - name: Validate endpoints run: | source .venv/bin/activate python -c " from fastapi.testclient import TestClient from app.main import app c = TestClient(app) assert c.post('/reset', json={}).status_code == 200 r = c.post('/step', json={'side': 'hold', 'quantity': 0}) assert r.status_code == 200 and -1.0 <= r.json()['reward'] <= 1.0 assert c.get('/state').status_code == 200 print('endpoints OK') " - name: Validate tasks (parquet round-trip) run: | source .venv/bin/activate python scripts/validate_tasks.py - name: Verify inference.py structure run: | test -f inference.py grep -q "from openai import OpenAI" app/council/llm.py grep -q "API_BASE_URL" app/council/llm.py grep -q "MODEL_NAME" app/council/llm.py grep -q '\[START\]' inference.py grep -q '\[STEP\]' inference.py grep -q '\[COUNCIL\]' inference.py grep -q '\[END\]' inference.py - name: Council mock smoke test (no GPU, no API key) run: | source .venv/bin/activate python inference.py --task task_easy --mock --no-cache > /tmp/run.log 2>&1 tail -5 /tmp/run.log grep -q '\[START\]' /tmp/run.log grep -q '\[COUNCIL\]' /tmp/run.log grep -q '\[END\]' /tmp/run.log docker-build: name: Docker Build runs-on: ubuntu-latest timeout-minutes: 20 steps: - uses: actions/checkout@v4 - name: Build image run: docker build -t stocker:ci . - name: Container /reset returns 200 run: | docker run -d --name stocker-test -p 7860:7860 stocker:ci for i in $(seq 1 30); do if curl -sf http://localhost:7860/health >/dev/null 2>&1; then break; fi sleep 2 done CODE=$(curl -s -o /dev/null -w "%{http_code}" -X POST \ -H "Content-Type: application/json" -d '{}' http://localhost:7860/reset) [ "$CODE" = "200" ] || exit 1 docker stop stocker-test && docker rm stocker-test