Les producteurs de _reseaux/ écrivent la file par une seule porte : plan.py (bascule reseaux-file) #1700

Merged
hermes-fusion merged 4 commits from hermes/migration-file-reseaux-sqlite into main 2026-10-16 19:40:06 +02:00
Collaborator

Ce que fait cette PR

La file des réseaux (_reseaux/file.json) était écrite par quatre passages, chacun avec sa copie de la recette : lire le fichier entier, modifier une entrée, le réécrire entier. Aucune transaction, aucun verrou. Deux passages rapprochés perdaient les marques de l'autre, et la base du service d'état (reseaux_file, STUD-1107) — devenue le foyer de cette file — ne recevait rien tant que pousser-tableau.sh n'était pas passé.

Cette PR fait passer les quatre passages par un module unique, _reseaux/plan.py :

  • plan.ecrire(entrees) écrit le fichier toujours et en premier (c'est lui que pousser-tableau.sh fait entrer dans la base), puis la base dès que python3 etat_client.py basculer reseaux-file est fait ;
  • plan.lire() suit le même foyer (la base quand la bascule est posée), et rend la forme du fichier (pas de null en plus) ;
  • un service muet n'est jamais un repli silencieux : fichier présent, il fait foi et c'est dit sur la sortie d'erreur ; fichier absent, on refuse de lire plutôt que de laisser un producteur réécrire une file vide.

Contenu de la branche (rebasée sur main le 2026-10-15)

  • 189ddc4e — table reseaux_file, routes GET /reseaux / POST /reseaux/remplacer, basculer reseaux-file [--essai] / revenir reseaux-file, lecture depuis la base, bascules.nom en motif (_reparer_bascules). C'est le contenu de la PR #1682 (hermes/migration-reseaux-sqlite, carte t_b914b746), que cette branche reprend et rebase sur main : les cinq conflits (OUTILS.md, _tableau/LXC-132.md, etat_client.py, etat_partage.py, etat_partage.sql) venaient de la migration des sujets, entrée dans main depuis. La résolution garde le texte et le code de main (sujets, bascules en motif, _reparer_bascules) et n'ajoute que ce qui manque à la file des réseaux : la table, reseaux_lire/reseaux_remplacer, entree_reseaux, les branches reseaux-file de basculer/revenir, les commandes reseaux <fichier> et en-service <nom>, $RESEAUX_FILE. #1682 est aujourd'hui conflit + ci-rouge : celle-ci porte le même travail, réparé et vert.
  • cd2bfe20 — les producteurs (_reseaux/plan.py, preparer-file.py, marquer.py, tiktok-web.py, tiktok-brouillon.py, controle-voix-file.py), le retour arrière à l'octet, les deux bancs que le changement de porte rendait faux, _reseaux/COMPTES.md.

Le banc controle/test_etat_partage.py (cas 8b) comparait la liste des tables de la base réparée à une liste écrite à la main : elle a menti dès que le schéma a gagné les tables des sujets (« bascules_avant est restée derrière » alors qu'elle était partie). Il demande maintenant la liste au schéma du dépôt.

Aucun champ de l'API du tableau n'a changé (/api/...) : le front n'est pas concerné.

Preuves

  • python3 controle/test_plan_reseaux.py → exit=0, 37 cas (ok), sortie complète sur la carte t_265acd1f.
  • Banques rejouées, code 0 : controle/test_etat_partage.py, controle/test_etat_reseaux.py, _infra/test_etat_service.py, _tableau/test_collecte.py, controle/test_dedoublonnage_uploads.py, controle/test_dependances_reseaux.py, controle/test_garde_voix_shorts.py, controle/test_trace_voix_fabrication.py, controle/test_purger_episodes.py, _tableau/test_onglet_reseaux.py, _tableau/test_collecte_ciblee.py.
  • _reseaux/test_tiktok_web.py : 5 cas rouges, identiques sur main (mêmes 5 noms, mêmes messages, mesurés le 2026-10-15 dans un arbre de travail détaché sur origin/main) — antérieurs à cette PR. Ce qu'elle corrige dans ce banc : il patche tw.FILE, que le module ne lit plus, et écrivait donc le VRAI _reseaux/file.json ; la file se déplace maintenant par $PLAN_RESEAUX (comme dans controle/test_dedoublonnage_uploads.py, même défaut).
  • _tableau/rust : cargo test --release --locked → 18 suites, 77 tests passés, 0 échec ; cargo build --release --locked → Finished, aucun avertissement. Aucun fichier Rust dans cette PR.
  • Le cas « retour arrière à l'octet » du banc a été rejoué avec l'ancien code : il échoue (b'...]\n' != b'...]').

Points ouverts, pour le chef

  1. Deux passages écrivent encore le fichier seul : outils/remplacer-video.py (remplacement de texte dans tous les *.json de _reseaux/) et _reseaux/serveur/etat.py (fusion avec le dossier d'état du 130). La base ne les voit qu'au pousser-tableau.sh suivant ; une écriture de l'un d'eux suivie d'une écriture de producteur avant ce passage est perdue. C'est écrit dans _reseaux/COMPTES.md (« Les chemins d'écriture de file.json »).
  2. PR #1682 porte le même travail, en conflit et rouge : à fermer quand celle-ci est fusionnée, ou à arbitrer.
## Ce que fait cette PR La file des réseaux (`_reseaux/file.json`) était écrite par quatre passages, chacun avec sa copie de la recette : lire le fichier entier, modifier une entrée, le réécrire entier. Aucune transaction, aucun verrou. Deux passages rapprochés perdaient les marques de l'autre, et la base du service d'état (`reseaux_file`, STUD-1107) — devenue le foyer de cette file — ne recevait rien tant que `pousser-tableau.sh` n'était pas passé. Cette PR fait passer les quatre passages par un module unique, `_reseaux/plan.py` : - `plan.ecrire(entrees)` écrit le fichier **toujours et en premier** (c'est lui que `pousser-tableau.sh` fait entrer dans la base), puis la base dès que `python3 etat_client.py basculer reseaux-file` est fait ; - `plan.lire()` suit le même foyer (la base quand la bascule est posée), et rend la forme du fichier (pas de `null` en plus) ; - un service muet n'est jamais un repli silencieux : fichier présent, il fait foi et c'est dit sur la sortie d'erreur ; fichier absent, on refuse de lire plutôt que de laisser un producteur réécrire une file vide. ## Contenu de la branche (rebasée sur `main` le 2026-10-15) - `189ddc4e` — table `reseaux_file`, routes `GET /reseaux` / `POST /reseaux/remplacer`, `basculer reseaux-file [--essai]` / `revenir reseaux-file`, lecture depuis la base, `bascules.nom` en motif (`_reparer_bascules`). **C'est le contenu de la PR #1682** (`hermes/migration-reseaux-sqlite`, carte t_b914b746), que cette branche reprend **et rebase sur `main`** : les cinq conflits (`OUTILS.md`, `_tableau/LXC-132.md`, `etat_client.py`, `etat_partage.py`, `etat_partage.sql`) venaient de la migration des sujets, entrée dans `main` depuis. La résolution garde le texte et le code de `main` (sujets, `bascules` en motif, `_reparer_bascules`) et n'ajoute que ce qui manque à la file des réseaux : la table, `reseaux_lire`/`reseaux_remplacer`, `entree_reseaux`, les branches `reseaux-file` de `basculer`/`revenir`, les commandes `reseaux <fichier>` et `en-service <nom>`, `$RESEAUX_FILE`. #1682 est aujourd'hui `conflit` + `ci-rouge` : celle-ci porte le même travail, réparé et vert. - `cd2bfe20` — les producteurs (`_reseaux/plan.py`, `preparer-file.py`, `marquer.py`, `tiktok-web.py`, `tiktok-brouillon.py`, `controle-voix-file.py`), le retour arrière à l'octet, les deux bancs que le changement de porte rendait faux, `_reseaux/COMPTES.md`. Le banc `controle/test_etat_partage.py` (cas 8b) comparait la liste des tables de la base réparée à une liste écrite à la main : elle a menti dès que le schéma a gagné les tables des sujets (« `bascules_avant` est restée derrière » alors qu'elle était partie). Il demande maintenant la liste au schéma du dépôt. Aucun champ de l'API du tableau n'a changé (`/api/...`) : le front n'est pas concerné. ## Preuves - `python3 controle/test_plan_reseaux.py` → `exit=0`, 37 cas (`ok`), sortie complète sur la carte t_265acd1f. - Banques rejouées, code 0 : `controle/test_etat_partage.py`, `controle/test_etat_reseaux.py`, `_infra/test_etat_service.py`, `_tableau/test_collecte.py`, `controle/test_dedoublonnage_uploads.py`, `controle/test_dependances_reseaux.py`, `controle/test_garde_voix_shorts.py`, `controle/test_trace_voix_fabrication.py`, `controle/test_purger_episodes.py`, `_tableau/test_onglet_reseaux.py`, `_tableau/test_collecte_ciblee.py`. - `_reseaux/test_tiktok_web.py` : 5 cas rouges, **identiques sur `main`** (mêmes 5 noms, mêmes messages, mesurés le 2026-10-15 dans un arbre de travail détaché sur `origin/main`) — antérieurs à cette PR. Ce qu'elle corrige dans ce banc : il patche `tw.FILE`, que le module ne lit plus, et écrivait donc le VRAI `_reseaux/file.json` ; la file se déplace maintenant par `$PLAN_RESEAUX` (comme dans `controle/test_dedoublonnage_uploads.py`, même défaut). - `_tableau/rust` : `cargo test --release --locked` → 18 suites, 77 tests passés, 0 échec ; `cargo build --release --locked` → `Finished`, aucun avertissement. Aucun fichier Rust dans cette PR. - Le cas « retour arrière à l'octet » du banc a été rejoué avec l'ancien code : il échoue (`b'...]\n' != b'...]'`). ## Points ouverts, pour le chef 1. Deux passages écrivent encore le fichier seul : `outils/remplacer-video.py` (remplacement de texte dans tous les `*.json` de `_reseaux/`) et `_reseaux/serveur/etat.py` (fusion avec le dossier d'état du 130). La base ne les voit qu'au `pousser-tableau.sh` suivant ; une écriture de l'un d'eux suivie d'une écriture de producteur avant ce passage est perdue. C'est écrit dans `_reseaux/COMPTES.md` (« Les chemins d'écriture de `file.json` »). 2. PR #1682 porte le même travail, en conflit et rouge : à fermer quand celle-ci est fusionnée, ou à arbitrer.
Cause : `reseaux-file.json` était le dernier état lu par le tableau qui vivait encore
dans un fichier — lu par `collecte.py`, recopié par `_reseaux/pousser-tableau.sh`, sans
transaction, sans contrainte, et sans qu'on puisse prouver ce que le tableau avait lu.

Remède : table `reseaux_file` (une ligne par entrée, `rang` pour garder l'ordre du
fichier, un champ par colonne, `quand` refusé s'il n'existe pas au calendrier), routes
`GET /reseaux` et `POST /reseaux/remplacer`, `basculer reseaux-file [--essai]` et
`revenir reseaux-file` sur la recette de STUD-1107 (comparaison avant bascule, ancien
gardé en `.bak-sqlite-<date>`, retour qui rend l'état en vigueur dans la forme des
producteurs de `_reseaux/`), lecture depuis la base dès que la bascule est posée, et
`bascules.nom` qui passe d'une liste fermée de noms à un motif — `_reparer_bascules`
refait la table d'une base ouverte avant ce changement, sinon l'insertion sortait en
`CHECK constraint failed`.

Banc : `controle/test_etat_partage.py` (ok 8a et 8b : essai, formes refusées, bascule,
`.bak-sqlite`, écriture dans la base, retour arrière, re-bascule, base d'avant le
2026-10-14) et le cas de `_tableau/test_collecte.py` qui lit la file du service.

(cherry picked from commit 0edc0c7dcc)
Les producteurs de _reseaux/ passent par une seule porte d'écriture : plan.py
All checks were successful
Tests du tableau (Rust + Svelte) / Rust (_tableau/rust) (push) Successful in 22s
Tests du tableau (Rust + Svelte) / Front Svelte (_tableau/front) (push) Successful in 14s
Tests du tableau (Rust + Svelte) / Rust (_tableau/rust) (pull_request) Successful in 22s
Tests du tableau (Rust + Svelte) / Front Svelte (_tableau/front) (pull_request) Successful in 12s
c60d73d906
Cause : la file des réseaux (`_reseaux/file.json`) était écrite par quatre passages —
`preparer-file.py`, `marquer.py`, `tiktok-web.py`, `tiktok-brouillon.py` — chacun avec sa copie de la
recette : lire le fichier entier, modifier une entrée, le réécrire entier. Aucune transaction, aucun
verrou. Deux passages rapprochés perdaient les marques de l'autre, et la base du service d'état
(`reseaux_file`, STUD-1107) — devenue le foyer de cette file — ne recevait rien tant que
`pousser-tableau.sh` n'était pas passé : le tableau ne pouvait pas prouver ce qu'il avait lu.

Remède : un module unique, `_reseaux/plan.py` (`lire`, `ecrire`, `chemin`), par lequel passent les
quatre passages. `ecrire` écrit le fichier TOUJOURS et EN PREMIER (c'est lui que `pousser-tableau.sh`
fait entrer dans la base), puis la base dès que `basculer reseaux-file` est fait ; `lire` suit le même
foyer, dans la forme du fichier (pas de `null` en plus). Un service muet n'est jamais un repli
silencieux : fichier présent, il fait foi et c'est dit sur la sortie d'erreur ; fichier absent, on
refuse de lire plutôt que de laisser un producteur réécrire une file vide. Le chemin se déplace par
`$PLAN_RESEAUX`, pour les essais.

Deux corrections trouvées en chemin :
- `etat_client.py revenir reseaux-file` ajoutait un retour à la ligne que les producteurs n'écrivent
  pas : le fichier rendu n'était pas, à l'octet, celui qu'ils écrivent (mesuré sur `_reseaux/file.json`
  et `_tableau/reseaux-file.json`, qui finissent tous deux sur `]`).
- `controle/test_dedoublonnage_uploads.py` détournait l'écriture du fichier par un `open()` du chemin de
  production : sans `$PLAN_RESEAUX`, il écrivait le VRAI `_reseaux/file.json` (mesuré : le fichier du
  dépôt écrasé, temporaire vide) et laissait `STUDIO_ETAT_URL` d'un appelant décider du foyer.

`_reseaux/COMPTES.md` dit maintenant les six passages d'écriture de `file.json`, lesquels passent par
`plan`, et ce qu'il faut faire après les deux qui écrivent le fichier seul (`outils/remplacer-video.py`,
`_reseaux/serveur/etat.py`).

Banc : `controle/test_plan_reseaux.py` — 37 cas contre un vrai service d'état sur 127.0.0.1 (sans
bascule, bascule, service muet, valeur refusée par le schéma, retour arrière à l'octet, `marquer.py` de
bout en bout, garde des écritures directes). Le cas du retour arrière reproduit le défaut du retour à la
ligne : remis en place, il échoue.
Collaborator

fusion-auto : pas de fusion, la PR est en conflit avec main : à rebaser. Le chef de l'atelier renvoie la carte à l'agent ; l'étiquette tombera d'elle-même une fois corrigé.

`fusion-auto` : pas de fusion, la PR est en conflit avec main : à rebaser. Le chef de l'atelier renvoie la carte à l'agent ; l'étiquette tombera d'elle-même une fois corrigé.
hermes-bot force-pushed hermes/migration-file-reseaux-sqlite from c60d73d906
All checks were successful
Tests du tableau (Rust + Svelte) / Rust (_tableau/rust) (push) Successful in 22s
Tests du tableau (Rust + Svelte) / Front Svelte (_tableau/front) (push) Successful in 14s
Tests du tableau (Rust + Svelte) / Rust (_tableau/rust) (pull_request) Successful in 22s
Tests du tableau (Rust + Svelte) / Front Svelte (_tableau/front) (pull_request) Successful in 12s
to cd2bfe20b9
All checks were successful
Tests du tableau (Rust + Svelte) / Rust (_tableau/rust) (pull_request) Successful in 20s
Tests du tableau (Rust + Svelte) / Front Svelte (_tableau/front) (pull_request) Successful in 12s
Tests du tableau (Rust + Svelte) / Front Svelte (_tableau/front) (push) Successful in 40s
Tests du tableau (Rust + Svelte) / Rust (_tableau/rust) (push) Successful in 49s
2026-10-15 02:19:50 +02:00
Compare
Collaborator

fusion-auto : pas de fusion, la PR est en conflit avec main : à rebaser. Le chef de l'atelier renvoie la carte à l'agent ; l'étiquette tombera d'elle-même une fois corrigé.

`fusion-auto` : pas de fusion, la PR est en conflit avec main : à rebaser. Le chef de l'atelier renvoie la carte à l'agent ; l'étiquette tombera d'elle-même une fois corrigé.
Merge remote-tracking branch 'httpforge/main' into HEAD
All checks were successful
Tests du tableau (Rust + Svelte) / Front Svelte (_tableau/front) (pull_request) Successful in 3m47s
Tests du tableau (Rust + Svelte) / Front Svelte (_tableau/front) (push) Successful in 3m50s
Tests du tableau (Rust + Svelte) / Rust (_tableau/rust) (pull_request) Successful in 14m12s
Tests du tableau (Rust + Svelte) / Rust (_tableau/rust) (push) Successful in 14m55s
4e5acb24fb
hermes-fusion deleted branch hermes/migration-file-reseaux-sqlite 2026-10-16 19:40:06 +02:00
Collaborator

Fusionnée automatiquement par fusion-auto (forge) : CI verte, sans conflit.

Fusionnée automatiquement par `fusion-auto` (forge) : CI verte, sans conflit.
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
3 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!1700
No description provided.