3737 - name : Run pytest
3838 run : uv run pytest -q
3939
40+ # Blocking requirement-traceability audit. Collection-only run: the
41+ # TraceabilityPlugin (registered in the root conftest) collects every
42+ # @pytest.mark.req(...) marker, writes the matrix artifact, and — because
43+ # DEXPACE_TRACEABILITY_GATE is set — fails the run if any MUST-level
44+ # requirement in an enabled subsystem is neither covered by a marker nor
45+ # listed N/A in docs/traceability-na-ids.txt (the deviations-ledger bridge).
46+ - name : Run traceability audit (blocking)
47+ env :
48+ DEXPACE_TRACEABILITY_ENABLE : " seams,http,bodies,io,context,pipeline,recovery,redirect,retry,auth,xcut,serde,pagination,sse,observability,config,transport,async,nfr"
49+ DEXPACE_TRACEABILITY_NA : docs/traceability-na-ids.txt
50+ DEXPACE_TRACEABILITY_MATRIX : build/traceability-matrix.json
51+ DEXPACE_TRACEABILITY_GATE : " 1"
52+ run : uv run pytest -q --co
53+
54+ - name : Upload traceability matrix
55+ if : always()
56+ uses : actions/upload-artifact@v4
57+ with :
58+ name : traceability-matrix-py${{ matrix.python-version }}
59+ path : build/traceability-matrix.json
60+ if-no-files-found : error
61+
4062 - name : Run mypy
4163 run : uv run mypy --strict
4264
@@ -45,3 +67,223 @@ jobs:
4567
4668 - name : Run ruff format --check
4769 run : uv run ruff format --check
70+
71+ - name : Run import-linter
72+ run : uv run lint-imports
73+
74+ packaging :
75+ name : packaging invariants
76+ runs-on : ubuntu-latest
77+ steps :
78+ - name : Checkout
79+ uses : actions/checkout@v6
80+
81+ - name : Install uv
82+ uses : astral-sh/setup-uv@v8.2.0
83+ with :
84+ enable-cache : true
85+ python-version : " 3.12"
86+
87+ - name : Check namespace layout
88+ run : uv run --no-project python tools/check_namespace_packages.py
89+
90+ - name : Check MIT headers
91+ run : uv run --no-project python tools/check_mit_headers.py
92+
93+ - name : Check runtime dependency budget
94+ run : uv run --no-project python tools/check_dependency_audit.py
95+
96+ - name : Build core + stdlib wheels
97+ run : |
98+ uv build --package dexpace-sdk-core --wheel -o dist
99+ uv build --package dexpace-sdk-http-stdlib --wheel -o dist
100+
101+ - name : Smoke-import wheels in a fresh venv
102+ run : |
103+ uv venv /tmp/wheel-smoke
104+ uv pip install --python /tmp/wheel-smoke \
105+ dist/dexpace_sdk_core-*.whl \
106+ dist/dexpace_sdk_http_stdlib-*.whl
107+ /tmp/wheel-smoke/bin/python tools/smoke_wheel_import.py
108+
109+ coverage :
110+ name : coverage gate
111+ runs-on : ubuntu-latest
112+ steps :
113+ - name : Checkout
114+ uses : actions/checkout@v6
115+
116+ - name : Install uv
117+ uses : astral-sh/setup-uv@v8.2.0
118+ with :
119+ enable-cache : true
120+ python-version : " 3.12"
121+
122+ - name : Sync workspace
123+ run : uv sync
124+
125+ # Blocking aggregate line+branch coverage gate. The 80% floor lives in
126+ # [tool.coverage.report].fail_under in pyproject.toml; pytest-cov exits
127+ # non-zero when the total drops below it.
128+ - name : Run coverage gate
129+ run : uv run pytest -q --cov --cov-report=term-missing
130+
131+ typecheck-divergence :
132+ name : typecheck divergence (pyright, non-blocking)
133+ runs-on : ubuntu-latest
134+ # Advisory early-warning lane: pyright cross-checks mypy's verdict so a
135+ # divergence between the two checkers surfaces early. This is NOT a gate —
136+ # `mypy --strict` in the `test` job remains the sole blocking type check.
137+ # `continue-on-error` at both the job and step level keeps a red result
138+ # (e.g. pyright's stricter completeness scoring) from ever failing CI.
139+ # pyright is layered in per-run via `uv run --with`, so it stays out of the
140+ # committed dependency set and uv.lock.
141+ continue-on-error : true
142+ steps :
143+ - name : Checkout
144+ uses : actions/checkout@v6
145+
146+ - name : Install uv
147+ uses : astral-sh/setup-uv@v8.2.0
148+ with :
149+ enable-cache : true
150+ python-version : " 3.14"
151+
152+ - name : Sync workspace
153+ run : uv sync
154+
155+ - name : pyright --verifytypes (advisory)
156+ continue-on-error : true
157+ run : uv run --with pyright pyright --verifytypes dexpace.sdk.core --ignoreexternal
158+
159+ - name : pyright typing-conformance corpus (advisory)
160+ continue-on-error : true
161+ run : uv run --with pyright pyright packages/dexpace-sdk-core/tests/typing_conformance.py
162+
163+ version-skew :
164+ name : version skew (Python ${{ matrix.python-version }}, ${{ matrix.resolution }})
165+ runs-on : ubuntu-latest
166+ strategy :
167+ fail-fast : false
168+ matrix :
169+ python-version : ["3.12", "3.13", "3.14"]
170+ resolution : ["highest", "lowest-direct"]
171+ steps :
172+ - name : Checkout
173+ uses : actions/checkout@v6
174+
175+ - name : Install uv
176+ uses : astral-sh/setup-uv@v8.2.0
177+ with :
178+ enable-cache : true
179+ python-version : ${{ matrix.python-version }}
180+
181+ # Resolve the workspace at either edge of its declared dependency window.
182+ # `highest` exercises the newest adapters against head core (the standard
183+ # release runtime). `lowest-direct` pins every *direct* dependency to its
184+ # declared floor: core to the compatible-release lower bound each adapter
185+ # and the TCK pin (dexpace-sdk-core>=0.1,<0.2) and each transport to its
186+ # minimum (httpx>=0.27, aiohttp>=3.9, requests>=2.32, furl>=2.1.3). Running
187+ # both edges across Python 3.12-3.14 turns the forward-compatibility
188+ # promise -- newest adapters and the generated petstore canary run against
189+ # the oldest supported core -- into a passing pipeline, not a prose claim.
190+ - name : Sync workspace (${{ matrix.resolution }} resolution)
191+ run : uv sync --resolution ${{ matrix.resolution }}
192+
193+ # Newest adapters (built from HEAD) plus the TCK conformance battery, run
194+ # against the resolved core. `--no-sync` keeps the environment exactly as
195+ # the resolution step left it rather than re-resolving to the default edge.
196+ - name : Adapters + TCK conformance
197+ run : >
198+ uv run --no-sync pytest -q
199+ packages/dexpace-sdk-http-stdlib/tests
200+ packages/dexpace-sdk-http-httpx/tests
201+ packages/dexpace-sdk-http-aiohttp/tests
202+ packages/dexpace-sdk-http-requests/tests
203+ packages/dexpace-sdk-tck/src/dexpace/sdk/tck
204+
205+ # The petstore canary is the "generated at vN.M runs on vN.x" proof: a
206+ # generated SDK driven end to end over the certified core. Running it at
207+ # both dependency edges proves the generated output keeps working against
208+ # both head core and the oldest supported core.
209+ - name : Petstore canary
210+ run : uv run --no-sync pytest -q examples/petstore/tests
211+
212+ free-threaded :
213+ name : free-threaded CPython (${{ matrix.python-version }})
214+ runs-on : ubuntu-latest
215+ strategy :
216+ fail-fast : false
217+ matrix :
218+ python-version : ["3.13t", "3.14t"]
219+ # Exercises the codebase's explicit-lock claims (no GIL assumption) on a real
220+ # GIL-disabled interpreter, not just under code review. Only the
221+ # lock-sensitive core suites run here, and each spins real threads: the
222+ # single-use body once-guards, the single-flight token cache, the
223+ # ContextStore cap-drain, the emit-once logger latch, and the SSE
224+ # cross-thread close. Core is pure Python (only furl), so the environment
225+ # holds core plus the test runner alone -- no C-extension transports, which
226+ # do not yet ship free-threaded wheels.
227+ env :
228+ # Fail loudly if an imported extension would force the GIL back on rather
229+ # than silently re-enabling it and voiding the exercise.
230+ PYTHON_GIL : " 0"
231+ steps :
232+ - name : Checkout
233+ uses : actions/checkout@v6
234+
235+ - name : Install uv
236+ uses : astral-sh/setup-uv@v8.2.0
237+ with :
238+ enable-cache : true
239+
240+ - name : Install free-threaded interpreter
241+ run : uv python install ${{ matrix.python-version }}
242+
243+ - name : Create free-threaded environment
244+ run : uv venv --python ${{ matrix.python-version }} .venv-ft
245+
246+ # `--only-binary hypothesis` uses its universal wheel instead of building
247+ # its sdist, whose build pulls a Rust/PyO3 toolchain with no free-threaded
248+ # support yet. hypothesis is imported by the core test conftest.
249+ - name : Install core + test runner
250+ run : >
251+ uv pip install --python .venv-ft --only-binary hypothesis
252+ ./packages/dexpace-sdk-core
253+ pytest pytest-asyncio hypothesis
254+
255+ - name : Verify the interpreter is GIL-disabled
256+ run : .venv-ft/bin/python -c "import sys; assert not sys._is_gil_enabled(), 'GIL is enabled'; print(sys.version)"
257+
258+ - name : Run lock-sensitive suites (GIL disabled)
259+ run : >
260+ .venv-ft/bin/python -m pytest -q
261+ packages/dexpace-sdk-core/tests/http/test_single_use_bodies.py
262+ packages/dexpace-sdk-core/tests/auth/test_token_cache.py
263+ packages/dexpace-sdk-core/tests/context/test_context_store.py
264+ packages/dexpace-sdk-core/tests/instrumentation/test_logger.py
265+ packages/dexpace-sdk-core/tests/sse/test_connection_lifecycle.py
266+
267+ version-source :
268+ name : single version train
269+ runs-on : ubuntu-latest
270+ # One version train for every published package (NFR-14): the version lives
271+ # once in each pyproject and is mirrored in uv.lock, and a built wheel
272+ # carries that real version -- never a placeholder such as 0.0.0 or
273+ # "unknown" (NFR-15). A bump touches all published packages together or this
274+ # gate fails.
275+ steps :
276+ - name : Checkout
277+ uses : actions/checkout@v6
278+
279+ - name : Install uv
280+ uses : astral-sh/setup-uv@v8.2.0
281+ with :
282+ enable-cache : true
283+ python-version : " 3.12"
284+
285+ - name : Build a wheel to inspect its metadata version
286+ run : uv build --package dexpace-sdk-core --wheel -o dist
287+
288+ - name : Verify one version train (pyproject + uv.lock + wheel)
289+ run : uv run --no-project python tools/check_version_train.py --wheel-dir dist
0 commit comments