Sujets : le relevé de /opt/studio/sujets.json passe dans la base du service d'état (t_ae236aa9) #1694

Merged
hermes-fusion merged 1 commit from hermes/migration-sujets-sqlite into main 2026-10-15 01:50:03 +02:00
Collaborator

Carte t_ae236aa9 (sujets.json → SQLite). Branche hermes/migration-sujets-sqlite, commit ffb882a6, rebasée sur main ae760c38.

Cause. /opt/studio/sujets.json — les vidéos des chaînes voisines qui font plusieurs fois la médiane de la leur, ce qui remplace vidIQ ou TubeBuddy (demande de l'opérateur, 2026-09-25) — n'avait qu'un foyer : un fichier sur le LXC 132, réécrit d'un bloc par la collecte (_tableau/sujets.py, appelée par tableau-bord.py). Ni requêtable, ni relu ailleurs, et une donnée que l'Agenda lit.

Remède. Cinq tables dans etat_partage.sql (sujets_releve, sujets_ids, sujets_chaines, sujets_videos — une ligne par vidéo, rang = le rang affiché —, sujets_absentes), sujets_lire/detail/remplacer dans etat_partage.py, trois routes dans _infra/etat-service.py, basculer sujets [--essai] / revenir sujets dans etat_client.py, et l'écriture par socle.sujets_ecrire (fichier d'abord, base ensuite). Détail complet dans le message du commit.

API. Trois routes NOUVELLES (GET /sujets, GET /sujets/detail, POST /sujets/remplacer) ; aucun champ d'API existant ne change, et le front Svelte ne consomme pas ces routes. Rien à faire côté svelte.

Ce qui est mesuré

La bascule, son aller-retour et son refus, sur le relevé vivant du 132 (102 vidéos, 16 chaînes, relevé du 2026-10-14T07:18), lu par ssh et comparé à une base jetable :

$ ssh -i ~/.ssh/id_132 studio@192.168.1.86 'cat /opt/studio/sujets.json' > /var/tmp/sujets-132.json
$ ETAT_SUJETS=/var/tmp/sujets-132.json python3 etat_client.py basculer sujets --essai
comparaison /var/tmp/sujets-132.json : 102 vidéos, 16 chaînes, 16 identifiants, 0 introuvables (2026-10-14T07:18), IDENTIQUES dans une base jetable
code=0

Les bancs, ajoutés et touchés :

