Skip to content
Draft
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
129 changes: 129 additions & 0 deletions .github/workflows/canonical-verification.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
name: Canonical Verification

# One exact-SHA admission graph shared by protected-branch CI and release
# publication. It preserves the existing proof-bearing nox verification lane;
# optional and networked jobs remain separate in the calling CI workflow.

on:
workflow_call:
inputs:
ref:
description: Exact 40-character commit SHA to verify
required: true
type: string
base-rev:
description: Exact comparison base SHA (falls back to the target parent)
required: false
default: ""
type: string
requirement-branch:
description: Branch name used to resolve an optional requirement UID
required: false
default: ""
type: string

permissions:
contents: read

jobs:
verify:
# Ubuntu 24.04 restricts unprivileged user namespaces through AppArmor.
# Keep the proof-bearing job on 22.04 so Bubblewrap enforces the sandbox
# without disabling a host security control on the runner.
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
fetch-depth: 0
ref: ${{ inputs.ref }}

- name: Bind verification to the exact commit and resolve policy base
id: commit
env:
EXPECTED_SHA: ${{ inputs.ref }}
REQUESTED_BASE_SHA: ${{ inputs.base-rev }}
run: |
set -euo pipefail
if ! printf '%s\n' "${EXPECTED_SHA}" | grep -Eq '^[0-9a-f]{40}$'; then
echo "Canonical verification requires a full lowercase commit SHA, got '${EXPECTED_SHA}'" >&2
exit 1
fi

actual_sha="$(git rev-parse HEAD)"
if [ "${actual_sha}" != "${EXPECTED_SHA}" ]; then
echo "Checkout mismatch: expected ${EXPECTED_SHA}, got ${actual_sha}" >&2
exit 1
fi

base_sha="${REQUESTED_BASE_SHA}"
if ! printf '%s\n' "${base_sha}" | grep -Eq '^[0-9a-f]{40}$' \
|| [ "${base_sha}" = "0000000000000000000000000000000000000000" ] \
|| [ "${base_sha}" = "${EXPECTED_SHA}" ] \
|| ! git cat-file -e "${base_sha}^{commit}" 2>/dev/null; then
if ! base_sha="$(git rev-parse "${EXPECTED_SHA}^")"; then
echo "Cannot resolve a policy base for root commit ${EXPECTED_SHA}" >&2
exit 1
fi
fi

echo "Verified exact commit ${EXPECTED_SHA}; policy base ${base_sha}"
echo "base_rev=${base_sha}" >> "${GITHUB_OUTPUT}"

- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.12"

- name: Install uv
uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v8

- name: Restore pinned Isabelle archive
# Untrusted PR and manual-release refs may consume this cache but must
# never populate the shared key used by protected-branch verification.
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: .cache/raes-sdl/tooling/archives/Isabelle2025-2_linux.tar.gz
key: isabelle-linux-x86-64-2025-2-a20a507bc7c1270d

- name: Install proof sandbox
run: |
if ! command -v bwrap >/dev/null 2>&1 || ! command -v fc-list >/dev/null 2>&1 || [ -z "$(fc-list --format='%{file}\n' | head -n 1)" ]; then
sudo apt-get update
sudo apt-get install --no-install-recommends -y bubblewrap fontconfig fonts-dejavu-core
fi
test -d /etc/fonts
test -d /usr/share/fonts
test -n "$(fc-list --format='%{file}\n' | head -n 1)"

- name: Acquire pinned Isabelle distribution
run: uv run --project implementations/python --frozen python -m tools.isabelle_tool acquire

- name: Resolve requirement UID from branch
id: requirement
env:
BRANCH: ${{ inputs.requirement-branch }}
run: |
REQ_UID="$(printf '%s\n' "${BRANCH}" | grep -oE '[A-Z]{3}-[0-9]{3}' | head -n1 || true)"
echo "Resolved branch='${BRANCH}' uid='${REQ_UID}'"
echo "uid=${REQ_UID}" >> "${GITHUB_OUTPUT}"

