Repository navigation
fix(17391): Lab17 -- le schema passe en Mermaid, l'attribution DS-STAR corrigee #14041
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: markdown-rendering-guard | |
| # Durable guard for the class of markdown *rendering* regressions that slip past | |
| # agent review because an agent cannot render markdown in its head (the same blind | |
| # spot that let oversized exercise "hints" and, more recently, the cost-frontmatter | |
| # blocks ship to main). The defects render as ugly, title-sized text blocks on the | |
| # very first open of a notebook, yet are invisible in a source diff. | |
| # | |
| # Two complementary scanners run here: | |
| # | |
| # 1. detect_markdown_rendering.py -- the HARD gate. Catches YAML frontmatter dumped | |
| # into a rendered markdown cell (`---\ntitle:...\ncost:\n ...\n---`), which | |
| # markdown-it promotes to one oversized setext-H2 block, plus accidental | |
| # setext-oversized prose. Baselined: the corpus already carries ~480 pre-existing | |
| # violations, so `--check --baseline` FAILS the PR only on NEW error-level ones | |
| # (hashes are content-stable across cell reordering). Burn the baseline down with | |
| # `--update-baseline` after a remediation batch. See #8056 / the cost-frontmatter | |
| # exemplar #8323. | |
| # | |
| # 2. scan_md_hierarchy.py -- informational. Surfaces hint-as-heading (Indice / | |
| # Astuce / Conseil / Piste written as an H1..H3 -> renders larger than body text), | |
| # collapsed-markdown tables, and multi-H1 hierarchy defects. Report-only for now | |
| # (no baseline yet, ~120 pre-existing notebooks); it prints the honest count so a | |
| # remediation batch can drive it down. Its plural-blind regex was the root cause | |
| # of the vacuous #3968 closure (it reported 0 hints while ~200 plural-form hint | |
| # headings survived) -- fixed in the same PR that adds this guard. Promoting it to | |
| # a baselined hard gate is a tracked follow-up on #3966. | |
| # | |
| # Scope: main-repo notebooks only. Submodules are separate repos with their own CI. | |
| on: | |
| pull_request: | |
| branches: [ main ] | |
| paths: | |
| - '**.ipynb' | |
| - '_quarto.yml' | |
| - 'scripts/notebook_tools/detect_markdown_rendering.py' | |
| - 'scripts/notebook_tools/scan_md_hierarchy.py' | |
| - 'scripts/notebook_tools/markdown_rendering_baseline.json' | |
| - '.github/workflows/markdown-rendering-guard.yml' | |
| push: | |
| branches: [ main ] | |
| paths: | |
| - '**.ipynb' | |
| - '_quarto.yml' | |
| - 'scripts/notebook_tools/detect_markdown_rendering.py' | |
| - 'scripts/notebook_tools/scan_md_hierarchy.py' | |
| - 'scripts/notebook_tools/markdown_rendering_baseline.json' | |
| workflow_dispatch: | |
| permissions: | |
| contents: read | |
| concurrency: | |
| # #13363/cascade : `github.ref` vaut `refs/heads/main` pour TOUS les commits | |
| # de main, donc `cancel-in-progress: true` faisait tuer le controle du commit | |
| # precedent par le merge suivant. Mesure du 2026-08-28 sur 30 runs main : | |
| # 26-27 annules, 2-3 verdicts rendus. Le backstop cense attraper une mauvaise | |
| # cascade etait tue par la cascade -- il defaillait exactement quand il servait. | |
| # Sur une branche de PR, superseder est CORRECT (seule la tete compte) ; sur | |
| # main, chaque commit est un sujet distinct qu'aucun autre ne peut remplacer. | |
| group: markdown-rendering-guard-${{ github.event_name == 'pull_request' && github.ref || github.sha }} | |
| cancel-in-progress: ${{ github.event_name == 'pull_request' }} | |
| jobs: | |
| render-scan: | |
| name: "markdown-rendering guard (main-repo notebooks)" | |
| # Route vers la jambe Linux auto-hebergee (#13378 tranche 5, decision ai-01 | |
| # 2026-09-02) : garde Python pur, sans secret. La garde same-repo ci-dessous | |
| # saute les PRs de fork (pr_gate.py compte `skipped` comme OK) : aucun code de | |
| # fork n'atteint le runner. Retour arriere = revert de la PR. | |
| runs-on: [self-hosted, coursia-ephemeral, coursia-linux] | |
| if: github.event.pull_request.head.repo.full_name == null || github.event.pull_request.head.repo.full_name == github.repository | |
| timeout-minutes: 10 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-python@v5 | |
| with: | |
| python-version: '3.11' | |
| # The Quarto closure scan (below) reads _quarto.yml via PyYAML -- a bare | |
| # setup-python environment does not provide it, and pre-#11850 the | |
| # resulting ImportError was swallowed as an "empty render-list", killing | |
| # the closure scan while accusing _quarto.yml of being invalid. | |
| - name: "Install detector dependencies (pyyaml for the closure scan)" | |
| run: pip install pyyaml | |
| # #12064: the embedded positive/negative controls were documented as | |
| # "the guard workflow runs it" but nothing called --selfcheck -- a rule | |
| # refactor losing an observed defect form would have passed CI silently. | |
| # Runs the controls for ALL rules that carry them (yaml_block_open_no_close | |
| # both outage forms, code_stmt_in_markdown stub/indent/fence game). | |
| - name: "Rule selfcheck -- embedded positive/negative controls" | |
| run: python scripts/notebook_tools/detect_markdown_rendering.py --selfcheck | |
| - name: "HARD gate -- fail on any NEW frontmatter / setext oversize" | |
| run: | | |
| if ! python scripts/notebook_tools/detect_markdown_rendering.py \ | |
| --check \ | |
| --baseline scripts/notebook_tools/markdown_rendering_baseline.json; then | |
| echo "" | |
| echo "::error::A markdown cell was added/changed that renders as an oversized, unformatted text block (YAML frontmatter dumped into a rendered cell, or prose accidentally underlined by '---'/'==='). It looks fine in the source diff but ugly on first open." | |
| echo "Fix options (per-family editorial choice):" | |
| echo " * move the metadata into the notebook 'metadata' (invisible, machine-readable), OR" | |
| echo " * render it inside a fenced \`\`\`yaml block, OR" | |
| echo " * format it as a small markdown table." | |
| echo "Then re-run: python scripts/notebook_tools/detect_markdown_rendering.py --report <notebook>" | |
| echo "If the change is an intentional, already-known violation, burn it into the baseline with --update-baseline (reviewer sign-off)." | |
| exit 1 | |
| fi | |
| - name: "Informational -- hint-as-heading / collapsed-markdown / multi-H1 census" | |
| if: always() | |
| run: | | |
| echo "Typographic-hierarchy census (report-only, not gating this PR):" | |
| python scripts/notebook_tools/scan_md_hierarchy.py MyIA.AI.Notebooks | tail -1 | |
| echo "" | |
| echo "Hint-as-heading lines (Indice/Astuce/Conseil/Piste as an H1..H3 -> oversized):" | |
| python scripts/notebook_tools/scan_md_hierarchy.py MyIA.AI.Notebooks | grep -c 'HINT-AS-HEADING' || true | |
| # Quarto closure scan: detects frontmatter / setext defects in notebooks | |
| # linked from the render-list (one hop). The trigger paths include | |
| # ``**/*.ipynb`` and ``_quarto.yml`` so this catches the failure mode of | |
| # #11630 where a PR added a link to a broken notebook without touching | |
| # the notebook itself. Informational for now -- the corpus carries | |
| # ~358 known violations to remediate. (#11630 acceptance #1+#4.) | |
| - name: "Informational -- Quarto closure scan (render-list + linked notebooks)" | |
| if: always() | |
| run: | | |
| python scripts/notebook_tools/detect_markdown_rendering.py \ | |
| --closure --quarto-yml _quarto.yml MyIA.AI.Notebooks --max-findings 8 |