Repository navigation
609 lines (547 loc) · 25.8 KB
/
Copy pathci.yml
File metadata and controls
609 lines (547 loc) · 25.8 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
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
name: CI
# Two things at once, because this repo is two things: a published component
# library AND the docs site at muicharts.2plot.dev.
#
# library the wheel build, the measured dash>=3.3 package floor, and the
# Python range setup.py claims (3.9-3.13);
# satellite the 2plot network baseline — a secretless pytest run (the 17
# MUI Pro pages must degrade to their license banners: zero env
# in CI is the proof, LESSONS §18), the real Docker image booted
# and probed by the same battery that runs against production,
# and version fingerprints asserted INSIDE the artifact.
#
# TWO different Dash floors, and conflating them is the easy mistake:
#
# * the PACKAGE needs >= 3.3 — measured by the `package` job's floor venv.
# * the DOCS SITE needs >= 4.1 — dash-improve-my-llms pins `dash<5,>=4.1`,
# so the smoke matrix starts there. (Local dev may sit lower with a
# checked-out dimll; PyPI artifacts cannot.)
#
# Deliberately NOT `push: branches: [main]`. cd.yml runs on that push and its
# first job `uses:` this workflow, so a push to main would start two runs
# that contend for the concurrency group and cancel each other. Pull requests
# get their own CI; `main` is owned by CD. No coverage gap — CD cannot deploy
# without this workflow passing first.
on:
pull_request:
workflow_dispatch:
# Called by cd.yml so a deploy can never ship something the matrix rejected.
workflow_call:
# Read-only. Nothing here publishes, comments or tags; the deploy lives in
# cd.yml behind a `production` environment and the PyPI release in release.yml.
permissions:
contents: read
concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true
env:
PIP_DISABLE_PIP_VERSION_CHECK: "1"
# The fleet probe convention (sync 1.6.44 item 4). These curls hit the
# app booted inside this runner, and a UA-less request is counted as an
# unidentified crawler-lane client by the very tracker under test — so
# the smoke steps were writing ledger rows into the container they were
# checking. `curl/8` leads because a string carrying only the internal
# suffix classifies crawler-lane; the `2plot-internal` substring is what
# makes track_visit and record_read drop the row at write time.
PROBE_UA: "curl/8 2plot-internal/probe ci-smoke"
# Never let a CI run inherit production behaviour: require_owned_base_url()
# keys off RENDER / APP_ENV=production, and the traffic reporter keys off
# the webhook secret. Both must stay inert here.
APP_ENV: ci
jobs:
lint:
name: lint
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.14"
cache: pip
- run: pip install flake8
- name: flake8
# Config in .flake8 — including the BUDGETED first-run debt ledger
# for pages/*.py. Tighten there, not here.
run: flake8 lib pages components docs tests scripts run.py usage.py _validate_init.py
- name: py_compile sweep of docs/
# Sync 1.6.44 item 7, ported with a CORRECTION measured on this repo.
#
# The item's premise is that `.flake8` excludes `docs/*/`, so
# `flake8 docs/` exits 0 with zero output on a file that does not
# parse. THAT IS NOT TRUE HERE: this repo's .flake8 has no docs
# exclusion, `docs` is already on the flake8 line above, and a file
# containing `def broken(:` is reported E902 and fails the step.
#
# The sweep is ported anyway, because the narrower claim survives and
# was measured: flake8 catches most syntax errors but NOT ALL. Two
# shapes are compile-time SyntaxErrors that flake8 reports nothing at
# all for —
# print(a=1, a=2) duplicate keyword argument
# def f(): nonlocal q no binding for nonlocal
# — both flake8 exit 0, both py_compile exit 1. A docs example
# carrying either would ship, and `.. exec::` would raise at render
# time on a page nobody re-opened.
#
# FAILS ON AN EMPTY CORPUS deliberately: a sweep that swept nothing
# and a sweep that found nothing print the same green, and only one
# of them is evidence. The count is printed beside the result.
run: |
python - <<'PY'
import py_compile, sys
from pathlib import Path
files = sorted(Path("docs").rglob("*.py"))
print(f"py_compile sweep: {len(files)} file(s) under docs/")
if not files:
print("::error::the docs/ sweep found no Python files — "
"an empty corpus is not a pass")
sys.exit(1)
bad = []
for f in files:
try:
py_compile.compile(str(f), doraise=True, cfile=str(f) + "c")
except py_compile.PyCompileError as exc:
bad.append(f"{f}: {exc}")
finally:
Path(str(f) + "c").unlink(missing_ok=True)
for line in bad:
print(f"::error::{line}")
print(f"{len(files) - len(bad)} compiled, {len(bad)} failed")
sys.exit(1 if bad else 0)
PY
# The workflows lint themselves. Not belt-and-braces: an invalid
# workflow file is the one defect CI structurally cannot report,
# because the run dies before a job exists to fail. A double-quoted
# string inside a ${{ }} expression is a LEX error that invalidates
# the whole file — it silently killed every CI and CD run on
# boilerplate.2plot.dev for four days (LESSONS §14).
- name: actionlint
run: |
bash <(curl -fsSL https://raw.githubusercontent.com/rhysd/actionlint/v1.7.7/scripts/download-actionlint.bash) 1.7.7
./actionlint -color
docs-tests:
name: Docs site · pytest (zero secrets)
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.14"
cache: pip
- name: Install the docs site
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
# markdown2dash pins gunicorn<22 against the CVE-driven gunicorn>=23
# floor. Same two-command install as the Dockerfile and render.yaml.
pip install --no-deps markdown2dash==0.1.2
pip install -e .
pip install pytest
- name: Confirm the pinned dependency versions
run: |
python - <<'PY'
import dash, dash_improve_my_llms as pkg, gunicorn
import dash_mantine_components as dmc
def parts(v):
return tuple(int(x) for x in v.split(".")[:3] if x.isdigit())
# The SITE floor: dash-improve-my-llms pins dash>=4.1, so this is
# what requirements.txt actually resolves to in production.
assert parts(dash.__version__)[:2] >= (4, 1), dash.__version__
# 2.8.0 is the ledger-round floor and run.py REFUSES TO BOOT
# below it. (2.8.0 brings classify() — the one classifier the
# tracker delegates to — and on_document_read, the ledger's
# `reads` row; 2.7.1 the llms.txt v2 discovery relations, the
# text/plain Accept ramp and the representation digest;
# 2.7.0 dedups the prerender's H1 and the home
# footer's llms.txt link; 2.6.1 made the prerender visible rather
# than `hidden`, which this site has no <noscript> fallback
# under; 2.6.0 stopped the sitemap inventing lastmod dates;
# 2.5.1 brought configure_seo and the per-page crawler identity;
# 2.3.4 resolve_site_title.) Asserted here so a stale resolve
# fails the matrix instead of the deploy.
assert parts(pkg.__version__) >= (2, 8, 0), pkg.__version__
# 2.8.0 is a floor, not a preference: on 2.7 the network-standard
# mobile drawer renders as a floating card from identical props.
assert parts(dmc.__version__)[:2] >= (2, 8), dmc.__version__
# 21.x carried two request-smuggling CVEs (CVE-2024-6827,
# CVE-2024-1135); the requirements floor is >=23.
assert parts(gunicorn.__version__)[:2] >= (23, 0), gunicorn.__version__
print(f"dash {dash.__version__}, dash-improve-my-llms "
f"{pkg.__version__}, dash-mantine-components {dmc.__version__}, "
f"gunicorn {gunicorn.__version__}")
PY
# No MUI_PRO_API_KEY, no CROSS_APP_WEBHOOK_SECRET, no
# NETWORK_BULLETIN_URL here ON PURPOSE. tests/conftest.py pins them
# empty, and the app's degraded postures (license banners on the 17
# Pro pages, a dormant traffic reporter) are only provable when
# nothing is configured.
- name: Test suite (zero secrets)
run: pytest tests -q
- name: Boot under a production server
run: |
gunicorn run:server -b 127.0.0.1:8550 --daemon --access-logfile - --error-logfile -
for _ in $(seq 1 30); do
curl -sf -A "$PROBE_UA" http://127.0.0.1:8550/healthz && break
sleep 1
done
# A page that renders under the test client can still fail under a
# real WSGI worker — different import path, different working
# directory, no test-client conveniences.
curl -sf -A "$PROBE_UA" http://127.0.0.1:8550/ > /dev/null
curl -sf -A "$PROBE_UA" http://127.0.0.1:8550/sparkline > /dev/null
# The battery, against the same server this satellite deploys.
python3 scripts/network_smoke.py --base-url http://127.0.0.1:8550
docker:
name: docker image · boot · battery
runs-on: ubuntu-latest
timeout-minutes: 25
needs: [docs-tests]
steps:
- uses: actions/checkout@v4
# The same build Render runs. This is where a dependency-resolution
# failure surfaces — at CI time, not deploy time — and where a missing
# COPY line dies loudly instead of at gunicorn boot (LESSONS §19).
- uses: docker/setup-buildx-action@v3
- name: Build the production image
uses: docker/build-push-action@v6
with:
context: .
tags: dash-mui-charts-docs:ci
load: true
cache-from: type=gha
cache-to: type=gha,mode=max
# pip metadata is invisible from outside a running host, so the
# versions are asserted here, inside the artifact that actually ships.
- name: Version fingerprints inside the image
run: |
docker run --rm dash-mui-charts-docs:ci python -c "
from importlib.metadata import version
def parts(v):
return tuple(int(x) for x in v.split('.')[:3] if x.isdigit())
v = version('dash')
print('dash', v)
assert parts(v)[:2] >= (4, 1), f'expected dash >=4.1, image has {v}'
v = version('dash-improve-my-llms')
print('dash-improve-my-llms', v)
assert parts(v) >= (2, 8, 0), f'expected >=2.8.0 (classify() + on_document_read: the one classifier and the ledger row), image has {v}'
v = version('dash-mantine-components')
print('dash-mantine-components', v)
assert parts(v)[:2] >= (2, 8), f'expected dmc>=2.8 (mobile drawer renders as a card below it), image has {v}'
# The vendored Clerk package: the image must carry the sha-verified
# 1.0.5 tarball, not whatever an older vendor/ left behind. Below
# 1.0.3 Sign Out never reaches the server and a signed cookie keeps
# serving gated pages for days; 1.0.5 is what ends the return-trip
# stale gate.
v = version('dash-clerk-auth')
print('dash-clerk-auth', v)
assert parts(v) >= (1, 0, 5), f'expected dash-clerk-auth>=1.0.5, image has {v}'
v = version('gunicorn')
print('gunicorn', v)
assert parts(v)[:2] >= (23, 0), f'expected gunicorn>=23 (CVE-2024-6827/-1135), image has {v}'
# markdown2dash installs with --no-deps to dodge its gunicorn<22
# pin; this proves the dodge kept working AND kept the package.
import markdown2dash # noqa: F401
print('markdown2dash importable')
import dash_mui_charts
print('dash_mui_charts', dash_mui_charts.__version__,
len(dash_mui_charts.__all__), 'components')
assert len(dash_mui_charts.__all__) == 13
"
# Boot with no secrets: the Pro pages fall back to their license
# banners, the traffic reporter stays dormant. What this catches is
# any import-time or preload crash — the class of failure where the
# platform loops the worker and the deploy never goes live.
- name: Boot the container and wait for /healthz
run: |
docker run -d --name docs -p 8550:8550 dash-mui-charts-docs:ci
for i in $(seq 1 60); do
if curl -sf -A "$PROBE_UA" http://127.0.0.1:8550/healthz > /dev/null; then
echo "healthy after ~$((i*2))s"
exit 0
fi
if [ "$(docker inspect -f '{{.State.Running}}' docs)" != "true" ]; then
echo "container exited during boot:"
docker logs docs
exit 1
fi
sleep 2
done
echo "never became healthy; last logs:"
docker logs --tail 100 docs
exit 1
# The SAME script CD runs against https://muicharts.2plot.dev, so a
# failure in CI and a failure in production read identically.
- name: Smoke battery against the booted container
run: python3 scripts/network_smoke.py --base-url http://127.0.0.1:8550
- name: Container logs (for the record)
if: always()
run: docker logs --tail 40 docs 2>/dev/null || true
smoke:
name: Docs · Dash ${{ matrix.dash }} · Python ${{ matrix.python }}
runs-on: ubuntu-latest
timeout-minutes: 25
strategy:
fail-fast: false
matrix:
# The SITE floor (dash-improve-my-llms pins dash>=4.1), the two
# intermediate minors, and the current release. The package's own
# dash>=3.3 floor is measured separately, in the `package` job.
dash: ["4.1.0", "4.2.0", "4.3.0", "4.4.1"]
# THE SITE LANE's Python — the fleet minor, equal to the
# Dockerfile's FROM tag and render.yaml's PYTHON_VERSION minor
# (SYNC-1.6.22-1.6.29 item 5). The wheel's own supported range is
# a different question, measured in `package-python-range`.
python: ["3.14"]
include:
# The compat window, against the current Dash: the fleet minor
# plus the two below it. A full cross-product would be 12 jobs
# for very little extra signal.
#
# These legs were 3.10 and 3.13 while the site lane sat on 3.12,
# which left a five-wide window with holes in it once the image
# moved to 3.14. Three wide and adjacent is what the item asks
# for, and 3.9-3.13 coverage did not disappear — it lives in
# `package-python-range`, where the wheel's requires-python
# claim is what is actually being tested.
- dash: "4.4.1"
python: "3.13"
- dash: "4.4.1"
python: "3.12"
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python }}
cache: pip
# node is what makes the JS checks real: the matrix rebuilds the
# component bundle and wrappers from source, so a broken webpack
# config or a component that stopped generating fails HERE, per
# supported environment, not on the next release.
- uses: actions/setup-node@v4
with:
node-version: "20"
- name: Install Dash ${{ matrix.dash }} first
# The version under test goes in BEFORE the rest, so the other
# requirements resolve against it rather than dragging in a newer
# Dash. requirements.txt only floors dash, so pip leaves the pin be.
run: |
python -m pip install --upgrade pip
python -m pip install "dash==${{ matrix.dash }}"
- name: Install documentation-site requirements
run: |
python -m pip install -r requirements.txt
python -m pip install --no-deps markdown2dash==0.1.2
- name: Report the resolved Dash version
# A silent upgrade here would make the whole matrix meaningless.
run: |
RESOLVED=$(python -c "import dash; print(dash.__version__)")
echo "requested=${{ matrix.dash }} resolved=$RESOLVED"
if [ "$RESOLVED" != "${{ matrix.dash }}" ]; then
echo "::warning::Dash resolved to $RESOLVED, not ${{ matrix.dash }}"
fi
- name: Rebuild the components from source
run: |
npm ci
npm run build
- name: Validate the generated wrappers
run: npm run validate-init
- name: Smoke test
run: python scripts/smoke_test.py --json smoke-${{ matrix.dash }}-py${{ matrix.python }}.json
- uses: actions/upload-artifact@v4
if: always()
with:
name: smoke-${{ matrix.dash }}-py${{ matrix.python }}
path: smoke-*.json
if-no-files-found: ignore
package:
name: Build + verify the wheel
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v4
with:
# check_release.py compares the git commit times of the bundle and
# src/lib/components. A shallow clone can omit the commit that
# last touched one of them, turning the check into a false
# "no git history" skip.
fetch-depth: 0
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Release consistency
# Version drift between package.json and package-info.json, a stale
# bundle, packaging drift. None of these break a test run — this
# repo ran fine for a full cycle advertising five different versions.
run: python scripts/check_release.py
- name: Build
run: |
python -m pip install --upgrade pip build twine
python -m build
- name: Check metadata
run: python -m twine check dist/*
- name: Install the wheel in a clean venv and import it
# The package must work with ONLY `dash` present — nothing from
# requirements.txt, which is the docs site's dependency set.
run: |
python -m venv /tmp/clean
/tmp/clean/bin/pip install --upgrade pip
/tmp/clean/bin/pip install dist/*.whl
/tmp/clean/bin/python - <<'PY'
import pathlib
import dash_mui_charts as dmx
print("version:", dmx.__version__)
bundle = pathlib.Path(dmx.__file__).parent / "dash_mui_charts.min.js"
assert bundle.exists(), "JS bundle missing from the wheel"
print("bundle:", bundle.stat().st_size // 1024, "KB")
expected = [
"BarChart", "CandlestickChart", "CompositeChart", "Heatmap",
"LineChart", "LiveTradingChart", "PieChart", "ScatterChart",
"SimpleTreeView", "SparklineChart", "TimeClock", "TreeView",
"TreeViewPro",
]
for name in expected:
assert hasattr(dmx, name), f"missing component: {name}"
assert sorted(dmx.__all__) == sorted(expected), (
f"__all__ drift: {sorted(set(dmx.__all__) ^ set(expected))}"
)
print(f"components OK ({len(expected)})")
# The wheel must carry the library and nothing else. `lib`,
# `pages` and `assets` are docs-site directories that a careless
# packages= would happily install as top-level packages.
from importlib.metadata import distribution
top = (distribution("dash_mui_charts").read_text("top_level.txt") or "").split()
assert top == ["dash_mui_charts"], f"wheel installs more than the library: {top}"
print("top_level.txt:", top)
PY
- name: Measure the dash floor the wheel claims
# setup.py says dash>=3.3.0 — this is the measurement behind that
# number. The docs site cannot go this low (dimll pins >=4.1); the
# package can, and users on Dash 3.3 installs are real.
run: |
python -m venv /tmp/floor
/tmp/floor/bin/pip install --upgrade pip
/tmp/floor/bin/pip install "dash==3.3.0" dist/*.whl
/tmp/floor/bin/python - <<'PY'
import dash
import dash_mui_charts as dmx
layout = dmx.LineChart(
id="lc",
series=[{"data": [1, 2, 3], "label": "a"}],
xAxis=[{"data": [0, 1, 2]}],
).to_plotly_json()
for name in dmx.__all__:
cls = getattr(dmx, name)
assert callable(cls), name
print(f"dash={dash.__version__} dash_mui_charts={dmx.__version__} floor OK")
PY
- name: Assert the wheel version matches package.json
run: |
PY_VER=$(python -c "import json;print(json.load(open('package.json'))['version'])")
WHEEL_VER=$(/tmp/clean/bin/python -c "import dash_mui_charts;print(dash_mui_charts.__version__)")
echo "package.json=$PY_VER installed=$WHEEL_VER"
test "$PY_VER" = "$WHEEL_VER"
- uses: actions/upload-artifact@v4
with:
name: dist
path: dist/
package-python-range:
name: Package · Python ${{ matrix.python }}
needs: package
runs-on: ubuntu-latest
timeout-minutes: 10
strategy:
fail-fast: false
matrix:
# Every interpreter python_requires in setup.py claims. This is what
# makes that claim measured rather than asserted — and it installs
# ONLY the wheel plus Dash, never the docs requirements, because the
# package's floor is not the docs site's.
python: ["3.9", "3.10", "3.11", "3.12", "3.13"]
steps:
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python }}
- uses: actions/download-artifact@v4
with:
name: dist
path: dist/
- name: Install the wheel (pulls in dash, nothing else)
run: |
python -m pip install --upgrade pip
python -m pip install dist/*.whl
- name: Import and build a chart layout
run: |
python - <<'PY'
import dash
import dash_mui_charts as dmx
layout = [
dmx.LineChart(id="lc", series=[{"data": [1, 2, 3]}],
xAxis=[{"data": [0, 1, 2]}]),
dmx.BarChart(id="bc", series=[{"data": [4, 5, 6]}],
xAxis=[{"scaleType": "band", "data": ["a", "b", "c"]}]),
dmx.PieChart(id="pc", series=[{"data": [
{"id": 0, "value": 10, "label": "A"}]}]),
dmx.SparklineChart(id="sc", data=[1, 3, 2]),
dmx.SimpleTreeView(id="tv", items=[
{"itemId": "/", "label": "Home"}]),
dmx.TimeClock(id="tc", value="10:30"),
]
for component in layout:
component.to_plotly_json()
print(f"dash={dash.__version__} dash_mui_charts={dmx.__version__} "
f"components={len(dmx.__all__)} OK")
PY
lint-js:
name: JS sources parse
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "20"
# The committed bundle is what users actually load, and it is a build
# artifact nobody reviews line by line. Parsing it here is the
# cheapest guard against committing a truncated or half-written build.
- name: Parse the committed component bundle
run: |
for f in dash_mui_charts/*.js; do node --check "$f"; done
- name: Parse assets/*.js individually
# These are load-bearing at runtime: muiChartsFunctions.js is the
# functions-as-props registry, 00-loading-theme.js wins the asset
# race by name, 01-nav-restore.js replays the sidebar state. They
# only ever fail in the browser console.
run: |
for f in assets/*.js; do node --check "$f"; done
pip-audit:
name: pip-audit
runs-on: ubuntu-latest
timeout-minutes: 10
# GATING as of the gate wave, and that is a deliberate promotion. It was
# advisory (continue-on-error) for as long as the audit had a permanent
# red line in it: clerk-backend-api 5.x capped cryptography below four
# fixes, so the step reported the same four advisories on every run and
# a fifth, actionable one would have scrolled past unnoticed. With the
# 1.0.5 vendor bump the chain reaches cryptography>=50 and the baseline
# is QUIET — which is the network's condition for flipping this on. A
# red pip-audit now means something new landed. Keep it that way: fix
# the dependency, don't re-add continue-on-error or --ignore-vuln.
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.14"
- run: pip install pip-audit
# The four cryptography suppressions that used to sit here are GONE, and
# this is the release they were waiting for. The old chain was
# vendor/dash_clerk_auth-0.9.1 (requires clerk-backend-api<6) ->
# clerk-backend-api 5.x (pins cryptography>=45,<47), capping cryptography
# at 46.x while the fixes landed in 48.0.1 / 49.0.0 / 50.0.0.
# dash-clerk-auth 1.0.1 widened its own cap to `<8`, so requirements.txt
# now pins clerk-backend-api>=7.0.0 (which allows cryptography<51) and
# cryptography>=50.0.0 — the fix is reached instead of suppressed.
# Nothing is ignored here any more; keep it that way.
- name: pip-audit
run: |
pip-audit -r requirements.txt --skip-editable