Files
cs249r_book/.github/workflows/slides-validate-dev.yml
Vijay Janapa Reddi e5d00632cf fix(ci): gate sibling preview deploys on validate workflows passing
Rolls the same gating pattern from staffml out to kits, slides,
instructors, site, and mlsysim, all of which had the same race —
preview-dev and validate-dev triggered independently on push to dev with
no `needs:` between them, so a deploy could ship even when validate
failed on the same SHA. (labs and tinytorch already gate via
`workflow_run` and were left alone.)

For each pair: validate-dev gains a workflow_call trigger and rekeys
concurrency to `head_ref || run_id`. preview-dev adds a `validate` job
that calls validate-dev via `uses:` and the existing build-and-deploy
job grows a `needs: [validate, ...]`. Build doesn't start until validate
finishes, so wall-clock per push regresses by roughly the validate
duration; can be upgraded to the staffml-style parallel-build split per
component if that becomes painful.
2026-05-01 13:25:27 -04:00

207 lines
7.7 KiB
YAML

name: '📊 Slides · ✅ Validate (Dev)'
# =============================================================================
# Lecture Slides — Content & Build Validation
# =============================================================================
#
# Validates slide deck content integrity: SVG well-formedness, LaTeX frame
# matching, and Quarto portal build output.
#
# Flow:
# 1. VALIDATE_SVGS — Check all SVGs are well-formed XML
# 2. BUILD_SITE — Quarto HTML portal renders successfully
# 3. VALIDATE_TEX — Check .tex files for frame begin/end mismatches
# 4. SUMMARY — Aggregate results
#
# Triggers:
# - push: dev branch, slides/** paths
# - pull_request: slides/** or workflow file changes
# - workflow_dispatch: manual
#
# Deploys to: N/A (validate only)
#
# Related:
# - slides-preview-dev.yml — Dev preview deploy
# - slides-publish-live.yml — Production deploy with PDF release
# - slides-build-pdfs.yml — Build all slide deck PDFs
#
# =============================================================================
on:
workflow_dispatch:
# Reusable: slides-preview-dev.yml calls this via `uses:` so the deploy
# job can `needs:` a green validate. Standalone push/PR triggers stay
# so the publish guard and README badge still see direct runs on dev.
workflow_call:
pull_request:
paths:
- 'slides/**'
- '.github/workflows/slides-validate-dev.yml'
- '.github/workflows/slides-preview-dev.yml'
push:
branches: [dev]
paths:
- 'slides/**'
- '.github/workflows/slides-validate-dev.yml'
- '.github/workflows/slides-preview-dev.yml'
permissions:
contents: read
concurrency:
# `head_ref || run_id` keeps PR cancel-on-amend behavior while making
# push and workflow_call runs unique per-run, so a push to dev that
# triggers both this workflow standalone AND Preview's `uses:` call
# doesn't collide on a shared group. See staffml-validate-dev.yml.
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
jobs:
# ===========================================================================
# Stage 1: SVG validation (well-formed XML)
# ===========================================================================
validate-svgs:
name: '🔍 Validate SVGs'
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: 📥 Checkout
uses: actions/checkout@v6
- name: 🔍 Check SVG XML validity
working-directory: slides
run: |
echo "🔍 Checking SVG XML well-formedness..."
fails=0
total=0
for svg in $(find vol1 vol2 -name "*.svg" 2>/dev/null); do
total=$((total + 1))
if ! python3 -c "import xml.etree.ElementTree as ET; ET.parse('$svg')" 2>/dev/null; then
echo " ❌ $svg"
fails=$((fails + 1))
fi
done
echo ""
echo "📊 Checked $total SVGs, $fails failures"
if [ "$fails" -gt 0 ]; then
exit 1
fi
echo "✅ All SVGs are valid XML"
# ===========================================================================
# Stage 2: Quarto site build
# ===========================================================================
build-site:
name: '🔨 Build Slides Portal'
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: 📥 Checkout
uses: actions/checkout@v6
- name: 🔧 Setup Quarto
uses: quarto-dev/quarto-actions/setup@v2
- name: 🔨 Build Slides Portal (HTML)
working-directory: slides
run: quarto render
- name: 🔍 Validate build output
run: |
if [ ! -f "slides/_build/index.html" ]; then
echo "❌ CRITICAL: index.html missing from build output."
exit 1
fi
echo "✅ Site built successfully"
echo "📊 Build size: $(du -sh slides/_build | cut -f1)"
echo "📄 Pages built: $(find slides/_build -name '*.html' | wc -l)"
# ===========================================================================
# Stage 3: .tex file syntax check (lightweight — no full PDF build in CI)
# ===========================================================================
validate-tex:
name: '🔍 Validate LaTeX Syntax'
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: 📥 Checkout
uses: actions/checkout@v6
- name: 🔍 Check .tex files for common errors
working-directory: slides
run: |
echo "🔍 Checking .tex files for common issues..."
fails=0
for tex in $(find vol1 vol2 -name "*.tex" 2>/dev/null); do
# Check for unmatched begin/end frame
begins=$(grep -c '\\begin{frame}' "$tex" 2>/dev/null || echo 0)
ends=$(grep -c '\\end{frame}' "$tex" 2>/dev/null || echo 0)
if [ "$begins" != "$ends" ]; then
echo " ❌ $tex: frame mismatch (begin=$begins, end=$ends)"
fails=$((fails + 1))
fi
# Check for missing \note on frames
notes=$(grep -c '\\note{' "$tex" 2>/dev/null || echo 0)
if [ "$notes" -lt "$begins" ]; then
echo " ⚠️ $tex: $notes notes for $begins frames (some frames missing notes)"
fi
done
echo ""
if [ "$fails" -gt 0 ]; then
echo "❌ $fails .tex files have frame mismatches"
exit 1
fi
echo "✅ All .tex files pass syntax checks"
# ===========================================================================
# Stage 4: Link integrity (Tier 2 — non-blocking baseline)
# ===========================================================================
# Tier 1 pre-commit (shared/scripts/check-internal-links.py) already blocks
# broken internal links + anchors in markdown. This pass adds external
# reachability as a warning. Flip fail_on_broken=true once baseline is clean.
check-links:
name: '🔗 Check Links'
uses: ./.github/workflows/infra-link-check.yml
with:
path_pattern: './slides/**/*.{qmd,md}'
lycheeignore_path: 'shared/config/.lycheeignore'
fail_on_broken: false
max_concurrency: 8
# ===========================================================================
# Summary
# ===========================================================================
summary:
name: '📊 Summary'
runs-on: ubuntu-latest
needs: [validate-svgs, build-site, validate-tex, check-links]
if: always()
steps:
- name: 📊 Generate Summary
run: |
echo "## 📊 Slides Validation Results" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "| Check | Status |" >> $GITHUB_STEP_SUMMARY
echo "|-------|--------|" >> $GITHUB_STEP_SUMMARY
echo "| 🔍 SVG Validation | ${{ needs.validate-svgs.result }} |" >> $GITHUB_STEP_SUMMARY
echo "| 🔨 Site Build (HTML) | ${{ needs.build-site.result }} |" >> $GITHUB_STEP_SUMMARY
echo "| 🔍 LaTeX Syntax | ${{ needs.validate-tex.result }} |" >> $GITHUB_STEP_SUMMARY
echo "| 🔗 Link Check | ${{ needs.check-links.result }} (non-blocking) |" >> $GITHUB_STEP_SUMMARY
- name: ❌ Check for failures
run: |
if [ "${{ needs.validate-svgs.result }}" = "failure" ] || \
[ "${{ needs.build-site.result }}" = "failure" ] || \
[ "${{ needs.validate-tex.result }}" = "failure" ]; then
echo "❌ Validation failed"
exit 1
fi
if [ "${{ needs.check-links.result }}" = "failure" ]; then
echo "⚠️ Link check found issues (non-blocking)"
fi
echo "✅ Core checks passed"