Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
115 commits
Select commit Hold shift + click to select a range
7d1c214
Fix dependency CVEs and gate PRs, releases and a weekly scan
zeevmoney Sep 21, 2026
e5a88c1
Move httpserver_listen_address to conftest so the port is order-indep…
zeevmoney Sep 21, 2026
160f129
TEMP: revert permit/ to origin/main to isolate test_bulk_operations
zeevmoney Sep 21, 2026
f9b4857
Revert "TEMP: revert permit/ to origin/main to isolate test_bulk_oper…
zeevmoney Sep 21, 2026
95a1860
Document the resource instance ident format correctly
zeevmoney Sep 22, 2026
1e6b9e6
Fix the major correctness bugs and enable the xfail tests for 3.0.0
zeevmoney Sep 22, 2026
199c4be
Isolate the end-to-end tests and start the PDP with the env's own key
zeevmoney Sep 22, 2026
3d11c3a
Give the PDP time to warm up and ABAC policy time to propagate
zeevmoney Sep 22, 2026
e7ce61d
Remove dead code and dead dependencies for 3.0.0
zeevmoney Sep 22, 2026
c439afb
Skip only the ABAC decision assertions, with the evidence
zeevmoney Sep 22, 2026
98ea10a
Fix resource_relations.list() and document two backend contracts
zeevmoney Sep 22, 2026
e86f630
Tolerate rate limiting during test teardown
zeevmoney Sep 22, 2026
0865f96
Retry rate-limited requests instead of tolerating them
zeevmoney Sep 22, 2026
0146bb8
Make the rate-limit retry more patient
zeevmoney Sep 22, 2026
e33c160
Point the ABAC skip at PER-16209
zeevmoney Sep 22, 2026
5391be2
Make CheckQuery.context optional for type checkers
zeevmoney Sep 22, 2026
afc16f4
Migrate packaging, dependencies and CI to uv
zeevmoney Sep 23, 2026
9f795f8
Send the standard "Bearer" scheme in every Authorization header
zeevmoney Sep 23, 2026
306032f
Support Python 3.14 and raise dependency floors that no longer install
zeevmoney Sep 23, 2026
d6aa721
Ship a typed public surface with py.typed (PEP 561)
zeevmoney Sep 23, 2026
1696305
Reduce the ABAC skip to its ticket reference
zeevmoney Sep 23, 2026
76ed0ab
Format the ABAC skip
zeevmoney Sep 23, 2026
ba3ab05
Exclude pydantic 2 releases that bundle a vulnerable pydantic.v1
zeevmoney Sep 24, 2026
92f4e65
Say why setup.py still lists the type files in package_data
zeevmoney Sep 24, 2026
b4e7a69
Remove references to non-public code from comments
zeevmoney Sep 24, 2026
0e3bd01
Build the pydantic candidate grid with loops; drop a no-op mkdir
zeevmoney Sep 24, 2026
87b5f35
Accept audit logs without pdp_config_id or from the GENERIC engine
zeevmoney Sep 24, 2026
aae3163
Check that only GENERIC logs parse as GenericEngineDecisionLog
zeevmoney Sep 24, 2026
69e356a
Test that the blocking client keeps parity with the async one
zeevmoney Sep 24, 2026
027a107
Cover instance attributes and back-references in the parity test
zeevmoney Sep 24, 2026
8aea675
Bind the test HTTP server to a free port
zeevmoney Sep 24, 2026
e826d87
Mark credentialed tests e2e and run the rest offline
zeevmoney Sep 24, 2026
f6a6296
Delete the invite test's resource instance by resource:key
zeevmoney Sep 24, 2026
3d1008c
Send the facade's assign_role and unassign_role through users
zeevmoney Sep 24, 2026
bea1d74
Say the deprecated permit.api methods go away in 4.0
zeevmoney Sep 24, 2026
e69a717
Test resource actions and action groups offline
zeevmoney Sep 24, 2026
008b9c8
Drain the timeout tests' late requests before the next test
zeevmoney Sep 24, 2026
029a894
Check every DeprecationWarning in the facade test
zeevmoney Sep 24, 2026
db2daac
Pass models as well as dicts to the deprecated facade in its test
zeevmoney Sep 24, 2026
47fc662
Fail test_envs.py fast when its API keys are not set
zeevmoney Sep 24, 2026
0686201
Warn on import when permit runs on pydantic 1
zeevmoney Sep 24, 2026
6db7fdf
Document what permit 4.0 removes
zeevmoney Sep 24, 2026
ba653a2
Keep the warnings module out of permit's public names
zeevmoney Sep 24, 2026
d822667
Say how to show or silence the 4.0 deprecation warnings
zeevmoney Sep 24, 2026
a0158b1
Name pip-audit gaps in the audit report and Slack message
zeevmoney Sep 24, 2026
4333fa7
Run pip-audit on every tree without building a venv
zeevmoney Sep 24, 2026
c0352bc
Move the artifact and Slack actions to their Node 24 releases
zeevmoney Sep 24, 2026
b4f20fc
Run pre-commit without the Node 20 cache action
zeevmoney Sep 24, 2026
1f9723e
Drop the actionlint input the action does not declare
zeevmoney Sep 24, 2026
ff25ea9
Keep the SDK's pytest.ini out of the audit script tests
zeevmoney Sep 24, 2026
106fb42
Pin every job to the ubuntu-24.04 runner
zeevmoney Sep 24, 2026
5f87275
Post the audit PR comment the hashFiles guard always skipped
zeevmoney Sep 24, 2026
68f0917
Drop the private pip-audit cache directory
zeevmoney Sep 24, 2026
df74adc
Name only the tree in the Slack pip-audit gap line
zeevmoney Sep 24, 2026
f0314b2
Keep the Trivy install step from logging a scan warning
zeevmoney Sep 24, 2026
43a9da3
Correct two outdated comments in the Security workflow
zeevmoney Sep 24, 2026
e264607
Publish the package under Permit.io and use a fictional test user
zeevmoney Sep 25, 2026
c41a601
Share the offline test config and request helpers
zeevmoney Sep 25, 2026
5b4db08
Point the blocking client's deprecation warnings at the caller
zeevmoney Sep 25, 2026
ab6590c
Keep PYDANTIC_VERSION out of permit's public names
zeevmoney Sep 25, 2026
89225b1
Stop passing module globals when warning at a blocking call
zeevmoney Sep 25, 2026
09feaa3
Keep run_coroutine_sync's one-argument call and new names private
zeevmoney Sep 25, 2026
caa7258
Test the once-per-line warning and the no-caller case in a script
zeevmoney Sep 25, 2026
3948d0c
Note the private PYDANTIC_VERSION alias in the model header steps
zeevmoney Sep 25, 2026
26f9efc
Add offline regression tests for request bodies and API calls
zeevmoney Sep 25, 2026
d79fb47
Check permit/api/models.py against the live API schema
zeevmoney Sep 25, 2026
fd67abe
Accept wildcard relationship tuples and NATS PDP API keys
zeevmoney Sep 25, 2026
aef7f5a
Pin the PDP route of each single-object facts write
zeevmoney Sep 25, 2026
81325bc
Retry the schema download and exit 2 on any drift-check error
zeevmoney Sep 25, 2026
8b31c6c
Time-limit the Schema Drift jobs and post manual runs to Slack
zeevmoney Sep 25, 2026
8559e3e
Give the generator cutoff as a UTC time and correct the drift docs
zeevmoney Sep 25, 2026
1773379
Add an agent skill that migrates projects from permit 2.x to 3.0.0
zeevmoney Sep 27, 2026
89b956a
Add a guide for upgrading from permit 2.x to 3.0.0
zeevmoney Sep 27, 2026
e3a158b
Test the migration scanner, skill and guide offline
zeevmoney Sep 27, 2026
ba3b1f1
Link the migration guide and skill from the README
zeevmoney Sep 27, 2026
fd97907
Track the migration fixtures' .python-version files
zeevmoney Sep 27, 2026
feb4ecb
Resolve names by scope in the migration scanner
zeevmoney Sep 27, 2026
4d112e2
Ask before dropping await on the blocking client
zeevmoney Sep 27, 2026
c54d1fa
Treat compiled requirements files as locks in the scanner
zeevmoney Sep 27, 2026
84c504a
Correct the A4 advice for DetailedAuditLogModel.objects
zeevmoney Sep 27, 2026
9b01bdb
List certifi, sniffio and exceptiongroup under C2
zeevmoney Sep 27, 2026
b4b659f
Report T2 on names annotated with an SDK model
zeevmoney Sep 27, 2026
750172b
Flag warning filters written for 2.x's deprecation text
zeevmoney Sep 27, 2026
9a4ffef
Describe ContextStore.transform() as 2.x ran it
zeevmoney Sep 27, 2026
fead785
Stop the migration skill on any Python pin below 3.10
zeevmoney Sep 27, 2026
1c95fb0
Correct and complete the upgrade guide's details
zeevmoney Sep 27, 2026
a177719
Test the guide's code, commands and tables against 3.0
zeevmoney Sep 27, 2026
ef567ee
Align the remaining A3 and C2 mentions with the fixes
zeevmoney Sep 27, 2026
96db617
Accept 3.14's TypeError from asyncio.run in the A2 guide test
zeevmoney Sep 27, 2026
ea6caf1
Leave bytecode caches out of the fixture tracking test
zeevmoney Sep 27, 2026
b7ac9d6
Store the migration sample apps' dependency files as *.fixture
zeevmoney Sep 27, 2026
ce70476
Move the migration skill's tests to skills/tests
zeevmoney Sep 27, 2026
e01c59a
Describe permit 4.0 as a future release
zeevmoney Sep 27, 2026
fb55833
Merge the final PR #126 commit into the uv migration
zeevmoney Sep 28, 2026
e1652e2
Merge main into per-16221/uv-migration
zeevmoney Sep 28, 2026
f189142
Carry main's package metadata and requirements into pyproject
zeevmoney Sep 28, 2026
2375520
Pin packaging in the dev group
zeevmoney Sep 28, 2026
d76b95a
Check the runtime requirements in pyproject.toml, not requirements.txt
zeevmoney Sep 28, 2026
fadcd24
Name the uv command for regenerating the sync stubs
zeevmoney Sep 28, 2026
8dc7fa0
Move model generation from the Makefile to scripts/generate_models.sh
zeevmoney Sep 28, 2026
e676eb4
Run both CI script test files from uv.lock with their own pytest.ini
zeevmoney Sep 28, 2026
a9ebc71
Restore main's README sections and document the uv workflow
zeevmoney Sep 28, 2026
b1475d1
Scan all four audit trees compiled from pyproject.toml
zeevmoney Sep 28, 2026
d6c5960
Check the built wheel and sdist for type information on release
zeevmoney Sep 28, 2026
880f6ba
Run the compatibility and migration skill jobs on uv
zeevmoney Sep 28, 2026
37a5143
Say where the pre-commit job gets pre-commit from
zeevmoney Sep 28, 2026
f8b8911
Say that exclude-newer delays Dependabot security updates
zeevmoney Sep 28, 2026
3caa62c
Combine the CI and packaging halves of the uv catch-up
zeevmoney Sep 28, 2026
91e350d
Name the publish checksum and CI script tests in the uv docs
zeevmoney Sep 28, 2026
8198244
Explain how to lock a security fix inside the 7-day cooldown
zeevmoney Sep 28, 2026
74872ae
Keep Dependabot off pydantic and let it read the tomli pin
zeevmoney Sep 28, 2026
26f96cd
Note that 3.14.0 pre-releases match no pydantic line in the wheel
zeevmoney Sep 28, 2026
9a18d1a
Pin CI's uv in the dev group and make required-version a floor
zeevmoney Sep 28, 2026
bf21f3e
Keep Dependabot off uv_build and explain the 3.10-only audit
zeevmoney Sep 28, 2026
3351fbd
Check uv.lock with the uv on PATH instead of a second pinned copy
zeevmoney Sep 29, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 48 additions & 22 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -1,45 +1,71 @@
version: 2
updates:
# Python dependencies (requirements.txt + requirements-dev.txt).
# Python dependencies: pyproject.toml ([project].dependencies and the
# dependency groups) plus uv.lock, which Dependabot re-locks in the same PR.
#
# This package publishes open `>=` ranges rather than a lockfile, so a
# Dependabot PR here raises the *floor* consumers are allowed to install on,
# not just the version CI happens to resolve. That is the whole point: the
# floor is the exposure, and the audit gate in security.yml scans it
# explicitly.
- package-ecosystem: "pip"
# Consumers never see uv.lock -- this package publishes open `>=` ranges --
# so a Dependabot PR here raises the *floor* consumers are allowed to install
# on, not just the version CI happens to resolve. That is the whole point:
# the floor is the exposure, and the audit gate in security.yml scans it
# explicitly. pydantic's floors are the exception: they are kept by hand
# (see `ignore` below).
- package-ecosystem: "uv"
directory: "/"
schedule:
interval: "weekly"
day: "monday"
open-pull-requests-limit: 5
# REQUIRED, not cosmetic. With a setup.py present Dependabot classifies
# this project as a library and defaults to `widen`, which only relaxes
# upper bounds and would never raise a `>=` floor -- so the automation
# would silently never do the one thing this file exists to do.
# `increase` raises the lower bound instead.
# REQUIRED, not cosmetic. Left at `auto`, Dependabot may classify this
# published package as a library and default to `widen`, which only
# relaxes upper bounds and would never raise a `>=` floor -- so the
# automation would silently never do the one thing this file exists to
# do. `increase` raises the lower bound instead (and moves the exact `==`
# pins in the dev group).
versioning-strategy: increase
# Wait 7 days before proposing a release, 14 for a major. A brand-new
# version is the window in which a compromised or yanked package is most
# likely to still be live, and nothing here is urgent enough to need
# day-zero adoption. Security updates are exempt from cooldown by
# Dependabot and still arrive immediately.
# day-zero adoption. Security updates are exempt from this cooldown, but
# the `uv lock` Dependabot runs still applies exclude-newer = "7 days" from
# pyproject.toml, so a fix released less than 7 days ago cannot be locked
# and its update fails until the release is 7 days old. To take such a fix
# sooner, lock it by hand with an exclude-newer-package entry (see
# CONTRIBUTING.md, "Dependencies").
cooldown:
default-days: 7
semver-major-days: 14
groups:
minor-and-patch:
update-types: ["minor", "patch"]
ignore:
# pydantic is dual-supported on purpose: permit/utils/pydantic_version.py
# branches on PYDANTIC_VERSION and every model imports from either
# `pydantic` (v1) or `pydantic.v1` (v2 compat shim). A Dependabot major
# bump cannot reason about that and would silently propose dropping v1
# support, so majors are handled by hand. Minor/patch still flow through.
# pydantic is updated by hand, for every update type. The SDK supports
# both majors on purpose (permit/utils/pydantic_version.py), and
# [project].dependencies has one pydantic line per Python range, with
# floors and `!=` exclusions whose reasons are in the comments there.
# Dependabot cannot keep those lines correct:
# - it skips a requirement whose marker contains `<`, so the
# python_version < "3.13" line would never be updated;
# - on the lines it does update, it rewrites each `!=X.Y.*` exclusion as
# `!=X.Y.0.dev`, which lets the X.Y releases back in (as PR #130 did to
# the old requirements.txt);
# - a major bump would drop pydantic 1 support.
# An ignore with no update-types also stops Dependabot security updates
# for pydantic. The audit gate in security.yml scans the newest pydantic
# and its pydantic 1 and pydantic 2 floors (resolved for Python 3.10), and
# fails on a fixable advisory. The pydantic versions in uv.lock move with
# `uv lock --upgrade-package pydantic`.
#
# Removal gate: drop this entry once the SDK stops supporting pydantic v1.
# Removal gate: drop this entry once pydantic is one requirement with no
# `!=` exclusions and no `<` marker.
- dependency-name: "pydantic"
update-types: ["version-update:semver-major"]
# uv_build, the build backend in [build-system], is updated by hand
# together with the uv version and checksum that python-sdk-publish.yml's
# build job pins. That uv builds releases with its own built-in backend
# only while the uv_build bound allows its version. A Dependabot PR that
# raised the bound past it would make release builds download uv_build
# from PyPI instead, around the checksum-verified binary, and nothing
# would fail.
- dependency-name: "uv_build"
commit-message:
prefix: "deps"
prefix-development: "deps-dev"
Expand All @@ -48,7 +74,7 @@ updates:

