name: CI on: push: branches: [master, main] pull_request: branches: [master, main] jobs: # Fast, no-Ollama guardrails. Pin the two-deployment-mode contract (hosted # free web vs local Docker) and the prefilter's zero-false-negative invariant # so a change for one deployment can't silently break the other. guardrails: name: Guardrail tests (deployment modes + prefilter) runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - name: Set up Python 3.11 uses: actions/setup-python@v6 with: python-version: "3.11" - name: Install dependencies run: pip install -r requirements.txt -r requirements-web.txt httpx - name: Deployment-mode guardrail (hosted web vs local Docker) run: python3 tests/test_deployment_modes.py - name: Prefilter — zero false negatives run: python3 tests/test_prefilter.py - name: End-to-end pipeline (real routes; LLM + geocoder mocked) run: python3 tests/test_e2e.py - name: Persistence — CSV cache/restore round-trip is lossless run: python3 tests/test_persistence.py - name: Merge — living-library merge keeps curation run: python3 tests/test_merge.py - name: Capture — single-post extraction (Shortcut path) run: python3 tests/test_capture.py benchmark: name: Benchmark smoke test (Ollama) runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - name: Set up Python 3.11 uses: actions/setup-python@v6 with: python-version: "3.11" - name: Install Python dependencies run: pip install -r requirements.txt - name: Install Ollama run: | curl -fsSL https://ollama.com/install.sh | sh ollama serve & # Wait for the server to be ready for i in $(seq 1 30); do curl -sf http://localhost:11434/api/tags && break sleep 2 done - name: Pull llama3.2 run: ollama pull llama3.2 - name: Run benchmark (Ollama / llama3.2) run: python3 tests/benchmark.py --provider ollama --model llama3.2 - name: Check quality regression run: | python3 - <<'EOF' import json, sys from pathlib import Path results_path = Path("tests/benchmark_results.json") baseline_path = Path("tests/benchmark_baseline.json") if not results_path.exists(): print("No results file found — benchmark must have failed.") sys.exit(1) with open(results_path) as f: results = json.load(f) score = results.get("llama3.2", {}).get("composite_score") if score is None: print("llama3.2 result missing from benchmark_results.json") sys.exit(1) print(f"llama3.2 composite_score: {score}") if not baseline_path.exists(): print("No baseline file yet — storing this run as the baseline.") baseline_path.write_text(json.dumps({"llama3.2": score}, indent=2)) sys.exit(0) with open(baseline_path) as f: baseline = json.load(f) baseline_score = baseline.get("llama3.2", 0) drop = baseline_score - score print(f"Baseline: {baseline_score} Current: {score} Drop: {drop:.1f}") if drop > 5: print(f"FAIL: composite_score dropped {drop:.1f} points (> 5 allowed)") sys.exit(1) print("PASS: quality within acceptable range.") EOF - name: Upload benchmark results if: always() uses: actions/upload-artifact@v7 with: name: benchmark-results path: tests/benchmark_results.json