Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
42 changes: 42 additions & 0 deletions .github/ci-path-filters.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# These filters match changes that require expensive jobs. Each filter starts by matching every
# path, then excludes the explicit safe-to-skip allowlist. New and unknown paths therefore run the
# expensive jobs by default. Changes to this policy also run both full CI and CodeQL.
full_ci:
- '**'
- '!*.md'
- '!**/*.md'
- '!.devcontainer/**'
- '!.gitattributes'
- '!.github/CODEOWNERS'
- '!.github/workflows/codeql.yml'
- '!.github/workflows/create-releases.yml'
- '!.github/workflows/examples.yml'
- '!.github/workflows/runtime-compatibility.yml'
- '!.github/workflows/stainless-maven-artifacts.yml'
- '!.gitignore'
- '!.release-please-manifest.json'
- '!.stats.yml'
- '!LICENSE'
- '!docs/**'
- '!openai-java-lib/.keep'
- '!release-please-config.json'

codeql:
- '**'
- '!*.md'
- '!**/*.md'
- '!.devcontainer/**'
- '!.gitattributes'
- '!.github/CODEOWNERS'
- '!.github/workflows/ci.yml'
- '!.github/workflows/create-releases.yml'
- '!.github/workflows/examples.yml'
- '!.github/workflows/runtime-compatibility.yml'
- '!.github/workflows/stainless-maven-artifacts.yml'
- '!.gitignore'
- '!.release-please-manifest.json'
- '!.stats.yml'
- '!LICENSE'
- '!docs/**'
- '!openai-java-lib/.keep'
- '!release-please-config.json'
122 changes: 103 additions & 19 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,36 @@ concurrency:
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

jobs:
changes:
name: CI / change classification
runs-on: ubuntu-24.04
timeout-minutes: 5
permissions:
contents: read
pull-requests: read

outputs:
full_ci: ${{ steps.filter.outputs.full_ci }}

steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
persist-credentials: false

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] Preserve authenticated history for pushes to the private Stainless mirror

