Couverture : le cliquet passe dans la base du service d'état (bascule couverture-objectif) #1697

Merged
hermes-fusion merged 2 commits from hermes/migration-couverture-objectif into main 2026-10-15 16:20:03 +02:00
Collaborator

Carte : t_3f89c2d1 (STUD-1110) — migrer _tableau/couverture-objectif.json (le cliquet de couverture :
objectif courant et dernière mesure, par famille) dans la base SQLite partagée, avec comparaison, bascule et
retour arrière. Recette établie par STUD-1107, comme les autres fichiers d'état.

Ce qui change

  • etat_partage.sql : table couverture_objectif — une seule ligne (id = 1), une colonne par valeur
    (coeur, ensemble, maj, mesure_coeur, mesure_ensemble, mesure_t, vu), contraintes sur les
    pourcentages et sur les heures ISO (fuseau compris). Pas de colonne JSON fourre-tout : la règle du fichier
    veut qu'une valeur ait une colonne et une contrainte. La table bascules n'a plus de liste fermée de
    noms ; etat_partage._reparer_bascules refait une base restée sur l'ancienne (celle de production l'est),
    sans perdre une ligne.
  • etat_partage.py : couverture_lire, couverture_remplacer, objectif_verifie (un cliquet qu'on ne
    sait pas ranger est REFUSÉ, jamais tronqué), _heure.
  • _infra/etat-service.py : GET /couverture, POST /couverture/remplacer.
  • etat_client.py : couverture(), couverture_remplacer(), basculer couverture-objectif [--essai],
    revenir couverture-objectif.
  • _tableau/couverture_commun.py (nouveau) : le foyer. Le fichier est écrit toujours (atomique, même
    forme qu'avant) ; la base fait foi quand la bascule est posée ; un service muet ou un refus est DIT sur la
    sortie d'erreur, jamais avalé.
  • _tableau/lancer-tests.py : lire_cliquet / ecrire_cliquet passent par ce module. Les règles du
    cliquet (paliers, refus d'une baisse, rien écrit sur un refus) ne bougent pas.
  • controle/bancs_concernes.py : le nouveau module entre dans PARTAGES (c'est une pièce de la porte).
  • Doc : OUTILS.md (la section du cliquet), _tableau/LXC-132.md, autopilotes/revue-de-code-de-la-semaine.md.

Ce qui fait foi, et ce qu'il faut savoir avant de basculer

Après la bascule, la base fait foi et le fichier en est la copie (écrite à chaque mesure, pour git et pour
le cas où le service se taise) : une mesure unique pour tout le studio, là où le fichier était une copie par
machine — mesuré le 2026-10-13 (STUD-1124), le PC et le conteneur des agents ne mesurent pas la même couverture.
Conséquence, écrite dans OUTILS.md : une machine qui mesure plus bas refusera pour toutes. La bascule n'est
pas posée par cette PR
: elle se pose par python3 etat_client.py basculer couverture-objectif, et c'est une
décision d'opérateur.

Aucun banc ne parle au service de production : le service n'est consulté que pour le cliquet du dépôt
(couverture_commun.FICHIER), et les bancs du lanceur détournent ce chemin — le banc le prouve en rendant
urlopen impossible.

Tests

Relevé local sur le PC (l'exécutant pc-studio), tête cf1146c8, base main = a3575e4d :

python3 _tableau/test_couverture_bascule.py            code 0  (11 cas)
python3 _tableau/test_lanceur_cliquet.py               code 0
python3 _tableau/test_lanceur_partiel.py               code 0
python3 _tableau/test_lanceur_seuil.py                 code 0
python3 _tableau/test_lanceur_compte.py                code 0
python3 controle/test_etat_partage.py                  code 0  « TOUT PASSE » (dont le nouveau cas 3 bis)
python3 _infra/test_etat_service.py                    code 0  (dont /couverture et /couverture/remplacer)
python3 controle/test_bancs_concernes.py               code 0
python3 _tableau/test_garde_couches.py|doublons|plafond code 0
python3 _tableau/garde_fous.py                         « rien à redire »

TZ=UTC python3 _tableau/lancer-tests.py --tout : le cliquet refuse sur cette machine, comme sur main —
cœur 99,1 % (99,4 % enregistrés en base), ensemble 96,7 % (99,0 %). Mesuré des deux côtés, mêmes chiffres :

main (a3575e4d)          : CLIQUET — la couverture a BAISSÉ : cœur 99,1 % < 99,4 % ; ensemble 96,7 % < 99,0 %   code 1
cette branche (cf1146c8) : CLIQUET — la couverture a BAISSÉ : cœur 99,1 % < 99,4 % ; ensemble 96,7 % < 99,0 %   code 1

Deux bancs rouges sur main le sont aussi ici, sans rapport : _tableau/test_accueil_parite.py et
_tableau/test_atelier_parite.py (mesurés rouges sur main seul, même assertion). La couverture de
couverture_commun.py (module neuf) est de 92 % (48/52 lignes).

Ce que je n'ai pas mesuré

  • la bascule réelle sur le service de production : 192.168.1.142:8799 ne répond pas d'ici
    (Connection refused). La comparaison, la bascule et le retour arrière sont mesurés contre le service du
    dépôt, lancé sur 127.0.0.1 avec une base jetable (_tableau/test_couverture_bascule.py) — pas contre la
    base de production, et l'ancien fichier n'a pas été renommé.
  • les bancs de navigateur (Chromium) : ils tournent dans la porte complète sur la CI de la forge, pas ici.
**Carte** : t_3f89c2d1 (STUD-1110) — migrer `_tableau/couverture-objectif.json` (le cliquet de couverture : objectif courant et dernière mesure, par famille) dans la base SQLite partagée, avec comparaison, bascule et retour arrière. Recette établie par STUD-1107, comme les autres fichiers d'état. ## Ce qui change - **`etat_partage.sql`** : table `couverture_objectif` — une seule ligne (`id = 1`), une colonne par valeur (`coeur`, `ensemble`, `maj`, `mesure_coeur`, `mesure_ensemble`, `mesure_t`, `vu`), contraintes sur les pourcentages et sur les heures ISO (fuseau compris). Pas de colonne JSON fourre-tout : la règle du fichier veut qu'une valeur ait une colonne et une contrainte. La table `bascules` n'a plus de **liste fermée** de noms ; `etat_partage._reparer_bascules` refait une base restée sur l'ancienne (celle de production l'est), sans perdre une ligne. - **`etat_partage.py`** : `couverture_lire`, `couverture_remplacer`, `objectif_verifie` (un cliquet qu'on ne sait pas ranger est REFUSÉ, jamais tronqué), `_heure`. - **`_infra/etat-service.py`** : `GET /couverture`, `POST /couverture/remplacer`. - **`etat_client.py`** : `couverture()`, `couverture_remplacer()`, `basculer couverture-objectif [--essai]`, `revenir couverture-objectif`. - **`_tableau/couverture_commun.py`** (nouveau) : le foyer. Le fichier est écrit **toujours** (atomique, même forme qu'avant) ; la **base fait foi** quand la bascule est posée ; un service muet ou un refus est DIT sur la sortie d'erreur, jamais avalé. - **`_tableau/lancer-tests.py`** : `lire_cliquet` / `ecrire_cliquet` passent par ce module. Les règles du cliquet (paliers, refus d'une baisse, rien écrit sur un refus) ne bougent pas. - **`controle/bancs_concernes.py`** : le nouveau module entre dans `PARTAGES` (c'est une pièce de la porte). - Doc : `OUTILS.md` (la section du cliquet), `_tableau/LXC-132.md`, `autopilotes/revue-de-code-de-la-semaine.md`. ## Ce qui fait foi, et ce qu'il faut savoir avant de basculer Après la bascule, **la base fait foi** et le fichier en est la copie (écrite à chaque mesure, pour git et pour le cas où le service se taise) : une mesure unique pour tout le studio, là où le fichier était une copie par machine — mesuré le 2026-10-13 (STUD-1124), le PC et le conteneur des agents ne mesurent pas la même couverture. Conséquence, écrite dans `OUTILS.md` : une machine qui mesure plus bas refusera pour toutes. **La bascule n'est pas posée par cette PR** : elle se pose par `python3 etat_client.py basculer couverture-objectif`, et c'est une décision d'opérateur. Aucun banc ne parle au service de production : le service n'est consulté que pour le cliquet du dépôt (`couverture_commun.FICHIER`), et les bancs du lanceur détournent ce chemin — le banc le prouve en rendant `urlopen` impossible. ## Tests Relevé local sur le PC (l'exécutant `pc-studio`), tête `cf1146c8`, base `main` = `a3575e4d` : ``` python3 _tableau/test_couverture_bascule.py code 0 (11 cas) python3 _tableau/test_lanceur_cliquet.py code 0 python3 _tableau/test_lanceur_partiel.py code 0 python3 _tableau/test_lanceur_seuil.py code 0 python3 _tableau/test_lanceur_compte.py code 0 python3 controle/test_etat_partage.py code 0 « TOUT PASSE » (dont le nouveau cas 3 bis) python3 _infra/test_etat_service.py code 0 (dont /couverture et /couverture/remplacer) python3 controle/test_bancs_concernes.py code 0 python3 _tableau/test_garde_couches.py|doublons|plafond code 0 python3 _tableau/garde_fous.py « rien à redire » ``` `TZ=UTC python3 _tableau/lancer-tests.py --tout` : **le cliquet refuse sur cette machine, comme sur `main`** — cœur 99,1 % (99,4 % enregistrés en base), ensemble 96,7 % (99,0 %). Mesuré des deux côtés, mêmes chiffres : ``` main (a3575e4d) : CLIQUET — la couverture a BAISSÉ : cœur 99,1 % < 99,4 % ; ensemble 96,7 % < 99,0 % code 1 cette branche (cf1146c8) : CLIQUET — la couverture a BAISSÉ : cœur 99,1 % < 99,4 % ; ensemble 96,7 % < 99,0 % code 1 ``` Deux bancs rouges sur `main` le sont aussi ici, sans rapport : `_tableau/test_accueil_parite.py` et `_tableau/test_atelier_parite.py` (mesurés rouges sur `main` seul, même assertion). La couverture de `couverture_commun.py` (module neuf) est de 92 % (48/52 lignes). ## Ce que je n'ai pas mesuré - **la bascule réelle sur le service de production** : `192.168.1.142:8799` ne répond pas d'ici (`Connection refused`). La comparaison, la bascule et le retour arrière sont mesurés contre le service du dépôt, lancé sur `127.0.0.1` avec une base jetable (`_tableau/test_couverture_bascule.py`) — pas contre la base de production, et l'ancien fichier n'a pas été renommé. - **les bancs de navigateur** (Chromium) : ils tournent dans la porte complète sur la CI de la forge, pas ici.
Couverture : le cliquet passe dans la base du service d'état (STUD-1110)
All checks were successful
Tests du tableau (Rust + Svelte) / Rust (_tableau/rust) (push) Successful in 26s
Tests du tableau (Rust + Svelte) / Front Svelte (_tableau/front) (push) Successful in 15s
Tests du tableau (Rust + Svelte) / Rust (_tableau/rust) (pull_request) Successful in 25s
Tests du tableau (Rust + Svelte) / Front Svelte (_tableau/front) (pull_request) Successful in 19s
cf1146c837
Le cliquet (_tableau/couverture-objectif.json : objectif courant et dernière mesure, par famille)
vivait dans une copie git, donc un par machine — mesuré le 2026-10-13 (STUD-1124), le PC et le
conteneur des agents ne mesurent pas la même couverture, et chacun comparait à sa propre copie.

La table couverture_objectif (une seule ligne, une colonne par valeur, contraintes sur les
pourcentages et sur les heures ISO) le reçoit. La bascule couverture-objectif (comparaison,
.bak-sqlite-<date>, copie réécrite depuis la base, retour arrière en une commande) passe par
etat_client.py, et le service d'état sert /couverture et /couverture/remplacer.

_tableau/couverture_commun.py porte le foyer : le fichier est écrit à chaque passage — c'est la
copie que git montre et ce dont on se sert si le service se tait — et la base fait foi quand la
bascule est posée. Un cliquet que la base refuse est DIT, jamais perdu en silence, et un chemin
détourné (celui des bancs du lanceur) ne parle jamais au service.

La table bascules n'a plus de liste fermée de noms : _reparer_bascules refait une base restée sur
l'ancienne, sans perdre une ligne.
Author
Collaborator

Relevé local, tête poussée cf1146c8, base main = a3575e4d, sur le PC (l'exécutant pc-studio).

$ TZ=UTC python3 _tableau/lancer-tests.py --tout --complet
ok    _tableau/test_couverture_bascule.py
ok    _tableau/test_lanceur_cliquet.py
ok    _tableau/test_lanceur_compte.py
ok    _tableau/test_lanceur_partiel.py
ok    _tableau/test_lanceur_seuil.py
ok    _tableau/test_garde_couches.py
ok    _tableau/test_garde_doublons.py
ok    _tableau/test_garde_plafond.py
… 168 bancs verts, 0 LENT
ROUGE _tableau/test_accueil_parite.py (code 1)
ROUGE _tableau/test_atelier_parite.py (code 1)
  couverture_commun.py                   52     48     92%
  CŒUR revue/retours*/serveur                          99%   objectif 100 %
  ENSEMBLE _tableau/                                   97%   objectif 100 %
CLIQUET — la couverture a BAISSÉ : cœur 99.1 % < 99.4 % (dernière mesure) ; ensemble 96.7 % < 99.0 % (dernière mesure).
code 1

Les deux ROUGE et le refus du cliquet sont ceux de main, mesurés sur la même machine, mêmes chiffres :

$ cd <arbre de origin/main a3575e4d> && TZ=UTC python3 _tableau/lancer-tests.py --tout
ROUGE _tableau/test_accueil_parite.py (code 1)
ROUGE _tableau/test_atelier_parite.py (code 1)
  CŒUR revue/retours*/serveur                          99%   objectif 100 %
  ENSEMBLE _tableau/                                   97%   objectif 100 %
CLIQUET — la couverture a BAISSÉ : cœur 99.1 % < 99.4 % ; ensemble 96.7 % < 99.0 %.
code 1

Le passage n'a rien écrit dans le cliquet du dépôt : git status reste propre après le lancement complet
(le cliquet refuse, donc il n'écrit rien, et un passage qui répète la même mesure n'écrit plus rien non plus).

Ce que je n'ai pas mesuré : la bascule réelle contre le service de production (192.168.1.142:8799 ne répond
pas d'ici — Connection refused) ; la comparaison/la bascule/le retour arrière sont mesurés contre le service
du dépôt sur 127.0.0.1 avec une base jetable, et l'ancien fichier n'a pas été renommé. Les bancs de
navigateur (Chromium) ne tournent pas ici, seulement dans la porte complète de la forge.

Relevé local, tête poussée `cf1146c8`, base `main` = `a3575e4d`, sur le PC (l'exécutant `pc-studio`). ``` $ TZ=UTC python3 _tableau/lancer-tests.py --tout --complet ok _tableau/test_couverture_bascule.py ok _tableau/test_lanceur_cliquet.py ok _tableau/test_lanceur_compte.py ok _tableau/test_lanceur_partiel.py ok _tableau/test_lanceur_seuil.py ok _tableau/test_garde_couches.py ok _tableau/test_garde_doublons.py ok _tableau/test_garde_plafond.py … 168 bancs verts, 0 LENT ROUGE _tableau/test_accueil_parite.py (code 1) ROUGE _tableau/test_atelier_parite.py (code 1) couverture_commun.py 52 48 92% CŒUR revue/retours*/serveur 99% objectif 100 % ENSEMBLE _tableau/ 97% objectif 100 % CLIQUET — la couverture a BAISSÉ : cœur 99.1 % < 99.4 % (dernière mesure) ; ensemble 96.7 % < 99.0 % (dernière mesure). code 1 ``` Les deux ROUGE et le refus du cliquet sont **ceux de `main`**, mesurés sur la même machine, mêmes chiffres : ``` $ cd <arbre de origin/main a3575e4d> && TZ=UTC python3 _tableau/lancer-tests.py --tout ROUGE _tableau/test_accueil_parite.py (code 1) ROUGE _tableau/test_atelier_parite.py (code 1) CŒUR revue/retours*/serveur 99% objectif 100 % ENSEMBLE _tableau/ 97% objectif 100 % CLIQUET — la couverture a BAISSÉ : cœur 99.1 % < 99.4 % ; ensemble 96.7 % < 99.0 %. code 1 ``` Le passage n'a **rien écrit** dans le cliquet du dépôt : `git status` reste propre après le lancement complet (le cliquet refuse, donc il n'écrit rien, et un passage qui répète la même mesure n'écrit plus rien non plus). Ce que je n'ai pas mesuré : la bascule réelle contre le service de production (`192.168.1.142:8799` ne répond pas d'ici — `Connection refused`) ; la comparaison/la bascule/le retour arrière sont mesurés contre le service du dépôt sur `127.0.0.1` avec une base jetable, et l'ancien fichier n'a pas été renommé. Les bancs de navigateur (Chromium) ne tournent pas ici, seulement dans la porte complète de la forge.
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é.
Author
Collaborator

Relecture (relecteur) — le fond est conforme, un seul point manque : la branche n'est plus fusionnable.

Mesuré sur cf1146c8 : CI 4/4 verte ; 11 cas du banc de bascule, bancs du lanceur, gardes, etat_partage et etat_service verts ; cargo test --release --locked 77 réussis / 0 échec ; vitest run 27 fichiers / 200 tests verts ; vite build code 0 ; suite complète 168 bancs verts, le seul rouge (test_accueil_parite.py) l'est aussi sur main ; cliquet refusant avec les mêmes chiffres que main ; aucun fichier du front ni du service Rust touché, aucun secret, aucun binaire, aucun emoji.

Ce qui bloque : git merge-tree contre main e6a8582e donne un conflit de contenu dans _infra/test_etat_service.py, etat_client.py, etat_partage.py et etat_partage.sql — #1694 (sujets) a touché les mêmes zones partagées. main porte déjà le même CHECK élargi de bascules et le même _reparer_bascules : la reprise est un « garder les deux » (NOMS avec sujets et couverture-objectif, table et routes de couverture EN PLUS de celles de sujets). À rebaser sur e6a8582e, bancs concernés rejoués, puis nouvelle relecture (relecteur).

**Relecture (relecteur)** — le fond est conforme, un seul point manque : la branche n'est plus fusionnable. Mesuré sur `cf1146c8` : CI 4/4 verte ; 11 cas du banc de bascule, bancs du lanceur, gardes, `etat_partage` et `etat_service` verts ; `cargo test --release --locked` 77 réussis / 0 échec ; `vitest run` 27 fichiers / 200 tests verts ; `vite build` code 0 ; suite complète 168 bancs verts, le seul rouge (`test_accueil_parite.py`) l'est aussi sur `main` ; cliquet refusant avec les mêmes chiffres que `main` ; aucun fichier du front ni du service Rust touché, aucun secret, aucun binaire, aucun emoji. Ce qui bloque : `git merge-tree` contre `main` `e6a8582e` donne un conflit de contenu dans `_infra/test_etat_service.py`, `etat_client.py`, `etat_partage.py` et `etat_partage.sql` — #1694 (sujets) a touché les mêmes zones partagées. `main` porte déjà le même CHECK élargi de `bascules` et le même `_reparer_bascules` : la reprise est un « garder les deux » (`NOMS` avec `sujets` et `couverture-objectif`, table et routes de couverture EN PLUS de celles de sujets). À rebaser sur `e6a8582e`, bancs concernés rejoués, puis nouvelle relecture (`relecteur`).
hermes-bot force-pushed hermes/migration-couverture-objectif from cf1146c837
All checks were successful
Tests du tableau (Rust + Svelte) / Rust (_tableau/rust) (push) Successful in 26s
Tests du tableau (Rust + Svelte) / Front Svelte (_tableau/front) (push) Successful in 15s
Tests du tableau (Rust + Svelte) / Rust (_tableau/rust) (pull_request) Successful in 25s
Tests du tableau (Rust + Svelte) / Front Svelte (_tableau/front) (pull_request) Successful in 19s
to ce3197891d
Some checks failed
Tests du tableau (Rust + Svelte) / Front Svelte (_tableau/front) (pull_request) Successful in 1m30s
Tests du tableau (Rust + Svelte) / Front Svelte (_tableau/front) (push) Successful in 1m32s
Tests du tableau (Rust + Svelte) / Rust (_tableau/rust) (pull_request) Failing after 5m39s
Tests du tableau (Rust + Svelte) / Rust (_tableau/rust) (push) Failing after 5m40s
2026-10-15 15:13:07 +02:00
Compare
Collaborator

fusion-auto : pas de fusion, la CI est rouge (Rust). 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 CI est rouge (Rust). Le chef de l'atelier renvoie la carte à l'agent ; l'étiquette tombera d'elle-même une fois corrigé.
fix rebase : NOMS gardait les deux versions
All checks were successful
Tests du tableau (Rust + Svelte) / Front Svelte (_tableau/front) (push) Successful in 1m37s
Tests du tableau (Rust + Svelte) / Front Svelte (_tableau/front) (pull_request) Successful in 1m35s
Tests du tableau (Rust + Svelte) / Rust (_tableau/rust) (push) Successful in 11m6s
Tests du tableau (Rust + Svelte) / Rust (_tableau/rust) (pull_request) Successful in 11m6s
c458019f47
Le rebase a laissé côte à côte la liste de `main` (`sorties`, `file_montage`,
`sujets`, `voix-des-shorts`, `reseaux-file`, `etat-agents`) et celle de la
branche d'origine (`couverture-objectif`). La seconde écrasait la première :
`etat_client.py basculer sujets` n'était plus reconnu, et
`_tableau/test_sujets_bascule.py` (banc de `main`) sortait en 1 sur la branche
alors qu'il passe sur `main` — le seul rouge que la porte reprochait à la
branche.

Remède : une seule liste, celle de `main` plus `couverture-objectif`.
hermes-fusion deleted branch hermes/migration-couverture-objectif 2026-10-15 16:20:04 +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
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!1697
No description provided.