Skip to content

Kaggle T4: let the Studio GPU payload share a card instead of waiting for the drain #7502

Kaggle T4: let the Studio GPU payload share a card instead of waiting for the drain

Kaggle T4: let the Studio GPU payload share a card instead of waiting for the drain #7502

# Workflow-trigger lint. Refuses two patterns that together powered the
# TanStack GHSA-g7cv-rxg3-hmpx supply-chain compromise:
#
# 1. `pull_request_target` -- runs a fork's workflow YAML against the
# base repository's secrets. There is no safe use of this trigger
# for a public open-source project.
#
# 2. Shared cache keys between PR-triggered workflows and the publish
# workflow. A fork PR can poison the cache; the publish workflow
# then restores the poisoned cache on next run.
#
# This lives in its own workflow, not inside security-audit.yml, for one
# reason: `on.pull_request` here carries NO `paths` / `paths-ignore`
# filter, and must never gain one. A gate that only runs for some PRs
# does not gate workflow changes, which is exactly what it exists to
# review. security-audit.yml is a heavy nightly audit whose filters are
# tuned for cost; coupling this lint to them once already opened that
# hole. scripts/lint_workflow_triggers.py enforces the invariant on
# whichever workflow runs it, so this file cannot quietly re-acquire a
# filter.
#
# Cheap pure-Python lint, runs in seconds. Fail-closed.
name: Workflow trigger lint
on:
pull_request:
push:
branches: [main]
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }}-${{ github.ref == 'refs/heads/main' && github.sha || '' }}
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
permissions:
contents: read
jobs:
workflow-trigger-lint:
name: workflow-trigger lint (pull_request_target / cache-poisoning)
runs-on: ubuntu-latest
# Raised from 5 when this job absorbed 12 more guard modules. Serial they are
# 209s on a 192-core box and 119s at -n 4, which is what a GitHub runner has;
# 5 minutes left no headroom for a slower runner and would have failed on
# timeout rather than on anything real.
timeout-minutes: 15
steps:
- name: Harden runner (egress block)
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
with:
egress-policy: block
disable-sudo: true
allowed-endpoints: >
api.github.com:443
github.com:443
codeload.github.com:443
objects.githubusercontent.com:443
pypi.org:443
files.pythonhosted.org:443
- 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 PyYAML
# pytest and PyYAML are pinned to the versions security-audit.yml pinned them
# to when `pytest tests/security` lived there. That suite exercises
# scripts/lint_workflow_triggers.py as a SUBPROCESS and asserts on its exit
# semantics, so a pytest or PyYAML that resolves differently changes what it is
# asserting against. xdist and vermin stay unpinned: neither is under test.
run: pip install pyyaml==6.0.2 pytest==9.0.3 pytest-xdist vermin
- name: Lint workflow triggers + cache keys
run: python3 scripts/lint_workflow_triggers.py
# What the dropped interpreter legs used to catch, as far as a static check can.
# A pull request runs only the newest leg now, so nothing EXECUTES the backend on
# the oldest one until the push to main. ast.parse at a feature_version covers
# syntax and nothing else, which would miss the actual shape of this regression:
# reaching for a stdlib name that does not exist yet, like the `anext` in
# core/research_runs.py that already requires 3.10. vermin reads both, so a symbol
# added after the floor fails here in seconds rather than on main in 23 minutes.
# No paths filter on this workflow, so it sees every pull request.
- name: Backend still runs on the oldest interpreter the matrix claims
run: python3 scripts/lint_backend_python_floor.py
# ONE pytest invocation, not one per file.
#
# These were 9 separate steps, each paying interpreter startup and collection to
# run a single module -- and each new guard added a tenth, an eleventh. Collapsed
# into one call, which also lets pytest share collection across them. This repo's
# conftest is expensive to import, so a step per module pays that cost every time:
# over the 17 modules this started with, 53.9s as one invocation against 300.8s as
# one each. `-n 4` then uses the cores the runner already has: 304 tests, 128s
# serial against 68s at -n 4, identical results either way.
#
# Pinned to 4 rather than `auto` deliberately. ubuntu-latest is a 4-core runner, so
# on CI the two are the same, but `auto` scales to the host and each xdist worker
# re-imports that expensive conftest. Measured on a 192-core machine: `auto` spawned
# 192 workers and took 327s, worse than running serially. A fixed width is the same
# everywhere and cannot be made pathological by the machine it lands on.
#
# tests/security rides along on the same invocation. It held its own ubuntu-latest
# runner in security-audit.yml for 72s of work behind a queue measured at 11096s,
# and the same argument that put the lockfile and load-orchestrator lanes into Lint
# CI applies: work with a narrow trigger, moved into a job that was going to occupy
# a runner on this commit anyway, can only reduce the slots a commit takes. Here the
# trigger widens too, since this workflow has no paths filter and
# security-audit.yml's pull_request does.
#
# This host and not Lint CI, where the other absorbed lanes went. Lint CI installs
# shellcheck from apt, so its harden-runner has to permit escalation and an apt
# mirror; a security gate moved there would run under a policy weaker than the one
# it has today. This workflow's harden-runner block is byte-for-byte identical to
# the one the job carried, so nothing about its isolation changes.
#
# The list grew as much as it shrank. Every module here reads a workflow file, so
# the edit that breaks it is by definition a workflow-only edit, and this is the
# only job in the repo with no paths filter. Ten guards were sitting outside it,
# collected first by Backend CI's unfiltered push on main -- after the change had
# already merged. tests/studio/test_workflow_guards_run_unfiltered.py keeps the
# list honest so the next one is not forgotten too.
- name: Workflow guard suites
run: |
python3 -m pytest -q -n 4 \
tests/studio/test_absorbed_lanes_still_run.py \
tests/studio/test_agent_guides_verdicts.py \
tests/studio/test_apt_steps_are_bounded.py \
tests/studio/test_backend_ci_matrix.py \
tests/studio/test_backend_ci_parallel_isolation.py \
tests/studio/test_cache_budget_discipline.py \
tests/studio/test_chat_ui_shards_cover_everything.py \
tests/studio/test_ui_shard_engines.py \
tests/studio/test_zoo_suite_parallel_isolation.py \
tests/studio/test_ci_shell_suite_coverage.py \
tests/studio/test_compile_caches_are_per_worker.py \
tests/studio/test_composer_rtl_bidi_attribute.py \
tests/studio/test_frontend_dep_removal.py \
tests/studio/test_frontend_dist_cache.py \
tests/studio/test_gguf_smoke_phases_stay_independent.py \
tests/studio/test_indicator_browsers_run_in_parallel.py \
tests/studio/test_inference_smoke_http_diagnostics.py \
tests/studio/test_install_phase_timing.py \
tests/studio/test_mac_bundled_job_phases.py \
tests/studio/test_mac_host_offload_optin.py \
tests/studio/test_settings_smoke_covers_every_tab.py \
tests/studio/test_playwright_install_avoids_with_deps.py \
tests/studio/test_macos_slots_per_commit.py \
tests/studio/test_main_runs_survive_merge_bursts.py \
tests/studio/test_pester_bootstrap_hardening.py \
tests/studio/test_playwright_suites_run_in_ci.py \
tests/studio/test_sdk_installs_are_major_bounded.py \
tests/studio/test_short_job_absorption.py \
tests/studio/test_smoke_workflows_share_one_script.py \
tests/studio/test_stt_model_search_locator_contract.py \
tests/studio/test_uv_cache_discipline.py \
tests/studio/test_version_compat_bundle.py \
tests/studio/test_windows_small_checks_stay_on_their_image.py \
tests/studio/test_windows_ui_lanes_are_isolated.py \
tests/studio/test_workflow_guards_run_unfiltered.py \
tests/security