Ctrl K
ComunidadGuías…

AUDIT-2026-09-07.md

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
# Audit du 7 septembre 2026 — corrections et suite

État : sources basées sur `375f0d2` (0.12.8), corrections regroupées pour 0.12.9.
Les quatre bugs de la première passe et les trois bugs supplémentaires
décrits dans les deux premières sections sont **corrigés localement**.
Une nouvelle passe, détaillée en fin de document, a reproduit **cinq autres
bugs, également corrigés localement sur demande**. Cet audit n'est pas une preuve d'absence de bugs dans
le reste du dépôt.

## Quatre corrections vérifiées

1. **Source partiellement indisponible** (`merge_daily._executer`) : une
   journalière ne remplace plus sa version complète par les seuls clips encore
   présents. Le jour et son état sont conservés ; le passage retourne 1 et
   explique le report. Si une journée sélectionnée est concernée, ses agrégats
   hebdomadaires/mensuels ne sont pas reconstruits à partir d'un jeu incomplet.
   Ce report des agrégats est conservateur et global au passage. Une exclusion
   volontaire reste différente : elle autorise la reconstruction.
2. **Dates d'exclusion** (`blink_registre._exclusion_a_retenir`) : deux copies
   portant le même booléen ne font plus reculer la date de décision. Une
   ancienne sauvegarde ne peut donc plus rendre prioritaire une réintégration
   périmée, ni une exclusion périmée dans le cas inverse.
3. **Réglages des agrégats après sélection** : écarter/réintégrer un clip
   respecte désormais `merge_semaine` et `merge_mois` indépendamment.
4. **Fuseau des fusions manuelles** : Actualiser transmet le fuseau configuré.
   Le traitement d'une sélection calcule aussi son filtre de jour dans ce
   même fuseau. `runtime.options_fusion()` centralise les options partagées
   par ces deux chemins et la boucle automatique.

Tests ajoutés : `test_blink_registre_exclusion_date.py`,
`test_merge_daily_source_partielle.py`, cas supplémentaires dans
`test_download_progression.py` et `test_serve_appliquer_selection_reglages.py`.
Les reproductions originales `build-win7/audit_20260907.py` montrent maintenant
la journalière intacte avec code 1, la dernière exclusion conservée, le fuseau
New York effectif et les deux agrégats désactivés.

