diff --git a/.github/workflows/cuda13-sglang-cu130.yml b/.github/workflows/cuda13-sglang-cu130.yml index da9e042b0..f5f36717c 100644 --- a/.github/workflows/cuda13-sglang-cu130.yml +++ b/.github/workflows/cuda13-sglang-cu130.yml @@ -5,12 +5,14 @@ on: paths: - .github/workflows/cuda13-sglang-cu130.yml - .github/workflows/cuda13-bundle-image.yml + - tools/ci/bundle_lock.py - tools/ci/cuda13_image_smoke.py - tools/ci/engine_launch_contract.py - tools/ci/engine_launch_probe.py - conformance/engine-flags/** - Cargo.toml - Cargo.lock + - uv.lock - packages/sie_audio_prep/** - packages/sie_gateway/** - packages/sie_server_sidecar/** @@ -19,6 +21,7 @@ on: - packages/sie_sdk/src/** - packages/sie_server/Dockerfile.cuda13 - packages/sie_server/src/** + - packages/sie_server/bundle-locks/** - packages/sie_server/bundles/** - packages/sie_server/models/** - packages/sie_server/bundles/sglang-cu130.yaml diff --git a/.github/workflows/cuda13-tensorrt-llm.yml b/.github/workflows/cuda13-tensorrt-llm.yml index df74603ce..921f2758c 100644 --- a/.github/workflows/cuda13-tensorrt-llm.yml +++ b/.github/workflows/cuda13-tensorrt-llm.yml @@ -5,12 +5,14 @@ on: paths: - .github/workflows/cuda13-tensorrt-llm.yml - .github/workflows/cuda13-bundle-image.yml + - tools/ci/bundle_lock.py - tools/ci/cuda13_image_smoke.py - tools/ci/engine_launch_contract.py - tools/ci/engine_launch_probe.py - conformance/engine-flags/** - Cargo.toml - Cargo.lock + - uv.lock - packages/sie_audio_prep/** - packages/sie_gateway/** - packages/sie_server_sidecar/** @@ -19,6 +21,7 @@ on: - packages/sie_sdk/src/** - packages/sie_server/Dockerfile.cuda13 - packages/sie_server/src/** + - packages/sie_server/bundle-locks/** - packages/sie_server/bundles/** - packages/sie_server/models/** - packages/sie_server/bundles/tensorrt-llm.yaml diff --git a/.github/workflows/release-candidate.yml b/.github/workflows/release-candidate.yml index caf3cfa49..3c4306d53 100644 --- a/.github/workflows/release-candidate.yml +++ b/.github/workflows/release-candidate.yml @@ -28,6 +28,7 @@ on: - packages/sie_audio_prep/** - packages/sie_server_rust/** - packages/sie_telemetry/** + - packages/sie_server/bundle-locks/** - packages/sie_server/bundles/** - packages/sie_server/models/** - packages/sie_server/src/sie_server/adapters/sglang/** diff --git a/tools/ci/tests/test_release_candidate.py b/tools/ci/tests/test_release_candidate.py index 7ca94f0ff..669a33789 100644 --- a/tools/ci/tests/test_release_candidate.py +++ b/tools/ci/tests/test_release_candidate.py @@ -51,6 +51,8 @@ def test_candidate_uses_every_pure_builder_at_one_source_without_publication(): "tools/mise_tasks/docker_task.py", "tools/mise_tasks/helm.py", "packages/sie_server/Dockerfile.cuda13", + "packages/sie_server/bundle-locks/cuda13-sglang-cu130-amd64/pylock.toml", + "uv.lock", "packages/sie_audio_prep/build_wheel.py", "packages/sie_server_rust/Cargo.lock", "deploy/helm/sie-cluster/Chart.yaml", diff --git a/tools/ci/tests/test_required_ci.py b/tools/ci/tests/test_required_ci.py index 48b6ee68c..3b2aaeaca 100644 --- a/tools/ci/tests/test_required_ci.py +++ b/tools/ci/tests/test_required_ci.py @@ -257,12 +257,14 @@ def test_rust_audits_both_committed_dependency_graphs(): CUDA13_SHARED_PATHS = { ".github/workflows/cuda13-bundle-image.yml", + "tools/ci/bundle_lock.py", "tools/ci/cuda13_image_smoke.py", "tools/ci/engine_launch_contract.py", "tools/ci/engine_launch_probe.py", "conformance/engine-flags/**", "Cargo.toml", "Cargo.lock", + "uv.lock", "packages/sie_audio_prep/**", "packages/sie_gateway/**", "packages/sie_server_sidecar/**", @@ -271,6 +273,7 @@ def test_rust_audits_both_committed_dependency_graphs(): "packages/sie_sdk/src/**", "packages/sie_server/Dockerfile.cuda13", "packages/sie_server/src/**", + "packages/sie_server/bundle-locks/**", "packages/sie_server/bundles/**", "packages/sie_server/models/**", "packages/sie_server/pyproject.toml", @@ -316,6 +319,48 @@ def test_cuda13_callers_have_exact_inputs_and_narrow_paths(filename, expected): assert job["with"] == {"bundle": expected["bundle"], "image-tag": expected["image-tag"]} +def cuda13_image_sources() -> list[str]: + text = (ROOT / "packages/sie_server/Dockerfile.cuda13").read_text().replace("\\\n", " ") + sources = [] + for line in text.splitlines(): + words = line.split() + if words[:1] != ["COPY"] or any(word.startswith("--from=") for word in words): + continue + sources += [word for word in words[1:-1] if not word.startswith("--")] + return sources + + +@pytest.mark.parametrize("filename", CUDA_CALLERS) +def test_cuda13_caller_paths_cover_every_image_build_input(filename): + workflow = yaml.safe_load((ROOT / ".github/workflows" / filename).read_text()) + patterns = workflow.get("on", workflow.get(True))["pull_request"]["paths"] + sources = cuda13_image_sources() + assert {"uv.lock", "packages/sie_server/bundle-locks", "tools/ci/bundle_lock.py"} <= { + source.rstrip("/") for source in sources + } + for source in sources: + path = ROOT / source + for file in sorted(path.rglob("*")) if path.is_dir() else [path]: + if file.is_file(): + relative = file.relative_to(ROOT).as_posix() + assert any(fnmatch.fnmatchcase(relative, pattern) for pattern in patterns), relative + + +def test_root_lock_only_change_runs_bundle_resolution_and_the_binding_test(): + ci = yaml.safe_load((ROOT / ".github/workflows/ci.yml").read_text()) + assert ci.get("on", ci.get(True))["pull_request"] is None + resolution = [step.get("run", "") for step in ci["jobs"]["bundle-resolution"]["steps"]] + assert any("tools/ci/bundle_lock.py check" in command for command in resolution) + tooling = next( + step["run"] for step in ci["jobs"]["python"]["steps"] if "pytest -q tools/ci/tests" in step.get("run", "") + ) + assert "test_bundle_lock.py" not in tooling + for filename in CUDA_CALLERS: + workflow = yaml.safe_load((ROOT / ".github/workflows" / filename).read_text()) + patterns = workflow.get("on", workflow.get(True))["pull_request"]["paths"] + assert any(fnmatch.fnmatchcase("uv.lock", pattern) for pattern in patterns), filename + + def test_sglang_cuda13_model_configs_are_covered_by_caller_paths(): workflow = yaml.safe_load((ROOT / ".github/workflows/cuda13-sglang-cu130.yml").read_text()) triggers = workflow.get("on", workflow.get(True))