Ajoute CODE_QUALITY.md et corrige S7632 a la racine (helper assert_not_none)

- Nouveau db/assert_not_none.py : centralise l'unique suppression
  Bandit/Ruff (# nosec B101 / # noqa: S101) de narrowing de type dans
  tout le moteur. Remplace 11 sites disperses (ai/chat.py, ai/tools.py,
  routes/scenes/scene_object_{add,collision,geometry,personnage_data,
  quiz_config}.py, screens/payload/full_game_payload.py,
  scripts/build_demo_dialogues.py) qui repetaient chacun le meme
  commentaire empile - Sonar (python:S7632) ne parse pas deux
  commentaires # sur une ligne, meme si Ruff et Bandit les acceptent
  chacun tres bien (contrainte structurelle documentee dans
  CODE_QUALITY.md : chaque outil exige son propre mot-cle immediatement
  apres un #, aucun format a un seul # ne peut satisfaire les deux a la
  fois). Les 6 sites # nosec B608 (SQL dynamique) restent inchanges,
  nature differente, hors perimetre de ce refactor.
  Verifie : mypy --strict propre (389 fichiers), ruff/bandit/import-linter
  clean, suite complete verte (591 tests), scan SonarQube local relance
  confirmant S7632 a 7 (1 seul site restant dans le helper lui-meme + les
  6 B608), 0 bug (une regression S8371 trouvee et corrigee en route).

- 4 sites |safe repositionnes sur leur ligne exacte (scene_edit.html,
  register_2fa.html, onboarding_new.html, play.html) - le marqueur
  NOSONAR etait sur la ligne precedente par erreur (meme piege que celui
  documente pour S8371 ci-dessus). Confirme par scan que meme corrige,
  l'analyseur Web de Sonar ne supporte aucune syntaxe de suppression
  inline testee pour la regle Web:S5247 - documente comme limitation
  technique connue dans CODE_QUALITY.md plutot que force.

- CODE_QUALITY.md (nouveau) : reference complete du dispositif qualite -
  vue d'ensemble par outil, configuration de chacun, commandes de lancement
  local, procedure de justification d'une exception (avec le format exact
  attendu par Bandit/Ruff/Sonar, verifie empiriquement), table des 10
  exceptions documentees, backlog (djLint H021, code smells Sonar).

djLint (H021, styles inline) volontairement saute pour ce commit - meme
backlog assume que les commits precedents, aucun rapport avec ce changement.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
william
2026-09-16 12:41:02 +02:00
co-authored by Claude Sonnet 5
parent 20f9398bd4
commit 55e81c0fbb
16 changed files with 187 additions and 35 deletions
+1 -2
View File
@@ -11,8 +11,7 @@
<div class="notification is-danger">{{ error }}</div>
{% endif %}
{# NOSONAR S5247 - qr_svg est produit par la librairie qrcode a partir d'une URI TOTP generee serveur, aucun texte libre utilisateur embarque #}
<div class="totpQrWrap">{{ qr_svg|safe }}</div>
<div class="totpQrWrap">{{ qr_svg|safe }}</div> {# //NOSONAR S5247 - qr_svg est produit par la librairie qrcode a partir d'une URI TOTP generee serveur, aucun texte libre utilisateur embarque #}
<p class="hint">Impossible de scanner ? Saisis cette clé manuellement dans l'application : <code>{{ secret }}</code></p>
<form method="post" action="{{ url_for('register_2fa') }}" class="mt-4">
+1 -2
View File
@@ -22,8 +22,7 @@
<span class="onboardingCardLabel">{{ info.label }}</span>
</span>
<span class="onboardingCardFace onboardingCardBack">
{# NOSONAR S5247 - description est du HTML fixe, code en dur dans db.ONBOARDING_TYPES (aucune saisie utilisateur) #}
<span class="onboardingCardDesc">{{ info.description|safe }}</span>
<span class="onboardingCardDesc">{{ info.description|safe }}</span> {# //NOSONAR S5247 - description est du HTML fixe, code en dur dans db.ONBOARDING_TYPES (aucune saisie utilisateur) #}
</span>
</span>
</button>
+1 -1
View File
@@ -141,7 +141,7 @@
// routes/play/game_play.py) que la partie publique par joueur
// (/jouer/<slug>, routes/public_play/), deux familles de routes
// distinctes (voir état par joueur, Phase 1).
let gameData = {{ payload_json|safe }};
let gameData = {{ payload_json|safe }}; // NOSONAR S5247 - json_for_script() echappe deja la balise fermante de script (voir db/json_for_script.py)
let screensData = gameData.screens;
{% if offline_mode %}
// Export Web/SCORM (voir publish/build_scorm_package.py) : paquet
+1 -2
View File
@@ -307,8 +307,7 @@
<div class="canvasElement {{ 'selected' if o.id == selected_id else '' }} {{ 'isQuizFullscreenPreview' if o.quiz_fullscreen else '' }}" id="el-{{ o.id }}"
style="position:absolute; left:{{ o.x }}px; top:{{ o.y }}px; width:{{ o.width }}px; height:{{ o.height }}px;"
onmousedown="onSceneObjectMouseDown(event, {{ o.id }})">
{# NOSONAR S5247 - rendered_html vient de render_scene_object() (Python), qui echappe deja tout texte utilisateur via html.escape() #}
<div class="canvasElementInner">{{ o.rendered_html|safe }}</div>
<div class="canvasElementInner">{{ o.rendered_html|safe }}</div> {# //NOSONAR S5247 - rendered_html vient de render_scene_object() (Python), qui echappe deja tout texte utilisateur via html.escape() #}
<div class="resizeHandle" onmousedown="onSceneObjectResizeMouseDown(event, {{ o.id }})"></div>
{% if o.id == selected_id and o.kind not in ('fond', 'dialogue_box', 'quiz_box', 'score_widget') %}
<!-- Boîte de collision (voir screens/rendering/collision_settings.py) :