This workflow explicitly also runs in stainless-sdks/openai-java, which is private. On push, the pinned paths-filter action compares github.event.before with the pushed branch; checkout defaults to fetch-depth: 1, so the previous commit is absent and paths-filter executes git fetch --depth=1 origin <before-sha>. persist-credentials: false has already removed the token from local Git configuration, so that fetch cannot authenticate against the private origin. The classifier then fails and CI / required fails for inherited next, stl/**, and codegen/stl/** push workflows. Pull-request runs do not expose this because paths-filter uses the GitHub API there. Fetch complete history during the authenticated checkout, or retain read-only Git credentials until push classification completes.


- name: Classify changes
id: filter
uses: dorny/paths-filter@7b450fff21473bca461d4b92ce414b9d0420d706 # v4.0.2
with:
# On push, compare only the commits in this push. Pull requests use the GitHub API
# to compare the entire PR with its base branch.
base: ${{ github.ref }}
predicate-quantifier: every
filters: .github/ci-path-filters.yml
Comment thread
jbeckwith-oai marked this conversation as resolved.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] Enforce policy changes independently of the PR-controlled policy

On pull requests, this reads .github/ci-path-filters.yml from the proposed checkout, so the PR can change the very policy that decides whether its changes require CI. For example, a PR can append - '!**' to both full_ci and codeql while also changing arbitrary Java source. With predicate-quantifier: every, no changed path—including the policy file itself—matches either filter, both classifiers return false, all expensive jobs and CodeQL are skipped, and the sole required CI / required check succeeds. This violates the promised invariant that policy changes always run both workflows. Detect policy-file changes through a trusted, non-overridable check and force both classifiers to run, or load the classification policy from the trusted base revision.


lint:
name: CI / lint
needs: changes
if: needs.changes.outputs.full_ci == 'true'
runs-on: ubuntu-24.04
timeout-minutes: 15

Expand All @@ -46,7 +74,9 @@ jobs:

build:
name: CI / build + Jackson compatibility
needs: changes
if: >-
needs.changes.outputs.full_ci == 'true' &&
!(github.repository == 'stainless-sdks/openai-java' &&
github.event_name == 'push' &&
!startsWith(github.ref, 'refs/heads/stl/'))
Expand Down Expand Up @@ -76,6 +106,8 @@ jobs:

test:
name: CI / tests
needs: changes
if: needs.changes.outputs.full_ci == 'true'
runs-on: ubuntu-24.04
timeout-minutes: 30

Expand All @@ -100,7 +132,8 @@ jobs:

api_compatibility:
name: CI / API compatibility
if: github.event_name == 'pull_request'
needs: changes
if: needs.changes.outputs.full_ci == 'true' && github.event_name == 'pull_request'
runs-on: ubuntu-24.04
timeout-minutes: 20

Expand Down Expand Up @@ -140,6 +173,8 @@ jobs:

version_support_matrix:
name: CI / version support matrix
needs: changes
if: needs.changes.outputs.full_ci == 'true'
runs-on: ubuntu-24.04
timeout-minutes: 5

Expand Down Expand Up @@ -168,7 +203,10 @@ jobs:

runtime_compatibility:
name: CI / runtime compatibility / Java ${{ matrix.java }}
needs: version_support_matrix
needs:
- changes
- version_support_matrix
if: needs.changes.outputs.full_ci == 'true'
runs-on: ubuntu-24.04
timeout-minutes: 20

Expand Down Expand Up @@ -207,50 +245,96 @@ jobs:
name: CI / required
if: always()
needs:
- changes
- lint
- build
- test
- api_compatibility
- version_support_matrix
- runtime_compatibility
runs-on: ubuntu-24.04
timeout-minutes: 5

steps:
- name: Verify required jobs succeeded
env:
CHANGES_RESULT: ${{ needs.changes.result }}
FULL_CI: ${{ needs.changes.outputs.full_ci }}
EVENT_NAME: ${{ github.event_name }}
REPOSITORY: ${{ github.repository }}
REF: ${{ github.ref }}
LINT_RESULT: ${{ needs.lint.result }}
BUILD_RESULT: ${{ needs.build.result }}
TEST_RESULT: ${{ needs.test.result }}
API_COMPATIBILITY_RESULT: ${{ needs.api_compatibility.result }}
VERSION_SUPPORT_MATRIX_RESULT: ${{ needs.version_support_matrix.result }}
RUNTIME_COMPATIBILITY_RESULT: ${{ needs.runtime_compatibility.result }}
run: |
set -euo pipefail

failed_jobs=()
[[ "$LINT_RESULT" == "success" ]] || failed_jobs+=("lint: $LINT_RESULT")
[[ "$TEST_RESULT" == "success" ]] || failed_jobs+=("test: $TEST_RESULT")

if [[ "$EVENT_NAME" == "push" &&
"$REPOSITORY" == "stainless-sdks/openai-java" &&
"$REF" != refs/heads/stl/* ]]; then
[[ "$BUILD_RESULT" == "skipped" ]] ||
failed_jobs+=("build: expected skipped, got $BUILD_RESULT")
else
[[ "$BUILD_RESULT" == "success" ]] || failed_jobs+=("build: $BUILD_RESULT")
fi

if [[ "$EVENT_NAME" == "pull_request" && "$API_COMPATIBILITY_RESULT" != "success" ]]; then
failed_jobs+=("API compatibility: $API_COMPATIBILITY_RESULT")
expect_result() {
local job="$1"
local actual="$2"
local expected="$3"
if [[ "$actual" != "$expected" ]]; then
failed_jobs+=("$job: expected $expected, got $actual")
fi
}

expect_result "change classification" "$CHANGES_RESULT" "success"

case "$FULL_CI" in
true)
expected_lint="success"
expected_build="success"
expected_test="success"
expected_api_compatibility="skipped"
expected_version_support_matrix="success"
expected_runtime_compatibility="success"

if [[ "$EVENT_NAME" == "pull_request" ]]; then
expected_api_compatibility="success"
fi

if [[ "$EVENT_NAME" == "push" &&
"$REPOSITORY" == "stainless-sdks/openai-java" &&
"$REF" != refs/heads/stl/* ]]; then
expected_build="skipped"
fi
;;
false)
expected_lint="skipped"
expected_build="skipped"
expected_test="skipped"
expected_api_compatibility="skipped"
expected_version_support_matrix="skipped"
expected_runtime_compatibility="skipped"
;;
*)
failed_jobs+=("change classification: invalid full_ci output '$FULL_CI'")
;;
esac

if [[ "$FULL_CI" == "true" || "$FULL_CI" == "false" ]]; then
expect_result "lint" "$LINT_RESULT" "$expected_lint"
expect_result "build" "$BUILD_RESULT" "$expected_build"
expect_result "test" "$TEST_RESULT" "$expected_test"
expect_result \
"API compatibility" "$API_COMPATIBILITY_RESULT" "$expected_api_compatibility"
expect_result \
"version support matrix" \
"$VERSION_SUPPORT_MATRIX_RESULT" \
"$expected_version_support_matrix"
expect_result \
"runtime compatibility" \
"$RUNTIME_COMPATIBILITY_RESULT" \
"$expected_runtime_compatibility"
fi
[[ "$RUNTIME_COMPATIBILITY_RESULT" == "success" ]] ||
failed_jobs+=("runtime compatibility: $RUNTIME_COMPATIBILITY_RESULT")

if (( ${#failed_jobs[@]} > 0 )); then
printf 'Required CI job did not succeed: %s\n' "${failed_jobs[@]}"
exit 1
fi

echo "All required CI jobs succeeded."
echo "All required CI jobs succeeded or were safely skipped."
28 changes: 28 additions & 0 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,36 @@ concurrency:
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

jobs:
changes:
name: Classify changes
if: github.event_name != 'workflow_dispatch'
runs-on: ubuntu-24.04
timeout-minutes: 5
permissions:
contents: read
pull-requests: read

outputs:
codeql: ${{ steps.filter.outputs.codeql }}

steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
persist-credentials: false

- name: Classify changes
id: filter
uses: dorny/paths-filter@7b450fff21473bca461d4b92ce414b9d0420d706 # v4.0.2
with:
base: ${{ github.ref }}
predicate-quantifier: every
filters: .github/ci-path-filters.yml
Comment thread
jbeckwith-oai marked this conversation as resolved.

analyze:
name: Analyze Java and Kotlin
needs: changes
# Only an explicit safe-to-skip classification may reduce security coverage.
if: always() && needs.changes.outputs.codeql != 'false'
runs-on: ubuntu-latest
timeout-minutes: 45
permissions:
Expand Down
Loading