# GitHub Actions versions.
# Note: cooldown.semver-major-days is not supported for github-actions --
# Dependabot only honours it on semver-strict ecosystems like pip and npm.
# Dependabot only honours it on semver-strict ecosystems like uv and npm.
- package-ecosystem: "github-actions"
directory: "/"
schedule:
Expand Down
73 changes: 55 additions & 18 deletions .github/scripts/audit-deps.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,34 +8,39 @@
# file literally named requirements.txt, plus one Trivy report per tree:
#
# runtime-ceiling/ + trivy-runtime-ceiling.json
# requirements.txt alone, current resolution. What a fresh
# `pip install permit` gets today.
# pyproject.toml [project].dependencies alone, current resolution. What
# a fresh `pip install permit` gets today.
# runtime-floor/ + trivy-runtime-floor.json
# runtime-floor-pydantic-v2/ + trivy-runtime-floor-pydantic-v2.json
# requirements.txt alone, lowest-direct. Together, the lowest versions
# the PUBLISHED specs permit -- i.e. real consumer exposure. These are
# the trees that matter most for a library with open `>=` ranges.
# requirements.txt accepts either pydantic major, and lowest-direct
# picks the lowest release it allows, which is a pydantic 1 release, so
# runtime-floor alone never scans a pydantic 2 floor.
# pyproject.toml [project].dependencies alone, lowest-direct. Together,
# the lowest versions the PUBLISHED specs permit -- i.e. real consumer
# exposure. These are the trees that matter most for a library with open
# `>=` ranges. The dependencies accept either pydantic major, and
# lowest-direct picks the lowest release they allow, which is a pydantic 1
# release, so runtime-floor alone never scans a pydantic 2 floor.
# runtime-floor-pydantic-v2 holds pydantic to 2 and scans the lowest
# pydantic 2 (and the pydantic-core it pins) the specs permit.
# dev-ceiling/ + trivy-dev-ceiling.json
# requirements.txt + requirements-dev.txt, current resolution. Test
# tooling only; never ships to a user.
# [project].dependencies + the `dev` dependency group, current
# resolution. Test tooling only; never ships to a user.
#
# These are compiled from pyproject.toml, NOT exported from uv.lock: the lock
# pins one resolution for this repo's own CI, while the audit has to see what a
# consumer can resolve from the published ranges -- today's ceiling and the
# floors.
#
# Plus pip-audit-<tree>.json (advisory only) for each of the four trees.
#
# WHY RUNTIME IS COMPILED ALONE. Compiling the runtime and dev files together
# WHY RUNTIME IS COMPILED ALONE. Compiling the runtime and dev deps together
# lets a dev tool drag a runtime dependency's floor upward and hide the real
# exposure: when a dev tool needs a newer release of a runtime dependency than
# the floor in requirements.txt, the combined floor resolves that newer release,
# the floor in pyproject.toml, the combined floor resolves that newer release,
# but a consumer installing only `permit` can still land on the older one.
# Scanning the combined floor would silently under-report exactly the versions
# users can actually get.
#
# WHY COMPILE AT ALL. Trivy's pip analyzer only understands `==`. Pointed at
# this repo's raw requirements.txt it reports zero findings and exits 0 -- a
# a list of open ranges it reports zero findings and exits 0 -- a
# silently green gate. It also keys on the FILENAME, which is why each tree is
# written to its own directory as `requirements.txt` rather than scanned as a
# loose file (a loose file reports "Not scanned" and, again, exits 0).
Expand All @@ -46,6 +51,14 @@ REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"