- name: Run canonical verification graph
env:
RAES_REQUIREMENT_UID: ${{ steps.requirement.outputs.uid }}
GC_BASE_URL: ${{ vars.GC_BASE_URL }}
run: |
verify_args=(--base-rev "${{ steps.commit.outputs.base_rev }}")
if [ -n "${{ steps.requirement.outputs.uid }}" ]; then
verify_args+=(--requirement-uid "${{ steps.requirement.outputs.uid }}")
else
verify_args+=(--skip-requirement)
fi
uv tool run --from 'nox[uv]==2026.4.10' nox -f noxfile.py -s verify -- "${verify_args[@]}"

- name: Upload coverage report
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: coverage-report
path: |
implementations/python/coverage.xml
implementations/python/coverage.json
85 changes: 24 additions & 61 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,72 +17,35 @@ permissions:
pull-requests: write

jobs:
canonical:
permissions:
contents: read
uses: ./.github/workflows/canonical-verification.yml
with:
ref: ${{ github.sha }}
base-rev: ${{ github.event.pull_request.base.sha || github.event.before || github.sha }}
requirement-branch: ${{ github.head_ref || github.ref_name }}

# The repository's dev/main branch protections require the historical
# `verify` check context. Reusable jobs report nested contexts, so preserve
# that stable contract with a same-run result join. The reusable call owns the
# existing proof-bearing nox graph; this join cannot turn any failed or
# skipped admission job into success.
verify:
# Ubuntu 24.04 restricts unprivileged user namespaces through AppArmor.
# Keep the proof-bearing job on 22.04 so Bubblewrap enforces the sandbox
# without disabling a host security control on the runner.
runs-on: ubuntu-22.04
needs: canonical
if: always()
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
fetch-depth: 0
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.12"
- name: Install uv
uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v8
- name: Restore pinned Isabelle archive
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: .cache/raes-sdl/tooling/archives/Isabelle2025-2_linux.tar.gz
key: isabelle-linux-x86-64-2025-2-a20a507bc7c1270d
- name: Install proof sandbox
run: |
if ! command -v bwrap >/dev/null 2>&1 || ! command -v fc-list >/dev/null 2>&1 || [ -z "$(fc-list --format='%{file}\n' | head -n 1)" ]; then
sudo apt-get update
sudo apt-get install --no-install-recommends -y bubblewrap fontconfig fonts-dejavu-core
fi
test -d /etc/fonts
test -d /usr/share/fonts
test -n "$(fc-list --format='%{file}\n' | head -n 1)"
- name: Acquire pinned Isabelle distribution
run: uv run --project implementations/python --frozen python -m tools.isabelle_tool acquire
- name: Resolve policy base revision
id: base
run: |
if [ "${{ github.event_name }}" = "pull_request" ]; then
echo "base_rev=${{ github.event.pull_request.base.sha }}" >> "$GITHUB_OUTPUT"
else
echo "base_rev=${{ github.event.before }}" >> "$GITHUB_OUTPUT"
fi
- name: Resolve requirement UID from branch
id: requirement
env:
BRANCH: ${{ github.head_ref || github.ref_name }}
run: |
REQ_UID="$(printf '%s\n' "$BRANCH" | grep -oE '[A-Z]{3}-[0-9]{3}' | head -n1 || true)"
echo "Resolved branch='$BRANCH' uid='$REQ_UID'"
echo "uid=$REQ_UID" >> "$GITHUB_OUTPUT"
- name: Run canonical verification graph
- name: Preserve the required canonical verification status
env:
RAES_REQUIREMENT_UID: ${{ steps.requirement.outputs.uid }}
GC_BASE_URL: ${{ vars.GC_BASE_URL }}
CANONICAL_RESULT: ${{ needs.canonical.result }}
run: |
verify_args=(--base-rev "${{ steps.base.outputs.base_rev }}")
if [ -n "${{ steps.requirement.outputs.uid }}" ]; then
verify_args+=(--requirement-uid "${{ steps.requirement.outputs.uid }}")
else
verify_args+=(--skip-requirement)
if [ "${CANONICAL_RESULT}" != "success" ]; then
echo "Canonical verification concluded with '${CANONICAL_RESULT}'" >&2
exit 1
fi
uv tool run --from 'nox[uv]==2026.4.10' nox -f noxfile.py -s verify -- "${verify_args[@]}"
- name: Upload coverage report
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: coverage-report
path: |
implementations/python/coverage.xml
implementations/python/coverage.json

fuzz:
runs-on: ubuntu-latest
Expand Down
Loading