From 5f2a68d19c84b090f6300fabf7062334c319aa14 Mon Sep 17 00:00:00 2001 From: "mkeeler@launchdarkly.com" Date: Tue, 31 Mar 2026 21:15:16 +0000 Subject: [PATCH 01/10] ci: use draft releases to support immutable GitHub releases --- .github/workflows/release-please.yml | 215 +++++++++++++++++++-------- release-please-config.json | 4 + 2 files changed, 161 insertions(+), 58 deletions(-) diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml index 3051c2d2..cf44b092 100644 --- a/.github/workflows/release-please.yml +++ b/.github/workflows/release-please.yml @@ -58,14 +58,29 @@ jobs: needs: ['release-please'] permissions: id-token: write # Needed for OIDC to get release secrets from AWS. + attestations: write # Needed for actions/attest. + contents: write # Needed for creating tags. if: ${{ needs.release-please.outputs.package-server-ai-released == 'true' }} - outputs: - package-hashes: ${{ steps.build.outputs.package-hashes }} steps: - uses: actions/checkout@v4 with: fetch-depth: 0 + - name: Create release tag + env: + TAG_NAME: ${{ needs.release-please.outputs.package-server-ai-tag-name }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + if gh api "repos/${{ github.repository }}/git/ref/tags/${TAG_NAME}" >/dev/null 2>&1; then + echo "Tag ${TAG_NAME} already exists, skipping creation." + else + echo "Creating tag ${TAG_NAME}." + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git tag "${TAG_NAME}" + git push origin "${TAG_NAME}" + fi + - uses: ./.github/actions/ci with: workspace_path: packages/sdk/server-ai @@ -75,6 +90,17 @@ jobs: with: workspace_path: packages/sdk/server-ai + - name: Generate checksums file + env: + HASHES: ${{ steps.build.outputs.package-hashes }} + run: | + echo "$HASHES" | base64 -d > checksums.txt + + - name: Attest build provenance + uses: actions/attest@v4 + with: + subject-checksums: checksums.txt + - uses: launchdarkly/gh-actions/actions/release-secrets@release-secrets-v1.2.0 name: 'Get PyPI token' with: @@ -92,14 +118,29 @@ jobs: needs: ['release-please'] permissions: id-token: write # Needed for OIDC to get release secrets from AWS. + attestations: write # Needed for actions/attest. + contents: write # Needed for creating tags. if: ${{ needs.release-please.outputs.package-server-ai-langchain-released == 'true' }} - outputs: - package-hashes: ${{ steps.build.outputs.package-hashes }} steps: - uses: actions/checkout@v4 with: fetch-depth: 0 + - name: Create release tag + env: + TAG_NAME: ${{ needs.release-please.outputs.package-server-ai-langchain-tag-name }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + if gh api "repos/${{ github.repository }}/git/ref/tags/${TAG_NAME}" >/dev/null 2>&1; then + echo "Tag ${TAG_NAME} already exists, skipping creation." + else + echo "Creating tag ${TAG_NAME}." + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git tag "${TAG_NAME}" + git push origin "${TAG_NAME}" + fi + - uses: ./.github/actions/ci with: workspace_path: packages/ai-providers/server-ai-langchain @@ -109,6 +150,17 @@ jobs: with: workspace_path: packages/ai-providers/server-ai-langchain + - name: Generate checksums file + env: + HASHES: ${{ steps.build.outputs.package-hashes }} + run: | + echo "$HASHES" | base64 -d > checksums.txt + + - name: Attest build provenance + uses: actions/attest@v4 + with: + subject-checksums: checksums.txt + - uses: launchdarkly/gh-actions/actions/release-secrets@release-secrets-v1.2.0 name: 'Get PyPI token' with: @@ -153,45 +205,34 @@ jobs: password: ${{ env.PYPI_AUTH_TOKEN }} packages-dir: ${{ inputs.workspace_path }}/dist/ - release-server-ai-provenance: - needs: ['release-please', 'release-server-ai'] - if: ${{ needs.release-please.outputs.package-server-ai-released == 'true' }} - permissions: - actions: read # Needed for detecting the GitHub Actions environment. - id-token: write # Needed for provenance signing. - contents: write # Needed for uploading assets to the release. - uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v2.0.0 - with: - base64-subjects: "${{ needs.release-server-ai.outputs.package-hashes }}" - upload-assets: true - upload-tag-name: ${{ needs.release-please.outputs.package-server-ai-tag-name }} - - release-server-ai-langchain-provenance: - needs: ['release-please', 'release-server-ai-langchain'] - if: ${{ needs.release-please.outputs.package-server-ai-langchain-released == 'true' }} - permissions: - actions: read # Needed for detecting the GitHub Actions environment. - id-token: write # Needed for provenance signing. - contents: write # Needed for uploading assets to the release. - uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@5a775b367a56d5bd118a224a811bba288150a563 # v2.0.0 - with: - base64-subjects: "${{ needs.release-server-ai-langchain.outputs.package-hashes }}" - upload-assets: true - upload-tag-name: ${{ needs.release-please.outputs.package-server-ai-langchain-tag-name }} - release-server-ai-openai: runs-on: ubuntu-latest needs: ['release-please'] permissions: id-token: write # Needed for OIDC to get release secrets from AWS. + attestations: write # Needed for actions/attest. + contents: write # Needed for creating tags. if: ${{ needs.release-please.outputs.package-server-ai-openai-released == 'true' }} - outputs: - package-hashes: ${{ steps.build.outputs.package-hashes }} steps: - uses: actions/checkout@v4 with: fetch-depth: 0 + - name: Create release tag + env: + TAG_NAME: ${{ needs.release-please.outputs.package-server-ai-openai-tag-name }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + if gh api "repos/${{ github.repository }}/git/ref/tags/${TAG_NAME}" >/dev/null 2>&1; then + echo "Tag ${TAG_NAME} already exists, skipping creation." + else + echo "Creating tag ${TAG_NAME}." + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git tag "${TAG_NAME}" + git push origin "${TAG_NAME}" + fi + - uses: ./.github/actions/ci with: workspace_path: packages/ai-providers/server-ai-openai @@ -201,6 +242,17 @@ jobs: with: workspace_path: packages/ai-providers/server-ai-openai + - name: Generate checksums file + env: + HASHES: ${{ steps.build.outputs.package-hashes }} + run: | + echo "$HASHES" | base64 -d > checksums.txt + + - name: Attest build provenance + uses: actions/attest@v4 + with: + subject-checksums: checksums.txt + - uses: launchdarkly/gh-actions/actions/release-secrets@release-secrets-v1.2.0 name: 'Get PyPI token' with: @@ -213,32 +265,34 @@ jobs: password: ${{ env.PYPI_AUTH_TOKEN }} packages-dir: packages/ai-providers/server-ai-openai/dist/ - release-server-ai-openai-provenance: - needs: ['release-please', 'release-server-ai-openai'] - if: ${{ needs.release-please.outputs.package-server-ai-openai-released == 'true' }} - permissions: - actions: read # Needed for detecting the GitHub Actions environment. - id-token: write # Needed for provenance signing. - contents: write # Needed for uploading assets to the release. - uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v2.0.0 - with: - base64-subjects: "${{ needs.release-server-ai-openai.outputs.package-hashes }}" - upload-assets: true - upload-tag-name: ${{ needs.release-please.outputs.package-server-ai-openai-tag-name }} - release-server-ai-optimization: runs-on: ubuntu-latest needs: ['release-please'] permissions: id-token: write # Needed for OIDC to get release secrets from AWS. + attestations: write # Needed for actions/attest. + contents: write # Needed for creating tags. if: ${{ needs.release-please.outputs.package-server-ai-optimization-released == 'true' }} - outputs: - package-hashes: ${{ steps.build.outputs.package-hashes }} steps: - uses: actions/checkout@v4 with: fetch-depth: 0 + - name: Create release tag + env: + TAG_NAME: ${{ needs.release-please.outputs.package-server-ai-optimization-tag-name }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + if gh api "repos/${{ github.repository }}/git/ref/tags/${TAG_NAME}" >/dev/null 2>&1; then + echo "Tag ${TAG_NAME} already exists, skipping creation." + else + echo "Creating tag ${TAG_NAME}." + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git tag "${TAG_NAME}" + git push origin "${TAG_NAME}" + fi + - uses: ./.github/actions/ci with: workspace_path: packages/optimization @@ -248,6 +302,17 @@ jobs: with: workspace_path: packages/optimization + - name: Generate checksums file + env: + HASHES: ${{ steps.build.outputs.package-hashes }} + run: | + echo "$HASHES" | base64 -d > checksums.txt + + - name: Attest build provenance + uses: actions/attest@v4 + with: + subject-checksums: checksums.txt + - uses: launchdarkly/gh-actions/actions/release-secrets@release-secrets-v1.2.0 name: 'Get PyPI token' with: @@ -260,15 +325,49 @@ jobs: password: ${{ env.PYPI_AUTH_TOKEN }} packages-dir: packages/optimization/dist/ - release-server-ai-optimization-provenance: - needs: ['release-please', 'release-server-ai-optimization'] - if: ${{ needs.release-please.outputs.package-server-ai-optimization-released == 'true' }} + publish-release: + needs: ['release-please', 'release-server-ai', 'release-server-ai-langchain', 'release-server-ai-openai', 'release-server-ai-optimization'] + if: ${{ always() && needs.release-please.result == 'success' && (needs.release-please.outputs.package-server-ai-released == 'true' || needs.release-please.outputs.package-server-ai-langchain-released == 'true' || needs.release-please.outputs.package-server-ai-openai-released == 'true' || needs.release-please.outputs.package-server-ai-optimization-released == 'true') }} + runs-on: ubuntu-latest permissions: - actions: read # Needed for detecting the GitHub Actions environment. - id-token: write # Needed for provenance signing. - contents: write # Needed for uploading assets to the release. - uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v2.0.0 - with: - base64-subjects: "${{ needs.release-server-ai-optimization.outputs.package-hashes }}" - upload-assets: true - upload-tag-name: ${{ needs.release-please.outputs.package-server-ai-optimization-tag-name }} + contents: write + steps: + - name: Publish server-ai release + if: ${{ needs.release-please.outputs.package-server-ai-released == 'true' && needs.release-server-ai.result == 'success' }} + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + TAG_NAME: ${{ needs.release-please.outputs.package-server-ai-tag-name }} + run: > + gh release edit "$TAG_NAME" + --repo ${{ github.repository }} + --draft=false + + - name: Publish server-ai-langchain release + if: ${{ needs.release-please.outputs.package-server-ai-langchain-released == 'true' && needs.release-server-ai-langchain.result == 'success' }} + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + TAG_NAME: ${{ needs.release-please.outputs.package-server-ai-langchain-tag-name }} + run: > + gh release edit "$TAG_NAME" + --repo ${{ github.repository }} + --draft=false + + - name: Publish server-ai-openai release + if: ${{ needs.release-please.outputs.package-server-ai-openai-released == 'true' && needs.release-server-ai-openai.result == 'success' }} + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + TAG_NAME: ${{ needs.release-please.outputs.package-server-ai-openai-tag-name }} + run: > + gh release edit "$TAG_NAME" + --repo ${{ github.repository }} + --draft=false + + - name: Publish server-ai-optimization release + if: ${{ needs.release-please.outputs.package-server-ai-optimization-released == 'true' && needs.release-server-ai-optimization.result == 'success' }} + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + TAG_NAME: ${{ needs.release-please.outputs.package-server-ai-optimization-tag-name }} + run: > + gh release edit "$TAG_NAME" + --repo ${{ github.repository }} + --draft=false diff --git a/release-please-config.json b/release-please-config.json index ef66f9fa..ba0f9f06 100644 --- a/release-please-config.json +++ b/release-please-config.json @@ -7,6 +7,7 @@ "versioning": "default", "bump-minor-pre-major": true, "include-v-in-tag": false, + "draft": true, "extra-files": ["src/ldai/__init__.py", "PROVENANCE.md"], "component": "launchdarkly-server-sdk-ai" }, @@ -15,6 +16,7 @@ "versioning": "default", "bump-minor-pre-major": true, "include-v-in-tag": false, + "draft": true, "extra-files": ["src/ldai_langchain/__init__.py"], "component": "launchdarkly-server-sdk-ai-langchain" }, @@ -23,6 +25,7 @@ "versioning": "default", "bump-minor-pre-major": true, "include-v-in-tag": false, + "draft": true, "extra-files": ["src/ldai_openai/__init__.py"], "component": "launchdarkly-server-sdk-ai-openai" }, @@ -31,6 +34,7 @@ "versioning": "default", "bump-minor-pre-major": true, "include-v-in-tag": false, + "draft": true, "extra-files": ["src/ldai_optimization/__init__.py"], "component": "launchdarkly-server-sdk-ai-optimization" } From 49a59a488c63aa0a961d3851ceeeead6edc888a9 Mon Sep 17 00:00:00 2001 From: "mkeeler@launchdarkly.com" Date: Tue, 31 Mar 2026 21:42:15 +0000 Subject: [PATCH 02/10] ci: add force-tag-creation and publish_release option --- release-please-config.json | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/release-please-config.json b/release-please-config.json index ba0f9f06..6c43dfc3 100644 --- a/release-please-config.json +++ b/release-please-config.json @@ -8,7 +8,11 @@ "bump-minor-pre-major": true, "include-v-in-tag": false, "draft": true, - "extra-files": ["src/ldai/__init__.py", "PROVENANCE.md"], + "force-tag-creation": true, + "extra-files": [ + "src/ldai/__init__.py", + "PROVENANCE.md" + ], "component": "launchdarkly-server-sdk-ai" }, "packages/ai-providers/server-ai-langchain": { @@ -17,7 +21,10 @@ "bump-minor-pre-major": true, "include-v-in-tag": false, "draft": true, - "extra-files": ["src/ldai_langchain/__init__.py"], + "force-tag-creation": true, + "extra-files": [ + "src/ldai_langchain/__init__.py" + ], "component": "launchdarkly-server-sdk-ai-langchain" }, "packages/ai-providers/server-ai-openai": { @@ -26,7 +33,10 @@ "bump-minor-pre-major": true, "include-v-in-tag": false, "draft": true, - "extra-files": ["src/ldai_openai/__init__.py"], + "force-tag-creation": true, + "extra-files": [ + "src/ldai_openai/__init__.py" + ], "component": "launchdarkly-server-sdk-ai-openai" }, "packages/optimization": { @@ -35,7 +45,10 @@ "bump-minor-pre-major": true, "include-v-in-tag": false, "draft": true, - "extra-files": ["src/ldai_optimization/__init__.py"], + "force-tag-creation": true, + "extra-files": [ + "src/ldai_optimization/__init__.py" + ], "component": "launchdarkly-server-sdk-ai-optimization" } } From 7a40d96ba025748e34b36dda5a4f10f44cf7b9f5 Mon Sep 17 00:00:00 2001 From: "mkeeler@launchdarkly.com" Date: Tue, 31 Mar 2026 22:06:51 +0000 Subject: [PATCH 03/10] ci: simplify for attestation-only releases (no draft needed) Since actions/attest@v4 stores attestations via GitHub's attestation API (not as release assets), repos that only use attestation don't need draft releases. Release-please can publish the release directly. Changes: - Remove draft:true from release-please-config.json - Remove create-tag job/steps (force-tag-creation handles this) - Remove publish-release job (release is published directly) - Remove publish_release input from manual workflows --- .github/workflows/release-please.yml | 107 --------------------------- release-please-config.json | 4 - 2 files changed, 111 deletions(-) diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml index cf44b092..df3fe8ff 100644 --- a/.github/workflows/release-please.yml +++ b/.github/workflows/release-please.yml @@ -66,21 +66,6 @@ jobs: with: fetch-depth: 0 - - name: Create release tag - env: - TAG_NAME: ${{ needs.release-please.outputs.package-server-ai-tag-name }} - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - if gh api "repos/${{ github.repository }}/git/ref/tags/${TAG_NAME}" >/dev/null 2>&1; then - echo "Tag ${TAG_NAME} already exists, skipping creation." - else - echo "Creating tag ${TAG_NAME}." - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" - git tag "${TAG_NAME}" - git push origin "${TAG_NAME}" - fi - - uses: ./.github/actions/ci with: workspace_path: packages/sdk/server-ai @@ -126,21 +111,6 @@ jobs: with: fetch-depth: 0 - - name: Create release tag - env: - TAG_NAME: ${{ needs.release-please.outputs.package-server-ai-langchain-tag-name }} - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - if gh api "repos/${{ github.repository }}/git/ref/tags/${TAG_NAME}" >/dev/null 2>&1; then - echo "Tag ${TAG_NAME} already exists, skipping creation." - else - echo "Creating tag ${TAG_NAME}." - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" - git tag "${TAG_NAME}" - git push origin "${TAG_NAME}" - fi - - uses: ./.github/actions/ci with: workspace_path: packages/ai-providers/server-ai-langchain @@ -218,21 +188,6 @@ jobs: with: fetch-depth: 0 - - name: Create release tag - env: - TAG_NAME: ${{ needs.release-please.outputs.package-server-ai-openai-tag-name }} - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - if gh api "repos/${{ github.repository }}/git/ref/tags/${TAG_NAME}" >/dev/null 2>&1; then - echo "Tag ${TAG_NAME} already exists, skipping creation." - else - echo "Creating tag ${TAG_NAME}." - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" - git tag "${TAG_NAME}" - git push origin "${TAG_NAME}" - fi - - uses: ./.github/actions/ci with: workspace_path: packages/ai-providers/server-ai-openai @@ -278,21 +233,6 @@ jobs: with: fetch-depth: 0 - - name: Create release tag - env: - TAG_NAME: ${{ needs.release-please.outputs.package-server-ai-optimization-tag-name }} - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - if gh api "repos/${{ github.repository }}/git/ref/tags/${TAG_NAME}" >/dev/null 2>&1; then - echo "Tag ${TAG_NAME} already exists, skipping creation." - else - echo "Creating tag ${TAG_NAME}." - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" - git tag "${TAG_NAME}" - git push origin "${TAG_NAME}" - fi - - uses: ./.github/actions/ci with: workspace_path: packages/optimization @@ -324,50 +264,3 @@ jobs: with: password: ${{ env.PYPI_AUTH_TOKEN }} packages-dir: packages/optimization/dist/ - - publish-release: - needs: ['release-please', 'release-server-ai', 'release-server-ai-langchain', 'release-server-ai-openai', 'release-server-ai-optimization'] - if: ${{ always() && needs.release-please.result == 'success' && (needs.release-please.outputs.package-server-ai-released == 'true' || needs.release-please.outputs.package-server-ai-langchain-released == 'true' || needs.release-please.outputs.package-server-ai-openai-released == 'true' || needs.release-please.outputs.package-server-ai-optimization-released == 'true') }} - runs-on: ubuntu-latest - permissions: - contents: write - steps: - - name: Publish server-ai release - if: ${{ needs.release-please.outputs.package-server-ai-released == 'true' && needs.release-server-ai.result == 'success' }} - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - TAG_NAME: ${{ needs.release-please.outputs.package-server-ai-tag-name }} - run: > - gh release edit "$TAG_NAME" - --repo ${{ github.repository }} - --draft=false - - - name: Publish server-ai-langchain release - if: ${{ needs.release-please.outputs.package-server-ai-langchain-released == 'true' && needs.release-server-ai-langchain.result == 'success' }} - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - TAG_NAME: ${{ needs.release-please.outputs.package-server-ai-langchain-tag-name }} - run: > - gh release edit "$TAG_NAME" - --repo ${{ github.repository }} - --draft=false - - - name: Publish server-ai-openai release - if: ${{ needs.release-please.outputs.package-server-ai-openai-released == 'true' && needs.release-server-ai-openai.result == 'success' }} - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - TAG_NAME: ${{ needs.release-please.outputs.package-server-ai-openai-tag-name }} - run: > - gh release edit "$TAG_NAME" - --repo ${{ github.repository }} - --draft=false - - - name: Publish server-ai-optimization release - if: ${{ needs.release-please.outputs.package-server-ai-optimization-released == 'true' && needs.release-server-ai-optimization.result == 'success' }} - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - TAG_NAME: ${{ needs.release-please.outputs.package-server-ai-optimization-tag-name }} - run: > - gh release edit "$TAG_NAME" - --repo ${{ github.repository }} - --draft=false diff --git a/release-please-config.json b/release-please-config.json index 6c43dfc3..38ef2c02 100644 --- a/release-please-config.json +++ b/release-please-config.json @@ -7,7 +7,6 @@ "versioning": "default", "bump-minor-pre-major": true, "include-v-in-tag": false, - "draft": true, "force-tag-creation": true, "extra-files": [ "src/ldai/__init__.py", @@ -20,7 +19,6 @@ "versioning": "default", "bump-minor-pre-major": true, "include-v-in-tag": false, - "draft": true, "force-tag-creation": true, "extra-files": [ "src/ldai_langchain/__init__.py" @@ -32,7 +30,6 @@ "versioning": "default", "bump-minor-pre-major": true, "include-v-in-tag": false, - "draft": true, "force-tag-creation": true, "extra-files": [ "src/ldai_openai/__init__.py" @@ -44,7 +41,6 @@ "versioning": "default", "bump-minor-pre-major": true, "include-v-in-tag": false, - "draft": true, "force-tag-creation": true, "extra-files": [ "src/ldai_optimization/__init__.py" From 1ffbad93eab39c119f022f1fad278845ed99d4c7 Mon Sep 17 00:00:00 2001 From: "mkeeler@launchdarkly.com" Date: Tue, 31 Mar 2026 22:33:42 +0000 Subject: [PATCH 04/10] ci: remove force-tag-creation from attestation-only repo force-tag-creation only operates in conjunction with draft releases. Since this repo does not use draft releases (attestation-only, no artifact uploads to the release), force-tag-creation is not needed. --- release-please-config.json | 4 ---- 1 file changed, 4 deletions(-) diff --git a/release-please-config.json b/release-please-config.json index 38ef2c02..cf0d738a 100644 --- a/release-please-config.json +++ b/release-please-config.json @@ -7,7 +7,6 @@ "versioning": "default", "bump-minor-pre-major": true, "include-v-in-tag": false, - "force-tag-creation": true, "extra-files": [ "src/ldai/__init__.py", "PROVENANCE.md" @@ -19,7 +18,6 @@ "versioning": "default", "bump-minor-pre-major": true, "include-v-in-tag": false, - "force-tag-creation": true, "extra-files": [ "src/ldai_langchain/__init__.py" ], @@ -30,7 +28,6 @@ "versioning": "default", "bump-minor-pre-major": true, "include-v-in-tag": false, - "force-tag-creation": true, "extra-files": [ "src/ldai_openai/__init__.py" ], @@ -41,7 +38,6 @@ "versioning": "default", "bump-minor-pre-major": true, "include-v-in-tag": false, - "force-tag-creation": true, "extra-files": [ "src/ldai_optimization/__init__.py" ], From 59da396b8fa0307ab909600d929405b5c45cb359 Mon Sep 17 00:00:00 2001 From: "mkeeler@launchdarkly.com" Date: Tue, 31 Mar 2026 23:12:15 +0000 Subject: [PATCH 05/10] ci: switch from subject-checksums to subject-path for attestation --- .github/actions/build/action.yml | 11 ---------- .github/workflows/release-please.yml | 32 ++++------------------------ 2 files changed, 4 insertions(+), 39 deletions(-) diff --git a/.github/actions/build/action.yml b/.github/actions/build/action.yml index d096f3cd..fa68c42f 100644 --- a/.github/actions/build/action.yml +++ b/.github/actions/build/action.yml @@ -4,10 +4,6 @@ inputs: workspace_path: description: 'Path to the package to build.' required: true -outputs: - package-hashes: - description: "base64-encoded sha256 hashes of distribution files" - value: ${{ steps.package-hashes.outputs.package-hashes }} runs: using: composite @@ -15,10 +11,3 @@ runs: - name: Build distribution files shell: bash run: make -C ${{ inputs.workspace_path }} build - - - name: Hash build files for provenance - id: package-hashes - shell: bash - working-directory: ${{ inputs.workspace_path }}/dist - run: | - echo "package-hashes=$(sha256sum * | base64 -w0)" >> "$GITHUB_OUTPUT" diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml index df3fe8ff..104538b1 100644 --- a/.github/workflows/release-please.yml +++ b/.github/workflows/release-please.yml @@ -75,16 +75,10 @@ jobs: with: workspace_path: packages/sdk/server-ai - - name: Generate checksums file - env: - HASHES: ${{ steps.build.outputs.package-hashes }} - run: | - echo "$HASHES" | base64 -d > checksums.txt - - name: Attest build provenance uses: actions/attest@v4 with: - subject-checksums: checksums.txt + subject-path: 'packages/sdk/server-ai/dist/*' - uses: launchdarkly/gh-actions/actions/release-secrets@release-secrets-v1.2.0 name: 'Get PyPI token' @@ -120,16 +114,10 @@ jobs: with: workspace_path: packages/ai-providers/server-ai-langchain - - name: Generate checksums file - env: - HASHES: ${{ steps.build.outputs.package-hashes }} - run: | - echo "$HASHES" | base64 -d > checksums.txt - - name: Attest build provenance uses: actions/attest@v4 with: - subject-checksums: checksums.txt + subject-path: 'packages/ai-providers/server-ai-langchain/dist/*' - uses: launchdarkly/gh-actions/actions/release-secrets@release-secrets-v1.2.0 name: 'Get PyPI token' @@ -197,16 +185,10 @@ jobs: with: workspace_path: packages/ai-providers/server-ai-openai - - name: Generate checksums file - env: - HASHES: ${{ steps.build.outputs.package-hashes }} - run: | - echo "$HASHES" | base64 -d > checksums.txt - - name: Attest build provenance uses: actions/attest@v4 with: - subject-checksums: checksums.txt + subject-path: 'packages/ai-providers/server-ai-openai/dist/*' - uses: launchdarkly/gh-actions/actions/release-secrets@release-secrets-v1.2.0 name: 'Get PyPI token' @@ -242,16 +224,10 @@ jobs: with: workspace_path: packages/optimization - - name: Generate checksums file - env: - HASHES: ${{ steps.build.outputs.package-hashes }} - run: | - echo "$HASHES" | base64 -d > checksums.txt - - name: Attest build provenance uses: actions/attest@v4 with: - subject-checksums: checksums.txt + subject-path: 'packages/optimization/dist/*' - uses: launchdarkly/gh-actions/actions/release-secrets@release-secrets-v1.2.0 name: 'Get PyPI token' From e094cbabe36e01ab9c809927d8044fe6d0aaba43 Mon Sep 17 00:00:00 2001 From: "mkeeler@launchdarkly.com" Date: Wed, 1 Apr 2026 18:34:22 +0000 Subject: [PATCH 06/10] ci: remove unnecessary contents: write and fetch-depth: 0 --- .github/workflows/release-please.yml | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml index 104538b1..ae4e9122 100644 --- a/.github/workflows/release-please.yml +++ b/.github/workflows/release-please.yml @@ -59,12 +59,9 @@ jobs: permissions: id-token: write # Needed for OIDC to get release secrets from AWS. attestations: write # Needed for actions/attest. - contents: write # Needed for creating tags. if: ${{ needs.release-please.outputs.package-server-ai-released == 'true' }} steps: - uses: actions/checkout@v4 - with: - fetch-depth: 0 - uses: ./.github/actions/ci with: @@ -98,12 +95,9 @@ jobs: permissions: id-token: write # Needed for OIDC to get release secrets from AWS. attestations: write # Needed for actions/attest. - contents: write # Needed for creating tags. if: ${{ needs.release-please.outputs.package-server-ai-langchain-released == 'true' }} steps: - uses: actions/checkout@v4 - with: - fetch-depth: 0 - uses: ./.github/actions/ci with: @@ -169,12 +163,9 @@ jobs: permissions: id-token: write # Needed for OIDC to get release secrets from AWS. attestations: write # Needed for actions/attest. - contents: write # Needed for creating tags. if: ${{ needs.release-please.outputs.package-server-ai-openai-released == 'true' }} steps: - uses: actions/checkout@v4 - with: - fetch-depth: 0 - uses: ./.github/actions/ci with: @@ -208,12 +199,9 @@ jobs: permissions: id-token: write # Needed for OIDC to get release secrets from AWS. attestations: write # Needed for actions/attest. - contents: write # Needed for creating tags. if: ${{ needs.release-please.outputs.package-server-ai-optimization-released == 'true' }} steps: - uses: actions/checkout@v4 - with: - fetch-depth: 0 - uses: ./.github/actions/ci with: From d6c9cb34618c6a7f94f85eff3205ae99b91d8140 Mon Sep 17 00:00:00 2001 From: "mkeeler@launchdarkly.com" Date: Wed, 1 Apr 2026 19:08:03 +0000 Subject: [PATCH 07/10] ci: use format() for dry_run conditions to handle both string and boolean inputs --- .github/workflows/release-please.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml index ae4e9122..70b00332 100644 --- a/.github/workflows/release-please.yml +++ b/.github/workflows/release-please.yml @@ -144,14 +144,14 @@ jobs: workspace_path: ${{ inputs.workspace_path }} - uses: launchdarkly/gh-actions/actions/release-secrets@release-secrets-v1.2.0 - if: ${{ inputs.dry_run != true }} + if: ${{ format('{0}', inputs.dry_run) != 'true' }} name: 'Get PyPI token' with: aws_assume_role: ${{ vars.AWS_ROLE_ARN }} ssm_parameter_pairs: '/production/common/releasing/pypi/token = PYPI_AUTH_TOKEN' - name: Publish to PyPI - if: ${{ inputs.dry_run != true }} + if: ${{ format('{0}', inputs.dry_run) != 'true' }} uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0 with: password: ${{ env.PYPI_AUTH_TOKEN }} From 2ee25cc447b00aca1a68ce391352bb4957f18d14 Mon Sep 17 00:00:00 2001 From: "mkeeler@launchdarkly.com" Date: Wed, 1 Apr 2026 21:47:48 +0000 Subject: [PATCH 08/10] docs: update PROVENANCE.md and README.md for GitHub artifact attestations --- README.md | 4 ++-- packages/sdk/server-ai/PROVENANCE.md | 33 ++++++++++------------------ 2 files changed, 14 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 9f78db48..040c01cb 100644 --- a/README.md +++ b/README.md @@ -35,9 +35,9 @@ Read our [documentation](http://docs.launchdarkly.com) for in-depth instructions We encourage pull requests and other contributions from the community. Check out our [contributing guidelines](CONTRIBUTING.md) for instructions on how to contribute to this library. -## Verifying library build provenance with the SLSA framework +## Verifying library build provenance with GitHub artifact attestations -LaunchDarkly uses the [SLSA framework](https://slsa.dev/spec/v1.0/about) (Supply-chain Levels for Software Artifacts) to help developers make their supply chain more secure by ensuring the authenticity and build integrity of our published library packages. To learn more, see the [provenance guide](packages/sdk/server-ai/PROVENANCE.md). +LaunchDarkly uses [GitHub artifact attestations](https://docs.github.com/en/actions/security-for-github-actions/using-artifact-attestations/using-artifact-attestations-to-establish-provenance-for-builds) to help developers make their supply chain more secure by ensuring the authenticity and build integrity of our published library packages. To learn more, see the [provenance guide](packages/sdk/server-ai/PROVENANCE.md). ## About LaunchDarkly diff --git a/packages/sdk/server-ai/PROVENANCE.md b/packages/sdk/server-ai/PROVENANCE.md index def920c4..b000a746 100644 --- a/packages/sdk/server-ai/PROVENANCE.md +++ b/packages/sdk/server-ai/PROVENANCE.md @@ -1,45 +1,36 @@ -## Verifying SDK build provenance with the SLSA framework +## Verifying SDK build provenance with GitHub artifact attestations -LaunchDarkly uses the [SLSA framework](https://slsa.dev/spec/v1.0/about) (Supply-chain Levels for Software Artifacts) to help developers make their supply chain more secure by ensuring the authenticity and build integrity of our published SDK packages. +LaunchDarkly uses [GitHub artifact attestations](https://docs.github.com/en/actions/security-for-github-actions/using-artifact-attestations/using-artifact-attestations-to-establish-provenance-for-builds) to help developers make their supply chain more secure by ensuring the authenticity and build integrity of our published SDK packages. -As part of [SLSA requirements for level 3 compliance](https://slsa.dev/spec/v1.0/requirements), LaunchDarkly publishes provenance about our SDK package builds using [GitHub's generic SLSA3 provenance generator](https://github.com/slsa-framework/slsa-github-generator/blob/main/internal/builders/generic/README.md#generation-of-slsa3-provenance-for-arbitrary-projects) for distribution alongside our packages. These attestations are available for download from the GitHub release page for the release version under Assets > `multiple.intoto.jsonl`. +LaunchDarkly publishes provenance about our SDK package builds using [GitHub's `actions/attest` action](https://github.com/actions/attest). These attestations are stored in GitHub's attestation API and can be verified using the [GitHub CLI](https://cli.github.com/). -To verify SLSA provenance attestations, we recommend using [slsa-verifier](https://github.com/slsa-framework/slsa-verifier). Example usage for verifying a package is included below: +To verify build provenance attestations, we recommend using the [GitHub CLI `attestation verify` command](https://cli.github.com/manual/gh_attestation_verify). Example usage for verifying SDK packages is included below: - ``` # Set the version of the library to verify VERSION=0.16.1 ``` - ``` -# Download package from PyPi +# Download package from PyPI $ pip download --only-binary=:all: launchdarkly-server-sdk-ai==${VERSION} -# Download provenance from Github release into same directory -$ curl --location -O \ - https://github.com/launchdarkly/python-server-sdk-ai/releases/download/${VERSION}/multiple.intoto.jsonl - -# Run slsa-verifier to verify provenance against package artifacts -$ slsa-verifier verify-artifact \ ---provenance-path multiple.intoto.jsonl \ ---source-uri github.com/launchdarkly/python-server-sdk-ai \ -launchdarkly_server_sdk_ai-${VERSION}-py3-none-any.whl +# Verify provenance using the GitHub CLI +$ gh attestation verify launchdarkly_server_sdk_ai-${VERSION}-py3-none-any.whl -R launchdarkly/python-server-sdk-ai ``` Below is a sample of expected output. ``` -Verified signature against tlog entry index 150910243 at URL: https://rekor.sigstore.dev/api/v1/log/entries/108e9186e8c5677ab3f14fc82cd3deb769e07ef812cadda623c08c77d4e51fc03124ee7542c470a1 -Verified build using builder "https://github.com/slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@refs/tags/v2.0.0" at commit 8e2d4094b4833d075e70dfce43bbc7176008c4a1 -Verifying artifact launchdarkly_server_sdk_ai-0.3.0-py3-none-any.whl: PASSED +Loaded digest sha256:... for file://launchdarkly_server_sdk_ai-0.16.1-py3-none-any.whl +Loaded 1 attestation from GitHub API +✓ Verification succeeded! -PASSED: SLSA verification passed +launchdarkly_server_sdk_ai-0.16.1-py3-none-any.whl was attested by a trusted GitHub Actions workflow ``` -Alternatively, to verify the provenance manually, the SLSA framework specifies [recommendations for verifying build artifacts](https://slsa.dev/spec/v1.0/verifying-artifacts) in their documentation. +For more information, see [GitHub's documentation on verifying artifact attestations](https://docs.github.com/en/actions/security-for-github-actions/using-artifact-attestations/using-artifact-attestations-to-establish-provenance-for-builds#verifying-artifact-attestations-with-the-github-cli). **Note:** These instructions do not apply when building our libraries from source. From 7e4d3c4e499f03c0766e0e5542260ac03215a40b Mon Sep 17 00:00:00 2001 From: "mkeeler@launchdarkly.com" Date: Wed, 1 Apr 2026 21:53:29 +0000 Subject: [PATCH 09/10] docs: use real gh attestation verify output template and --owner flag --- packages/sdk/server-ai/PROVENANCE.md | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/packages/sdk/server-ai/PROVENANCE.md b/packages/sdk/server-ai/PROVENANCE.md index b000a746..1483ebec 100644 --- a/packages/sdk/server-ai/PROVENANCE.md +++ b/packages/sdk/server-ai/PROVENANCE.md @@ -18,7 +18,7 @@ VERSION=0.16.1 $ pip download --only-binary=:all: launchdarkly-server-sdk-ai==${VERSION} # Verify provenance using the GitHub CLI -$ gh attestation verify launchdarkly_server_sdk_ai-${VERSION}-py3-none-any.whl -R launchdarkly/python-server-sdk-ai +$ gh attestation verify launchdarkly_server_sdk_ai-${VERSION}-py3-none-any.whl --owner launchdarkly ``` Below is a sample of expected output. @@ -26,9 +26,22 @@ Below is a sample of expected output. ``` Loaded digest sha256:... for file://launchdarkly_server_sdk_ai-0.16.1-py3-none-any.whl Loaded 1 attestation from GitHub API + +The following policy criteria will be enforced: +- Predicate type must match:................ https://slsa.dev/provenance/v1 +- Source Repository Owner URI must match:... https://github.com/launchdarkly +- Subject Alternative Name must match regex: (?i)^https://github.com/launchdarkly/ +- OIDC Issuer must match:................... https://token.actions.githubusercontent.com + ✓ Verification succeeded! -launchdarkly_server_sdk_ai-0.16.1-py3-none-any.whl was attested by a trusted GitHub Actions workflow +The following 1 attestation matched the policy criteria + +- Attestation #1 + - Build repo:..... launchdarkly/python-server-sdk-ai + - Build workflow:. .github/workflows/release-please.yml + - Signer repo:.... launchdarkly/python-server-sdk-ai + - Signer workflow: .github/workflows/release-please.yml ``` For more information, see [GitHub's documentation on verifying artifact attestations](https://docs.github.com/en/actions/security-for-github-actions/using-artifact-attestations/using-artifact-attestations-to-establish-provenance-for-builds#verifying-artifact-attestations-with-the-github-cli). From 8b882caa532bd0e941edc391f8a9357a64fe5f85 Mon Sep 17 00:00:00 2001 From: "mkeeler@launchdarkly.com" Date: Wed, 1 Apr 2026 22:12:01 +0000 Subject: [PATCH 10/10] docs: restore original SLSA framework text in README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 040c01cb..9f78db48 100644 --- a/README.md +++ b/README.md @@ -35,9 +35,9 @@ Read our [documentation](http://docs.launchdarkly.com) for in-depth instructions We encourage pull requests and other contributions from the community. Check out our [contributing guidelines](CONTRIBUTING.md) for instructions on how to contribute to this library. -## Verifying library build provenance with GitHub artifact attestations +## Verifying library build provenance with the SLSA framework -LaunchDarkly uses [GitHub artifact attestations](https://docs.github.com/en/actions/security-for-github-actions/using-artifact-attestations/using-artifact-attestations-to-establish-provenance-for-builds) to help developers make their supply chain more secure by ensuring the authenticity and build integrity of our published library packages. To learn more, see the [provenance guide](packages/sdk/server-ai/PROVENANCE.md). +LaunchDarkly uses the [SLSA framework](https://slsa.dev/spec/v1.0/about) (Supply-chain Levels for Software Artifacts) to help developers make their supply chain more secure by ensuring the authenticity and build integrity of our published library packages. To learn more, see the [provenance guide](packages/sdk/server-ai/PROVENANCE.md). ## About LaunchDarkly