-
Notifications
You must be signed in to change notification settings - Fork 0
210 lines (182 loc) · 7.68 KB
/
Copy pathci.yml
File metadata and controls
210 lines (182 loc) · 7.68 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
name: ci
on:
pull_request:
branches: [main]
push:
branches: [main]
permissions:
contents: read
concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true
jobs:
lint-typecheck-unit:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.12"]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
cache: pip
cache-dependency-path: pyproject.toml
- name: Cache embedding model (test_embeddings downloads BAAI/bge-large-en-v1.5)
uses: actions/cache@v4
with:
path: ${{ github.workspace }}/.test-model-cache
key: fastembed-bge-large-en-v1.5-unit-${{ runner.os }}
- name: Install deps
run: |
python -m pip install --upgrade pip wheel
pip install -e ".[dev]"
- name: Lint (ruff)
run: |
ruff format --check sentinel tests
ruff check sentinel tests
- name: Typecheck (mypy --strict)
run: mypy
- name: Warm embedding model cache (retry past the intermittent HF/tqdm download flake)
run: |
for attempt in 1 2 3 4 5; do
if python -c "from fastembed import TextEmbedding; list(TextEmbedding(model_name='BAAI/bge-large-en-v1.5', cache_dir='${{ github.workspace }}/.test-model-cache').embed(['warmup'])); print('fastembed model ready')"; then
exit 0
fi
echo "fastembed warmup attempt $attempt failed; retrying in 5s"
sleep 5
done
echo "fastembed warmup failed after 5 attempts"
exit 1
- name: Unit tests
run: pytest tests/unit -v
- name: Upload coverage
if: always()
uses: actions/upload-artifact@v4
with:
name: coverage-xml
path: coverage.xml
integration:
runs-on: ubuntu-latest
needs: lint-typecheck-unit
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
cache: pip
cache-dependency-path: pyproject.toml
- name: Install deps
run: |
python -m pip install --upgrade pip wheel
pip install -e ".[dev]"
- name: Start compose stack
run: docker compose up -d --wait postgres redis kafka
- name: Integration tests
env:
SENTINEL_ENV: test
SENTINEL_POSTGRES_DSN: postgresql+asyncpg://sentinel:sentinel@localhost:5432/sentinel
SENTINEL_REDIS_URL: redis://localhost:6379/0
SENTINEL_KAFKA_BROKERS: localhost:29092
SENTINEL_ANTHROPIC_API_KEY: sk-placeholder
# Integration tests exercise external services, not internal code;
# coverage is enforced by the unit job (--no-cov skips the addopts gate).
run: pytest tests/integration -v -m integration --no-cov
- name: Tear down
if: always()
run: docker compose down -v
# Full corpus replay + regression gate vs the committed baseline. Named
# `evals-gate` (not `evals-smoke`) because it runs the full 10-case corpus,
# not just the 5-case `--smoke` subset — the gate's job is to block PRs on
# regression, so we want the strongest signal CI can afford. Deterministic
# (cassette replay, no API spend), takes <2 minutes including compose boot.
evals-gate:
runs-on: ubuntu-latest
needs: lint-typecheck-unit
# Job-level env so every step (Migrate, evals-reset, evals, compare) sees
# the full Settings surface. `load_settings()` enforces several required
# fields, not just SENTINEL_POSTGRES_DSN — splitting env per-step would
# require duplicating the block and is fragile against new required vars.
# SENTINEL_ANTHROPIC_API_KEY is a placeholder — the cassette transport
# short-circuits before any real API call, but the Anthropic client
# constructor requires a non-empty key.
env:
SENTINEL_ENV: test
SENTINEL_EVAL_MODE: "true"
SENTINEL_EVAL_CORPUS_DIR: sentinel/evals/corpus
SENTINEL_EVAL_CASSETTE_DIR: sentinel/evals/cassettes
SENTINEL_EVAL_CASSETTE_MODE: replay
SENTINEL_POSTGRES_DSN: postgresql+asyncpg://sentinel:sentinel@localhost:5432/sentinel
SENTINEL_REDIS_URL: redis://localhost:6379/0
SENTINEL_KAFKA_BROKERS: localhost:29092
SENTINEL_ANTHROPIC_API_KEY: sk-placeholder-cassette-replay
SENTINEL_GENERIC_WEBHOOK_SECRET: ci-eval-secret
# fastembed default cache (/var/cache/fastembed) needs root on the CI
# runner. Point it at the workspace so the eval scorer can download/
# cache the embedding model under a writable path.
SENTINEL_EMBEDDING_MODEL_CACHE_DIR: ${{ github.workspace }}/.fastembed-cache
# Disable tqdm during the fastembed/HuggingFace model download. tqdm's
# threaded progress-bar cleanup crashes on the CI runner (AttributeError:
# type object 'tqdm' has no attribute '_lock', in thread_map →
# _decr_instances); a disabled tqdm short-circuits close() before that
# path. Dep versions are unchanged from the last green run — this is an
# environment/timing fragility in the live download, not a code regression.
TQDM_DISABLE: "1"
HF_HUB_DISABLE_PROGRESS_BARS: "1"
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
cache: pip
cache-dependency-path: pyproject.toml
- name: Cache embedding model
uses: actions/cache@v4
with:
path: ${{ github.workspace }}/.fastembed-cache
key: fastembed-bge-large-en-v1.5-evals-${{ runner.os }}
- run: |
python -m pip install --upgrade pip wheel
pip install -e ".[dev]"
# Bring up the same data tier the integration job uses. Evals replay
# cassettes (no Anthropic API call) but the eval CLI runs the full
# in-process FastAPI app → real Postgres for incident/diagnosis rows,
# real Kafka for the outbox → enrichment → diagnosis round-trip, real
# Redis for webhook idempotency. The gate exercises the actual
# production pipeline against deterministic responses.
- name: Start compose stack
run: docker compose up -d --wait postgres redis kafka
- name: Migrate
run: make migrate
- name: Warm embedding model cache
# Seed .fastembed-cache so `make evals` never triggers the live HuggingFace
# download, whose threaded tqdm cleanup intermittently crashes with
# `del tqdm._lock`. Run with tqdm ENABLED here (TQDM_DISABLE=0) — the
# job-level TQDM_DISABLE=1 is what trips that cleanup path; the unit job
# (tqdm enabled) downloads fine. Retry covers transient HF unavailability.
env:
TQDM_DISABLE: "0"
run: |
for attempt in 1 2 3 4 5; do
if python -c "from fastembed import TextEmbedding; list(TextEmbedding(model_name='BAAI/bge-large-en-v1.5', cache_dir='${{ github.workspace }}/.fastembed-cache').embed(['warmup'])); print('fastembed model ready')"; then
exit 0
fi
echo "fastembed warmup attempt $attempt failed; retrying in 5s"
sleep 5
done
echo "fastembed warmup failed after 5 attempts"
exit 1
- name: Evals gate (cassette replay + regression gate)
run: |
make evals-reset
make evals
make evals-compare
- name: Upload eval results
if: always()
uses: actions/upload-artifact@v4
with:
name: evals-gate-results
path: evals/results/
- name: Tear down
if: always()
run: docker compose down -v