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
2 changes: 2 additions & 0 deletions .claude/rules/pr-review-discipline.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ Single-seed ou single-fold = **CHANGES_REQUESTED** sauf flag explicite `[POC]` d

6. **PRs notebook : vérifier le verdict du check-run `Output-failure ratchet (base vs PR)`** (organe `scripts/notebook_tools/check_output_failure_text.py`, enregistré **bloquant** dans `scripts/ci/fast_lane_registry.py`). Le check-run DOIT être `success`. `TOOL_FAILURE` (bannières « `program is not installed` » ou rendu d'échec) ou `MACHINE_PATH` (chemin machine dans une sortie) qui **augmente** sur la PR (N → N+k, type `0 → 21`) = **régression → `CHANGES_REQUESTED`**, même si les points 1-3 passent : les bannières d'échec ne sont **pas** des exceptions Python, elles ne déclenchent ni `exec_count` nul ni `grep -nE "raise NotImplementedError|assert False|1/0"` — une PR qui **remplace** un rendu SVG de factor-graph par une bannière est le dégât exact de #3473/#11685, pas un « ça tourne ». Récurrence : #13517 (PR #13036 LDA — bannières 0→21, `MACHINE_PATH` 0→14, approuvée par Hermes alors que le garde rend rc=1) ; juin → #3473 (~15 filles) ; 18/08 → #11693.

7. **PRs notebook : lire le check-run ADVISORY `Output-collapse ratchet (base vs PR, advisory)`** (organe `scripts/notebook_tools/check_output_collapse.py`, enregistré `blocking=False` dans `scripts/ci/fast_lane_registry.py`, #15327). Conclusion neutre par design — le signal vit dans le détail du check-run. Un finding `SIGNATURE` (sortie base substantielle remplacée par « `Execution sautee (API non configuree)` » et consœurs) = **re-exécution sans les clés → `CHANGES_REQUESTED`** : les cellules se sont « exécutées avec succès » en dégradation gracieuse (`if api_ok:`), c'est le contournement de C.2 par la porte de secours. Contre-exemple mesuré (fondateur) : #15209, `Lean-7b-Examples.ipynb` `6b327a9bf` → `56d98429a` — 11 → 11 cellules, 0 erreur, `execution_count` réels partout, et **10637 → 2985** caractères de sortie (cellules `2195 → 147`, `2568 → 38`, `2074 → 42`) : tous les organes verts, la perte réelle. Un finding `MAGNITUDE` (perte d'un ordre de grandeur par cellule, non couvert par les exemptions automatiques contenu-déplacé/purge-diagnostic) exige une **justification dans le body** (allègement déclaré, au même titre que les autres ratchets) — sans elle : `CHANGES_REQUESTED`.

**Advisory `.NET execution_count` ≠ outputs vides autorisés (#5214).** L'advisory autorise à sauter la ré-exécution **CI** (pas de kernel .NET en CI), **pas** à committer des sorties vides : `.NET Interactive` s'exécute **localement** sur chaque worker → une cellule .NET committée **DOIT** porter `execution_count != null`. `validate_pr_notebooks.py` FAIL sur `.NET` + `null`, et ne tolère `null` que là où l'exécution locale est aussi impossible (QC Cloud, Lean). Verdict attendu dans le body : `EXEC_PROVED` vs `STRUCTURAL_ONLY` (refus).

### E. Documentation / Admin : groupement obligatoire
Expand Down
82 changes: 82 additions & 0 deletions .github/workflows/notebook-output-collapse-ratchet.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
name: Notebook Output Collapse Ratchet

# #15327 / réserve user sur #15209 (2026-09-08) — une ré-exécution ne doit
# pas laisser s'effondrer silencieusement le VOLUME de sortie des cellules
# code. Miroir de notebook-output-flood-ratchet.yml, qui lui attrape
# l'inflation.
#
# Cas fondateur : Lean-7b-Examples.ipynb (6b327a9bf -> 56d98429a), 11 -> 11
# cellules code exécutées « avec succès », 0 erreur, execution_counts
# réels — et 10637 -> 2985 caractères de sortie (-71,9 %). Les gardes
# if api_ok: du notebook ont fait leur travail de dégradation gracieuse :
# trois cellules ont imprimé « Execution sautee (API non configuree) » au
# lieu de leur vraie sortie (2195 -> 147, 2568 -> 38, 2074 -> 42 chars).
#
# Contrainte de design (mesure ai-01 sur #15327, 2026-09-09) : la
# contraction AGREGÉE seule est un signal à ~100 % de faux positifs (3/3
# contractions > 50 % sur 14 jours étaient des PRs légitimes). L'organe
# discrimine donc par la CAUSE : signature de dégradation gracieuse et
# perte d'un ordre de grandeur PAR CELLULE, avec exemption des deux causes
# légitimes mécaniquement détectables (contenu déplacé vers un notebook
# créé dans le même diff ; purge de texte diagnostique CS####/warning).
#
# Placement ADVISORY (point 3 de l'issue) : le verdict est publié sous le
# check-run `Output-collapse ratchet (base vs PR, advisory)` avec une
# conclusion neutre — jamais bloquant tant que le seuil n'est pas calibré
# davantage sur l'historique.

# Livré directement dans la voie rapide (#12567) : le verdict par-PR est
# rendu par `fast-lane-shadow.yml` via l'entrée TRANCHE2 de
# `scripts/ci/fast_lane_registry.py`. Ce fichier ne garde que
# `workflow_dispatch` (relance manuelle).
on:
workflow_dispatch:
inputs:
base_ref:
description: 'Base ref to ratchet against (default origin/main)'
required: false
default: 'origin/main'

permissions:
contents: read

concurrency:
group: notebook-output-collapse-ratchet-${{ github.ref }}
cancel-in-progress: true

jobs:
ratchet:
name: Output-collapse ratchet (base vs PR, advisory)
# Même jambe que les siblings (#13378 tranche 5) : garde Python pur,
# sans secret. La garde same-repo ci-dessous saute les PRs de fork :
# aucun code de fork n'atteint le runner.
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
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
filter: blob:none

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'

# Le self-test passe FIRST et gate le garde : contrôles positifs et
# négatifs + replay du cas de référence (#15209, 3 findings signature).
- name: Detector self-test (positive + negative control)
run: python scripts/notebook_tools/check_output_collapse.py --self-test

- name: Output-collapse axis unit tests
run: python -m pytest scripts/notebook_tools/tests/test_check_output_collapse.py -q

# Refs reach the script via env, never inline in the run script
# (script-injection guard, same as the siblings).
- name: Ratchet check (advisory)
env:
BASE_REF: ${{ github.event.inputs.base_ref }}
PR_BASE: ${{ github.base_ref }}
run: |
BASE="${BASE_REF:-origin/${PR_BASE:-main}}"
python scripts/notebook_tools/check_output_collapse.py "$BASE"
12 changes: 12 additions & 0 deletions scripts/ci/check_self_hosted_runner_policy.py
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,18 @@
# ci-dessus : garde Python pur stdlib, garde same-repo au niveau job,
# shell workflow_dispatch-only absorbe par fast-lane TRANCHE2).
"notebook-output-flood-ratchet.yml",
# jumeau output-collapse (#15327, owner myia-po-2023:CoursIA) : verdict
# ADVISORY rendu par fast-lane TRANCHE2 (entree 5, check-run
# `Output-collapse ratchet (base vs PR, advisory)`), shell
# workflow_dispatch-ONLY pour re-run manuel -- meme profil que les deux
# jumeaux ci-dessus. Detecteur check_output_collapse.py, stdlib-only,
# garde same-repo au niveau job, aucun GITHUB_TOKEN cote job. L'organe a
# recu l'approval design (Hermes 02:29Z) ; la review ai-01 03:33Z a
# demande la voie 2 : allowlist canonique + test de politique rejoue
# (test_current_repository_self_hosted_jobs_satisfy_isolation_policy,
# scan du repo courant -- aucune autre liste a synchroniser).
# Rollback = revert de la PR (l'entree disparait de l'allowlist).
"notebook-output-collapse-ratchet.yml",
"notebook-validation.yml",
"owui-playwright-check.yml",
"perimeter-review-guard.yml",
Expand Down
38 changes: 36 additions & 2 deletions scripts/ci/fast_lane_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -392,8 +392,8 @@ class Guard:
# TRANCHE 2 d'absorption (#12567) -- meme contrat que la tranche 1 (nom
# canonique, conclusion reelle, workflow d'origine retire de pull_request),
# trois formes moteur nouvelles par rapport a la tranche 1, portees par
# QUATRE gardes (deux instances du ratchet autonome : failure-text puis
# output-flood, #14959) :
# CINQ gardes (trois instances du ratchet autonome : failure-text,
# output-flood puis output-collapse advisory, #14959/#15327) :
#
# - ratchet AUTONOME : le script fait lui-meme son diff base...HEAD, la lane
# ne fournit que {base_ref}. Son self-test est un PRE-CONTROLE (`pre_argv`)
Expand Down Expand Up @@ -457,6 +457,40 @@ class Guard:
needs_base=True,
absorbed=True,
),
# Output-volume COLLAPSE ratchet, advisory (#15327): the two siblings
# above watch output GROWTH (flood) and failure banners; nothing watched
# contraction -- #15209 lost 72 % of its output chars (11 code cells
# executed "successfully", graceful-degradation guards if api_ok:) while
# every gate stayed green. Design constraint (ai-01 measurement on the
# issue, 2026-09-09): AGGREGATE contraction alone is ~100 % FP (3/3
# contractions > 50 % over 14 days were legitimate), so this ratchet
# flags per-CELL order-of-magnitude loss and graceful-degradation
# signatures (execution sautee / non configure / mode simulation /
# skipped), exempting the two mechanically detectable legitimate causes:
# content moved to a notebook created by the same diff, and
# diagnostic-text purge (CS####/warning lines). Advisory until the
# threshold is calibrated further on history (issue point 3).
# Source : notebook-output-collapse-ratchet.yml (stub dispatch-only).
Guard(
name="Output-collapse ratchet (base vs PR, advisory)",
source="notebook-output-collapse-ratchet.yml",
paths=[
"**.ipynb",
"scripts/notebook_tools/check_output_collapse.py",
".github/workflows/notebook-output-collapse-ratchet.yml",
],
pre_argv=[
"python", "scripts/notebook_tools/check_output_collapse.py",
"--self-test",
],
argv=[
"python", "scripts/notebook_tools/check_output_collapse.py",
"{base_ref}",
],
blocking=False,
needs_base=True,
absorbed=True,
),
# Forme 2 : iter par notebook change, rc=1 defaut / rc=2 illisible.
# Source : fabricated-output-gate.yml (job `fabricated-output`).
Guard(
Expand Down
Loading
Loading