$ python3 _tableau/test_sujets_bascule.py
OK : le relevé des sujets passe dans la base et en revient (fichier d'abord, bascule, copie, retour arrière), la forme est refusée avant d'entrer, et aucun banc ne parle au service de production

$ python3 _infra/test_etat_service.py
OK : le service d'état répond sur chaque route, refuse en 400/403/404, dit ses pannes en 500, et pose son schéma avant d'écouter

$ python3 controle/test_etat_partage.py
(code 0)

Le banc neuf couvre : une base d'avant ce nom de bascule refaite sans perte (le CHECK de bascules restauré), le refus d'un relevé sans videos ni avec un champ inconnu (rien n'entre, la bascule n'est pas posée), l'essai sur le relevé réel, la bascule (comptes par table, .bak-sqlite-<date>, copie réécrite depuis la base), le service muet (« fichier-muet », et le fichier quand même écrit), le service injoignable avant l'écriture (« fichier », chemin d'avant la bascule), le retour arrière puis la re-bascule.

Suite complète, python3 _tableau/lancer-tests.py --tout --sans-couverture : 169 bancs verts, 1 rouge — _tableau/test_accueil_parite.py, qui est déjà rouge sur un arbre neuf de origin/main (vérifié sur ae760c38, même assertion : « le JSON doré n'est plus la sortie de Python », clés monetisation, seuil_heures_apres, fin_seuil, courbes). Hors périmètre de cette carte ; à prendre telle quelle.

Ce que je n'ai pas pu mesurer. La bascule elle-même (sans --essai) n'a pas été posée : le service d'état n'est pas joignable aujourd'hui — 192.168.1.142 ne répond pas (ping 100 % de pertes, curl sort en 7), le LXC 130 étant éteint par décision de l'opérateur. Le fichier fait donc foi comme avant, et la collecte du 132 continue d'écrire le fichier seul (socle.sujets_ecrire rend « fichier »). Le code est prêt et éprouvé sur une base jetable : dès que le service répond, basculer sujets puis la double écriture prennent le relais sans autre changement. Commande à jouer alors :

ssh -i ~/.ssh/id_132 studio@192.168.1.86 'cat /opt/studio/sujets.json' > /tmp/sujets-132.json
ETAT_SUJETS=/tmp/sujets-132.json python3 etat_client.py basculer sujets --essai   # puis sans --essai

Point de collision à signaler. etat_partage.sql, etat_partage.py, _infra/etat-service.py, etat_client.py et la ligne etat_client.py d'OUTILS.md sont touchés par chacune des migrations en cours (hermes/migration-atelier-sqlite, -historique-sqlite, -instagram-sqlite, -reseaux-sqlite, -choix-vignettes-sqlite, toutes non fusionnées). J'ai repris le même motif de bascules.nom que la branche historique (motif GLOB + _reparer_bascules) pour que la fusion de l'une ou de l'autre ne laisse qu'une copie : la résolution de conflit attendue est de garder une seule ligne de CHECK et un seul _reparer_bascules.

Carte `t_ae236aa9` (sujets.json → SQLite). Branche `hermes/migration-sujets-sqlite`, commit `ffb882a6`, rebasée sur `main` `ae760c38`. **Cause.** `/opt/studio/sujets.json` — les vidéos des chaînes voisines qui font plusieurs fois la médiane de la leur, ce qui remplace vidIQ ou TubeBuddy (demande de l'opérateur, 2026-09-25) — n'avait qu'un foyer : un fichier sur le LXC 132, réécrit d'un bloc par la collecte (`_tableau/sujets.py`, appelée par `tableau-bord.py`). Ni requêtable, ni relu ailleurs, et une donnée que l'Agenda lit. **Remède.** Cinq tables dans `etat_partage.sql` (`sujets_releve`, `sujets_ids`, `sujets_chaines`, `sujets_videos` — une ligne par vidéo, `rang` = le rang affiché —, `sujets_absentes`), `sujets_lire/detail/remplacer` dans `etat_partage.py`, trois routes dans `_infra/etat-service.py`, `basculer sujets [--essai]` / `revenir sujets` dans `etat_client.py`, et l'écriture par `socle.sujets_ecrire` (fichier d'abord, base ensuite). Détail complet dans le message du commit. **API.** Trois routes NOUVELLES (`GET /sujets`, `GET /sujets/detail`, `POST /sujets/remplacer`) ; aucun champ d'API existant ne change, et le front Svelte ne consomme pas ces routes. Rien à faire côté `svelte`. **Ce qui est mesuré** La bascule, son aller-retour et son refus, sur le relevé **vivant** du 132 (102 vidéos, 16 chaînes, relevé du 2026-10-14T07:18), lu par ssh et comparé à une base jetable : ``` $ ssh -i ~/.ssh/id_132 studio@192.168.1.86 'cat /opt/studio/sujets.json' > /var/tmp/sujets-132.json $ ETAT_SUJETS=/var/tmp/sujets-132.json python3 etat_client.py basculer sujets --essai comparaison /var/tmp/sujets-132.json : 102 vidéos, 16 chaînes, 16 identifiants, 0 introuvables (2026-10-14T07:18), IDENTIQUES dans une base jetable code=0 ``` Les bancs, ajoutés et touchés : ``` $ python3 _tableau/test_sujets_bascule.py OK : le relevé des sujets passe dans la base et en revient (fichier d'abord, bascule, copie, retour arrière), la forme est refusée avant d'entrer, et aucun banc ne parle au service de production $ python3 _infra/test_etat_service.py OK : le service d'état répond sur chaque route, refuse en 400/403/404, dit ses pannes en 500, et pose son schéma avant d'écouter $ python3 controle/test_etat_partage.py (code 0) ``` Le banc neuf couvre : une base d'avant ce nom de bascule refaite sans perte (le CHECK de `bascules` restauré), le refus d'un relevé sans `videos` ni avec un champ inconnu (rien n'entre, la bascule n'est pas posée), l'essai sur le relevé réel, la bascule (comptes par table, `.bak-sqlite-<date>`, copie réécrite depuis la base), le service muet (« fichier-muet », et le fichier quand même écrit), le service injoignable avant l'écriture (« fichier », chemin d'avant la bascule), le retour arrière puis la re-bascule. Suite complète, `python3 _tableau/lancer-tests.py --tout --sans-couverture` : **169 bancs verts, 1 rouge** — `_tableau/test_accueil_parite.py`, qui est **déjà rouge sur un arbre neuf de `origin/main`** (vérifié sur `ae760c38`, même assertion : « le JSON doré n'est plus la sortie de Python », clés `monetisation`, `seuil_heures_apres`, `fin_seuil`, `courbes`). Hors périmètre de cette carte ; à prendre telle quelle. **Ce que je n'ai pas pu mesurer.** La bascule elle-même (sans `--essai`) n'a **pas** été posée : le service d'état n'est pas joignable aujourd'hui — `192.168.1.142` ne répond pas (ping 100 % de pertes, `curl` sort en 7), le LXC 130 étant éteint par décision de l'opérateur. Le fichier fait donc foi comme avant, et la collecte du 132 continue d'écrire le fichier seul (`socle.sujets_ecrire` rend « fichier »). Le code est prêt et éprouvé sur une base jetable : dès que le service répond, `basculer sujets` puis la double écriture prennent le relais sans autre changement. Commande à jouer alors : ``` ssh -i ~/.ssh/id_132 studio@192.168.1.86 'cat /opt/studio/sujets.json' > /tmp/sujets-132.json ETAT_SUJETS=/tmp/sujets-132.json python3 etat_client.py basculer sujets --essai # puis sans --essai ``` **Point de collision à signaler.** `etat_partage.sql`, `etat_partage.py`, `_infra/etat-service.py`, `etat_client.py` et la ligne `etat_client.py` d'`OUTILS.md` sont touchés par **chacune des migrations en cours** (`hermes/migration-atelier-sqlite`, `-historique-sqlite`, `-instagram-sqlite`, `-reseaux-sqlite`, `-choix-vignettes-sqlite`, toutes non fusionnées). J'ai repris le même motif de `bascules.nom` que la branche historique (motif GLOB + `_reparer_bascules`) pour que la fusion de l'une ou de l'autre ne laisse qu'une copie : la résolution de conflit attendue est de garder une seule ligne de CHECK et un seul `_reparer_bascules`.
Sujets : le relevé de sujets.json passe dans la base du service d'état
All checks were successful
Tests du tableau (Rust + Svelte) / Rust (_tableau/rust) (push) Successful in 20s
Tests du tableau (Rust + Svelte) / Front Svelte (_tableau/front) (push) Successful in 12s
Tests du tableau (Rust + Svelte) / Rust (_tableau/rust) (pull_request) Successful in 19s
Tests du tableau (Rust + Svelte) / Front Svelte (_tableau/front) (pull_request) Successful in 11s
ffb882a6d2
Cause : `/opt/studio/sujets.json` (les vidéos des chaînes voisines qui font plusieurs fois la médiane de la
leur — ce qui remplace un abonnement à vidIQ ou TubeBuddy) n'avait qu'un foyer : un fichier sur le LXC 132,
réécrit d'un bloc par la collecte (`_tableau/sujets.py`, appelé par `tableau-bord.py`). Ni requêtable, ni
relu ailleurs, et une donnée que l'Agenda lit. C'est la migration « sujets.json » de la série de STUD-1107.

Remède :

- `etat_partage.sql` : cinq tables — `sujets_releve` (l'heure du relevé), `sujets_ids` (pseudo → identifiant
  de chaîne, résolu une fois et gardé), `sujets_chaines` (une ligne par chaîne suivie, sa médiane par format,
  NULL quand elle n'a pas cinq vidéos du format), `sujets_videos` (une ligne par vidéo du classement, `rang`
  = le rang affiché, `video` unique) et `sujets_absentes` (les pseudos introuvables, dans l'ordre du relevé).
  `t` est vérifié par motif ET par `strftime` ; `bascules.nom` passe d'une liste fermée à un motif GLOB
  (SQLite ne modifie pas un CHECK, et douze migrations sont en cours), et `_reparer_bascules` refait une base
  restée sur l'ancienne liste, sans perdre une ligne (idempotent).
- `etat_partage.py` : `sujets_lire` / `sujets_detail` / `sujets_remplacer`, en une transaction, et le REFUS
  d'un relevé qui n'a pas la forme du producteur (une clé manquante, un champ que la table ignore) : un
  classement vide est indistinguable du calme, le piège de STUD-52.
- `_infra/etat-service.py` : `GET /sujets` (`{"releve": …}` — un relevé nu rendrait 404 sur une base vide),
  `GET /sujets/detail` (des comptes) et `POST /sujets/remplacer`. Aucun champ d'API existant ne change, et
  le front Svelte ne consomme pas ces routes.
- `etat_client.py` : `sujets()`, `sujets_detail()`, `sujets_remplacer()`, puis `basculer sujets [--essai]` et
  `revenir sujets` — sans le verrou de montage : le montage n'a rien à voir avec ce relevé, et les montages
  tournent en continu (l'attendre rendrait la bascule impossible). Son écrivain vivant sur le 132, la bascule
  lit une copie désignée par `$ETAT_SUJETS` (absent, elle le dit et s'arrête, au lieu de basculer du vide).
- `_tableau/socle.py` : l'accès au service d'état en `urllib` (une seule fois pour tous les fichiers d'état
  dont l'écrivain vit sur le 132, où `etat_client.py` n'est pas livré) et `sujets_ecrire`. Le FICHIER d'abord,
  toujours — c'est lui que la page lit —, la base ensuite quand la bascule est posée ; service muet alors que
  la bascule est CONNUE : « fichier-muet », et c'est DIT sur la sortie d'erreur. Aucun banc ne parle au
  service de production : hors du 132, il n'est appelé que si `STUDIO_ETAT_URL` le désigne (leçon du 13/10).
- `_tableau/sujets.py` : la collecte écrit par le socle (la règle n'est pas recopiée fichier d'état par
  fichier d'état).

Ce qui est lu ne change pas : `onglet_sujets.py` et `sujets.lire()` lisent toujours le fichier — une page qui
interrogerait le service dépendrait d'un service muet à chaque rendu. Passé la bascule, le fichier est la
COPIE que la page lit.

Tests : `_tableau/test_sujets_bascule.py` (nouveau : base d'avant le nom refaite sans perte, essai sur le
relevé réel, bascule, copie réécrite, `.bak-sqlite-<date>`, re-bascule, service muet, retour arrière) ;
`_infra/test_etat_service.py` (les trois routes, le refus d'une forme fausse) ; `_tableau/test_sujets.py`,
`test_onglet_sujets.py`, `test_serveur_suite.py`, `test_tableau_rendu.py` inchangés et verts. Suite complète
(`lancer-tests.py --tout --sans-couverture`) : 169 bancs verts, 1 rouge — `test_accueil_parite.py`, déjà rouge
sur `origin/main` avant ce correctif (vérifié sur un arbre neuf de `ae760c38`). Doc : `OUTILS.md`,
`_tableau/LXC-132.md`.
hermes-fusion deleted branch hermes/migration-sujets-sqlite 2026-10-15 01:50:04 +02:00
Collaborator

Fusionnée automatiquement par fusion-auto (forge) : CI verte (Rust et Front Svelte), PR relue (étiquette relu), sans conflit.

Fusionnée automatiquement par `fusion-auto` (forge) : CI verte (Rust et Front Svelte), PR relue (étiquette `relu`), sans conflit.
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
foXaCe/studio!1694
No description provided.