Validation finale, sept corrections incluses : **526 tests réussis sous
CPython 3.12 et CPython 3.8.10** (exécutés sur l'hôte, pas dans la VM Win7).
`python tests.py` (intégration vidéo/interface/processus) et `docs.py --check`
réussissent également.
Tests locaux uniquement, avec données temporaires ; aucun réveil de caméra,
aucune suppression distante, aucun arrêt d'une instance utilisateur. Des
ResourceWarning préexistants restent émis par certains tests (boucle asyncio,
icône tray), sans échec. Le TLS du relais Blink n'a pas été modifié.

## Trois bugs supplémentaires reproduits puis corrigés

### P1 — La mise à jour continue après un arrêt échoué

`maj.finaliser()` ignore le code de sortie de `stop`. Après vingt vérifications
où l'ancienne instance est toujours vivante, la fonction poursuit néanmoins
la permutation et la relance. Selon les protections de fichiers de l'OS, cela
peut mener à un mélange d'anciens processus et de nouveaux fichiers, ou à une
relance concurrente, au lieu de refuser proprement l'installation.

Reproduction : `build-win7/audit-root-update-stop.py`. Arrêt simulé avec code 1,
instance toujours vivante ; `_permuter` et `_relancer` sont tout de même appelés,
et `finaliser` retourne 0 si la permutation réussit. Tous les processus et
remplacements sont simulés : aucune installation réelle touchée. Correction
appliquée : code d'arrêt non nul ou instance encore active après le délai
interdisent désormais permutation et relance. Les trois cas (échec, délai
épuisé, arrêt confirmé après attente) sont couverts par
`test_maj_finaliser_arret.py`.

### P2 — Une sélection sur un homonyme reconstruit la mauvaise caméra

`serve.Handler.do_POST`, branche `/api/appliquer-selection`, transmet le nom
brut de l'entrée à `merge --camera`. Or `merge_daily` distingue les caméras de
même nom sur deux réseaux par des clés comme `jardin` et `jardin (2)`.

Reproduction : deux entrées `jardin`, réseaux 111 et 222 ; exclusion du clip
du réseau 222. L'exclusion vise le bon fichier, mais la commande de
reconstruction contient `--camera jardin` au lieu de `jardin (2)`. L'ancienne
journalière du second appareil peut ainsi conserver le clip exclu jusqu'au
prochain passage global. Reproduction isolée dans
`build-win7/audit-followup-20260907.py`.

Correction : la sélection utilise les mêmes clés de collision que la fusion.
Le test supplémentaire dans `test_serve_appliquer_selection_reglages.py`
vérifie l'exclusion puis la réintégration du second homonyme, sans modifier
le statut du premier.

### P2 — PMT tronquée : exception non contrôlée du démultiplexeur

`blink_ts_demux.DemuxeurTSVideo._pmt()` accède aux octets 10 et 11 alors que
`_section()` n'exige que trois octets. Un paquet TS complet de 188 octets,
avec champ d'adaptation et section PMT tronquée à trois octets, déclenche
`IndexError`. Dans la piste WebRTC, cette exception interrompt la lecture du
flux au lieu d'ignorer la table invalide et d'attendre la suivante.

Même diagnostic `build-win7/audit-followup-20260907.py`. Résultat obtenu sur
un flux synthétique malformé, **pas observé sur les caméras réelles**.

Correction : validation du type de table, de sa longueur complète et des
limites des descripteurs avant de retenir un PID vidéo. Les tests de
`test_blink_ts_demux.py` couvrent toutes les troncatures d'une PMT minimale
avec H.264, des longueurs invalides et la reprise jusqu'à l'émission d'une
NAL après réception de la table valide suivante.

Robustesse liée : le tampon élémentaire n'est pas plafonné tant qu'aucun
séparateur H.264 n'est reçu. Sur 1 880 000 octets TS synthétiques invalides,
1 840 000 octets restent retenus. C'est une croissance reproduite, pas une
preuve d'épuisement mémoire en exploitation ; le volume réel dépend aussi
de la durée maximale de session et des données reçues.

## Périmètre de cette continuation

Revue ciblée du registre, des fusions/jours/périodes, des appels manuels
serveur, des options de runtime, du démultiplexage WebRTC et du remplacement
de version ; lectures complémentaires des boucles de téléchargement et de
surveillance. Les douze corrections sont regroupées dans la version 0.12.9.
Le risque de croissance du tampon sur flux sans séparateur
reste une piste de durcissement distincte, non corrigée par cette passe.

## Nouvelle passe demandée — cinq bugs corrigés après reproduction

L'audit initial était sans modification applicative. Reproductions réunies dans
`build-win7/audit-additional-20260907.py` (diagnostic local, dossier ignoré
par Git), exécutées avec succès sous Python 3.12 et 3.8.10 sur l'hôte.
Ce ne sont pas des essais dans la VM Win7 ni sur les caméras. Les appels
réseau, remplacements et démarrages/arrêts réels sont neutralisés ; les
fichiers d'état sont exclusivement temporaires. Les scénarios et sorties
ci-dessous décrivent les bugs avant correction ; les tests de non-régression
ajoutés ensuite vérifient le comportement corrigé.

### P1 — Mise à jour : les processus deviennent invisibles avec un stockage redirigé

Sources : `maj.py:904`, `runtime.py:689`.

Une installation sans `BLINK_HOME`, dont le stockage est redirigé par
`blink_home.txt`, garde ses fiches `.blink_run` dans le dossier du programme.
Mais `maj.installer()` injecte `BLINK_HOME=<dossier des données>` dans
l'environnement du finaliseur. `_dossier_controle()` respecte aussi cette
variable : finaliseur et commande `stop` cherchent alors les fiches dans les
données au lieu de l'installation.

Reproduction avec les vraies fonctions `installer()`, `finaliser()` et
`_arreter_instances()`, leurs opérations externes simulées : une instance
visible avant la préparation, zéro dans l'environnement réellement généré
pour le finaliseur ; `stop` conclut que rien ne tourne, `finaliser` demande
la permutation et retourne 0. L'ancienne fiche reste présente et son
processus est toujours simulé vivant.

C'est un chemin distinct de l'arrêt échoué corrigé plus haut : cette fois
le code d'arrêt vaut 0 parce que la recherche porte sur la mauvaise racine.
Les nouveaux contrôles ne suffisent donc pas dans ce cas. Selon l'OS,
remplacement de fichiers encore utilisés ou échec sur fichiers verrouillés,
puis risque de relance concurrente. Concerne le chemin de mise à jour des
bundles standard ; la mise à jour automatique de l'édition Win7 est désactivée.

Correction : `BLINK_CONTROL_HOME` transmet séparément la racine de contrôle.
Un `BLINK_HOME` explicitement fourni par l'utilisateur reste respecté ; celui
ajouté temporairement par la mise à jour est retiré à la relance pour suivre
à nouveau le pointeur de stockage. L'ancienne commande `stop`, qui ignore
la nouvelle variable, reçoit le contrôle via son `BLINK_HOME`.
Pour la première mise à jour depuis un ancien lanceur, le finaliseur retrouve
les fiches à partir de l'installation et du dossier transmis ; il refuse
proprement une ambiguïté entre deux racines, sans arrêter ni remplacer.
L'attente tient aussi compte des enfants/travailleurs survivant au superviseur.

### P1 — Deux changements de suppression automatique peuvent annuler une désactivation

Source : `serve.py:3671` (lecture), `serve.py:3676` (écriture).

La route `/api/suppression-auto` fait un lecture-modification-écriture sans
verrou. L'écriture atomique évite un JSON tronqué, pas la perte d'une
modification concurrente. L'interface ne désactive que la case manipulée :
les cases de deux caméras peuvent envoyer des requêtes simultanées.

Reproduction avec deux threads exécutant les vrais handlers : Salon est
initialement actif. Désactiver Salon et activer Jardin lisent tous deux cet
état. La première requête écrit l'ensemble vide ; la seconde réécrit Salon
et Jardin. Les deux répondent HTTP 200 avec `ok: true`, alors que Salon est
réactivé à tort. Cas confirmé sur 20/20 répétitions supplémentaires avec
ordonnancement imposé ; cela démontre la fenêtre de concurrence, pas sa
fréquence en utilisation réelle.

Conséquence : les prochaines copies réussies peuvent de nouveau déclencher
la suppression à la source malgré la désactivation demandée. Aucune
suppression distante n'a été effectuée pendant le diagnostic.

Correction : toute la transaction prend le verrou interprocessus de
configuration, également employé lors du changement de stockage. Un délai
épuisé répond 409 ; une erreur disque répond 500, sans faux succès.

### P1 — Changer de stockage peut rétablir une ancienne autorisation de suppression

Source : `runtime.py:608`.

Le transfert de configuration ne copie que les réglages généraux et la
session. `blink_suppression_auto.json` reste sur place. Passer d'un dossier
A à un dossier B neuf perd les choix ; désactiver explicitement la
suppression dans B puis revenir à A réactive les anciens choix de A.
Le même défaut de transfert remet aussi la langue à sa valeur par défaut
dans B, mais l'effet sur la suppression est le risque important.

Reproduction avec les fonctions réelles d'écriture/lecture et de changement
de stockage : suppression active dans A, vide dans B, désactivée explicitement
dans B, puis de nouveau active au retour à A. La clé employée est celle
réellement calculée par `camera_setting_key_from_entry()`.

Correction : langue et suppression automatique sont copiées avant de publier
le pointeur. Une préférence absente impose aussi son défaut (français,
aucune suppression), au lieu d'adopter d'anciens choix présents à destination.
Un échec de copie laisse la racine active et le pointeur inchangés.

### P2 — La mise à jour ne restaure que la première instance

Source : `maj.py:759`.

`finaliser()` mémorise seulement `fiches[0].verbes`, alors que `stop` arrête
toutes les instances. Le logiciel permet pourtant plusieurs lancements
séparés, par exemple une interface et une boucle de téléchargement.

Reproduction : deux fiches, `serve --port 8765` et `download --loop 1`,
arrêt réussi et plus aucun survivant. Le finaliseur retourne 0 mais la seule
composition transmise à `_relancer` contient `serve --port 8765`. La boucle
de téléchargement n'est pas rétablie.

Correction : toutes les compositions distinctes sont mémorisées et relancées,
aussi en cas d'échec de permutation avec retour à l'ancienne version. Les
doublons exacts ne sont pas relancés deux fois ; sans fiche, le repli `start`
est conservé.

### P3 — Le nettoyage de progression peut effacer une publication toute neuve

Source : `runtime.py:1869`.

`_etats_travail()` décide de supprimer une fiche d'après son contenu lu,
puis fait `unlink` sans synchronisation avec le worker qui écrit au même
chemin. Un état terminé expiré peut être remplacé entre ces deux étapes
par la progression du travail suivant du même processus persistant.

Reproduction : lecture d'une ancienne fin expirée, publication réelle de
`nouveau, 0/100` pendant cette fenêtre, puis nettoyage. La fiche neuve a
disparu et `travail_en_cours()` renvoie `{}`. L'interface peut perdre la barre
et croire momentanément le traitement terminé, jusqu'au prochain tick.
Ce cas ne démontre ni perte de vidéo ni lancement effectif d'une fusion.

Correction : publication et purge partagent un verrou OS léger. La purge
relit la fiche sous verrou et vérifie qu'elle correspond encore au contenu
qu'elle voulait retirer. Elle n'attend pas si le verrou est pris ; une
publication attend au maximum 250 ms. Les erreurs restent non bloquantes
pour le téléchargement. Le verrou est libéré par l'OS si son détenteur meurt.

### Tests de cette correction

Ajouts : `test_maj_instances_stockage.py`,
`test_suppression_auto_transactions.py`,
`test_runtime_progression_concurrence.py`, cas de processus enfants dans
`test_maj_finaliser_arret.py`. Le test historique du pointeur dans
`test_blink2video_audit.py` neutralise désormais le `BLINK_HOME` de sa fixture
pour bien isoler le cas annoncé, distinct du stockage forcé testé séparément.

La concurrence de progression est exercée avec des threads et un processus
de test Windows, tous sur un dossier temporaire ; la branche POSIX est
simulée, sans prétendre à un essai Linux. Aucun accès aux caméras ni arrêt
d'instance utilisateur, TLS Blink inchangé.

Validation finale : **553 tests réussis sous CPython 3.12 et CPython 3.8.10**,
sur l'hôte Windows. `python tests.py` (intégration vidéo/interface/processus),
`python docs.py --check` et `git diff --check` réussissent également. Les
ResourceWarning préexistants de la suite n'empêchent pas ces validations.

### Point ancien confirmé, non compté comme nouveau

Le générateur d'autostart macOS émet un plist XML invalide si un chemin
contient `&` (reproduction : `plistlib.loads()` lève `ExpatError`). Ce manque
d'échappement était déjà consigné dans `AUDIT-2026-08-13.md`, section 28.59 ;
il n'est donc pas inclus dans les cinq nouveaux bugs. Pas de test launchd
réel sur macOS pendant cette passe.