From 94526ed3a28e5dda42ebb0c9a8e4508d83a6ff3c Mon Sep 17 00:00:00 2001 From: Behnam Mozafari Date: Wed, 13 May 2026 16:35:45 +1000 Subject: [PATCH 1/5] UID2-7041: push by digest, promote tag only after attest Co-Authored-By: Claude Opus 4.7 (1M context) --- actions/shared_publish_to_docker/action.yaml | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/actions/shared_publish_to_docker/action.yaml b/actions/shared_publish_to_docker/action.yaml index 09eac13c..ea55b008 100644 --- a/actions/shared_publish_to_docker/action.yaml +++ b/actions/shared_publish_to_docker/action.yaml @@ -92,15 +92,14 @@ runs: image_ref: ${{ steps.extractImageTag.outputs.firstTag }} scan_type: ${{ inputs.scan_type }} - - name: Push to Docker + - name: Push to Docker (by digest, untagged) id: push uses: docker/build-push-action@ca052bb54ab0790a636c9b5f226502c73d547a25 # v5 with: context: ${{ inputs.docker_context }} file: ${{ inputs.docker_file }} - push: true - tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} + outputs: type=image,name=${{ inputs.docker_registry }}/${{ inputs.docker_image_name }},push-by-digest=true,push=true build-args: | JAR_VERSION=${{ inputs.new_version }} IMAGE_VERSION=${{ inputs.new_version }} @@ -111,3 +110,16 @@ runs: with: subject_name: ${{ inputs.docker_registry }}/${{ inputs.docker_image_name }} subject_digest: ${{ steps.push.outputs.digest }} + + - name: Promote digest to consumable tag(s) + shell: bash + env: + DIGEST: ${{ steps.push.outputs.digest }} + TAGS: ${{ steps.meta.outputs.tags }} + SOURCE: ${{ inputs.docker_registry }}/${{ inputs.docker_image_name }} + run: | + set -euo pipefail + while IFS= read -r tag; do + [[ -z "$tag" ]] && continue + docker buildx imagetools create -t "$tag" "${SOURCE}@${DIGEST}" + done <<< "$TAGS" From 125ddbdde12d84ed79ee0b70d89d4094156f9971 Mon Sep 17 00:00:00 2001 From: Behnam Mozafari Date: Wed, 13 May 2026 16:39:21 +1000 Subject: [PATCH 2/5] UID2-7041: promote tag after attest in Java publish workflow Co-Authored-By: Claude Opus 4.7 (1M context) --- ...hared-publish-java-to-docker-versioned.yaml | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/.github/workflows/shared-publish-java-to-docker-versioned.yaml b/.github/workflows/shared-publish-java-to-docker-versioned.yaml index e67b7794..8aa5558f 100644 --- a/.github/workflows/shared-publish-java-to-docker-versioned.yaml +++ b/.github/workflows/shared-publish-java-to-docker-versioned.yaml @@ -209,14 +209,13 @@ jobs: image_ref: ${{ steps.extractImageTag.outputs.firstTag }} scan_type: image - - name: Push to Docker + - name: Push to Docker (by digest, untagged) id: push uses: docker/build-push-action@ca052bb54ab0790a636c9b5f226502c73d547a25 # v5 with: context: ${{inputs.working_dir}} - push: true - tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} + outputs: type=image,name=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}${{ inputs.append_image_name }},push-by-digest=true,push=true build-args: | JAR_VERSION=${{ steps.version.outputs.new_version }} IMAGE_VERSION=${{ steps.version.outputs.new_version }} @@ -228,6 +227,19 @@ jobs: subject_name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}${{ inputs.append_image_name }} subject_digest: ${{ steps.push.outputs.digest }} + - name: Promote digest to consumable tag(s) + shell: bash + env: + DIGEST: ${{ steps.push.outputs.digest }} + TAGS: ${{ steps.meta.outputs.tags }} + SOURCE: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}${{ inputs.append_image_name }} + run: | + set -euo pipefail + while IFS= read -r tag; do + [[ -z "$tag" ]] && continue + docker buildx imagetools create -t "$tag" "${SOURCE}@${DIGEST}" + done <<< "$TAGS" + - name: Create Release uses: IABTechLab/uid2-shared-actions/actions/shared_create_releases@v3 with: From 47843b8051632ec4bdc18e47ad01053277f39cbe Mon Sep 17 00:00:00 2001 From: Behnam Mozafari Date: Wed, 13 May 2026 16:56:25 +1000 Subject: [PATCH 3/5] UID2-7041: TEMP smoke harness for tag-after-verify (delete after capture) Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/smoke-7041.yaml | 110 ++++++++++++++++++++++++++++++ 1 file changed, 110 insertions(+) create mode 100644 .github/workflows/smoke-7041.yaml diff --git a/.github/workflows/smoke-7041.yaml b/.github/workflows/smoke-7041.yaml new file mode 100644 index 00000000..9bed9374 --- /dev/null +++ b/.github/workflows/smoke-7041.yaml @@ -0,0 +1,110 @@ +name: UID2-7041 smoke — tag-after-verify + +# Throwaway smoke that exercises the Task 1 flow inline using LOCAL action +# references (./actions/...) so this branch's changes are actually tested +# (not the @v3 alias). Delete after capturing green + forced-failure runs. + +on: + workflow_dispatch: + inputs: + force_verify_failure: + description: If 'true', inject a failure between attest and promote to prove the gate works. + type: boolean + default: false + +env: + TEST_IMAGE: ghcr.io/${{ github.repository }}/test-7041 + TEST_TAG: smoke-${{ github.run_id }} + +jobs: + smoke: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + id-token: write + attestations: write + artifact-metadata: write + steps: + - name: Checkout + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + + - name: Set up Buildx + uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3 + + - name: Log in to GHCR + uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Lowercase image name + id: lc + run: | + IMG="$(printf '%s' "${TEST_IMAGE}" | tr '[:upper:]' '[:lower:]')" + echo "image=${IMG}" >> "$GITHUB_OUTPUT" + + - name: Write throwaway Dockerfile + run: | + cat > Dockerfile.test-7041 <<'DOCKERFILE' + FROM alpine:3.20 + RUN echo "uid2-7041 smoke, run $GITHUB_RUN_ID" > /uid2-7041.txt + DOCKERFILE + + - name: Push by digest (untagged) + id: push + uses: docker/build-push-action@ca052bb54ab0790a636c9b5f226502c73d547a25 # v5 + with: + context: . + file: Dockerfile.test-7041 + outputs: type=image,name=${{ steps.lc.outputs.image }},push-by-digest=true,push=true + + - name: Assert tag does not yet exist + env: + GH_TOKEN: ${{ github.token }} + run: | + set -eu + if docker buildx imagetools inspect "${{ steps.lc.outputs.image }}:${TEST_TAG}" >/dev/null 2>&1; then + echo "::error::Tag already exists before promote — should not happen." + exit 1 + fi + echo "Confirmed: tag does not exist pre-promote." + + - name: Attest image (system under test) + uses: ./actions/attest_image + with: + subject_name: ${{ steps.lc.outputs.image }} + subject_digest: ${{ steps.push.outputs.digest }} + + - name: Inject forced failure between attest and promote + if: ${{ inputs.force_verify_failure }} + run: | + echo "::error::UID2-7041 forced failure — promote step must be skipped." + exit 1 + + - name: Promote digest to consumable tag (system under test) + shell: bash + env: + DIGEST: ${{ steps.push.outputs.digest }} + TAGS: ${{ steps.lc.outputs.image }}:${{ env.TEST_TAG }} + SOURCE: ${{ steps.lc.outputs.image }} + run: | + set -euo pipefail + while IFS= read -r tag; do + [[ -z "$tag" ]] && continue + docker buildx imagetools create -t "$tag" "${SOURCE}@${DIGEST}" + done <<< "$TAGS" + + - name: Verify the consumable tag resolves and is attested + if: ${{ !inputs.force_verify_failure }} + env: + GH_TOKEN: ${{ github.token }} + run: | + set -eu + docker buildx imagetools inspect "${{ steps.lc.outputs.image }}:${TEST_TAG}" >/dev/null + gh attestation verify \ + "oci://${{ steps.lc.outputs.image }}:${TEST_TAG}" \ + --owner "${{ github.repository_owner }}" \ + --cert-identity-regex "^https://github\.com/${{ github.repository }}/" + echo "Smoke green: tag exists and attestation verifies." From 09c250c2576cb0d8f897615f9b1bdb881534503b Mon Sep 17 00:00:00 2001 From: Behnam Mozafari Date: Wed, 13 May 2026 16:57:21 +1000 Subject: [PATCH 4/5] UID2-7041: smoke trigger on push for first run --- .github/workflows/smoke-7041.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/smoke-7041.yaml b/.github/workflows/smoke-7041.yaml index 9bed9374..f18f1986 100644 --- a/.github/workflows/smoke-7041.yaml +++ b/.github/workflows/smoke-7041.yaml @@ -5,6 +5,8 @@ name: UID2-7041 smoke — tag-after-verify # (not the @v3 alias). Delete after capturing green + forced-failure runs. on: + push: + branches: [bmz-UID2-7041-retag-promotion] workflow_dispatch: inputs: force_verify_failure: From b05c09b6de2b54b740b391726f1d6fd0a3349526 Mon Sep 17 00:00:00 2001 From: Behnam Mozafari Date: Wed, 13 May 2026 16:59:39 +1000 Subject: [PATCH 5/5] UID2-7041: drop smoke harness; runs 25783580596 (green) + 25783628840 (forced fail) captured Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/smoke-7041.yaml | 112 ------------------------------ 1 file changed, 112 deletions(-) delete mode 100644 .github/workflows/smoke-7041.yaml diff --git a/.github/workflows/smoke-7041.yaml b/.github/workflows/smoke-7041.yaml deleted file mode 100644 index f18f1986..00000000 --- a/.github/workflows/smoke-7041.yaml +++ /dev/null @@ -1,112 +0,0 @@ -name: UID2-7041 smoke — tag-after-verify - -# Throwaway smoke that exercises the Task 1 flow inline using LOCAL action -# references (./actions/...) so this branch's changes are actually tested -# (not the @v3 alias). Delete after capturing green + forced-failure runs. - -on: - push: - branches: [bmz-UID2-7041-retag-promotion] - workflow_dispatch: - inputs: - force_verify_failure: - description: If 'true', inject a failure between attest and promote to prove the gate works. - type: boolean - default: false - -env: - TEST_IMAGE: ghcr.io/${{ github.repository }}/test-7041 - TEST_TAG: smoke-${{ github.run_id }} - -jobs: - smoke: - runs-on: ubuntu-latest - permissions: - contents: read - packages: write - id-token: write - attestations: write - artifact-metadata: write - steps: - - name: Checkout - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - - - name: Set up Buildx - uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3 - - - name: Log in to GHCR - uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Lowercase image name - id: lc - run: | - IMG="$(printf '%s' "${TEST_IMAGE}" | tr '[:upper:]' '[:lower:]')" - echo "image=${IMG}" >> "$GITHUB_OUTPUT" - - - name: Write throwaway Dockerfile - run: | - cat > Dockerfile.test-7041 <<'DOCKERFILE' - FROM alpine:3.20 - RUN echo "uid2-7041 smoke, run $GITHUB_RUN_ID" > /uid2-7041.txt - DOCKERFILE - - - name: Push by digest (untagged) - id: push - uses: docker/build-push-action@ca052bb54ab0790a636c9b5f226502c73d547a25 # v5 - with: - context: . - file: Dockerfile.test-7041 - outputs: type=image,name=${{ steps.lc.outputs.image }},push-by-digest=true,push=true - - - name: Assert tag does not yet exist - env: - GH_TOKEN: ${{ github.token }} - run: | - set -eu - if docker buildx imagetools inspect "${{ steps.lc.outputs.image }}:${TEST_TAG}" >/dev/null 2>&1; then - echo "::error::Tag already exists before promote — should not happen." - exit 1 - fi - echo "Confirmed: tag does not exist pre-promote." - - - name: Attest image (system under test) - uses: ./actions/attest_image - with: - subject_name: ${{ steps.lc.outputs.image }} - subject_digest: ${{ steps.push.outputs.digest }} - - - name: Inject forced failure between attest and promote - if: ${{ inputs.force_verify_failure }} - run: | - echo "::error::UID2-7041 forced failure — promote step must be skipped." - exit 1 - - - name: Promote digest to consumable tag (system under test) - shell: bash - env: - DIGEST: ${{ steps.push.outputs.digest }} - TAGS: ${{ steps.lc.outputs.image }}:${{ env.TEST_TAG }} - SOURCE: ${{ steps.lc.outputs.image }} - run: | - set -euo pipefail - while IFS= read -r tag; do - [[ -z "$tag" ]] && continue - docker buildx imagetools create -t "$tag" "${SOURCE}@${DIGEST}" - done <<< "$TAGS" - - - name: Verify the consumable tag resolves and is attested - if: ${{ !inputs.force_verify_failure }} - env: - GH_TOKEN: ${{ github.token }} - run: | - set -eu - docker buildx imagetools inspect "${{ steps.lc.outputs.image }}:${TEST_TAG}" >/dev/null - gh attestation verify \ - "oci://${{ steps.lc.outputs.image }}:${TEST_TAG}" \ - --owner "${{ github.repository_owner }}" \ - --cert-identity-regex "^https://github\.com/${{ github.repository }}/" - echo "Smoke green: tag exists and attestation verifies."