# The declared minimum. Resolving at the floor of supported Python is the
# worst case a consumer can legitimately be in.
#
# Python 3.10 only, on purpose. On 3.13 and 3.14 the pydantic floors are higher
# (1.10.18/2.8.0 and 1.10.25/2.13), so those exact versions are never resolved
# here. An advisory that affects everything below its fixed version, which is
# almost every advisory, affects the lower 3.10 floor whenever it affects a
# higher one, so it still fails this gate. What is missed is an advisory
# confined to a later range that leaves the 3.10 floor out; the ceiling trees
# still cover the newest releases.
PYTHON_VERSION="${AUDIT_PYTHON_VERSION:-3.10}"

# A resolved tree with almost nothing in it means the compile silently produced
Expand All @@ -57,7 +70,17 @@ compile_tree() {
local name="$1" resolution="$2"
shift 2
mkdir -p "${OUT}/${name}"
local args=(--python-version "${PYTHON_VERSION}" --quiet -o "${OUT}/${name}/requirements.txt")
# --no-sources: the published build ignores [tool.uv.sources] (uv build
# --no-sources), so the audit must too. --exclude-newer false: the publish-age
# cooldown in pyproject.toml applies to this repo's `uv lock` only; consumers
# resolve against the index as it is today.
local args=(
--no-sources
--exclude-newer false
--python-version "${PYTHON_VERSION}"
--quiet
-o "${OUT}/${name}/requirements.txt"
)
if [ -n "${resolution}" ]; then
args+=(--resolution "${resolution}")
fi
Expand All @@ -78,12 +101,26 @@ echo "::group::Resolving dependency trees (python ${PYTHON_VERSION})"
# lowest-direct, not lowest: pin the declared bounds to their floor but let
# transitives resolve normally. Plain `lowest` would drag every transitive back
# to its first ever release and drown the report in irrelevant history.
compile_tree runtime-ceiling "" "${REPO_ROOT}/requirements.txt"
compile_tree runtime-floor "lowest-direct" "${REPO_ROOT}/requirements.txt"
# Passing pyproject.toml compiles [project].dependencies only; dependency
# groups are added solely by an explicit --group.
compile_tree runtime-ceiling "" "${REPO_ROOT}/pyproject.toml"
compile_tree runtime-floor "lowest-direct" "${REPO_ROOT}/pyproject.toml"
echo "pydantic>=2" >"${OUT}/pydantic-v2-constraint.txt"
compile_tree runtime-floor-pydantic-v2 "lowest-direct" "${REPO_ROOT}/requirements.txt" \
compile_tree runtime-floor-pydantic-v2 "lowest-direct" "${REPO_ROOT}/pyproject.toml" \
--constraints "${OUT}/pydantic-v2-constraint.txt"
compile_tree dev-ceiling "" "${REPO_ROOT}/requirements.txt" "${REPO_ROOT}/requirements-dev.txt"
compile_tree dev-ceiling "" "${REPO_ROOT}/pyproject.toml" \
--group "${REPO_ROOT}/pyproject.toml:dev"

# The package-count check above cannot tell a dev tree from a runtime one, so a
# --group that silently matched nothing would scan the runtime tree twice and
# report the dev tooling as clean.
if ! grep -q '^pytest==' "${OUT}/dev-ceiling/requirements.txt"; then
message="Tree 'dev-ceiling' does not contain pytest, so the 'dev' dependency group"
message+=" was not resolved. Refusing to scan a runtime-only tree and report the dev"
message+=" tooling as clean."
echo "::error title=Dependency resolution failed::${message}"
exit 1
fi
echo "::endgroup::"

# Trivy exits non-zero on findings when --exit-code is set. We do not set it:
Expand Down
10 changes: 5 additions & 5 deletions .github/scripts/check_schema_drift.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
permit/api/models.py is generated from https://api.permit.io/v2/openapi.json and
then edited by hand in a few places. Nothing regenerates it on a schedule, so the
API schema can move on without the SDK noticing. This script generates models from
the current schema with the same generator and flags as `make generate-models`,
the current schema with the same generator and flags as scripts/generate_models.sh,
then compares the two modules structurally: it parses both with `ast` and compares
classes, fields, field types, required vs optional, defaults, aliases, the model
`Config.extra` setting and enum members. Formatting, field order, titles,
Expand Down Expand Up @@ -64,8 +64,8 @@
# The generator release that produced permit/api/models.py (0.33.0 was current on
# its 2025-09-17 timestamp). --exclude-newer freezes the generator's own
# dependencies and formatters at the end of that day (UTC), whose pydantic-core has
# no Python 3.14 wheel, hence --python 3.11. The Makefile's generate-models target
# uses the same values; test_check_schema_drift.py keeps the two in step.
# no Python 3.14 wheel, hence --python 3.11. scripts/generate_models.sh uses the
# same values; test_check_schema_drift.py keeps the two in step.
GENERATOR_PYTHON = "3.11"
GENERATOR_EXCLUDE_NEWER = "2025-09-18T00:00:00Z"
GENERATOR_PACKAGE = "datamodel-code-generator==0.33.0"
Expand Down Expand Up @@ -500,8 +500,8 @@ def render(result: Result, compared_with: str) -> str:
out.append("")
if result.new or result.stale:
out.append(
"To resolve: regenerate the models (`make generate-models`, see the comment above generate-models in "
"the Makefile), or add each intended difference to `.github/scripts/schema_drift_allowlist.json` "
"To resolve: regenerate the models (`bash scripts/generate_models.sh`, see the comment at the top of "
"that script), or add each intended difference to `.github/scripts/schema_drift_allowlist.json` "
"with a one-line reason."
)
out.append("")
Expand Down
13 changes: 11 additions & 2 deletions .github/scripts/format_audit.py
Original file line number Diff line number Diff line change
Expand Up @@ -489,12 +489,21 @@ def render(
out.append("### How to fix")
out.append("")
out.append(
"Raise the affected lower bound in `requirements.txt` (or `requirements-dev.txt`) "
"to at least the *Fixed in* version above. Because this package publishes open "
"Raise the affected lower bound in `pyproject.toml` (`[project].dependencies`, or the "
"pin in the `dev` dependency group) to at least the *Fixed in* version above, then "
"run `uv lock`. Because this package publishes open "
"`>=` ranges, the floor is what consumers can actually install -- bumping only the "
"resolved version does not close the hole."
)
out.append("")
out.append(
"This audit resolves without the 7-day publish-age cooldown (`exclude-newer`) in "
"`[tool.uv]`, but `uv lock` applies it. If `uv lock` says the fixed version was "
"filtered by `exclude-newer`, add `exclude-newer-package = { <package> = false }` "
"under `[tool.uv]` in `pyproject.toml`, run `uv lock` and commit both files. Once "
"the release is 7 days old, remove the entry and run `uv lock` again."
)
out.append("")
out.append(
"An advisory with no fix available does not block the build -- it is reported "
"here so it can be tracked, but no version bump can resolve it. Suppression "
Expand Down
10 changes: 5 additions & 5 deletions .github/scripts/pytest.ini
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Configuration for the audit script tests alone. pytest reads the first
# pytest.ini it finds walking up from the tests it is given, so this file keeps
# it from reaching the SDK's pytest.ini at the repository root, whose
# asyncio_mode option belongs to pytest-asyncio. These tests need only pytest
# and the standard library, and warn about nothing: any warning is an error.
# Configuration for the CI script tests alone (format_audit.py and
# check_schema_drift.py), passed with -c so pytest does not use the SDK's
# configuration in pyproject.toml ([tool.pytest]), whose testpaths and
# asyncio_mode belong to the SDK's suite. These tests need only pytest and the
# standard library, and warn about nothing: any warning is an error.
[pytest]
filterwarnings = error
21 changes: 12 additions & 9 deletions .github/scripts/test_check_schema_drift.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@
These pin what the workflow relies on: which differences fail and which are only
reported, that the allowlist suppresses exactly what it records and nothing else,
that a run which could not compare exits 2 instead of passing, and that the
generator the script runs is the one `make generate-models` runs. No network and no
generator: each test compares small model modules written as source text.
generator the script runs is the one scripts/generate_models.sh runs. No network
and no generator: each test compares small model modules written as source text.

Run with: python -m pytest .github/scripts/test_check_schema_drift.py
Run with:
uv run --only-dev pytest -c .github/scripts/pytest.ini .github/scripts/test_check_schema_drift.py
"""

from __future__ import annotations
Expand Down Expand Up @@ -481,14 +482,16 @@ def test_pipes_and_backticks_in_schema_text_cannot_break_the_table():
assert row.count("`") == 6


# --- the generator is the one make generate-models runs -----------------------
# --- the generator is the one scripts/generate_models.sh runs -----------------


def test_generator_matches_the_makefile():
makefile = (REPO_ROOT / "Makefile").read_text(encoding="utf-8")
recipe = re.search(r"^generate-models:\n((?:\t.*\n?)+)", makefile, re.MULTILINE)
assert recipe, "the Makefile has no generate-models recipe"
words = shlex.split(recipe.group(1).replace("\\\n", " "))
def test_generator_matches_the_generate_models_script():
script = (REPO_ROOT / "scripts" / "generate_models.sh").read_text(encoding="utf-8")
# The command starts on a line beginning with `uvx ` and continues over every
# line that ends in a backslash.
command = re.search(r"^uvx (?:.*\\\n)*.*", script, re.MULTILINE)
assert command, "scripts/generate_models.sh has no line starting with `uvx `"
words = shlex.split(command.group(0).replace("\\\n", " "))
assert words[:7] == [
"uvx",
"--python",
Expand Down
11 changes: 10 additions & 1 deletion .github/scripts/test_format_audit.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
of a fence or a workflow command, and a pip-audit that did not check a tree is
always named rather than passing for a clean result.

Run with: python -m pytest .github/scripts/test_format_audit.py
Run with:
uv run --only-dev pytest -c .github/scripts/pytest.ini .github/scripts/test_format_audit.py
"""

from __future__ import annotations
Expand Down Expand Up @@ -349,6 +350,14 @@ def test_fixable_high_blocks():
assert Finding("CVE-1", "pkg", "1.0", "HIGH", "2.0", "t", "", "trivy").blocking is True


def test_fix_instructions_cover_a_fix_uv_lock_still_filters_out():
# The gate resolves with --exclude-newer false, so it blocks on the day a fix
# is released, while `uv lock` keeps that release out for 7 days. The report
# must say how to lock it anyway, or the block cannot be cleared.
out = render([Finding("CVE-1", "pkg", "1.0", "HIGH", "2.0", "t", "", "trivy")], [], "", blocking=True)
assert "exclude-newer-package = { <package> = false }" in out


def test_gate_exits_1_on_fixable_high(tmp_path: Path):
report = tmp_path / "trivy.json"
report.write_text(json.dumps(trivy_report(vuln())))
Expand Down
Loading
Loading