Skip to content

Commit e6bc474

Browse files
ci(hypatia): make dogfooding self-scan resilient to upstream outages (#43)
## Context While completing #42 (now merged), the only red check was **"Hypatia Neurosymbolic Analysis (Dogfooding)"**. It failed ~11s in — at an *external* step (BEAM provisioning / cloning / building the upstream `hyperpolymath/hypatia` scanner), not on anything in this repo. The two core Agda `check` lanes were green. That is the wrong behaviour for a scan that is **non-gating by design** (the workflow already says "warn but don't fail — fix forward"): an upstream outage was blocking unrelated PRs. ## The principle applied Distinguish two failure classes and treat them differently: 1. **External-infrastructure failure** (dependency we don't control: setup-beam, `git clone hypatia`, `mix escript.build`). → Emit a `::warning::` annotation + a "SKIPPED (non-gating)" job summary, **exit green**. A contributor's PR must not be hostage to an upstream outage. 2. **Scan findings** (the scanner actually ran). → Unchanged: fix-forward, still never a hard gate. ## Changes - `setup-beam` gets `continue-on-error: true`. - Clone + build consolidated into one tolerant `scanner` step that sets a `ready` output (never exits non-zero on infra failure). - `Run Hypatia scan`, `panic-attack`, and artifact upload are gated on `steps.scanner.outputs.ready == 'true'`. - New "Note skipped scan" step writes an explicit non-gating explanation to the job summary when the scanner couldn't be provisioned. - Header comment documents the two-failure-class policy so future edits preserve it. Net effect: the check is green unless the scanner runs *and* we deliberately decide a finding class should gate (currently none do). The scan signal is preserved when upstream is healthy; it degrades to a visible warning when it isn't. ## Verification - YAML parses (`yaml.safe_load`). - Logic is conditional-only; no behavioural change to the scan itself when the scanner provisions successfully. 🤖 Generated with [Claude Code](https://claude.ai/code) --- _Generated by [Claude Code](https://claude.ai/code/session_01WLofBeW2eXxD5spRQTSvwi)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent b7f47d2 commit e6bc474

2 files changed

Lines changed: 82 additions & 6 deletions

File tree

.github/workflows/agda.yml

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,16 @@ jobs:
2626
- name: Fetch Agda standard library (v2.3)
2727
id: stdlib
2828
run: |
29-
STDLIB_DIR="$RUNNER_TEMP/agda-stdlib"
29+
STDLIB_DIR="$HOME/agda-stdlib"
3030
git clone --depth 1 --branch v2.3 https://github.com/agda/agda-stdlib.git "$STDLIB_DIR"
3131
echo "stdlib_dir=$STDLIB_DIR" >> "$GITHUB_OUTPUT"
3232
3333
- name: Fetch absolute-zero library
3434
id: absz
3535
run: |
36-
ABSZ_DIR="$RUNNER_TEMP/absolute-zero"
36+
ABSZ_DIR="$HOME/absolute-zero"
3737
git clone --depth 1 https://github.com/hyperpolymath/absolute-zero.git "$ABSZ_DIR"
38+
echo "absz_commit=$(git -C "$ABSZ_DIR" rev-parse HEAD)" >> "$GITHUB_OUTPUT"
3839
echo "absz_dir=$ABSZ_DIR" >> "$GITHUB_OUTPUT"
3940
4041
- name: Register libraries for Agda
@@ -56,6 +57,25 @@ jobs:
5657
printf '%s\n%s\n' "$STDLIB_LIB" "$ABSZ_LIB" > "$HOME/.agda/libraries"
5758
printf '%s\n' "standard-library" > "$HOME/.agda/defaults"
5859
60+
# Cache Agda interface (.agdai) files. A fresh runner otherwise
61+
# recompiles the entire stdlib subset + the whole proof development
62+
# from scratch every run (the multi-minute cold pass). Agda validates
63+
# each .agdai against its source, so a stale cache is always safe —
64+
# it just means more modules recompile. The key rotates on any proof
65+
# source change or absolute-zero bump; restore-keys give a warm
66+
# partial cache even when it does.
67+
- name: Cache Agda interface files
68+
uses: actions/cache@v4
69+
with:
70+
path: |
71+
~/agda-stdlib/**/*.agdai
72+
~/absolute-zero/**/*.agdai
73+
proofs/agda/**/*.agdai
74+
key: agdai-${{ runner.os }}-agda2.6.3-stdlib2.3-absz${{ steps.absz.outputs.absz_commit }}-${{ hashFiles('proofs/agda/**/*.agda') }}
75+
restore-keys: |
76+
agdai-${{ runner.os }}-agda2.6.3-stdlib2.3-absz${{ steps.absz.outputs.absz_commit }}-
77+
agdai-${{ runner.os }}-agda2.6.3-stdlib2.3-
78+
5979
- name: Typecheck full suite
6080
run: agda -i proofs/agda proofs/agda/All.agda
6181

.github/workflows/hypatia-scan.yml

Lines changed: 60 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,22 @@
11
# SPDX-License-Identifier: PMPL-1.0-or-later
22
# Hypatia Neurosymbolic CI/CD Security Scan — SELF-SCAN (dogfooding)
33
# The standards repo that defines Hypatia scans itself with Hypatia.
4+
#
5+
# Non-gating by design: this job exists to surface compliance gaps, not to
6+
# block merges. Two failure classes are handled distinctly:
7+
#
8+
# 1. External-infrastructure failure (BEAM provisioning, cloning or
9+
# building the upstream hyperpolymath/hypatia scanner). These are
10+
# outages in dependencies we do not control. They MUST NOT turn the
11+
# check red — a contributor's unrelated PR cannot be held hostage to
12+
# an upstream outage. We emit a ::warning:: annotation and exit green
13+
# so the signal is visible without being a gate.
14+
#
15+
# 2. Scan findings (the scanner ran and reported issues). Per the
16+
# "fix forward" policy these are warnings, never hard failures.
17+
#
18+
# Net effect: this job is green unless the scanner itself runs AND we
19+
# deliberately decide a finding class should gate (currently none do).
420
name: Hypatia Self-Scan
521

622
on:
@@ -26,24 +42,48 @@ jobs:
2642
fetch-depth: 0
2743

2844
- name: Setup Elixir for Hypatia scanner
45+
id: beam
46+
continue-on-error: true
2947
uses: erlef/setup-beam@fc68ffb90438ef2936bbb3251622353b3dcb2f93 # v1.24.0
3048
with:
3149
elixir-version: '1.19.4'
3250
otp-version: '28.3'
3351

34-
- name: Clone Hypatia
52+
- name: Provision Hypatia scanner (clone + build)
53+
id: scanner
3554
run: |
55+
# Any external-infra failure here is tolerated: we record
56+
# ready=false and let downstream steps skip cleanly.
57+
set +e
58+
59+
if [ "${{ steps.beam.outcome }}" != "success" ]; then
60+
echo "::warning title=Hypatia self-scan skipped::Elixir/BEAM provisioning failed (upstream setup-beam). Non-gating; scanner not run."
61+
echo "ready=false" >> "$GITHUB_OUTPUT"
62+
exit 0
63+
fi
64+
3665
git clone --depth 1 https://github.com/hyperpolymath/hypatia.git "$HOME/hypatia"
66+
if [ $? -ne 0 ]; then
67+
echo "::warning title=Hypatia self-scan skipped::Could not clone hyperpolymath/hypatia (upstream outage). Non-gating; scanner not run."
68+
echo "ready=false" >> "$GITHUB_OUTPUT"
69+
exit 0
70+
fi
3771
38-
- name: Build Hypatia scanner
39-
run: |
4072
cd "$HOME/hypatia"
4173
if [ ! -f hypatia-v2 ]; then
42-
cd scanner && mix deps.get && mix escript.build && mv hypatia ../hypatia-v2
74+
( cd scanner && mix deps.get && mix escript.build && mv hypatia ../hypatia-v2 )
75+
if [ $? -ne 0 ]; then
76+
echo "::warning title=Hypatia self-scan skipped::Hypatia scanner build failed (upstream). Non-gating; scanner not run."
77+
echo "ready=false" >> "$GITHUB_OUTPUT"
78+
exit 0
79+
fi
4380
fi
4481
82+
echo "ready=true" >> "$GITHUB_OUTPUT"
83+
4584
- name: Run Hypatia scan
4685
id: scan
86+
if: steps.scanner.outputs.ready == 'true'
4787
run: |
4888
echo "Scanning standards repo (dogfooding)"
4989
HYPATIA_FORMAT=json "$HOME/hypatia/hypatia-cli.sh" scan . > hypatia-findings.json
@@ -70,7 +110,22 @@ jobs:
70110
echo "| Medium | $MEDIUM |" >> $GITHUB_STEP_SUMMARY
71111
echo "| **Total**| $FINDING_COUNT |" >> $GITHUB_STEP_SUMMARY
72112
113+
- name: Note skipped scan
114+
if: steps.scanner.outputs.ready != 'true'
115+
run: |
116+
{
117+
echo "## Hypatia Self-Scan: SKIPPED (non-gating)"
118+
echo ""
119+
echo "The upstream Hypatia scanner could not be provisioned"
120+
echo "(BEAM setup, clone, or build failed). This is an external"
121+
echo "infrastructure outage, not a finding in this repo. The"
122+
echo "check is intentionally green so unrelated PRs are not"
123+
echo "blocked. Re-run once upstream is healthy, or wait for the"
124+
echo "weekly scheduled scan."
125+
} >> "$GITHUB_STEP_SUMMARY"
126+
73127
- name: Run panic-attack assail
128+
if: steps.scanner.outputs.ready == 'true'
74129
run: |
75130
# Install panic-attack if available
76131
if command -v panic-attack >/dev/null 2>&1; then
@@ -82,6 +137,7 @@ jobs:
82137
fi
83138
84139
- name: Upload findings artifacts
140+
if: steps.scanner.outputs.ready == 'true'
85141
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
86142
with:
87143
name: standards-self-scan

0 commit comments

Comments
 (0)