diff --git a/.github/workflows/agentex-tutorials-test.yml b/.github/workflows/agentex-tutorials-test.yml index 41b495d71..51f8a2141 100644 --- a/.github/workflows/agentex-tutorials-test.yml +++ b/.github/workflows/agentex-tutorials-test.yml @@ -9,6 +9,10 @@ on: jobs: find-tutorials: + # Repo guard: this workflow is specific to the production repo. Staging carries the + # same file (the trunks are kept SHA-identical) but has none of its secrets, so + # without this it runs and fails red on every codegen push. + if: github.repository == 'scaleapi/scale-agentex-python' runs-on: ubuntu-latest outputs: tutorials: ${{ steps.get-tutorials.outputs.tutorials }} @@ -235,7 +239,7 @@ jobs: retention-days: 1 test-summary: - if: always() + if: always() && github.repository == 'scaleapi/scale-agentex-python' needs: [find-tutorials, test-tutorial] runs-on: ubuntu-latest name: Test Summary diff --git a/.github/workflows/bandit-ci.yml b/.github/workflows/bandit-ci.yml new file mode 100644 index 000000000..d4690a71e --- /dev/null +++ b/.github/workflows/bandit-ci.yml @@ -0,0 +1,69 @@ +name: Bandit + +on: + # Scan changed files in PRs: + pull_request: {} + +jobs: + bandit-scan: + name: Bandit + runs-on: ubuntu-22.04 + if: (github.actor != 'dependabot[bot]') && (github.actor != 'github-actions[bot]') + steps: + - name: Install PyCQA/bandit + shell: bash + run: | + pip install bandit + - name: Checkout base branch + uses: actions/checkout@v3 + with: + ref: ${{ github.event.pull_request.base.ref }} + fetch-depth: 1 + submodules: false + - name: Run a baseline scan + shell: bash + run: | + bandit --recursive --aggregate file . -f json -o baseline.json || true + - name: Checkout feature branch + shell: bash + run: | + git fetch origin $GITHUB_HEAD_REF + git checkout $GITHUB_HEAD_REF + - name: Run Scan off of baseline + shell: bash + run: | + bandit --recursive --aggregate file . --baseline baseline.json -f json -o results.json || true + - name: Install logging prerequisites + shell: bash {0} + run: | + sudo apt-get -y install jq curl + - name: Generate logger template + shell: bash {0} # don't fail the job if the logging fails + run: | + jq -n --arg organization $GITHUB_REPOSITORY_OWNER \ + -n --arg time $( date +'%Y-%m-%dT%H:%M:%SZ' ) \ + -n --arg action $GITHUB_WORKFLOW \ + -n --arg repository $GITHUB_REPOSITORY \ + -n --arg sha $GITHUB_SHA \ + -n --arg branch $GITHUB_HEAD_REF \ + -n --arg link "https://github.com/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}" \ + -f .github/workflows/output-template.json > tmp-output.json + - name: Format results appropriately from results.json + shell: bash {0} # don't fail the job if the logging fails + run: | + jq '.results | map({"path": .filename, "message": .issue_text, "line": .line_number})' results.json > tmp.json + # --slurpfile, not --argjson "$( output.json + - name: Send unified results to logging cluster + shell: bash {0} # don't fail the job if the logging fails + run: | + curl -X POST \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer ${{ secrets.N8N_PRODSEC_ACTIONS_TOKEN }}" \ + -d @./output.json \ + ${{ secrets.N8N_PRODSEC_ACTIONS_ENDPOINT }} diff --git a/.github/workflows/build-and-push-tutorial-agent.yml b/.github/workflows/build-and-push-tutorial-agent.yml index b35154389..33c691d8b 100644 --- a/.github/workflows/build-and-push-tutorial-agent.yml +++ b/.github/workflows/build-and-push-tutorial-agent.yml @@ -25,6 +25,10 @@ permissions: jobs: check-permissions: + # Repo guard: this workflow is specific to the production repo. Staging carries the + # same file (the trunks are kept SHA-identical) but has none of its secrets, so + # without this it runs and fails red on every codegen push. + if: github.repository == 'scaleapi/scale-agentex-python' runs-on: ubuntu-latest steps: - name: Check event type and permissions diff --git a/.github/workflows/harness-integration.yml b/.github/workflows/harness-integration.yml index ab20929a8..819006a50 100644 --- a/.github/workflows/harness-integration.yml +++ b/.github/workflows/harness-integration.yml @@ -12,6 +12,10 @@ on: jobs: conformance: + # Repo guard: this workflow is specific to the production repo. Staging carries the + # same file (the trunks are kept SHA-identical) but has none of its secrets, so + # without this it runs and fails red on every codegen push. + if: github.repository == 'scaleapi/scale-agentex-python' runs-on: ubuntu-latest steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 @@ -38,6 +42,10 @@ jobs: # trigger above uses a `test_harness_*.py` glob so new suites are picked up # automatically. live-matrix: + # Repo guard: this workflow is specific to the production repo. Staging carries the + # same file (the trunks are kept SHA-identical) but has none of its secrets, so + # without this it runs and fails red on every codegen push. + if: github.repository == 'scaleapi/scale-agentex-python' runs-on: ubuntu-latest strategy: matrix: diff --git a/.github/workflows/lint-pr.yaml b/.github/workflows/lint-pr.yaml index dc165a271..49a894981 100644 --- a/.github/workflows/lint-pr.yaml +++ b/.github/workflows/lint-pr.yaml @@ -24,8 +24,17 @@ jobs: # These bots may not always emit Conventional-Commits-formatted titles # (dependabot's default "Bump foo from 1.0 to 1.1" doesn't match) and we # don't want their PRs blocked by this check. Mirrors validate-pr-base. + # + # agentex-sdk-sync[bot] is this repo's own SDK automation. release-please + # runs here as a CLI under that App rather than as the release-please[bot] + # GitHub App, so its release pull requests are authored by + # agentex-sdk-sync[bot] and the entry above never matched them. Their + # titles come from release-please's configured pull-request-title-pattern, + # "release: ", which is not a Conventional Commits type and cannot + # be changed without also changing the string release-please parses back + # when it cuts the release. The same App opens the promote pull requests. case "$PR_AUTHOR" in - stainless-app|stainless-app\[bot\]|release-please\[bot\]|github-actions\[bot\]|dependabot\[bot\]) + stainless-app|stainless-app\[bot\]|release-please\[bot\]|github-actions\[bot\]|dependabot\[bot\]|agentex-sdk-sync\[bot\]) echo "PR is from automation ($PR_AUTHOR); skipping title check." exit 0 ;; @@ -93,7 +102,7 @@ jobs: # Exempt automated PRs (must mirror validate-pr-title's list). case "$PR_AUTHOR" in - stainless-app|stainless-app\[bot\]|release-please\[bot\]|github-actions\[bot\]|dependabot\[bot\]) + stainless-app|stainless-app\[bot\]|release-please\[bot\]|github-actions\[bot\]|dependabot\[bot\]|agentex-sdk-sync\[bot\]) delete_comment echo "PR is from automation ($PR_AUTHOR); allowing PR targeting main." exit 0 diff --git a/.github/workflows/opengrep-ci.yml b/.github/workflows/opengrep-ci.yml new file mode 100644 index 000000000..92cb7da2a --- /dev/null +++ b/.github/workflows/opengrep-ci.yml @@ -0,0 +1,18 @@ +name: OpenGrep + +on: + pull_request: {} + +concurrency: + group: opengrep-${{ github.event.pull_request.number }} + cancel-in-progress: true + +permissions: + contents: read + pull-requests: write + id-token: write + +jobs: + opengrep: + uses: scaleapi/required-actions/.github/workflows/opengrep-ci.yml@opengrep-4core-runner + secrets: inherit diff --git a/.github/workflows/opengrep-fp.yml b/.github/workflows/opengrep-fp.yml new file mode 100644 index 000000000..5693b4421 --- /dev/null +++ b/.github/workflows/opengrep-fp.yml @@ -0,0 +1,18 @@ +name: OpenGrep FP Triage + +on: + pull_request_review_comment: + types: [created] + +permissions: + pull-requests: write + id-token: write + +jobs: + triage: + if: | + (startsWith(github.event.comment.body, '/fp') || + startsWith(github.event.comment.body, '/FP')) && + !endsWith(github.actor, '[bot]') + uses: scaleapi/required-actions/.github/workflows/opengrep-fp.yml@main + secrets: inherit diff --git a/.github/workflows/output-template.json b/.github/workflows/output-template.json new file mode 100644 index 000000000..e6303bcf9 --- /dev/null +++ b/.github/workflows/output-template.json @@ -0,0 +1,13 @@ +{ + "source": "github", + "organization": "\($organization)", + "timestamp": "\($time)", + "action": "\($action)", + "meta": { + "repository": "\($repository)", + "commit": "\($sha)", + "branch": "\($branch)", + "link": "\($link)" + }, + "results": [] +} diff --git a/.github/workflows/publish-pypi.yml b/.github/workflows/publish-pypi.yml index b5ff5ca9b..23c5f58fd 100644 --- a/.github/workflows/publish-pypi.yml +++ b/.github/workflows/publish-pypi.yml @@ -20,6 +20,10 @@ on: jobs: publish: + # Repo guard: this workflow is specific to the production repo. Staging carries the + # same file (the trunks are kept SHA-identical) but has none of its secrets, so + # without this it runs and fails red on every codegen push. + if: github.repository == 'scaleapi/scale-agentex-python' name: publish runs-on: ubuntu-latest diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml new file mode 100644 index 000000000..e9ddc6392 --- /dev/null +++ b/.github/workflows/release-please.yml @@ -0,0 +1,80 @@ +name: Release Please + +# Hand-edited from the stlc-generated template. `.github/workflows/*.yml` is +# scaffold-once, so this survives every later build -- upstream's own source cites +# exactly this PAT-to-App swap as the reason that preservation exists. Do NOT run +# `stlc build --rewrite-scaffold` without reapplying these three changes. +# +# What changed from the generated file, and why each is load-bearing: +# +# 1. App token instead of `secrets.RELEASE_PLEASE_TOKEN`, which does not exist +# and which we do not want to create -- eliminating PATs was the point of the +# App migration. It is deliberately NOT `GITHUB_TOKEN`: releases created by +# GITHUB_TOKEN do not trigger other workflows, so publish-*.yml would never +# fire and the release would stop one hop short of the registry. +# +# 2. The `npx release-please@16` CLI instead of googleapis/release-please-action. +# scale-agentex-typescript sets `allowed_actions: selected` and does not permit +# that action; the CLI needs only actions/-owned steps, which +# `github_owned_allowed: true` covers on both production repos. +# +# 3. `issues: write` on the minted token. release-please drives its +# autorelease:pending -> autorelease:tagged labels through the Issues API. +# Without it you get duplicate release pull requests. The generated file omits +# it, and the omission is silent until it bites. +# +# Requires AGENTEX_SDK_SYNC_PRIVATE_KEY (secret) and AGENTEX_SDK_SYNC_APP_ID +# (variable) on the PRODUCTION repo -- a workflow only reads secrets from the repo +# it runs in, and the guard below means that is production. +on: + push: + branches: + - main + workflow_dispatch: + +permissions: + contents: read + +jobs: + release-please: + # Self-routing: this file is SHA-identical on the staging trunk, where it must + # stay inert. Only production cuts releases. + if: github.repository == 'scaleapi/scale-agentex-python' + runs-on: ubuntu-latest + steps: + - name: Mint release token + id: release-token + uses: actions/create-github-app-token@v2 + with: + app-id: ${{ vars.AGENTEX_SDK_SYNC_APP_ID }} + private-key: ${{ secrets.AGENTEX_SDK_SYNC_PRIVATE_KEY }} + owner: scaleapi + repositories: scale-agentex-python + permission-contents: write + permission-pull-requests: write + permission-issues: write + permission-metadata: read + + - uses: actions/setup-node@v4 + with: + node-version: '20' + + - name: Release PR + GitHub release + env: + RP_TOKEN: ${{ steps.release-token.outputs.token }} + run: | + # release-pr opens or updates the version-bump pull request; + # github-release turns an already-merged one into the tag + GitHub Release + # that publish-pypi.yml / publish-npm.yml trigger on. Both are idempotent, + # so running the pair on every push carries a release the whole way. + # + # No checkout step is needed: release-please reads the config and manifest + # from the repo over the API. + npx --yes release-please@16 release-pr \ + --token="$RP_TOKEN" --repo-url="${{ github.repository }}" \ + --config-file=release-please-config.json \ + --manifest-file=.release-please-manifest.json + npx --yes release-please@16 github-release \ + --token="$RP_TOKEN" --repo-url="${{ github.repository }}" \ + --config-file=release-please-config.json \ + --manifest-file=.release-please-manifest.json diff --git a/.github/workflows/trufflehog-bypass.yml b/.github/workflows/trufflehog-bypass.yml new file mode 100644 index 000000000..51bdc8d3a --- /dev/null +++ b/.github/workflows/trufflehog-bypass.yml @@ -0,0 +1,16 @@ +name: TruffleHog Bypass Handler + +on: + issue_comment: + types: [created] + +jobs: + bypass: + if: | + github.event.issue.pull_request && + contains(github.event.comment.body, '/trufflehog-bypass') + uses: scaleapi/required-actions/.github/workflows/trufflehog-bypass-handler.yml@main + permissions: + pull-requests: write + contents: read + actions: write diff --git a/.github/workflows/trufflehog-ci.yml b/.github/workflows/trufflehog-ci.yml new file mode 100644 index 000000000..39d4a30b8 --- /dev/null +++ b/.github/workflows/trufflehog-ci.yml @@ -0,0 +1,13 @@ +name: TruffleHog Secret Scan + +on: + pull_request: + branches: [master, main] + +jobs: + scan: + uses: scaleapi/required-actions/.github/workflows/trufflehog-scan.yml@main + permissions: + contents: read + pull-requests: write + id-token: write diff --git a/.github/workflows/trufflehog-weekly.yml b/.github/workflows/trufflehog-weekly.yml new file mode 100644 index 000000000..f2efa6007 --- /dev/null +++ b/.github/workflows/trufflehog-weekly.yml @@ -0,0 +1,26 @@ +name: TruffleHog Weekly Scan + +on: + schedule: + - cron: '0 3 * * 0' + workflow_dispatch: + inputs: + since_commit: + description: 'Override: Scan from this commit SHA (leave empty to use stored value)' + required: false + type: string + full_scan: + description: 'Run full history scan (ignores since_commit)' + required: false + type: boolean + default: false + +jobs: + scan: + uses: scaleapi/required-actions/.github/workflows/trufflehog-weekly-scan.yml@main + with: + since_commit: ${{ inputs.since_commit || '' }} + full_scan: ${{ inputs.full_scan || false }} + permissions: + contents: read + id-token: write diff --git a/.stats.yml b/.stats.yml index a15b6b97b..217405cdc 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1 @@ configured_endpoints: 75 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/sgp/agentex-sdk-fbc0683871d6abb03588f30d9468eeeeacb2b8538eb0c9002813e6df68c5802b.yml -openapi_spec_hash: 4ecd8d496f056dccf80826264ddf8fe1 -config_hash: 593e89b291976a5e84e4c3c3f8324354 diff --git a/release-please-config.json b/release-please-config.json index 7bae5f5a3..88c752298 100644 --- a/release-please-config.json +++ b/release-please-config.json @@ -20,7 +20,7 @@ ] } ], - "$schema": "https://raw.githubusercontent.com/stainless-api/release-please/main/schemas/config.json", + "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json", "include-v-in-tag": true, "include-component-in-tag": true, "versioning": "prerelease", diff --git a/scripts/utils/upload-artifact.sh b/scripts/utils/upload-artifact.sh index e766fabe6..bd14f19fb 100755 --- a/scripts/utils/upload-artifact.sh +++ b/scripts/utils/upload-artifact.sh @@ -20,7 +20,7 @@ UPLOAD_RESPONSE=$(curl -v -X PUT \ if echo "$UPLOAD_RESPONSE" | grep -q "HTTP/[0-9.]* 200"; then echo -e "\033[32mUploaded build to Stainless storage.\033[0m" - echo -e "\033[32mInstallation: pip install 'https://pkg.stainless.com/s/agentex-sdk-python/$SHA/$FILENAME'\033[0m" + echo -e "\033[32mInstallation: pip install 'https://pkg.stainless.com/s/scale-agentex-python-staging/$SHA/$FILENAME'\033[0m" else echo -e "\033[31mFailed to upload artifact.\033[0m" exit 1 diff --git a/src/agentex/_client.py b/src/agentex/_client.py index b52ae6b78..b67494a95 100644 --- a/src/agentex/_client.py +++ b/src/agentex/_client.py @@ -71,7 +71,7 @@ ] ENVIRONMENTS: Dict[str, str] = { - "production": "http://localhost:5003", + "production": "https://agentex.sgp.scale.com", "development": "http://localhost:5003", }