Repository navigation
972 lines (943 loc) · 49.7 KB
/
Copy pathci.yml
File metadata and controls
972 lines (943 loc) · 49.7 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
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
# ADR 0007 rule 3 (docs/adr/0007-dependency-policy.md, #948): PR-gating jobs
# below install with `-c python/constraints/<combo>-py3.NN.txt` so a PR's
# result doesn't change because of what an upstream package released that
# day — only because of what the PR itself changed. One constraints file per
# job's own exact extras/dev-group/python combo, NEVER a file merged across
# combos: `openai-agents`, `google-adk`, `crewai` and `llama-index-llms-openai`
# all put ceilings on packages (`websockets`, `regex`, `openai`) that other
# combos resolve past, so a merged file is unsatisfiable for those jobs (see
# scripts/compile_constraints.py's module docstring). The jobs whose purpose
# IS a fresh resolve (`all-extra-resolves`, `anthropic-stacks`, `adk-stacks`,
# and everything in nightly-matrix.yml) deliberately keep resolving fresh —
# they are the canary ADR 0007 rule 4 relies on. Refresh the lock with
# `python scripts/compile_constraints.py` (see CONTRIBUTING.md), or merge the
# PR lock-refresh.yml opens after each green nightly (ADR 0011, #763); keep
# `scripts/compile_constraints.py`'s `_COMBOS` in sync with the jobs below —
# each entry's `extras`/`dev_group` must match that job's install line exactly.
name: CI
# #755: `develop` too, so two individually green PRs that combine into a broken
# `develop` fail at merge time instead of at promotion to `main`. No
# `merge_group:` trigger: the repo has no merge queue.
on:
push:
branches: [main, develop]
pull_request:
# Least privilege: this workflow only reads the checkout, never writes back.
permissions:
contents: read
# #761: a new push to a PR supersedes its previous run, so cancel that run
# instead of finishing all of its jobs. Pushes to a branch are never cancelled:
# every merge commit there gets a complete result.
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
# #1005: pip caches are restored on every run but saved only by pushes to
# main/develop (the `actions/cache/save` step after each install, guarded on
# github.event_name). A PR's own cache is scoped to refs/pull/<N>/merge, so no
# other PR could read it; PRs restore what develop saved instead. The key is
# pip-<os>-py<minor>-<job>-<hash of the job's constraints file> (pyproject.toml
# for the fresh-resolve jobs): the minor, not the patch, so runners on
# different patches of one minor share an entry (#1004). It holds downloaded
# wheels only, so a fresh-resolve job still resolves against the index.
defaults:
run:
working-directory: python
# #765: each job's checks are the python/noxfile.py session of the same name, so
# `nox -s <job>` runs locally exactly what the job runs here (CONTRIBUTING.md,
# "The pre-PR gate"). The job installs into the runner's Python, where its lock
# and cache steps live, then runs that session in place: --no-venv uses the
# job's Python, --no-install skips the session's own installs. pipx keeps nox
# and its dependencies out of the job's environment. Change a check in the
# session, not here; tests/unit/test_noxfile.py keeps the two in step.
env:
NOX_VERSION: "2026.8.17"
jobs:
base-only:
# Working instruction #9: install ONLY the base package and run the base
# suite to catch accidental top-level framework imports. The installed-wheel
# check runs afterward in its own environment.
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.11"
- id: pip-cache
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ~/.cache/pip
key: pip-${{ runner.os }}-py3.11-${{ github.job }}-${{ hashFiles('python/constraints/base-only-py3.11.txt') }}
restore-keys: pip-${{ runner.os }}-py3.11-${{ github.job }}-
# -c locks the resolve so an upstream release can't turn this job red on
# its own (ADR 0007 rule 3, #948); refresh with scripts/compile_constraints.py.
- run: python -m pip install --upgrade pip && pip install -e . --group dev -c constraints/base-only-py3.11.txt
- if: github.event_name == 'push' && steps.pip-cache.outputs.cache-hit != 'true'
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ~/.cache/pip
key: ${{ steps.pip-cache.outputs.cache-primary-key }}
- name: base import, no pydantic (#730), tests/unit, installed-wheel fixtures (BG §1.4)
run: pipx run --spec "nox==$NOX_VERSION" nox --no-venv --no-install -s base-only
all-extra-resolves:
# #697: `[all]` once bundled framework extras that are not co-installable
# upstream, and no job ever resolved it, so `pip install "donkey-kit[all]"`
# shipped broken (resolution-too-deep). Dry-run it with a current pip on every
# supported Python; `timeout` turns a resolver that backtracks for minutes
# into a failure instead of a hung job. The nightly matrix repeats this to
# catch upstream releases between PRs.
runs-on: ubuntu-latest
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: ${{ matrix.python-version }}
- id: pip-cache
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ~/.cache/pip
key: pip-${{ runner.os }}-py${{ matrix.python-version }}-${{ github.job }}-${{ hashFiles('python/pyproject.toml') }}
restore-keys: pip-${{ runner.os }}-py${{ matrix.python-version }}-${{ github.job }}-
- run: python -m pip install --upgrade pip
- name: pip resolves donkey-kit[all]
run: pipx run --spec "nox==$NOX_VERSION" nox --no-venv --no-install -s all-extra-resolves
- if: github.event_name == 'push' && steps.pip-cache.outputs.cache-hit != 'true'
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ~/.cache/pip
key: ${{ steps.pip-cache.outputs.cache-primary-key }}
typecheck-and-lint:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.11"
- id: pip-cache
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ~/.cache/pip
key: pip-${{ runner.os }}-py3.11-${{ github.job }}-${{ hashFiles('python/constraints/typecheck-and-lint-py3.11.txt') }}
restore-keys: pip-${{ runner.os }}-py3.11-${{ github.job }}-
# -c locks the resolve (ADR 0007 rule 3, #948); refresh with scripts/compile_constraints.py.
- run: python -m pip install --upgrade pip && pip install -e ".[llm,cli]" --group dev -c constraints/typecheck-and-lint-py3.11.txt
- if: github.event_name == 'push' && steps.pip-cache.outputs.cache-hit != 'true'
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ~/.cache/pip
key: ${{ steps.pip-cache.outputs.cache-primary-key }}
# mypy --strict (working instruction #8) and over each python/examples
# package but LangGraph (#721, checked in langgraph-demo), ruff check and
# ruff format --check (#765), import-linter (§1.1), the verification-claims
# (#718), doc-link (#785) and fixture-scrub (#821) scripts, and vulture (#720).
- name: types, lint, format, import contracts, repo checks, dead code
run: pipx run --spec "nox==$NOX_VERSION" nox --no-venv --no-install -s typecheck-and-lint
new-dependencies:
# #936: a direct dependency new to this PR must exist on PyPI, so a
# hallucinated or typosquatted name fails here instead of at install time.
# The allowlist itself is checked offline by test_house_style_config.py;
# this job adds the network check a unit test cannot make. The script is
# stdlib-only and exits early when no name is new, so it needs no install
# and no path filter.
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
timeout-minutes: 10
env:
# Bind the base ref to an env var so it is never interpolated into a
# run: body directly (injection hardening).
BASE_REF: ${{ github.base_ref }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.11"
- name: fetch the base branch's pyproject.toml
# A base without python/pyproject.toml counts as declaring nothing.
run: |
git fetch --no-tags --depth=1 origin "$BASE_REF"
git show "FETCH_HEAD:python/pyproject.toml" > "$RUNNER_TEMP/base-pyproject.toml" || true
- name: new direct dependencies exist on PyPI (#936)
run: pipx run --spec "nox==$NOX_VERSION" nox --no-venv --no-install -s new-dependencies -- --base "$RUNNER_TEMP/base-pyproject.toml"
commit-identities:
# #1048: a squash merge turns every PR-commit author into a Co-authored-by
# trailer on develop, and GitHub credits whoever claimed that email. A
# placeholder like `x <x@x>` credited an unrelated account and could only be
# removed by rewriting develop, so it fails here, before the merge. The
# script is stdlib-only and needs no install.
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
timeout-minutes: 5
env:
# Bind the SHAs to env vars so they are never interpolated into a run:
# body directly (injection hardening).
BASE_SHA: ${{ github.event.pull_request.base.sha }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
# Full history, so BASE_SHA..HEAD_SHA resolves.
fetch-depth: 0
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.11"
- name: no placeholder git identities in the PR's commits (#1048)
run: pipx run --spec "nox==$NOX_VERSION" nox --no-venv --no-install -s commit-identities -- "$BASE_SHA..$HEAD_SHA"
test:
runs-on: ubuntu-latest
timeout-minutes: 15
strategy:
# #761: one failing Python version must not cancel the others; the matrix
# exists to show which versions fail.
fail-fast: false
matrix:
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
env:
# Bind the matrix value to an env var so it is never interpolated into a
# run: body directly (injection hardening); also names this job's lock.
PYTHON_VERSION: ${{ matrix.python-version }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: ${{ matrix.python-version }}
- id: pip-cache
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ~/.cache/pip
key: pip-${{ runner.os }}-py${{ matrix.python-version }}-${{ github.job }}-${{ hashFiles(format('python/constraints/test-py{0}.txt', matrix.python-version)) }}
restore-keys: pip-${{ runner.os }}-py${{ matrix.python-version }}-${{ github.job }}-
# [local] here (and only here) so the simulator's ASGI tests execute; the
# base-only and typecheck jobs deliberately omit it (importorskip guards
# skip the ASGI tests there, proving import donkey_kit.simulator stays
# green without starlette/uvicorn). [otel] likewise: the GenAI-span
# assertions (BG §1.6, #192) importorskip opentelemetry.sdk, so they only
# actually execute in this job — the typecheck job stays [llm,cli] and
# relies on the absence-only opentelemetry mypy override. [a2a] the same
# way: donkey.serve()'s round-trip tests (#224) importorskip a2a.
# -c locks the resolve per Python version (ADR 0007 rule 3, #948);
# refresh with scripts/compile_constraints.py (combo "test").
- run: >-
python -m pip install --upgrade pip &&
pip install -e ".[llm,cli,local,otel,a2a]" --group dev -c "constraints/test-py${PYTHON_VERSION}.txt"
- if: github.event_name == 'push' && steps.pip-cache.outputs.cache-hit != 'true'
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ~/.cache/pip
key: ${{ steps.pip-cache.outputs.cache-primary-key }}
# The default suite, the local gateway simulator's boot (BG §1.4), the
# offline seams under a dead HTTP(S)_PROXY (#801), the default suite again
# under a dead proxy and OTLP collector (#747), and on the 3.11 leg only,
# branch coverage (#751) with its report in the job summary and its floor
# (`fail_under`, pyproject.toml). Under --no-venv the session is named
# `test` and reads the leg's version from the interpreter.
- name: suites, offline seams, coverage on 3.11 (nox -s test)
run: pipx run --spec "nox==$NOX_VERSION" nox --no-venv --no-install -s test
openai-1x:
# #769: the `llm` extra's floor is openai>=1.66, but every other job
# resolves the 2.x line, so nothing tested the 1.x releases the floor
# allows. This job adds `"openai<2"` to the `test` job's install line and
# runs the same default and local_gateway suites. The pin is this job's
# only: the extra keeps its floor and no ceiling (§8.4). Locked like the
# other PR jobs (combo "openai-1x"); the nightly `lowest-direct` job covers
# the floor itself.
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.12"
- id: pip-cache
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ~/.cache/pip
key: pip-${{ runner.os }}-py3.12-${{ github.job }}-${{ hashFiles('python/constraints/openai-1x-py3.12.txt') }}
restore-keys: pip-${{ runner.os }}-py3.12-${{ github.job }}-
# -c locks the resolve (ADR 0007 rule 3, #948); refresh with scripts/compile_constraints.py.
- run: >-
python -m pip install --upgrade pip &&
pip install -e ".[llm,cli,local,otel]" --group dev "openai<2" -c constraints/openai-1x-py3.12.txt
- if: github.event_name == 'push' && steps.pip-cache.outputs.cache-hit != 'true'
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ~/.cache/pip
key: ${{ steps.pip-cache.outputs.cache-primary-key }}
# openai resolved to a 1.x release, then the default suite and the local
# gateway simulator's boot (BG §1.4), as the `test` job runs them.
- name: openai is 1.x; default and local_gateway suites
run: pipx run --spec "nox==$NOX_VERSION" nox --no-venv --no-install -s openai-1x
anthropic-stacks:
# #701: anthropic>=1.0 is built on httpx2 and rejects the shared httpx client,
# so donkey.anthropic hands it a bridged httpx2 client instead, while
# anthropic<1 still gets the shared client itself. No other job installs
# anthropic, so its tests run on both majors here. The `<1` pin tests the
# older major only; the extra keeps its floor and no ceiling (§8.4). The
# typed-refusal bridge case for the Anthropic client runs here too (#983).
runs-on: ubuntu-latest
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
anthropic: ["anthropic>=1", "anthropic<1"]
name: anthropic-stacks (${{ matrix.anthropic }})
env:
# Bind the matrix value to an env var so it is never interpolated into a
# run: body directly (injection hardening).
ANTHROPIC_SPEC: ${{ matrix.anthropic }}
# #748: this job installs anthropic for real, so an
# unexpected `pytest.importorskip("anthropic")` elsewhere must fail here,
# not silently skip (see tests/conftest.py).
DONKEY_CONTRACT_EXTRA: anthropic
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.12"
- id: pip-cache
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ~/.cache/pip
key: pip-${{ runner.os }}-py3.12-${{ github.job }}-${{ hashFiles('python/pyproject.toml') }}
restore-keys: pip-${{ runner.os }}-py3.12-${{ github.job }}-
- run: python -m pip install --upgrade pip && pip install -e ".[llm,anthropic]" --group dev "$ANTHROPIC_SPEC"
- if: github.event_name == 'push' && steps.pip-cache.outputs.cache-hit != 'true'
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ~/.cache/pip
key: ${{ steps.pip-cache.outputs.cache-primary-key }}
- run: pipx run --spec "nox==$NOX_VERSION" nox --no-venv --no-install -s "anthropic-stacks($ANTHROPIC_SPEC)"
adk-stacks:
# #735: below google-adk 2.4, Gemini silently drops client_kwargs and
# donkey.adk.gemini() would bypass the gateway; it now refuses instead. No
# other job installs google-adk, so the real-ADK round-trip and redirect
# tests run here, at the extra's floor and at the newest release, with the
# typed-refusal bridge case for adk.gemini() (#955). The `==` pin tests the
# floor only; the extra keeps no ceiling (§8.4).
runs-on: ubuntu-latest
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
adk: ["google-adk>=2.4", "google-adk==2.4.0"]
name: adk-stacks (${{ matrix.adk }})
env:
# Bind the matrix value to an env var so it is never interpolated into a
# run: body directly (injection hardening).
ADK_SPEC: ${{ matrix.adk }}
# #748: this job installs google-adk for real, so an
# unexpected `pytest.importorskip` on it elsewhere must fail here, not
# silently skip (see tests/conftest.py).
DONKEY_CONTRACT_EXTRA: adk
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.12"
- id: pip-cache
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ~/.cache/pip
key: pip-${{ runner.os }}-py3.12-${{ github.job }}-${{ hashFiles('python/pyproject.toml') }}
restore-keys: pip-${{ runner.os }}-py3.12-${{ github.job }}-
- run: python -m pip install --upgrade pip && pip install -e ".[llm,adk]" --group dev "$ADK_SPEC"
- if: github.event_name == 'push' && steps.pip-cache.outputs.cache-hit != 'true'
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ~/.cache/pip
key: ${{ steps.pip-cache.outputs.cache-primary-key }}
- run: pipx run --spec "nox==$NOX_VERSION" nox --no-venv --no-install -s "adk-stacks($ADK_SPEC)"
agent-framework-middleware:
# #739: policy_middleware() is only meaningful on a real Agent Framework
# Agent, which no other job installs. Run its tests with the extra present,
# with the shared-client correlation and last_call tests (#740) and the
# typed-refusal bridge case for an Agent Framework Agent run (#983).
runs-on: ubuntu-latest
timeout-minutes: 10
env:
# #748: this job installs agent_framework for real, so an
# unexpected `pytest.importorskip` on it elsewhere must fail here, not
# silently skip (see tests/conftest.py).
DONKEY_CONTRACT_EXTRA: agent_framework
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.12"
- id: pip-cache
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ~/.cache/pip
key: pip-${{ runner.os }}-py3.12-${{ github.job }}-${{ hashFiles('python/constraints/agent-framework-middleware-py3.12.txt') }}
restore-keys: pip-${{ runner.os }}-py3.12-${{ github.job }}-
# -c locks the resolve (ADR 0007 rule 3, #948); refresh with scripts/compile_constraints.py.
- run: python -m pip install --upgrade pip && pip install -e ".[llm,agent_framework]" --group dev -c constraints/agent-framework-middleware-py3.12.txt
- if: github.event_name == 'push' && steps.pip-cache.outputs.cache-hit != 'true'
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ~/.cache/pip
key: ${{ steps.pip-cache.outputs.cache-primary-key }}
- run: pipx run --spec "nox==$NOX_VERSION" nox --no-venv --no-install -s agent-framework-middleware
llamaindex-transport:
# #740: LlamaIndex sends through the shared client, so its calls carry the
# run's correlation id, populate donkey.last_call and refuse as typed errors
# through typed_refusals(). No other job installs it, so run those tests here,
# with the typed-refusal bridge case for a FunctionAgent run (#955).
runs-on: ubuntu-latest
timeout-minutes: 10
env:
# #748: this job installs llamaindex for real, so an
# unexpected `pytest.importorskip` on it elsewhere must fail here, not
# silently skip (see tests/conftest.py).
DONKEY_CONTRACT_EXTRA: llamaindex
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.12"
- id: pip-cache
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ~/.cache/pip
key: pip-${{ runner.os }}-py3.12-${{ github.job }}-${{ hashFiles('python/constraints/llamaindex-transport-py3.12.txt') }}
restore-keys: pip-${{ runner.os }}-py3.12-${{ github.job }}-
# -c locks the resolve (ADR 0007 rule 3, #948); refresh with scripts/compile_constraints.py.
- run: python -m pip install --upgrade pip && pip install -e ".[llm,llamaindex]" --group dev -c constraints/llamaindex-transport-py3.12.txt
- if: github.event_name == 'push' && steps.pip-cache.outputs.cache-hit != 'true'
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ~/.cache/pip
key: ${{ steps.pip-cache.outputs.cache-primary-key }}
- run: pipx run --spec "nox==$NOX_VERSION" nox --no-venv --no-install -s llamaindex-transport
agents-strands-last-call:
# #899: the OpenAI Agents SDK and Strands send through the shared client, but
# no other job installs them, so their donkey.last_call tests would only
# skip. Run them with both extras present, with the typed-refusal bridge cases
# for an Agents SDK Runner run (#955) and a Strands Agent run (#983).
runs-on: ubuntu-latest
timeout-minutes: 10
env:
# #748: this job installs both frameworks for real, so an unexpected
# `pytest.importorskip` on either must fail here, not silently skip (see
# tests/conftest.py; a comma-separated list is how one job owns two).
DONKEY_CONTRACT_EXTRA: "openai-agents,strands"
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.12"
- id: pip-cache
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ~/.cache/pip
key: pip-${{ runner.os }}-py3.12-${{ github.job }}-${{ hashFiles('python/constraints/agents-strands-last-call-py3.12.txt') }}
restore-keys: pip-${{ runner.os }}-py3.12-${{ github.job }}-
# -c locks the resolve (ADR 0007 rule 3, #948); refresh with scripts/compile_constraints.py.
- run: python -m pip install --upgrade pip && pip install -e ".[llm,openai-agents,strands]" --group dev -c constraints/agents-strands-last-call-py3.12.txt
- if: github.event_name == 'push' && steps.pip-cache.outputs.cache-hit != 'true'
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ~/.cache/pip
key: ${{ steps.pip-cache.outputs.cache-primary-key }}
- run: pipx run --spec "nox==$NOX_VERSION" nox --no-venv --no-install -s agents-strands-last-call
adapter-contract:
# #742: the adapter contract suite, one leg per ADAPTERS extra, each with its
# real framework installed. DONKEY_CONTRACT_EXTRA turns that framework's
# skips into failures, so a broken install cannot pass as a skip. The matrix
# must list exactly the registry's extras: test_integration_checklist.py
# asserts it. The example conformance check loads examples/<fw>/main.py by
# path, so it does not depend on `python -m pytest` putting python/ on
# sys.path (#1014: the nightly runs plain `pytest`).
runs-on: ubuntu-latest
timeout-minutes: 15
strategy:
fail-fast: false
matrix:
extra:
- langgraph
- adk
- strands
- agent_framework
- openai-agents
- anthropic
- crewai
- llamaindex
name: adapter-contract (${{ matrix.extra }})
env:
# Bind the matrix value to an env var so it is never interpolated into a
# run: body directly (injection hardening).
DONKEY_CONTRACT_EXTRA: ${{ matrix.extra }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.12"
- id: pip-cache
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ~/.cache/pip
key: pip-${{ runner.os }}-py3.12-${{ github.job }}-${{ hashFiles(format('python/constraints/adapter-contract-{0}-py3.12.txt', matrix.extra)) }}
restore-keys: pip-${{ runner.os }}-py3.12-${{ github.job }}-
# Dev tooling is the `dev` dependency group (#943), not an extra.
# -c locks the resolve (ADR 0007 rule 3, #948), one file per matrix leg
# (combo "adapter-contract-<extra>"); refresh with scripts/compile_constraints.py.
- run: >-
python -m pip install --upgrade pip &&
pip install -e ".[llm,local,$DONKEY_CONTRACT_EXTRA]" --group dev
-c "constraints/adapter-contract-${DONKEY_CONTRACT_EXTRA}-py3.12.txt"
- if: github.event_name == 'push' && steps.pip-cache.outputs.cache-hit != 'true'
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ~/.cache/pip
key: ${{ steps.pip-cache.outputs.cache-primary-key }}
# #748: the whole suite (`python -m pytest -q`), not a file list, so every
# test that needs this leg's framework runs against the real install
# instead of skipping. For the deep adapters (BG §1.8), LangGraph and the
# OpenAI Agents SDK, that includes their refusal mapping, run correlation
# and conformance tests.
# Then the strict example smoke: build the example's native object and
# drive it through the conformance kit in-process, with no network.
- name: whole suite and strict example smoke
run: pipx run --spec "nox==$NOX_VERSION" nox --no-venv --no-install -s "adapter-contract($DONKEY_CONTRACT_EXTRA)"
benchmark:
# BG §1.6 / #194 AC #3: the GenAI span adds < 1 ms per governed call. The
# `benchmark`-marked test is deselected from the default run (addopts), so it
# runs here as its own blocking gate — a regression that makes span
# instrumentation expensive fails the build. `[otel]` for the real SDK
# TracerProvider it measures against; `-s` so the measured µs/call is visible
# in the job log even on a pass.
#
# #753: the fixed 1ms budget has ~55x headroom over the ~18us/call
# measured, so it would not catch a 10x regression on its own. Every run
# writes the measured result to a JSON file; on a `main` push only (never
# a PR) it is uploaded as an artifact and compared against the most recent
# earlier successful `main` run's artifact by
# scripts/compare_benchmark_trend.py, which fails this job on a regression
# at or past `--alert-threshold`. No gh-pages branch, no new third-party
# action.
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: read
# Read earlier runs' artifacts (compare_benchmark_trend.py).
actions: read
env:
# Job-level `env:` cannot use the `runner` context (an invalid-workflow
# error that would stop every job in this file), so the file sits in the
# workspace. `github.*` values are workflow-owned, not PR input.
DONKEY_BENCHMARK_JSON: ${{ github.workspace }}/benchmark-result.json
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.11"
- id: pip-cache
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ~/.cache/pip
key: pip-${{ runner.os }}-py3.11-${{ github.job }}-${{ hashFiles('python/constraints/benchmark-py3.11.txt') }}
restore-keys: pip-${{ runner.os }}-py3.11-${{ github.job }}-
# -c locks the resolve (ADR 0007 rule 3, #948); refresh with scripts/compile_constraints.py.
- run: python -m pip install --upgrade pip && pip install -e ".[llm,cli,local,otel]" --group dev -c constraints/benchmark-py3.11.txt
- if: github.event_name == 'push' && steps.pip-cache.outputs.cache-hit != 'true'
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ~/.cache/pip
key: ${{ steps.pip-cache.outputs.cache-primary-key }}
- name: GenAI span overhead < 1ms/call (BG §1.6, #194)
run: pipx run --spec "nox==$NOX_VERSION" nox --no-venv --no-install -s benchmark
- name: Upload the benchmark result for trending (#753)
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: benchmark-result
path: ${{ env.DONKEY_BENCHMARK_JSON }}
if-no-files-found: error
retention-days: 90
- name: Alert on a relative regression vs. the previous main run (#753)
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
env:
GH_TOKEN: ${{ github.token }}
REPO: ${{ github.repository }}
RUN_ID: ${{ github.run_id }}
run: >-
python scripts/compare_benchmark_trend.py
--current "$DONKEY_BENCHMARK_JSON"
--repo "$REPO" --workflow ci.yml --run-id "$RUN_ID"
--alert-threshold 2.0
quickstart:
# #203 / BG §1.4: the gateway-free 15-minute quickstart, executed on a clean
# runner so the documented first-run experience can never silently rot. No
# Anypoint credentials and no real gateway — examples/quickstart/main.py
# boots the local simulator in-process, so this proves `pip install` →
# typed refusal works end to end with nothing but the published extras.
# The example is the exact code the docs quickstart shows; keeping it in CI
# is what stops the two from drifting. `timeout` bounds wall-clock so a hang
# or a slow regression fails rather than blocking.
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.11"
- id: pip-cache
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ~/.cache/pip
key: pip-${{ runner.os }}-py3.11-${{ github.job }}-${{ hashFiles('python/constraints/quickstart-py3.11.txt') }}
restore-keys: pip-${{ runner.os }}-py3.11-${{ github.job }}-
# -c locks the resolve (ADR 0007 rule 3, #948); refresh with scripts/compile_constraints.py.
- run: pip install -e ".[llm,local,otel]" -c constraints/quickstart-py3.11.txt
- if: github.event_name == 'push' && steps.pip-cache.outputs.cache-hit != 'true'
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ~/.cache/pip
key: ${{ steps.pip-cache.outputs.cache-primary-key }}
- name: run the gateway-free quickstart (must exit 0, no credentials)
run: pipx run --spec "nox==$NOX_VERSION" nox --no-venv --no-install -s quickstart
langgraph-demo:
# #199 / BG §1.8: the Phase-1 acceptance artefact — Scenario A end to end
# against the simulator only. examples/langgraph/main.py triages five support
# tickets through a real two-node LangGraph graph, blocks the PII-laden one as
# a typed PIIDetected (the pii_block:every=5 scenario), and exports every
# call's span to a bundled local OTLP collector (zero-config export, #194).
# Timed on a clean runner (no credentials, no gateway) so the demo can never
# silently rot, and the same build() factory is re-run through the
# customer-facing conformance plugin — the exact `--donkey-conformance`
# invocation a team runs against its own agent. The dev group for pytest + the
# conformance plugin entry point.
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.11"
- id: pip-cache
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ~/.cache/pip
key: pip-${{ runner.os }}-py3.11-${{ github.job }}-${{ hashFiles('python/constraints/langgraph-demo-py3.11.txt') }}
restore-keys: pip-${{ runner.os }}-py3.11-${{ github.job }}-
# -c locks the resolve (ADR 0007 rule 3, #948); refresh with scripts/compile_constraints.py.
- run: python -m pip install --upgrade pip && pip install -e ".[llm,langgraph,local,otel]" --group dev -c constraints/langgraph-demo-py3.11.txt
- if: github.event_name == 'push' && steps.pip-cache.outputs.cache-hit != 'true'
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ~/.cache/pip
key: ${{ steps.pip-cache.outputs.cache-primary-key }}
# mypy --strict over examples.langgraph with langgraph installed (#721),
# the demo under a 120 s bound, then conformance against its build()
# factory: the `--donkey-conformance` invocation a team runs on its agent.
- name: typecheck, run, and conformance-test the Scenario-A demo
run: pipx run --spec "nox==$NOX_VERSION" nox --no-venv --no-install -s langgraph-demo
openai-agents-demo:
# #223 / BG §1.8: the second deep adapter's worked demo, the counterpart of
# langgraph-demo. examples/openai_agents/main.py triages the same five
# support tickets through a real agents.Agent under Runner.run against the
# simulator only, blocks the PII-laden one as a typed PIIDetected (the
# pii_block:every=5 scenario on /chat/completions), and exports every call's
# span to a bundled local OTLP collector. Timed on a clean runner, then the
# same build() factory is re-run through the customer-facing conformance
# plugin. Unlike langgraph-demo there is no separate mypy step: `agents.*`
# stays in pyproject's follow_imports = "skip" list (src/ imports it
# statically), so typecheck-and-lint already checks examples.openai_agents.
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.11"
- id: pip-cache
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ~/.cache/pip
key: pip-${{ runner.os }}-py3.11-${{ github.job }}-${{ hashFiles('python/constraints/openai-agents-demo-py3.11.txt') }}
restore-keys: pip-${{ runner.os }}-py3.11-${{ github.job }}-
# -c locks the resolve (ADR 0007 rule 3, #948); refresh with scripts/compile_constraints.py.
- run: python -m pip install --upgrade pip && pip install -e ".[llm,openai-agents,local,otel]" --group dev -c constraints/openai-agents-demo-py3.11.txt
- if: github.event_name == 'push' && steps.pip-cache.outputs.cache-hit != 'true'
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ~/.cache/pip
key: ${{ steps.pip-cache.outputs.cache-primary-key }}
# The demo under a 120 s bound, then conformance against its build()
# factory: the `--donkey-conformance` invocation a team runs on its agent.
- name: run and conformance-test the Scenario-A demo
run: pipx run --spec "nox==$NOX_VERSION" nox --no-venv --no-install -s openai-agents-demo
# The nightly framework matrix (§8.4) is .github/workflows/nightly-matrix.yml.
# It resolves fresh against the newest upstream releases: `matrix` re-checks
# LangGraph and the OpenAI Agents SDK, the conformance-tested adapters,
# against §8 signatures, and
# `framework-legs` runs every ADAPTERS entry (#748). A red run opens or
# updates one `nightly-failure` tracking issue (#755).
docs-llms-drift:
# #205 / BG §1.10: llms.txt, llms-full.txt, and the per-page .md files are
# generated from the Nextra pages (ordering + titles from _meta.js, content
# from each .mdx + its frontmatter — the single source of truth). Regenerate
# here and fail on any diff, lockfile-style, so the committed copies can
# never drift from the pages. The generator uses only Node built-ins, so no
# npm install is needed.
runs-on: ubuntu-latest
timeout-minutes: 10
defaults:
run:
working-directory: website
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
with:
node-version: 24
# npm test (the analytics beacon gate and the reader-facing link ref), then
# regenerate and fail on any diff under public/. The session lives in
# python/noxfile.py with the others; it runs node, npm and git only.
- name: website unit tests; generated llms artifacts are current
run: pipx run --spec "nox==$NOX_VERSION" nox -f ../python/noxfile.py --no-venv --no-install -s docs-llms-drift
docs-site:
# #759: build, type-check and link-check the Nextra site on PRs, so broken
# MDX, a bad import or a dead internal link fails here instead of in the
# release deploy (docs.yml builds only from main). Job-level path filter:
# a workflow-level `paths:` would skip the required `ci-ok` aggregate, so
# the job always starts and its steps no-op when the PR leaves the site
# alone. On pushes to main/develop it always runs.
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: read
defaults:
run:
working-directory: website
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
# Two commits, so HEAD^1 (the base tip of a PR merge commit) exists.
fetch-depth: 2
- name: does this change touch the site?
id: scope
working-directory: .
run: |
if [ "$GITHUB_EVENT_NAME" != "pull_request" ]; then
echo "run=true" >> "$GITHUB_OUTPUT"
exit 0
fi
if git diff --name-only HEAD^1 HEAD | grep -qE '^(website/|\.github/workflows/(ci|docs)\.yml$)'; then
echo "run=true" >> "$GITHUB_OUTPUT"
else
echo "run=false" >> "$GITHUB_OUTPUT"
echo "No website/** or docs workflow changes; skipping the site checks."
fi
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
if: steps.scope.outputs.run == 'true'
with:
# No `cache: npm`: setup-* built-in caching saves from PRs too, which
# #1005 rules out for ci.yml. The install is a few seconds.
node-version: 24
# npm ci, type-check, lint, the static export built with the project-prefix
# base path (as preview:pages does), then its internal links and anchors.
# The session installs node_modules itself (npm ci), so the job has no
# install step, and it hands the token to the build alone.
- name: npm ci, type-check, lint, build, check internal links (nox -s docs-site)
if: steps.scope.outputs.run == 'true'
run: pipx run --spec "nox==$NOX_VERSION" nox -f ../python/noxfile.py --no-venv --no-install -s docs-site
env:
# For prebuild's data/roadmap.json refresh (GitHub API).
GITHUB_TOKEN: ${{ github.token }}
pip-audit:
# #757: known vulnerabilities in what a consumer installs today, blocking.
# The install is resolved fresh (no -c): the question is what
# `pip install donkey-kit` / `donkey-kit[all]` pulls in now, not what the
# PR lock pinned (ADR 0007 rule 3 governs the lock; this job is an audit,
# like all-extra-resolves is a resolve check). pip only resolves (--dry-run
# --report), so nothing is installed; the pinned set, minus donkey-kit
# itself, goes to pip-audit with --no-deps. nightly-matrix.yml repeats this
# so a new advisory reaches a human between PRs.
name: pip-audit (${{ matrix.target }})
runs-on: ubuntu-latest
timeout-minutes: 15
strategy:
fail-fast: false
matrix:
include:
- target: base
spec: "."
- target: all
spec: ".[all]"
env:
# Bind the matrix value to an env var so it is never interpolated into a
# run: body directly (injection hardening).
SPEC: ${{ matrix.spec }}
# Names this leg's nox session, pip-audit($TARGET).
TARGET: ${{ matrix.target }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.12"
- run: python -m pip install --upgrade pip
# The session resolves $SPEC into the exact pin list, minus donkey-kit, at
# the path given after `--`; the action below audits that list. Run
# without a path, as locally, the session audits the list itself.
- name: resolve the install into an exact pin list
run: >-
pipx run --spec "nox==$NOX_VERSION" nox --no-venv --no-install -s "pip-audit($TARGET)"
-- "$RUNNER_TEMP/pip-audit-requirements.txt"
- uses: pypa/gh-action-pip-audit@1220774d901786e6f652ae159f7b6bc8fea6d266 # v1.1.0
with:
inputs: ${{ runner.temp }}/pip-audit-requirements.txt
no-deps: true
dependency-review:
# #757 (from #233): flag a PR that adds a dependency with a known
# vulnerability, or under a licence incompatible with Apache-2.0, at review
# time. Reads the dependency graph diff between base and head, so it needs
# no checkout. PRs only: there is no diff to review on a push. The one job
# that calls no nox session: with no checkout there is no noxfile, and the
# graph diff exists only on GitHub (python/noxfile.py's dependency-review
# session skips, saying so; docs/ci.md convention 5).
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/dependency-review-action@a1d282b36b6f3519aa1f3fc636f609c47dddb294 # v5.0.0
with:
deny-licenses: GPL-2.0-only, GPL-2.0-or-later, GPL-3.0-only, GPL-3.0-or-later, AGPL-3.0-only, AGPL-3.0-or-later
workflow-lint:
# #757: actionlint (workflow syntax, expressions, shellcheck over run:
# bodies) and zizmor (Actions security audits: unpinned or impostor uses:,
# excessive permissions, template injection, persisted credentials) over
# .github/. Both tools are fetched at a pinned version and verified by
# sha256 before they run, the same way secret-scan.yml fetches gitleaks.
runs-on: ubuntu-latest
timeout-minutes: 10
defaults:
run:
working-directory: .
env:
ACTIONLINT_VERSION: "1.7.12"
# sha256 of actionlint_${ACTIONLINT_VERSION}_linux_amd64.tar.gz, from the release's checksums.txt
ACTIONLINT_SHA256: "8aca8db96f1b94770f1b0d72b6dddcb1ebb8123cb3712530b08cc387b349a3d8"
ZIZMOR_VERSION: "1.30.1"
# sha256 of zizmor-${ZIZMOR_VERSION}-py3-none-manylinux_2_28_x86_64.whl, from PyPI
ZIZMOR_SHA256: "eee12266b793cb87ad4a7e3af2e72404f8a63e3de5eb099b80bf7b1cfd232a8e"
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.12"
- name: install actionlint
working-directory: ${{ runner.temp }}
run: |
tarball="actionlint_${ACTIONLINT_VERSION}_linux_amd64.tar.gz"
curl -fsSLo "$tarball" \
"https://github.com/rhysd/actionlint/releases/download/v${ACTIONLINT_VERSION}/${tarball}"
echo "${ACTIONLINT_SHA256} ${tarball}" | sha256sum -c -
tar xzf "$tarball" actionlint
sudo install actionlint /usr/local/bin/actionlint
- name: install zizmor
run: |
echo "zizmor==${ZIZMOR_VERSION} --hash=sha256:${ZIZMOR_SHA256}" > "$RUNNER_TEMP/zizmor.txt"
python -m pip install --require-hashes --no-deps -r "$RUNNER_TEMP/zizmor.txt"
# The two installs above are this job's alone (tests/unit/test_noxfile.py
# lists them as CI-only); the session runs the tools on PATH.
- name: actionlint and zizmor over .github/ (nox -s workflow-lint)
env:
# Read-only token for zizmor's online audits (impostor commits,
# known-vulnerable actions); the session withholds it from actionlint.
GH_TOKEN: ${{ github.token }}
run: pipx run --spec "nox==$NOX_VERSION" nox -f python/noxfile.py --no-venv --no-install -s workflow-lint
ci-ok:
# #755: the one required status check. Branch protection requires only this
# job, so renaming a job or changing a matrix never silently drops a
# required check. `needs:` lists EVERY other job in this file (a new job is
# added here in the same PR; tests/unit/test_ci_workflows.py asserts it).
# `if: always()` so it still reports when a dependency failed: a skipped
# required check would otherwise count as passing. `skipped` is a pass
# (`new-dependencies` only runs on PRs); `failure` and `cancelled` are not.
if: always()
needs:
- base-only
- all-extra-resolves
- typecheck-and-lint
- new-dependencies
- commit-identities
- test
- openai-1x
- anthropic-stacks
- adk-stacks
- agent-framework-middleware
- llamaindex-transport
- agents-strands-last-call
- adapter-contract
- benchmark
- quickstart
- langgraph-demo
- openai-agents-demo
- docs-llms-drift
- docs-site
- pip-audit
- dependency-review
- workflow-lint
runs-on: ubuntu-latest
timeout-minutes: 10
defaults:
run:
# No checkout here, so python/ does not exist.
working-directory: .
steps:
- name: every CI job succeeded or was skipped
env:
NEEDS: ${{ toJSON(needs) }}
run: |
failed=$(jq -r 'to_entries[] | select(.value.result != "success" and .value.result != "skipped") | "\(.key): \(.value.result)"' <<<"$NEEDS")
if [ -n "$failed" ]; then
echo "::error::CI jobs did not succeed:"
echo "$failed"
exit 1
fi
echo "All CI jobs succeeded or were skipped."