diff --git a/.github/workflows/bridge-gate.yml b/.github/workflows/bridge-gate.yml new file mode 100644 index 0000000..551551a --- /dev/null +++ b/.github/workflows/bridge-gate.yml @@ -0,0 +1,59 @@ +# SPDX-License-Identifier: MPL-2.0 +# Copyright (c) 2026 Jonathan D.A. Jewell +# +# bridge-gate.yml -- merge-orchestration CVE/bump gate. +# Wired from hyperpolymath/hypatia docs/design/merge-orchestration (artifact 4, PR #459). +# +# Gates dependency-bump PRs on: +# B3 nix bump -> fail (Nix removed estate-wide; close, do not merge) +# B1 reachable unmitigable CVE -> fail (via panic-attack's OSV-backed Patch-Bridge) +# A clean patch/minor CVE-clear bump passes. A failing gate is a red check; to actually +# BLOCK auto-merge, add "bridge-gate" to this repo's required status checks (owner action). +# +# Note: this repo (panic-attack) is the Patch-Bridge itself -- the gate builds and runs +# it against its own dependency tree, which is fine (self-triage). +# +# Review notes: +# - B2 (major -> review) is left to the existing human-review path. +# - Production hardening: a released binary or a hyperpolymath/standards reusable workflow. +# - github context via env: only (no ${{ }} in run lines) per estate rules. +name: bridge-gate +on: + pull_request: + types: [opened, synchronize, reopened] +permissions: + contents: read +jobs: + bridge-gate: + if: ${{ startsWith(github.head_ref, 'dependabot/') || startsWith(github.head_ref, 'renovate/') }} + runs-on: ubuntu-latest + timeout-minutes: 25 + steps: + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 + + - name: B3 gate -- nix bumps (no network) + env: + HEAD_REF: ${{ github.head_ref }} + run: | + if [[ "$HEAD_REF" == dependabot/nix/* ]]; then + echo "::error::B3: nix bump -- Nix removed estate-wide; close, do not merge." + exit 1 + fi + + - name: Build panic-attack (Patch-Bridge) from this repo + run: | + cargo build --release --features http + echo "$PWD/target/release" >> "$GITHUB_PATH" + + - name: Bridge triage (queries OSV) -> BridgeReport + run: panic-attack bridge triage . --output bridge-report.json + + - name: Apply B1 verdict -- reachable unmitigable CVE -> flag + run: | + blockers=$(jq '[.cves[]? | select(.classification=="Unmitigable" and .reachability.status=="Reachable")] | length' bridge-report.json) + if [ "$blockers" -gt 0 ]; then + echo "::error::B1: $blockers reachable unmitigable CVE(s) -> safety=flag (auto-merge blocked)" + jq -r '.cves[]? | select(.classification=="Unmitigable" and .reachability.status=="Reachable") | " - \(.vulnerability.cve // .vulnerability.id): \(.rationale)"' bridge-report.json + exit 1 + fi + echo "B1 PASS: no reachable unmitigable CVEs -> arm-eligible (pool-gated)."