only rebuild images if something in tools changes#4
Merged
Conversation
Signed-off-by: Tom Hennen <tom.hennen@gmail.com>
TomHennen
added a commit
that referenced
this pull request
Apr 25, 2026
wrangle-test PR #4 (grant-python-id-token-perms) merged to main, adding id-token: write + actions: read to test-python and test-python-uv jobs so the new provenance step in wrangle's reusable workflow can run. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
TomHennen
added a commit
that referenced
this pull request
Apr 26, 2026
The example workflow's `build:` job and the README's quick-start both granted only `contents: read` when calling wrangle's reusable workflow. That reusable workflow has a nested provenance job that calls slsa-github-generator, which declares `contents: write` for its upload-assets job. GitHub validates a called workflow's permissions at startup regardless of any `if:` condition that would skip the job at runtime — so callers granting only `contents: read` got `startup_failure` immediately. We hit this same bug in wrangle-test (fixed in tomhennen/wrangle-test PRs #4 and #5) but the example workflow was never exercised end-to-end and inherited the same gap. Adopters copying from either the example or the README would have seen the same failure. Fix: - Add contents: write, id-token: write, actions: read to the build: job in gh_workflow_examples/build_python.yml. - Same set in build/actions/python/README.md's quick-start snippet. - Add bats assertions for both so this regression can't return. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
TomHennen
added a commit
that referenced
this pull request
Apr 26, 2026
* Implement Python build type Composite action (build/actions/python/action.yml): - Input validation with globbing disabled - uv detection (uv.lock → uv sync/build/run) with PEP 517 fallback - Python version auto-detected from pyproject.toml via setup-python - pytest gating (skips gracefully if no tests found) - SPDX SBOM via syft - Artifact hash computation for SLSA generator Reusable workflow (build_and_publish_python.yml): - Three-job architecture: build (contents:read only) → publish (id-token:write, non-PR) → provenance (SLSA L3, non-PR) - PyPI Trusted Publishing via OIDC (no secrets) - PEP 740 Sigstore attestations on publish - SLSA L3 provenance via slsa-github-generator (tag ref per #147) Also adds: - 19 structural tests (build/actions/python/test.bats) - Workflow example (gh_workflow_examples/build_python.yml) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Update Python action SHA to include composite action The previous commit added the action but the workflow referenced the parent SHA where the action didn't exist yet. Update to the commit that contains the action. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Trigger fresh CI after companion permission fix * Split Python build into build-only workflow + adopter publish PyPI Trusted Publishing does not support reusable workflows — the OIDC token's workflow_ref must point to the adopter's own workflow. (pypi/warehouse#11096, open since 2022, no timeline) Changes: - Reusable workflow is now build-only: build, test, SBOM, hash computation. Exports hashes and version outputs. - Remove publish and provenance jobs from reusable workflow. - Workflow example (gh_workflow_examples/build_python.yml) includes publish + SLSA provenance jobs directly — adopters copy this and configure their Trusted Publisher against their own workflow file. - Update structural tests to verify build-only architecture. - Update companion template to build-only with minimal permissions. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Update Python spec to reflect build-only reusable workflow Document the PyPI Trusted Publishing limitation and the split architecture: wrangle owns build/test/SBOM, adopter owns publish. Reference #157 for when to revisit. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Trigger CI for end-to-end Python publish test * Trigger CI for zizmor suppression fix * Address PR review: cosign-verified syft, SBOM upload, hash format Resolves the three blocking issues from the PR review: 1. Replace `curl | sh` syft install with `tools/syft/install.sh` — Cosign keyless verifies checksums.txt (root of trust = Fulcio CA + Rekor), then SHA-256 verifies the binary against the trusted checksums. Installs to $WRANGLE_BIN_DIR per the install contract. Adds sigstore/cosign-installer step before the syft install. 2. Upload `metadata/python/<shortname>/` (SBOM) as a second artifact in the reusable workflow. Namespace both dist + metadata artifact names by shortname so multiple python builds don't collide; expose `dist-artifact-name` output for the adopter's publish job. 3. Refresh SPEC.md to match the build-only reusable workflow: drop `repository-url` input, replace stale outputs table, rewrite steps 7-10, and rewrite the sample workflow. Plus the smaller fixes: - Hash format: `cd dist && sha256sum -- *` (drops `./` prefix that would break slsa-verifier subject matching). - Replace `ls dist/*.whl | head` with `shopt -s nullglob` so the pipefail edge case can surface a friendly warning. - Example workflow: add `upload-assets: ${{ startsWith(github.ref, 'refs/tags/') }}` to use the `contents: write` permission, document the workflow_dispatch publish behavior. - Tighten the pytest-config grep (`grep -qF '[tool.pytest'`). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Bump composite-action pin in reusable workflow to 93cfefa The reusable workflow still pinned the composite action at the previous SHA (ae27083), so the integration test was running the pre-review code — including the curl|sh syft installer that crashed with EACCES on /boot/efi. Bumping the pin to 93cfefa picks up the cosign-verified syft install, the namespaced artifacts, the corrected hash format, and the SBOM upload. This is the one-commit-lag pattern (see CLAUDE.md). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Fix syft cosign identity: anchore releases from main, not tag refs Anchore's release workflow (anchore/syft/.github/workflows/release.yaml) is triggered by pushes to main, not by tag pushes — so the OIDC certificate's workflow_ref claim is @refs/heads/main, not @refs/tags/v<version>. The previous regex required tag refs and rejected every signature, aborting installs. Confirmed by decoding the .pem from github.com/anchore/syft/releases/download/v1.42.4/syft_1.42.4_checksums.txt.pem — Subject URI is "https://github.com/anchore/syft/.github/workflows/release.yaml@refs/heads/main". Switching to the literal --certificate-identity for that exact value; this is the strongest claim available given Anchore's release setup. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Bump composite-action pin to ba6dfa6 (cosign identity fix) Picks up the syft cosign --certificate-identity fix. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Move SLSA provenance into reusable workflow; extract helpers Address remaining PR-156 review comments: - SLSA provenance generation moves from the example workflow into wrangle's reusable workflow (parallel to the container build). The example workflow now downloads the provenance artifact and runs `slsa-verifier verify-artifact` before publishing — the recommended pattern, but adopters can drop the verify step. - Extract path validation to lib/validate_path.sh (shared) plus per-action validate_inputs.sh wrappers for python and container, satisfying CLAUDE.md's "extract inline run: blocks >5 lines or with logic" rule. - Extract python install/test logic to install_deps.sh and run_tests.sh; document pytest discovery convention. - Add build/actions/python/README.md with adopter onboarding, optional verify-before-publish recipe, and consumer-side verification commands. Update build/README.md and gh_workflow_examples/README.md to mention python. - Update SPEC.md to reflect provenance-in-reusable-workflow, the new provenance-artifact-name output, and the pytest convention. - Add behavioral tests for lib/validate_path.sh and the container validate_inputs.sh; refresh python test.bats for the helper extraction. The reusable workflow's composite-action SHA pin still references the pre-refactor commit and must be bumped to this commit's SHA in a follow-up commit before integration tests will pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Bump composite-action pin to c65a939 (helper-script extraction) c65a939 extracted validate_inputs.sh, install_deps.sh, run_tests.sh, and lib/validate_path.sh. The reusable workflow's `uses:` ref needs to point at that commit so the composite action it pulls includes those scripts; the previous pin (ba6dfa6) does not. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Trigger CI to exercise merged python-uv fixture The companion repo's add-python-uv-fixture branch landed on main, adding python-uv/ and the test-python-uv job. This empty commit re-runs the integration test so the new uv path coverage exercises the post-refactor composite (helper scripts + provenance-in-reusable-workflow). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Fix SLSA generator output name (provenance-name, not -download-name) The previous commit referenced ${{ jobs.provenance.outputs.provenance-download-name }} but slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v2.1.0 declares its output as `provenance-name`, not `provenance-download-name`. GitHub couldn't resolve the workflow_call output binding, so the integration test failed at startup before any job ran. Source: https://github.com/slsa-framework/slsa-github-generator/blob/v2.1.0/.github/workflows/generator_generic_slsa3.yml Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Trigger CI after companion permission fix wrangle-test PR #4 (grant-python-id-token-perms) merged to main, adding id-token: write + actions: read to test-python and test-python-uv jobs so the new provenance step in wrangle's reusable workflow can run. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Diagnostic: drop provenance-artifact-name workflow output Suspect: workflow_call.outputs.<name>.value referencing a job that is itself a `uses:` reusable-workflow call (the SLSA generator) may be what's tripping startup_failure on the integration run. Container's reusable workflow has zero workflow_call outputs and works; python's only structural difference is this one output binding. Removing it temporarily to confirm. If the integration test passes after this, replace this commit with a permanent fix: keep the output gone and have the example download provenance via `pattern: '*.intoto.jsonl'` instead of by name. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Drop contents: write from provenance job (caller permission cascade) Root cause of the integration startup_failure: the provenance job declared `contents: write` for upload-assets on tag pushes, but GitHub's permission model requires every caller of a reusable workflow to grant every scope the workflow declares. wrangle-test's test-python jobs grant `contents: read`, so GitHub rejected the call before any job ran. Container's reusable workflow doesn't hit this because its provenance job uses `packages: write` (registry attestations) — and wrangle-test already grants packages: write on test-container. Fix: drop `contents: write` AND `upload-assets:` from the provenance job. Provenance is uploaded as a workflow artifact (default behavior, 90-day retention). Adopters who want release-asset upload re-upload from their own publish job where they already hold `contents: write` for the release. This also restores the `provenance-artifact-name` workflow output — the diagnostic in the previous commit confirmed it wasn't the cause. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Grant contents: read to provenance job generator_generic_slsa3.yml declares `permissions: contents: read` at its workflow_call top level. When a job uses scoped permissions, GitHub denies all unlisted scopes — so without explicitly granting contents: read in the provenance job, the SLSA generator's call fails at startup. container_slsa3.yml uses `permissions: {}` instead, which is why the container reusable workflow's provenance job didn't need this and worked end-to-end. This is the third and final permission fix in the cascade. Read GitHub's permission resolution model: https://docs.github.com/en/actions/using-workflows/reusing-workflows#access-and-permissions Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Restore contents: write + upload-assets on provenance job Confirmed by reading slsa-github-generator v2.1.0's reference example: GitHub validates every called-job's permissions at workflow startup, regardless of `if:` conditions that would skip the job at runtime. The generic generator's upload-assets job declares contents: write, so the caller must grant it — even with upload-assets disabled. Since the write permission is unavoidable, restore upload-assets: ${{ startsWith(github.ref, 'refs/tags/') }} and use the permission for the real feature it enables (release-asset upload on tags). Wrangle-test PR #5 (companion) lifts test-python and test-python-uv to contents: write so the call from wrangle-test passes startup. Reference: https://github.com/slsa-framework/slsa-github-generator/blob/v2.1.0/internal/builders/generic/README.md Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Trigger CI after wrangle-test contents:write merge wrangle-test PR #5 (grant-python-contents-write) merged to main, upgrading test-python and test-python-uv to grant contents: write so the SLSA generator's upload-assets job permission validates at startup. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * README: split PEP 740 vs SLSA verification, clarify availability Address PR-156 review comments on build/actions/python/README.md: - The "Verifying after install" section conflated PEP 740 attestations (verified against PyPI, prove who published) and SLSA L3 provenance (verified against GitHub release assets, prove how it was built). Split into two sub-sections that name each system, where the artifact lives, and what `slsa-verifier`/`pip`/`sigstore-python` each verify. - Note that SLSA provenance is only attached to GitHub releases on tag pushes (because the reusable workflow's upload-assets is gated on `startsWith(github.ref, 'refs/tags/')`). Non-tag publishes leave provenance only as a 90-day workflow artifact, which external consumers can't retrieve. - Establish a wrangle convention: tag pushes publish provenance to the release; non-tag publishes don't have consumer-retrievable provenance. Adopters whose workflows don't push tags should publicize this. - Note that the verify call in the recipe is exercised by the integration test; consumer-side verification against a real release is tracked in #163. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Fix permission bug in example workflow + README quick-start The example workflow's `build:` job and the README's quick-start both granted only `contents: read` when calling wrangle's reusable workflow. That reusable workflow has a nested provenance job that calls slsa-github-generator, which declares `contents: write` for its upload-assets job. GitHub validates a called workflow's permissions at startup regardless of any `if:` condition that would skip the job at runtime — so callers granting only `contents: read` got `startup_failure` immediately. We hit this same bug in wrangle-test (fixed in tomhennen/wrangle-test PRs #4 and #5) but the example workflow was never exercised end-to-end and inherited the same gap. Adopters copying from either the example or the README would have seen the same failure. Fix: - Add contents: write, id-token: write, actions: read to the build: job in gh_workflow_examples/build_python.yml. - Same set in build/actions/python/README.md's quick-start snippet. - Add bats assertions for both so this regression can't return. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Remove setup.py-only fallback (it never worked) validate_inputs.sh accepted projects with only setup.py (no pyproject.toml) by emitting a warning and continuing. But actions/setup-python in the next step uses `python-version-file: pyproject.toml` which fails when the file doesn't exist. The fallback was therefore broken in practice — the action would green-light the input then crash a step later. Tighten the validator: pyproject.toml is required. Surface a helpful error pointing at the packaging docs so adopters know how to migrate. Update SPEC.md to match. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
3 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.