From 7c1ad5177e15394b7b9c2442476e863b294521ef Mon Sep 17 00:00:00 2001 From: Paul Newling Date: Thu, 21 May 2026 14:43:00 -0700 Subject: [PATCH 1/2] Surface publish-time gates as PR-visible jobs Extract require-latest, the pnpm-workspace/lock drift guard, and the scan-pnpm-repo Trivy scan from build-test-publish into separate preflight and security-scan jobs. Both are non-blocking on PRs and blocking on the default branch and in the merge queue, mirroring the existing check-changesets pattern. build-test-publish gates publish on both via needs + if. --- .github/workflows/node-simple-pnpm.yaml | 169 +++++++++++++++++++----- 1 file changed, 139 insertions(+), 30 deletions(-) diff --git a/.github/workflows/node-simple-pnpm.yaml b/.github/workflows/node-simple-pnpm.yaml index fb57885a..39f7dca6 100644 --- a/.github/workflows/node-simple-pnpm.yaml +++ b/.github/workflows/node-simple-pnpm.yaml @@ -471,6 +471,126 @@ jobs: npm-pkg-version: ${{ fromJSON(steps.npm-pkg-metadata.outputs.data).npm-pkg-version }} pnpm-version: ${{ fromJSON(steps.npm-pkg-metadata.outputs.data).pnpm-version }} + preflight: + name: preflight (require-latest) + runs-on: ubuntu-latest + # Surface SDK-version drift on PRs as a non-blocking check, but enforce it + # on the default branch and in the merge queue so publish cannot proceed + # with a stale @platforma-sdk dependency. + continue-on-error: ${{ github.ref_name != inputs.changeset-default-branch && github.event_name != 'merge_group' }} + needs: + - init + steps: + - uses: milaboratory/github-ci/actions/context@v4 + + - uses: milaboratory/github-ci/actions/env@v4 + with: + inputs: ${{ inputs.env }} + secrets: ${{ secrets.env }} + + - uses: actions/checkout@v4 + with: + lfs: ${{ inputs.checkout-git-lfs }} + submodules: ${{ inputs.checkout-submodules }} + fetch-depth: '0' + + - name: Check infrastructure requirements for publication + uses: milaboratory/github-ci/actions/node/require-latest@v4 + with: + packages: | + @platforma-sdk/block-tools + @platforma-sdk/tengo-builder + + - name: Check pnpm-lock.yaml is in sync with pnpm-workspace.yaml + shell: bash + env: + DEFAULT_BRANCH: origin/${{ inputs.changeset-default-branch }} + run: | + if git diff --name-only ${DEFAULT_BRANCH}..HEAD | grep -q -E '^pnpm-workspace.yaml$'; then + if ! git diff --name-only ${DEFAULT_BRANCH}..HEAD | grep -q -E '^pnpm-lock.yaml$'; then + echo "Changes in pnpm-workspace.yaml detected, but no updates in pnpm-lock.yaml were found in current branch" + exit 1 + fi + fi + + security-scan: + name: security scan (trivy) + runs-on: ${{ inputs.gha-runner-label }} + # Surface Trivy findings on PRs without blocking build/test; enforce on the + # default branch and in the merge queue so a vulnerable image cannot be + # published. + continue-on-error: ${{ github.ref_name != inputs.changeset-default-branch && github.event_name != 'merge_group' }} + needs: + - init + - metadata + permissions: + id-token: write + contents: read + steps: + - uses: milaboratory/github-ci/actions/context@v4 + + - uses: milaboratory/github-ci/actions/env@v4 + with: + inputs: ${{ inputs.env }} + secrets: ${{ secrets.env }} + + - uses: actions/checkout@v4 + with: + lfs: ${{ inputs.checkout-git-lfs }} + submodules: ${{ inputs.checkout-submodules }} + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v4 + with: + role-to-assume: ${{ env.AWS_CI_IAM_MONOREPO_SIMPLE_ROLE }} + role-duration-seconds: ${{ inputs.aws-login-duration }} + aws-region: ${{ inputs.aws-region }} + + - id: login-ecr + uses: aws-actions/amazon-ecr-login@v2 + with: + mask-password: 'true' + + - name: Login to Quay.io + if: inputs.docker-quay-push && env.QUAY_USERNAME != '' && env.QUAY_ROBOT_TOKEN != '' + uses: docker/login-action@v3 + with: + registry: quay.io + username: ${{ env.QUAY_USERNAME }} + password: ${{ env.QUAY_ROBOT_TOKEN }} + ecr: false + + - name: Login to Docker GA + if: inputs.docker-ga-push && env.QUAY_USERNAME != '' && env.QUAY_ROBOT_TOKEN != '' + uses: docker/login-action@v3 + with: + registry: containers.pl-open.science + username: ${{ env.QUAY_USERNAME }} + password: ${{ env.QUAY_ROBOT_TOKEN }} + ecr: false + + - name: Prepare environment for building a NodeJS application + uses: milaboratory/github-ci/actions/node/prepare-pnpm@v4 + env: + PNPM_VERSION: ${{ needs.metadata.outputs.pnpm-version }} + with: + node-version: ${{ inputs.node-version }} + cache-version: ${{ inputs.cache-version }} + pnpm-version: ${{ env.PNPM_VERSION || inputs.pnpm-version }} + cache-hashfiles-search-path: ${{ inputs.cache-hashfiles-search-path }} + npmrc-config: ${{ inputs.npmrc-config }} + + - name: Install NodeJS packages with pnpm + shell: bash + env: + NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + NPMJS_TOKEN: ${{ env.NPMJS_TOKEN }} + run: | + pnpm install --frozen-lockfile --prefer-offline + + - name: Perform security scan checks before publication + uses: milaboratory/github-ci/actions/docker/scan-pnpm-repo@v4 + check-changesets: name: check for changesets runs-on: ubuntu-latest @@ -525,11 +645,16 @@ jobs: matrix: include: ${{ fromJSON(inputs.pre-calculated-task-list) }} needs: + - preflight - check-changesets - metadata if: > inputs.pre-calculated && inputs.pre-calculated-task-list != '[]' && !failure() && !cancelled() && + ( + needs.preflight.result == 'success' || + needs.preflight.result == 'skipped' + ) && ( needs.check-changesets.result == 'success' || needs.check-changesets.result == 'skipped' @@ -608,17 +733,7 @@ jobs: env: NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} NPMJS_TOKEN: ${{ env.NPMJS_TOKEN }} - DEFAULT_BRANCH: origin/${{ inputs.changeset-default-branch }} - run: | - if git diff --name-only ${DEFAULT_BRANCH}..HEAD | grep -q -E '^pnpm-workspace.yaml$'; then - # Changes in pnpm-workspace.yaml have to be accompanied by pnpm-lock.yaml update - if ! git diff --name-only ${DEFAULT_BRANCH}..HEAD | grep -q -E '^pnpm-lock.yaml$'; then - echo "Changes in pnpm-workspace.yaml detected, but no updates in pnpm-lock.yaml were found in current branch" - exit 1 - fi - fi - pnpm install --frozen-lockfile --prefer-offline - name: Run changeset version @@ -641,11 +756,21 @@ jobs: name: unified (build test publish) runs-on: ${{ inputs.gha-runner-label }} needs: + - preflight + - security-scan - check-changesets - metadata - pre-calculated-build if: > !failure() && !cancelled() && + ( + needs.preflight.result == 'success' || + needs.preflight.result == 'skipped' + ) && + ( + needs.security-scan.result == 'success' || + needs.security-scan.result == 'skipped' + ) && ( needs.pre-calculated-build.result == 'success' || needs.pre-calculated-build.result == 'skipped' @@ -684,13 +809,6 @@ jobs: token: ${{ steps.app-token.outputs.token }} fetch-depth: '0' - - name: Check infrastructure requirements for publication - uses: milaboratory/github-ci/actions/node/require-latest@v4-beta - with: - packages: | - @platforma-sdk/block-tools - @platforma-sdk/tengo-builder - - name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@v4-beta with: @@ -751,17 +869,7 @@ jobs: env: NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} NPMJS_TOKEN: ${{ env.NPMJS_TOKEN }} - DEFAULT_BRANCH: origin/${{ inputs.changeset-default-branch }} - run: | - if git diff --name-only ${DEFAULT_BRANCH}..HEAD | grep -q -E '^pnpm-workspace.yaml$'; then - # Changes in pnpm-workspace.yaml have to be accompanied by pnpm-lock.yaml update - if ! git diff --name-only ${DEFAULT_BRANCH}..HEAD | grep -q -E '^pnpm-lock.yaml$'; then - echo "Changes in pnpm-workspace.yaml detected, but no updates in pnpm-lock.yaml were found in current branch" - exit 1 - fi - fi - pnpm install --frozen-lockfile --prefer-offline - name: Run changeset version @@ -862,9 +970,6 @@ jobs: test-coverage-reports: ${{ inputs.test-coverage-reports }} test-results-reports: ${{ inputs.test-results-reports }} - - name: Perform security scan checks before publication - uses: milaboratory/github-ci/actions/docker/scan-pnpm-repo@v4-beta - - name: Get GitHub App User ID if: steps.check-changes.outputs.has-changes == '1' id: get-user-id @@ -945,6 +1050,8 @@ jobs: needs: - init - metadata + - preflight + - security-scan - check-changesets - build-test-publish - pre-calculated-build @@ -966,6 +1073,8 @@ jobs: ${{ needs.pre-calculated-build.result }} ${{ needs.build-test-publish.result }} ${{ needs.check-changesets.result }} + ${{ needs.preflight.result }} + ${{ needs.security-scan.result }} product-name: ${{ inputs.app-name }} override-version: ${{ format('{0}', env.NPM_PKG_VERSION) }} override-tag: ${{ format('v{0}', env.NPM_PKG_VERSION) }} From cc1aa778d3c4ff633503cad7be23c1d1896c85cf Mon Sep 17 00:00:00 2001 From: Paul Newling Date: Thu, 21 May 2026 15:01:13 -0700 Subject: [PATCH 2/2] Revert security-scan extraction: scan needs build artifacts scan-pnpm-repo discovers images via dist/artifacts/*.json which is only populated by the build step. A pre-build security-scan job silently passes in auto-discovery mode (scan_npm_package returns 0 when no images are found and _require_docker=false), making the Trivy gate a no-op. Restore scan-pnpm-repo as a step in build-test-publish after the build. preflight (require-latest + lockfile drift) is unaffected since those checks have no artifact dependency. --- .github/workflows/node-simple-pnpm.yaml | 88 +------------------------ 1 file changed, 3 insertions(+), 85 deletions(-) diff --git a/.github/workflows/node-simple-pnpm.yaml b/.github/workflows/node-simple-pnpm.yaml index 39f7dca6..ba361706 100644 --- a/.github/workflows/node-simple-pnpm.yaml +++ b/.github/workflows/node-simple-pnpm.yaml @@ -513,84 +513,6 @@ jobs: fi fi - security-scan: - name: security scan (trivy) - runs-on: ${{ inputs.gha-runner-label }} - # Surface Trivy findings on PRs without blocking build/test; enforce on the - # default branch and in the merge queue so a vulnerable image cannot be - # published. - continue-on-error: ${{ github.ref_name != inputs.changeset-default-branch && github.event_name != 'merge_group' }} - needs: - - init - - metadata - permissions: - id-token: write - contents: read - steps: - - uses: milaboratory/github-ci/actions/context@v4 - - - uses: milaboratory/github-ci/actions/env@v4 - with: - inputs: ${{ inputs.env }} - secrets: ${{ secrets.env }} - - - uses: actions/checkout@v4 - with: - lfs: ${{ inputs.checkout-git-lfs }} - submodules: ${{ inputs.checkout-submodules }} - - - name: Configure AWS credentials - uses: aws-actions/configure-aws-credentials@v4 - with: - role-to-assume: ${{ env.AWS_CI_IAM_MONOREPO_SIMPLE_ROLE }} - role-duration-seconds: ${{ inputs.aws-login-duration }} - aws-region: ${{ inputs.aws-region }} - - - id: login-ecr - uses: aws-actions/amazon-ecr-login@v2 - with: - mask-password: 'true' - - - name: Login to Quay.io - if: inputs.docker-quay-push && env.QUAY_USERNAME != '' && env.QUAY_ROBOT_TOKEN != '' - uses: docker/login-action@v3 - with: - registry: quay.io - username: ${{ env.QUAY_USERNAME }} - password: ${{ env.QUAY_ROBOT_TOKEN }} - ecr: false - - - name: Login to Docker GA - if: inputs.docker-ga-push && env.QUAY_USERNAME != '' && env.QUAY_ROBOT_TOKEN != '' - uses: docker/login-action@v3 - with: - registry: containers.pl-open.science - username: ${{ env.QUAY_USERNAME }} - password: ${{ env.QUAY_ROBOT_TOKEN }} - ecr: false - - - name: Prepare environment for building a NodeJS application - uses: milaboratory/github-ci/actions/node/prepare-pnpm@v4 - env: - PNPM_VERSION: ${{ needs.metadata.outputs.pnpm-version }} - with: - node-version: ${{ inputs.node-version }} - cache-version: ${{ inputs.cache-version }} - pnpm-version: ${{ env.PNPM_VERSION || inputs.pnpm-version }} - cache-hashfiles-search-path: ${{ inputs.cache-hashfiles-search-path }} - npmrc-config: ${{ inputs.npmrc-config }} - - - name: Install NodeJS packages with pnpm - shell: bash - env: - NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - NPMJS_TOKEN: ${{ env.NPMJS_TOKEN }} - run: | - pnpm install --frozen-lockfile --prefer-offline - - - name: Perform security scan checks before publication - uses: milaboratory/github-ci/actions/docker/scan-pnpm-repo@v4 - check-changesets: name: check for changesets runs-on: ubuntu-latest @@ -757,7 +679,6 @@ jobs: runs-on: ${{ inputs.gha-runner-label }} needs: - preflight - - security-scan - check-changesets - metadata - pre-calculated-build @@ -767,10 +688,6 @@ jobs: needs.preflight.result == 'success' || needs.preflight.result == 'skipped' ) && - ( - needs.security-scan.result == 'success' || - needs.security-scan.result == 'skipped' - ) && ( needs.pre-calculated-build.result == 'success' || needs.pre-calculated-build.result == 'skipped' @@ -970,6 +887,9 @@ jobs: test-coverage-reports: ${{ inputs.test-coverage-reports }} test-results-reports: ${{ inputs.test-results-reports }} + - name: Perform security scan checks before publication + uses: milaboratory/github-ci/actions/docker/scan-pnpm-repo@v4 + - name: Get GitHub App User ID if: steps.check-changes.outputs.has-changes == '1' id: get-user-id @@ -1051,7 +971,6 @@ jobs: - init - metadata - preflight - - security-scan - check-changesets - build-test-publish - pre-calculated-build @@ -1074,7 +993,6 @@ jobs: ${{ needs.build-test-publish.result }} ${{ needs.check-changesets.result }} ${{ needs.preflight.result }} - ${{ needs.security-scan.result }} product-name: ${{ inputs.app-name }} override-version: ${{ format('{0}', env.NPM_PKG_VERSION) }} override-tag: ${{ format('v{0}', env.NPM_PKG_VERSION) }}