Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 12 additions & 9 deletions .github/workflows/bridge-gate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,17 @@ on:
permissions:
contents: read
jobs:
bridge-contract:
name: Bridge report contract
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/checkout@v7.0.1
- name: Reject blocking, malformed and unavailable assessments
run: bash scripts/ci/test-bridge-report.sh

bridge-gate:
needs: bridge-contract
if: ${{ startsWith(github.head_ref, 'dependabot/') || startsWith(github.head_ref, 'renovate/') }}
runs-on: ubuntu-latest
timeout-minutes: 25
Expand All @@ -51,12 +61,5 @@ jobs:
- 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)."
- name: Apply B1 verdict -- validate data and reject blockers
run: bash scripts/ci/check-bridge-report.sh bridge-report.json
5 changes: 5 additions & 0 deletions .machine_readable/descriptiles/STATE.a2ml
Original file line number Diff line number Diff line change
Expand Up @@ -308,3 +308,8 @@ new-regression-source = "test/neural_source_store_lifecycle_test.exs"
authored-new-test-functions = 22
qualification = "No execution receipt for this new lifecycle candidate. Existing runtime-path test receipts do not qualify it. Exact-source compile, focused and normal full gates remain required."
authority = "No automatic restart, dispatch, or deployment promotion from a source refusal."

[bridge-report-contract-2026-11-06]
status = "candidate; not merged or deployed"
change = "Validate schema 0.3.0 and vulnerability-data availability before interpreting native lowercase B1 verdicts"
validation = "29 local executable contract cases passed; workflow YAML and embedded shell parsed; upstream TOML dependency fix in panic-attack PR 265 still awaits qualified CI"
40 changes: 40 additions & 0 deletions scripts/ci/check-bridge-report.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#!/usr/bin/env bash
# SPDX-License-Identifier: MPL-2.0
# Copyright (c) 2026 Jonathan D.A. Jewell <j.d.a.jewell@open.ac.uk>
# Exit 0: qualified report has no B1 blockers; 1: blockers; 2: invalid/unavailable data.
set -euo pipefail
if [[ $# != 1 || ! -f "$1" ]]; then
echo '::error::B1 assessment unavailable: expected one report file.' >&2
exit 2
fi
# Read exactly one JSON document. jq's default stream handling could otherwise
# accept an empty file or let a second document overwrite the first verdict.
if ! blockers=$(jq -ers '
def natural: type == "number" and . >= 0 and . == floor;
if length != 1 then error("expected one report") else .[0] end
| if (
type == "object"
and .schema_version == "0.3.0"
and (.total_dependencies | natural)
and (.cves | type == "array")
and (
(.vulnerability_data == "queried" and .total_dependencies > 0)
or (.vulnerability_data == "no-dependencies" and .total_dependencies == 0 and (.cves | length) == 0)
)
and all(.cves[];
type == "object"
and (.classification | IN("mitigable", "unmitigable", "informational"))
and (.reachability | type == "object")
and (.reachability.status | IN("reachable", "unreachable", "phantom-declared", "phantom-transitive"))
)
) then [.cves[] | select(.classification == "unmitigable" and .reachability.status == "reachable")] | length
else error("unsupported, incomplete or unavailable bridge assessment") end
' "$1"); then
echo '::error::B1 assessment unavailable: invalid report or missing vulnerability data.' >&2
exit 2
fi
if [[ "$blockers" -gt 0 ]]; then
printf '::error::B1: %s reachable unmitigable CVE(s); auto-merge blocked.\n' "$blockers" >&2
exit 1
fi
echo 'B1 PASS: qualified report contains no reachable unmitigable CVEs.'
55 changes: 55 additions & 0 deletions scripts/ci/test-bridge-report.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
#!/usr/bin/env bash
# SPDX-License-Identifier: MPL-2.0
# Copyright (c) 2026 Jonathan D.A. Jewell <j.d.a.jewell@open.ac.uk>
set -euo pipefail
script_dir=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)
work=$(mktemp -d)
trap 'rm -rf "$work"' EXIT
cases=0
check() {
local expected=$1 label=$2 content=$3 actual=0
printf '%s' "$content" > "$work/report.json"
bash "$script_dir/check-bridge-report.sh" "$work/report.json" > "$work/output" 2>&1 || actual=$?
if [[ "$actual" != "$expected" ]]; then
printf 'FAIL %s: expected exit %s, got %s\n' "$label" "$expected" "$actual" >&2
cat "$work/output" >&2
exit 1
fi
cases=$((cases + 1))
}
clean='{"schema_version":"0.3.0","vulnerability_data":"queried","total_dependencies":1,"cves":[]}'
finding='{"classification":"unmitigable","reachability":{"status":"reachable"}}'
blocked=$(jq --argjson finding "$finding" '.cves=[$finding]' <<< "$clean")
check 0 clean "$clean"
check 1 blocking "$blocked"
check 0 mitigable "$(jq '.cves[0].classification="mitigable"' <<< "$blocked")"
check 0 informational "$(jq '.cves[0].classification="informational" | .cves[0].reachability.status="phantom-declared"' <<< "$blocked")"
check 0 no-dependencies "$(jq '.vulnerability_data="no-dependencies" | .total_dependencies=0' <<< "$clean")"
check 2 offline "$(jq '.vulnerability_data="offline-unavailable"' <<< "$clean")"
check 2 missing-data "$(jq 'del(.vulnerability_data)' <<< "$clean")"
check 2 missing-cves "$(jq 'del(.cves)' <<< "$clean")"
check 2 null-cves "$(jq '.cves=null' <<< "$clean")"
check 2 object-cves "$(jq '.cves={}' <<< "$clean")"
check 2 unknown-schema "$(jq '.schema_version="0.4.0"' <<< "$clean")"
check 2 legacy-schema "$(jq 'del(.schema_version)' <<< "$clean")"
check 2 uppercase-verdict "$(jq '.cves[0].classification="Unmitigable"' <<< "$blocked")"
check 2 uppercase-reachability "$(jq '.cves[0].reachability.status="Reachable"' <<< "$blocked")"
check 2 missing-classification "$(jq 'del(.cves[0].classification)' <<< "$blocked")"
check 2 missing-reachability "$(jq 'del(.cves[0].reachability)' <<< "$blocked")"
check 2 null-finding "$(jq '.cves=[null]' <<< "$clean")"
check 2 bogus-empty "$(jq '.vulnerability_data="no-dependencies"' <<< "$blocked")"
check 2 queried-zero "$(jq '.total_dependencies=0' <<< "$clean")"
check 2 negative-count "$(jq '.total_dependencies=-1' <<< "$clean")"
check 2 fractional-count "$(jq '.total_dependencies=1.5' <<< "$clean")"
check 2 string-count "$(jq '.total_dependencies="1"' <<< "$clean")"
check 2 empty ''
check 2 malformed '{'
check 2 null null
check 2 array '[]'
check 2 multiple-documents "$blocked $clean"
check 2 multiple-clean-documents "$clean $clean"
actual=0
bash "$script_dir/check-bridge-report.sh" "$work/missing.json" > /dev/null 2>&1 || actual=$?
[[ "$actual" == 2 ]]
cases=$((cases + 1))
printf 'PASS: %s bridge report contract cases\n' "$cases"
Loading