From a04b98596eab5ef52e6acf7fe88f8cb19c38ac03 Mon Sep 17 00:00:00 2001 From: Fred Amaral Date: Thu, 6 Aug 2026 01:14:21 -0600 Subject: [PATCH 1/5] feat(validation): add guard reporting support X-Lerian-Ref: 0x1 --- src/notify/pr-validation-reporter/README.md | 4 ++++ src/notify/pr-validation-reporter/action.yml | 18 +++++++++++++++++- src/validate/pr-checks-summary/README.md | 4 ++++ src/validate/pr-checks-summary/action.yml | 8 ++++++++ 4 files changed, 33 insertions(+), 1 deletion(-) diff --git a/src/notify/pr-validation-reporter/README.md b/src/notify/pr-validation-reporter/README.md index 8ae7294f..24b1dcfe 100644 --- a/src/notify/pr-validation-reporter/README.md +++ b/src/notify/pr-validation-reporter/README.md @@ -18,8 +18,11 @@ Posts a single mergeability summary comment aggregating all PR validation check | `size-result` | Result of PR size check | No | `skipped` | | `label-result` | Result of auto-label step | No | `skipped` | | `metadata-result` | Result of PR metadata check | No | `skipped` | +| `breaking-change-result` | Result of the blocking breaking change guard | No | `skipped` | | `dry-run` | When `true`, skip posting the summary comment | No | `false` | +When `breaking-change-result` is `skipped` or omitted, the report omits the guard row and preserves existing mergeability behavior. This optional default exists only for backward compatibility with direct action consumers. The mandatory `pr-validation` integration always supplies the guard result and offers no guard opt-out. When supplied, only `success` is mergeable. + ## Usage as composite step ```yaml @@ -39,6 +42,7 @@ jobs: size-result: ${{ needs.advisory-checks.outputs.size-result }} label-result: ${{ needs.advisory-checks.outputs.label-result }} metadata-result: ${{ needs.advisory-checks.outputs.metadata-result }} + breaking-change-result: ${{ needs.blocking-checks.outputs.breaking-change-result }} ``` ## Required permissions diff --git a/src/notify/pr-validation-reporter/action.yml b/src/notify/pr-validation-reporter/action.yml index dfcd7680..66c5a4bd 100644 --- a/src/notify/pr-validation-reporter/action.yml +++ b/src/notify/pr-validation-reporter/action.yml @@ -29,6 +29,10 @@ inputs: description: Result of PR metadata check (success/failure/skipped) required: false default: "skipped" + breaking-change-result: + description: Result of breaking change guard (success/failure/skipped) + required: false + default: skipped dry-run: description: When true, skip posting the summary comment required: false @@ -47,6 +51,7 @@ runs: SIZE_RESULT: ${{ inputs.size-result }} LABEL_RESULT: ${{ inputs.label-result }} METADATA_RESULT: ${{ inputs.metadata-result }} + BREAKING_CHANGE_RESULT: ${{ inputs.breaking-change-result }} with: github-token: ${{ inputs.github-token }} script: | @@ -59,9 +64,20 @@ runs: { label: 'PR Metadata', result: process.env.METADATA_RESULT, blocking: false }, ]; + if (process.env.BREAKING_CHANGE_RESULT !== 'skipped') { + checks.splice(3, 0, { + label: 'Breaking Change Guard', + result: process.env.BREAKING_CHANGE_RESULT, + blocking: true, + strict: true, + }); + } + const icon = (r) => ({ success: '✅', failure: '❌', skipped: '⏭️' }[r] ?? '⚠️'); - const blockingFailures = checks.filter(c => c.blocking && c.result === 'failure'); + const blockingFailures = checks.filter(c => + c.blocking && (c.strict ? c.result !== 'success' : c.result === 'failure') + ); const verdictHeader = blockingFailures.length > 0 ? `## 🚫 PR Blocked — ${blockingFailures.length} blocking failure${blockingFailures.length === 1 ? '' : 's'}\n\n` : `## ✅ PR Mergeable — no blocking failures\n\n`; diff --git a/src/validate/pr-checks-summary/README.md b/src/validate/pr-checks-summary/README.md index 3a02eabb..925829f4 100644 --- a/src/validate/pr-checks-summary/README.md +++ b/src/validate/pr-checks-summary/README.md @@ -17,8 +17,11 @@ Generates a summary table of all PR validation check results in the GitHub Actio | `size-result` | Result of PR size check | No | `skipped` | | `label-result` | Result of auto-label step | No | `skipped` | | `metadata-result` | Result of PR metadata check | No | `skipped` | +| `breaking-change-result` | Result of the breaking change guard | No | `skipped` | | `dry-run` | Whether this is a dry run | No | `false` | +When `breaking-change-result` is `skipped` or omitted, the summary omits the guard row and preserves existing behavior. This optional default exists only for backward compatibility with direct action consumers. The mandatory `pr-validation` integration always supplies the guard result and offers no guard opt-out. + ## Usage as composite step ```yaml @@ -37,6 +40,7 @@ jobs: size-result: ${{ needs.advisory-checks.outputs.size-result || 'skipped' }} label-result: ${{ needs.advisory-checks.outputs.label-result || 'skipped' }} metadata-result: ${{ needs.advisory-checks.outputs.metadata-result || 'skipped' }} + breaking-change-result: ${{ needs.blocking-checks.outputs.breaking-change-result || 'skipped' }} dry-run: "true" ``` diff --git a/src/validate/pr-checks-summary/action.yml b/src/validate/pr-checks-summary/action.yml index f8881f8e..fbd1a838 100644 --- a/src/validate/pr-checks-summary/action.yml +++ b/src/validate/pr-checks-summary/action.yml @@ -26,6 +26,10 @@ inputs: description: Result of PR metadata check required: false default: skipped + breaking-change-result: + description: Result of breaking change guard + required: false + default: skipped dry-run: description: Whether this is a dry run required: false @@ -43,6 +47,7 @@ runs: DESCRIPTION_RESULT: ${{ inputs.description-result }} LABEL_RESULT: ${{ inputs.label-result }} METADATA_RESULT: ${{ inputs.metadata-result }} + BREAKING_CHANGE_RESULT: ${{ inputs.breaking-change-result }} DRY_RUN: ${{ inputs.dry-run }} run: | icon() { @@ -67,6 +72,9 @@ runs: echo "| Source Branch | $(icon "$SOURCE_BRANCH_RESULT") ${SOURCE_BRANCH_RESULT} |" echo "| PR Title | $(icon "$TITLE_RESULT") ${TITLE_RESULT} |" echo "| PR Description | $(icon "$DESCRIPTION_RESULT") ${DESCRIPTION_RESULT} |" + if [ "$BREAKING_CHANGE_RESULT" != "skipped" ]; then + echo "| Breaking Change Guard | $(icon "$BREAKING_CHANGE_RESULT") ${BREAKING_CHANGE_RESULT} |" + fi echo "" echo "### Advisory" echo "" From aed47ce266ee24bd733619633026f6785b15ddc2 Mon Sep 17 00:00:00 2001 From: fredcamaral Date: Thu, 6 Aug 2026 07:24:15 +0000 Subject: [PATCH 2/5] fix(validation): address guard reporting review feedback - expose has-breaking-change-guard output on reporter and summary composites - render only exact skipped as skip icon; cancelled maps to failure icon - document breaking-change-result derivation in reporter usage example Co-authored-by: Codesmith --- src/notify/pr-validation-reporter/README.md | 39 +++++++++++++++++++- src/notify/pr-validation-reporter/action.yml | 17 +++++++++ src/validate/pr-checks-summary/README.md | 6 +++ src/validate/pr-checks-summary/action.yml | 22 ++++++++++- 4 files changed, 81 insertions(+), 3 deletions(-) diff --git a/src/notify/pr-validation-reporter/README.md b/src/notify/pr-validation-reporter/README.md index 24b1dcfe..ec8d2e0d 100644 --- a/src/notify/pr-validation-reporter/README.md +++ b/src/notify/pr-validation-reporter/README.md @@ -23,10 +23,47 @@ Posts a single mergeability summary comment aggregating all PR validation check When `breaking-change-result` is `skipped` or omitted, the report omits the guard row and preserves existing mergeability behavior. This optional default exists only for backward compatibility with direct action consumers. The mandatory `pr-validation` integration always supplies the guard result and offers no guard opt-out. When supplied, only `success` is mergeable. +## Outputs + +| Output | Description | +|--------|-------------| +| `has-breaking-change-guard` | Whether the breaking change guard result was reported, i.e. `breaking-change-result` was not `skipped` (`true`/`false`) | + ## Usage as composite step +The `blocking-checks` job must define a `breaking-change-result` output normalized to `success`/`failure`/`cancelled`/`skipped`, derived from the [`breaking-change-guard`](../../validate/breaking-change-guard/README.md) step: + ```yaml jobs: + blocking-checks: + runs-on: blacksmith-4vcpu-ubuntu-2404 + outputs: + # ...other check outputs... + breaking-change-result: ${{ steps.breaking-change-result.outputs.result }} + steps: + # ...other checks... + - name: Breaking Change Guard + id: breaking-change-guard + uses: LerianStudio/github-actions-shared-workflows/src/validate/breaking-change-guard@v1.x.x + with: + base-ref: ${{ github.base_ref }} + breaking-change-acknowledgement: 'BREAKING CHANGE APPROVED' + - name: Resolve breaking change result + id: breaking-change-result + if: always() + env: + GUARD_OUTCOME: ${{ steps.breaking-change-guard.outcome }} + HAS_BREAKING_CHANGES: ${{ steps.breaking-change-guard.outputs.has-breaking-changes }} + APPROVED: ${{ steps.breaking-change-guard.outputs.approved }} + run: | + if [ "$GUARD_OUTCOME" != "success" ]; then + echo "result=$GUARD_OUTCOME" >> "$GITHUB_OUTPUT" + elif [ "$HAS_BREAKING_CHANGES" = "true" ] && [ "$APPROVED" != "true" ]; then + echo "result=failure" >> "$GITHUB_OUTPUT" + else + echo "result=success" >> "$GITHUB_OUTPUT" + fi + pr-validation-report: runs-on: blacksmith-4vcpu-ubuntu-2404 needs: [blocking-checks, advisory-checks] @@ -42,7 +79,7 @@ jobs: size-result: ${{ needs.advisory-checks.outputs.size-result }} label-result: ${{ needs.advisory-checks.outputs.label-result }} metadata-result: ${{ needs.advisory-checks.outputs.metadata-result }} - breaking-change-result: ${{ needs.blocking-checks.outputs.breaking-change-result }} + breaking-change-result: ${{ needs.blocking-checks.outputs.breaking-change-result || 'skipped' }} ``` ## Required permissions diff --git a/src/notify/pr-validation-reporter/action.yml b/src/notify/pr-validation-reporter/action.yml index 66c5a4bd..2156a2bf 100644 --- a/src/notify/pr-validation-reporter/action.yml +++ b/src/notify/pr-validation-reporter/action.yml @@ -38,9 +38,26 @@ inputs: required: false default: "false" +outputs: + has-breaking-change-guard: + description: Whether the breaking change guard result was reported (true/false) + value: ${{ steps.guard-state.outputs.has-breaking-change-guard }} + runs: using: composite steps: + - name: Resolve breaking change guard state + id: guard-state + shell: bash + env: + BREAKING_CHANGE_RESULT: ${{ inputs.breaking-change-result }} + run: | + if [ "$BREAKING_CHANGE_RESULT" != "skipped" ]; then + echo "has-breaking-change-guard=true" >> "$GITHUB_OUTPUT" + else + echo "has-breaking-change-guard=false" >> "$GITHUB_OUTPUT" + fi + - name: Post PR validation summary if: inputs.dry-run != 'true' uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 diff --git a/src/validate/pr-checks-summary/README.md b/src/validate/pr-checks-summary/README.md index 925829f4..1620da93 100644 --- a/src/validate/pr-checks-summary/README.md +++ b/src/validate/pr-checks-summary/README.md @@ -22,6 +22,12 @@ Generates a summary table of all PR validation check results in the GitHub Actio When `breaking-change-result` is `skipped` or omitted, the summary omits the guard row and preserves existing behavior. This optional default exists only for backward compatibility with direct action consumers. The mandatory `pr-validation` integration always supplies the guard result and offers no guard opt-out. +## Outputs + +| Output | Description | +|--------|-------------| +| `has-breaking-change-guard` | Whether the breaking change guard result was reported, i.e. `breaking-change-result` was not `skipped` (`true`/`false`) | + ## Usage as composite step ```yaml diff --git a/src/validate/pr-checks-summary/action.yml b/src/validate/pr-checks-summary/action.yml index fbd1a838..a458fa1b 100644 --- a/src/validate/pr-checks-summary/action.yml +++ b/src/validate/pr-checks-summary/action.yml @@ -35,9 +35,26 @@ inputs: required: false default: "false" +outputs: + has-breaking-change-guard: + description: Whether the breaking change guard result was reported (true/false) + value: ${{ steps.guard-state.outputs.has-breaking-change-guard }} + runs: using: composite steps: + - name: Resolve breaking change guard state + id: guard-state + shell: bash + env: + BREAKING_CHANGE_RESULT: ${{ inputs.breaking-change-result }} + run: | + if [ "$BREAKING_CHANGE_RESULT" != "skipped" ]; then + echo "has-breaking-change-guard=true" >> "$GITHUB_OUTPUT" + else + echo "has-breaking-change-guard=false" >> "$GITHUB_OUTPUT" + fi + - name: Summary shell: bash env: @@ -53,8 +70,9 @@ runs: icon() { case "$1" in success) echo "✅" ;; - failure) echo "❌" ;; - *) echo "⏭️" ;; + failure|cancelled) echo "❌" ;; + skipped) echo "⏭️" ;; + *) echo "⚠️" ;; esac } From 06f10f249d86b966a849ec8e3008819bb477f58a Mon Sep 17 00:00:00 2001 From: fredcamaral Date: Thu, 6 Aug 2026 07:25:03 +0000 Subject: [PATCH 3/5] fix(validation): pass raw guard result without skipped fallback in docs Co-authored-by: Codesmith --- src/notify/pr-validation-reporter/README.md | 2 +- src/validate/pr-checks-summary/README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/notify/pr-validation-reporter/README.md b/src/notify/pr-validation-reporter/README.md index ec8d2e0d..40dfa9ed 100644 --- a/src/notify/pr-validation-reporter/README.md +++ b/src/notify/pr-validation-reporter/README.md @@ -79,7 +79,7 @@ jobs: size-result: ${{ needs.advisory-checks.outputs.size-result }} label-result: ${{ needs.advisory-checks.outputs.label-result }} metadata-result: ${{ needs.advisory-checks.outputs.metadata-result }} - breaking-change-result: ${{ needs.blocking-checks.outputs.breaking-change-result || 'skipped' }} + breaking-change-result: ${{ needs.blocking-checks.outputs.breaking-change-result }} ``` ## Required permissions diff --git a/src/validate/pr-checks-summary/README.md b/src/validate/pr-checks-summary/README.md index 1620da93..3b127007 100644 --- a/src/validate/pr-checks-summary/README.md +++ b/src/validate/pr-checks-summary/README.md @@ -46,7 +46,7 @@ jobs: size-result: ${{ needs.advisory-checks.outputs.size-result || 'skipped' }} label-result: ${{ needs.advisory-checks.outputs.label-result || 'skipped' }} metadata-result: ${{ needs.advisory-checks.outputs.metadata-result || 'skipped' }} - breaking-change-result: ${{ needs.blocking-checks.outputs.breaking-change-result || 'skipped' }} + breaking-change-result: ${{ needs.blocking-checks.outputs.breaking-change-result }} dry-run: "true" ``` From 30178ae9d7162ef1cf7612103faead304e8e5b38 Mon Sep 17 00:00:00 2001 From: fredcamaral Date: Thu, 6 Aug 2026 07:32:35 +0000 Subject: [PATCH 4/5] fix(validation): enable bash strict mode in guard result blocks Co-authored-by: Codesmith --- src/notify/pr-validation-reporter/README.md | 1 + src/notify/pr-validation-reporter/action.yml | 1 + src/validate/pr-checks-summary/action.yml | 1 + 3 files changed, 3 insertions(+) diff --git a/src/notify/pr-validation-reporter/README.md b/src/notify/pr-validation-reporter/README.md index 40dfa9ed..8af8c436 100644 --- a/src/notify/pr-validation-reporter/README.md +++ b/src/notify/pr-validation-reporter/README.md @@ -56,6 +56,7 @@ jobs: HAS_BREAKING_CHANGES: ${{ steps.breaking-change-guard.outputs.has-breaking-changes }} APPROVED: ${{ steps.breaking-change-guard.outputs.approved }} run: | + set -euo pipefail if [ "$GUARD_OUTCOME" != "success" ]; then echo "result=$GUARD_OUTCOME" >> "$GITHUB_OUTPUT" elif [ "$HAS_BREAKING_CHANGES" = "true" ] && [ "$APPROVED" != "true" ]; then diff --git a/src/notify/pr-validation-reporter/action.yml b/src/notify/pr-validation-reporter/action.yml index 2156a2bf..06834535 100644 --- a/src/notify/pr-validation-reporter/action.yml +++ b/src/notify/pr-validation-reporter/action.yml @@ -52,6 +52,7 @@ runs: env: BREAKING_CHANGE_RESULT: ${{ inputs.breaking-change-result }} run: | + set -euo pipefail if [ "$BREAKING_CHANGE_RESULT" != "skipped" ]; then echo "has-breaking-change-guard=true" >> "$GITHUB_OUTPUT" else diff --git a/src/validate/pr-checks-summary/action.yml b/src/validate/pr-checks-summary/action.yml index a458fa1b..8bdeafdc 100644 --- a/src/validate/pr-checks-summary/action.yml +++ b/src/validate/pr-checks-summary/action.yml @@ -49,6 +49,7 @@ runs: env: BREAKING_CHANGE_RESULT: ${{ inputs.breaking-change-result }} run: | + set -euo pipefail if [ "$BREAKING_CHANGE_RESULT" != "skipped" ]; then echo "has-breaking-change-guard=true" >> "$GITHUB_OUTPUT" else From 4ae1d8716644a1f504217bc0db6aca05b22a986f Mon Sep 17 00:00:00 2001 From: fredcamaral Date: Thu, 6 Aug 2026 07:53:41 +0000 Subject: [PATCH 5/5] docs(validation): document cancelled guard result in reporter input Co-authored-by: Codesmith --- src/notify/pr-validation-reporter/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/notify/pr-validation-reporter/action.yml b/src/notify/pr-validation-reporter/action.yml index 06834535..260d480f 100644 --- a/src/notify/pr-validation-reporter/action.yml +++ b/src/notify/pr-validation-reporter/action.yml @@ -30,7 +30,7 @@ inputs: required: false default: "skipped" breaking-change-result: - description: Result of breaking change guard (success/failure/skipped) + description: Result of breaking change guard (success/failure/cancelled/skipped) required: false default: skipped dry-run: