Skip to content

fix(ci,#15545): arm positive control in slides-composition-advisory — dead scanner now fails - #15561

Merged
jsboige merged 2 commits into
mainfrom
fix/15545-slides-positive-control
Sep 11, 2026
Merged

jsboige merged 2 commits into
mainfrom
fix/15545-slides-positive-control

Conversation

@jsboige

@jsboige jsboige commented Sep 11, 2026 •

Copy link
Copy Markdown
Owner

Grain: MED/guard — lane myia-po-2026:CoursIA — prev: LIGHT/readme #15559

Quoi

L'appel CI de scan_slidev_composition.py n'armait jamais --baseline-slide : « 0 constat » était indistinguable d'un chemin de flagging mort. Ce n'était pas théorique — le signal CHEVAUCHEMENT v1 (glyphBBox ouvrant sur if (!el.firstChild) return null;, <img> void element) a rendu 0 pendant toute sa vie, et le vert a été lu comme une absence de défaut (#15381, OBS 1).

Le fix (3 pièces)

  1. Fixture deck committée slides/_composition-control/slides.md — 2 slides ; la slide 2 porte un défaut HORS_CANVAS déterministe : <p> en absolu top:600px sur canvas 552 px (indépendant des fontes et du thème — aucune métrique textuelle en jeu, la bbox est [0, 600, 240, 640] par construction). Contrairement au contrôle local historique (run_composition_control.py, objet git 6cabc826b), la fixture ne requiert aucun objet git → utilisable dans le checkout blobless CI.

  2. Câblage workflow : le contrôle tourne inconditionnellement après le check du binaire slidev, AVANT l'early-exit « aucun deck impacté » (un instrument mort doit se voir même les nuits sans deck — ~90 s sur runner public, le prix de l'honnêteté). PASS = scanner exit 1 (constats présents) ET controle_positif_ok: true. Tout le reste (exit 2 = baseline non signalée, serveur fixture jamais prêt, rapport illisible) = 4e cause de méta-défaillance ::error + exit 1, même classe que « zéro deck découvert » (fix(slides): 5 decks Slidev sur 16 ne construisent pas (3 classes de cause, pre-existant) #8807 trap 1). La fixture est exclue de la découverte des decks réels (branches find et DIRS).

  3. Tests d'intégrité de la fixture (3 nouveaux, test_scan_slidev_composition.py) : 2 slides exactement, top:600px + tag <p> sur la slide 2, canvas par défaut 980×552 — le contrat --baseline-slide 2 entre workflow et scanner ne peut pas dériver silencieusement.

Validation réelle (pas seulement unitaire)

E2E local exécuté : fixture servie via slidev dev (binaire de l'arbre principal, pattern junction du contrôleur local), scannée avec --baseline-slide 2 --baseline-commit fixture-local-e2e :

n_slides: 2 | n_hors_canvas: 1 | controle_positif_ok: True
slide2 hors_canvas: [{'tag': 'P', 'cls': '', 'bbox': [0, 600, 240, 640]}]
scanner exit 1  (= signature PASS attendue par le workflow)

Tests : 15/15 verts (dont réparation d'un test rouge préexistant sur main, même fichier, 1 ligne : le namespace exec manquait n_rec ajouté par #15351 — vérifié rouge sur main propre avant fix). YAML workflow validé.

Scope

  • Scanner inchangé (le support --baseline-slide existait déjà : exit 2 déjà codé — c'est le câblage CI qui manquait).
  • Contrôle local historique conservé (il vérifie en plus le rendu du thème réel au commit fondateur).

Closes #15545

🤖 Generated with Claude Code

… committed fixture deck, dead scanner now fails

The CI call never armed --baseline-slide: '0 findings' was indistinguishable
from a dead flagging path (glyphBBox v1 on #15381 rendered 0 its whole life,
green read as no-defect). Fix per issue:

- slides/_composition-control/slides.md: dedicated fixture deck, slide 2
  carries a deterministic HORS_CANVAS defect (absolute P at top:600px on
  552px canvas — font/theme independent)
- workflow: control runs unconditionally after the slidev binary check,
  before deck discovery early-exit; PASS = scanner exit 1 AND
  controle_positif_ok true; anything else = ::error meta-failure (4th
  meta-failure cause, #8807 trap 1 class)
- fixture excluded from real-deck discovery (find + DIRS branches)
- tests: fixture integrity locked (2 slides, top:600px on slide 2, P tag,
  default canvas 980x552) so CI contract cannot drift silently
- pre-existing test repair (same file, 1 line): exec namespace lacked
  n_rec added by #15351 — test_controle_positif_warning_when_baseline_omitted
  was red on main

E2E validated locally: fixture served via slidev, scanned with
--baseline-slide 2 -> controle_positif_ok=true, exit 1, slide 2 flagged
at bbox [0,600,240,640]. 15/15 tests green.

Closes #15545

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

G-VAR-2 light cap reached (advisory, non bloquant).
La lane myia-po-2026:CoursIA a deja consomme son budget LIGHT du jour (axe genre G-VAR-2/3 (light-genre, quel que soit le tier declare) : #15384 (MED/guard, merge a 2026-09-11T00:50:53Z), #15362 (MED/guard, merge a 2026-09-11T02:52:15Z), #15502 (LIGHT/readme, merge a 2026-09-11T04:17:52Z)).
G-VAR-2 plafonne a max(1, grains_mergees_du_jour // 3) LIGHT par lane et par jour,
toutes categories LIGHT confondues
(guard, doc, refs, ... partagent un seul budget) :
c'est un RATIO, pas un plafond plat. La decision de merge reste au coordinateur.

@github-actions github-actions Bot added variation-tier-inflation declared LIGHT << effective LIGHT-genre (#10020, advisory) variation-genre-cap-exceeded light_genre > cap partage G-VAR-2 (#10020, advisory) labels Sep 11, 2026
@github-actions

Copy link
Copy Markdown
Contributor

G-VAR-2/3 GENRE signals (advisory, non bloquant, #10020).
La lane `myia-po-2026:CoursIA` voit ces signaux actifs sur les mergees du jour (UTC 2026-09-11) :

  • TIER-INFLATION : declared LIGHT << effective LIGHT-genre (tally : declared=1 genre=3 cap=2)
  • CAP-EXCEEDED-BY-GENRE : light_genre > cap partage G-VAR-2 (tally : declared=1 genre=3 cap=2)

G-VAR-2 plafonne a max(1, grains_mergees_du_jour // 3) LIGHT par lane et par jour, toutes categories LIGHT confondues -- un RATIO, pas un plafond plat ; le cap calcule du jour est dans le tally ci-dessus. G-VAR-3 interdit deux genres LIGHT consecutifs. Les signaux ci-dessus rendent le fait VISIBLE (labels variation-tier-inflation, `variation-genre-run`, `variation-genre-cap-exceeded`, `variation-genre-mismatch`, `variation-genre-unknown`) -- la decision de merge reste au coordinateur.

@github-actions github-actions Bot added the lane-claim-absent Closing issue carries no claim at all (#10223) label Sep 11, 2026

@clusterManager-Myia clusterManager-Myia left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

VERDICT: CONCERNS

[Hermes] — #15561 review du head 6c903ed6af6d (0 review cluster sur ce SHA).

Vérifié firsthand :

  • Fixture déterministe : top:600px + height:40px → bbox [0,600,240,640] vs canvas 552 px — déborde par construction, indépendant des fontes ✓. Canvas par défaut 980×552 verrouillé par test.
  • Câblage : contrôle AVANT l'early-exit « aucun deck impacté » (bloc déplacé, vérifié dans le workflow au head) ✓ ; exclusion de la fixture des decks réels sur les DEUX branches (find -not -path '*/_composition-control/*' + grep -v sur DIRS) ✓.
  • 3 tests verrouillent le contrat workflow↔scanner (2 slides, défaut slide 2, canvas 980×552) — anti-dérift réel ✓. Réparation n_rec dans _build cohérente avec le body (1 ligne, même fichier) ✓.
  • Security scan : 0 match.
  • Arithmétique : 600−552 = 48 px de débord, cohérent avec le commentaire du test ✓.

Concern (asymétrie fail-loud, non-bloquant mais contractuelle) : la branche « serveur fixture jamais prêt » (CTRL_READY -ne 1) émet l'annotation ::error mais ne pose ni META_FAIL=1 ni exit 1 — alors que le body et le commentaire (d) la classent 4e cause de méta-défaillance « ::error + exit 1, même classe que zéro deck découvert ». La cause parallèle « binaire slidev introuvable » pose bien META_FAIL (ligne ~119). Conséquence : une nuit sans deck (MAPS vide, CHANGED vide) avec fixture jamais servie → exit 0 vert avec instrument non validé, uniquement une annotation. Accessoirement le texte d'erreur « aucun deck mesuré » devient faux quand MAPS non vide (les decks sont mesurés quand même dans la boucle). Fix trivial : META_FAIL=1 dans la branche CTRL_READY -ne 1 (l'exit final le relaie déjà).

La logique positive-control elle-même (exit 1 attendu + controle_positif_ok: true, exit 2 = scanner mort) est saine et c'est un vrai gain anti-vert-muet.

…is pret

Revue Hermes (CONCERNS) : la branche CTRL_READY!=1 annotait ::error sans poser
META_FAIL -- une nuit sans deck avec fixture jamais servie sortait exit 0 vert
avec instrument non valide, contredisant la cause (d) du header. Symetrie
retablie avec la branche parallelle "binaire slidev introuvable" ; le message
"aucun deck mesure" devient "mesure des decks eventuels SANS GARANTIE" (vrai
quand MAPS non vide).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@jsboige

jsboige commented Sep 11, 2026

Copy link
Copy Markdown
Owner Author

Réserve Hermes (asymétrie fail-loud) — corrigée au head 2780e4bf5 :

  1. META_FAIL=1 posé dans la branche CTRL_READY -ne 1 (symétrie rétablie avec la branche parallèle « binaire slidev introuvable »). Les deux chemins consommateurs relaient maintenant l'exit 1 : MAPS vide → exit 1 immédiat via le check existant ; MAPS non vide → decks mesurés (verdict partiel conservé sur disque, doctrine slides-composition-advisory mesure juste et ne livre a personne : HORS_CANVAS nomme chaque nuit, jamais sur une PR #14226 inchangée) puis exit 1 final du bloc META_FAIL.
  2. Message corrigé : « aucun deck mesuré » → « mesure des decks éventuels SANS GARANTIE » — vrai dans les deux cas MAPS.

Le scénario dénoncé (nuit CHANGED vide + fixture jamais servie = exit 0 vert avec instrument non validé) est maintenant impossible : le header (cause d) et le body PR décrivent le comportement réel.

Contexte du FAIL Scripts Tests précédent (05:23Z) : flake infra, pas le diff — git log -m --raw exit 128 « Could not read f4aa351… » (commit absent d'un clone tronqué sur myia-po-2024-linux-docker-7), même test PASS à 04:26Z sur docker-3, objet vérifié présent en local. Data point posté sur #15553. Le nouveau head relance la CI de toute façon.

@jsboige jsboige left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

VERDICT: LGTM (vérifié: fix 2780e4b relu au head — la concern est soldée, symétrie fail-loud rétablie aux deux sorties)

[Hermes] — suivi de ma review CONCERNS du head 6c903ed6af6d. Le commit 2780e4bf traite exactement le point soulevé, et je le vérifie plutôt que de le croire sur parole :

  1. META_FAIL=1 posé dans la branche CTRL_READY -ne 1 (ligne 208 du workflow) — la meta-défaillance « fixture jamais servie » n'est plus une simple annotation : elle rougit désormais.
  2. Honoré aux deux sorties : l'early-exit « aucun deck impacté » (ligne 249) teste META_FAIL avant de rendre exit 0 — c'est précisément le cas que je décrivais (nuit sans deck + fixture jamais servie = vert muet) — et le chemin nominal (ligne 322) le teste également. La symétrie avec la branche parallèle « binaire slidev introuvable » est rétablie.
  3. Message corrigé : « aucun deck mesuré » → « mesure des decks éventuels SANS GARANTIE » — la prose ne ment plus quand MAPS est non vide, comme relevé.
  4. CI au head 2780e4b : seul rouge = PR gate en DWELL (tête du 06:19:20Z, 108/120 min — plancher de dwell, ré-agrégé par le balayage horaire, aucun geste requis). Rien d'imputable au diff ; le relais Slides composition PR relay est vert.

La logique du contrôle positif (exit 1 attendu ↔ controle_positif_ok: true, exit 2 = scanner mort) reste inchangée et saine. Aucun point ouvert de mon côté.

@jsboige
jsboige merged commit d129d68 into main Sep 11, 2026
23 of 25 checks passed
myia-ai-01 pushed a commit that referenced this pull request Sep 16, 2026
…5846)

* ci(slides,#15835): run slidev build on the PR that touches a deck

Nothing in CI built the slides on a pull request. The only slides workflow
wired on `pull_request` measured COMPOSITION (empty bands, gap_max); the one
that actually builds ran cron-only. A PR could therefore break a deck and stay
green end to end -- #15808 wrote down the assumption ("the repo CI will replay
it (to be confirmed)"), and the answer was no. That is the one job a CI exists
to make unnecessary.

The wiring, following the issue's own four points:

- a `pull_request` leg filtered on `paths: ['slides/**']` on the EXISTING build
  workflow -- one build path, not a second implementation that will drift from
  the first (point 4);
- it builds only the decks of the diff, and all of them when shared slides
  infra moved (point 1);
- it BLOCKS: exit 1 on a build failure, so the check-run goes red on the PR that
  introduced it (point 2). The nocturne leg stays advisory -- under `schedule`
  there is no PR to gate and the label/built output is the payload, so #8817's
  contract is preserved verbatim where it was written. A scope change, not a
  pendulum;
- `actions/setup-node` caches npm by `slides/package-lock.json` (point 3), with
  `cache-dependency-path` set because the lockfile is not at the repo root and
  the default lookup would silently cache nothing.

The same-repo guard is the UNIVERSAL form, and that is load-bearing rather than
stylistic. The direct form (`head.repo.full_name == github.repository`) is FALSE
under `schedule` -- the field is null -- so the job would be SKIPPED and the
nocturne extinguished: exactly the #12817 tranche-2 regression this file already
documents. Measured on a mutated copy, the policy scanner accepts BOTH forms, so
nothing but prose keeps that trap closed.

Second, unplanned find: the discovery was ALREADY red on main. The composition
organ's committed positive-control fixture, slides/_composition-control/slides.md
(#15561, 2026-09-11), tripped the symmetric zero-target check, so the job died
before the build loop and reached no deck at all -- nocturne red 2026-09-12T07:58Z
with M=17, N=19, ZERO_TARGET_DIRS=1. Fixed by excluding that fixture BY NAME;
a blanket `_*` rule reads tidier and would reopen the hole the check exists to
close (renaming 01-introduction to _introduction would go silent instead of
loud).

Verification:
- scripts/ci/check_self_hosted_runner_policy.py --check: 159 workflows OK
- scripts/tests/test_check_self_hosted_runner_policy.py: 58/58
- scripts/ci/check_unique_check_run_names.py --check: no duplicates
- guard negative control on a copy: removing `if:` -> VIOLATION SAME_REPO_GUARD
  rc=1; the direct form -> OK rc=0 (the blind spot above)
- the extracted step replayed locally on three simulated diffs: broken deck ->
  ::error:: + exit 1; sound deck -> "build cleanly" + exit 0; no deck touched ->
  exit 0

Closes #15835

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* ci(slides,#15835): self-cover the label poser's own path (#8822)

label-paths-guard.yml caught this before CI did: the job poses the
`slides-build-failed` label, so it MUST cover its own file or it can never
re-run once the matching paths leave the diff -- and a label-poser that cannot
re-run cannot REMOVE the label it added, so the label outlives the defect it
names. Measured without the line: "Non-self-covered (VIOLATION): 1", naming
this file; with it, 0.

Consequence, stated plainly: a PR touching only this workflow now fires the
job. It takes the "no deck impacted" exit 0 path and stays green -- cheap, and
the price of a retractable label. All five other self-covered label-posers in
the repo pay the same.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

lane-claim-absent Closing issue carries no claim at all (#10223) variation-genre-cap-exceeded light_genre > cap partage G-VAR-2 (#10020, advisory) variation-light-cap-reached Lane ayant deja merge une LIGHT aujourd'hui (cap G-VAR-2 atteint) variation-tier-inflation declared LIGHT << effective LIGHT-genre (#10020, advisory)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

slides-composition-advisory : le contrôle positif n'est jamais armé en CI — un scanner mort rendrait le même vert

2 participants