feat(outils) : le journal du miroir devient relevable, par fonction distante #113

Merged
foXaCe merged 2 commits from passe-20261018-101500-journalmiroir into main 2026-10-18 10:35:49 +02:00
Owner

FOXA-1215. Le mode double n'a d'intérêt que s'il rend visible ce que Nuvio Host ne sait pas
faire. Aujourd'hui le miroir range ses échecs dans un journal local sur l'appareil : personne ne
les lit, et la liste des manques se reconstitue à la main, comme dans l'étude FOXA-1103.

Cette PR rend ce journal relevable.

La commande et sa sortie

$ python3 outils/relever_journal_miroir.py /tmp/demo-journal

=== /tmp/demo-journal/sync_mirror_journal.jsonl ===
compteurs : réussis 141 · échecs 5 · dernier succès 2025-10-18T08:15Z (globaux, source : en-tête du journal)

FONCTION DISTANTE         échecs  refusés        réussis  dernier code  qualification
--------------------------------------------------------------------------------------
progress_push                  2        1     NON MESURÉ             —  sans code
addons_push                    1        0     NON MESURÉ         42501  droit à ouvrir
connexion                      1        0     NON MESURÉ             —  sans code
settings_push                  1        0     NON MESURÉ      PGRST202  manque de Host

dernier message par fonction :
  progress_push : refusé par l'historique de versions de la cible
  addons_push : {"code":"42501","message":"permission denied for table addons"}
  settings_push : {"code":"PGRST202","message":"Could not find the function public.push_settings"}

réussis PAR FONCTION : NON MESURÉ — les réussis par fonction demandent un compteur par `rpcName` …
Ce relevé ne décide rien : aucune promotion, aucune coupure. Elles restent à l'opérateur.

PGRST202 = manque de Host, 42501 = droit à ouvrir, le reste à qualifier. Le relevé
qualifie ; il ne décide rien.

⚠ La colonne qui manque, et pourquoi elle n'est pas remplie

Le format v1 du journal ne compte les succès que globalement (okCount) : les réussis par
fonction
n'existent nulle part dans le fichier. Le relevé écrit NON MESURÉ et nomme le contrat
qui manque — un compteur par rpcName dans MirrorJournal, les deux copies NuvioTV et
NuvioMobile changeant ensemble
.

Il n'écrit pas 0 : un 0 se lirait « cette fonction n'est jamais appelée », c'est-à-dire
exactement le contraire de la vérité quand le miroir est muet. C'est la règle du dépôt — un
silence n'est pas un zéro — appliquée au seul endroit où elle coûtait quelque chose.

Trois refus

refus pourquoi
rien lu, rien mesuré → code 2 aucun journal trouvé ne rend pas un tableau vide ; un tableau vide se lit « le miroir va bien »
la liste est BORNÉE → ≥n MirrorJournal ne garde que 50 échecs ; quand le compteur global les dépasse, un compte par fonction est un minimum
le relevé redige à son tour il sort de l'appareil : il ne fait pas confiance au fichier, qui peut venir d'un build antérieur ou d'une copie

Bénéfice secondaire mesuré : le relevé qualifie ce que le journal ne savait pas dire. Un
PGRST202 noyé dans {"code":…} devient une ligne « manque de Host » — c'est ce que le tableau
des manques attendait.

Le cas d'échec, testé d'abord

test_un_miroir_coupe_rend_un_releve_lisible_et_ne_leve_pas : miroir coupé (SIGN_IN) et cible
injoignable (Connection refused) rendent un relevé lisible, sans lever, et sans incidence sur la
lecture
— la règle qui veut qu'un échec du miroir ne remonte jamais à l'appelant ne change pas.

Les preuves

13 tests, dont ceux qui mordent sur les inventions possibles :

ce qui est mesuré comment
les réussis par fonction ne sont jamais inventés NON MESURÉ dans le tableau, null en JSON, et aucune ligne de fonction ne porte un chiffre
un journal tronqué rend des minorants failureCount: 80, deux lignes lisibles → ≥1, jamais 1
rien lu n'est pas zéro code 2 et le motif
une ligne illisible n'est ni un échec ni un succès comptée et nommée
un journal sans en-tête ne fabrique pas de compteurs ABSENTS / NON MESURÉS
le relevé redige un JWT et une apikey posés dans un message ne sortent pas
les compteurs frais gagnent le fichier .compteurs.json prime sur l'en-tête (FOXA-1957)
la commande se joue le test lance l'outil en sous-processus, comme l'opérateur

Porte

./run_tests.sh -q : garde d'octets VERT, parité conforme, 2144 passés, 14 sautés, 0 échec —
référence main 2131 mesurée sur le même banc (+13).

Ce que cette PR ne porte pas

La moitié qui manque au critère du ticket — les réussis par fonction — est côté application.
Elle demande de toucher MirrorJournal dans NuvioTV et NuvioMobile, les deux ensemble : un
format qui changerait d'un seul côté recreuserait exactement la divergence que FOXA-2959 vient de
payer. Le contrat est écrit dans l'outil (CONTRAT_ATTENDU) et dans son test.

⚠ CORRECTION D'UNE MESURE QUE J'AI PUBLIÉE ICI. La première version de ce corps affirmait que
« le banc de NuvioMobile est rouge sur main ». C'est faux — et c'était ma mesure qui était
fausse.
J'avais lancé testAndroidHostTest sans -Pnuvio.android.distribution=full, donc sur
le flavor playstore par défaut, où les greffons-scrapers n'existent pas (PluginScraperCodeFileStore
vit dans src/androidFull/kotlin) : le test, lui, est dans le source set partagé androidHostTest,
et il ne pouvait pas compiler. Le dépôt dit pourtant la commande canonique, dans
settings.gradle.kts :

./gradlew :composeApp:testAndroidHostTest -Pnuvio.android.distribution=full

Avec elle, le banc est VERT — mesuré : BUILD SUCCESSFUL in 48s, et les trois classes visées
(MirrorJournalTest, MirrorWriteSinkTest, PluginScraperCodeFileStoreTest) compilent et passent.

Il n'y a donc aucun préalable à réparer : la moitié application de FOXA-1215 est faisable, et
c'est la suite de ce ticket.

Rien dans JOURNAL.md / CLAUDE.md : la référence de porte se remesure sur le banc en vigueur
(RG78), ce n'est pas à cette PR de choisir le banc.

`FOXA-1215`. Le mode double n'a d'intérêt que s'il rend visible ce que Nuvio Host ne sait pas faire. Aujourd'hui le miroir range ses échecs dans un **journal local sur l'appareil** : personne ne les lit, et la liste des manques se reconstitue à la main, comme dans l'étude `FOXA-1103`. Cette PR rend ce journal **relevable**. ## La commande et sa sortie ``` $ python3 outils/relever_journal_miroir.py /tmp/demo-journal === /tmp/demo-journal/sync_mirror_journal.jsonl === compteurs : réussis 141 · échecs 5 · dernier succès 2025-10-18T08:15Z (globaux, source : en-tête du journal) FONCTION DISTANTE échecs refusés réussis dernier code qualification -------------------------------------------------------------------------------------- progress_push 2 1 NON MESURÉ — sans code addons_push 1 0 NON MESURÉ 42501 droit à ouvrir connexion 1 0 NON MESURÉ — sans code settings_push 1 0 NON MESURÉ PGRST202 manque de Host dernier message par fonction : progress_push : refusé par l'historique de versions de la cible addons_push : {"code":"42501","message":"permission denied for table addons"} settings_push : {"code":"PGRST202","message":"Could not find the function public.push_settings"} réussis PAR FONCTION : NON MESURÉ — les réussis par fonction demandent un compteur par `rpcName` … Ce relevé ne décide rien : aucune promotion, aucune coupure. Elles restent à l'opérateur. ``` `PGRST202` = **manque de Host**, `42501` = **droit à ouvrir**, le reste **à qualifier**. Le relevé qualifie ; il ne décide rien. ## ⚠ La colonne qui manque, et pourquoi elle n'est pas remplie Le format v1 du journal ne compte les succès que **globalement** (`okCount`) : les réussis **par fonction** n'existent nulle part dans le fichier. Le relevé écrit **NON MESURÉ** et nomme le contrat qui manque — un compteur par `rpcName` dans `MirrorJournal`, **les deux copies NuvioTV et NuvioMobile changeant ensemble**. Il n'écrit pas `0` : un `0` se lirait « cette fonction n'est jamais appelée », c'est-à-dire **exactement le contraire de la vérité** quand le miroir est muet. C'est la règle du dépôt — un silence n'est pas un zéro — appliquée au seul endroit où elle coûtait quelque chose. ## Trois refus | refus | pourquoi | |---|---| | **rien lu, rien mesuré** → code `2` | aucun journal trouvé ne rend pas un tableau vide ; un tableau vide se lit « le miroir va bien » | | **la liste est BORNÉE** → `≥n` | `MirrorJournal` ne garde que 50 échecs ; quand le compteur global les dépasse, un compte par fonction est un *minimum* | | **le relevé redige à son tour** | il sort de l'appareil : il ne fait pas confiance au fichier, qui peut venir d'un build antérieur ou d'une copie | Bénéfice secondaire mesuré : le relevé **qualifie** ce que le journal ne savait pas dire. Un `PGRST202` noyé dans `{"code":…}` devient une ligne « manque de Host » — c'est ce que le tableau des manques attendait. ## Le cas d'échec, testé d'abord `test_un_miroir_coupe_rend_un_releve_lisible_et_ne_leve_pas` : miroir coupé (`SIGN_IN`) **et** cible injoignable (`Connection refused`) rendent un relevé lisible, sans lever, et **sans incidence sur la lecture** — la règle qui veut qu'un échec du miroir ne remonte jamais à l'appelant ne change pas. ## Les preuves **13 tests**, dont ceux qui mordent sur les inventions possibles : | ce qui est mesuré | comment | |---|---| | les réussis par fonction ne sont jamais inventés | `NON MESURÉ` dans le tableau, `null` en JSON, et **aucune** ligne de fonction ne porte un chiffre | | un journal tronqué rend des minorants | `failureCount: 80`, deux lignes lisibles → `≥1`, jamais `1` | | rien lu n'est pas zéro | code `2` et le motif | | une ligne illisible n'est ni un échec ni un succès | comptée et nommée | | un journal sans en-tête ne fabrique pas de compteurs | `ABSENTS` / `NON MESURÉS` | | le relevé redige | un JWT et une `apikey` posés dans un message ne sortent pas | | les compteurs frais gagnent | le fichier `.compteurs.json` prime sur l'en-tête (`FOXA-1957`) | | la commande se joue | le test lance l'outil en sous-processus, comme l'opérateur | ## Porte `./run_tests.sh -q` : garde d'octets `VERT`, parité conforme, **2144 passés, 14 sautés, 0 échec** — référence `main` **2131** mesurée sur le même banc (+13). ## Ce que cette PR ne porte pas **La moitié qui manque au critère du ticket — les réussis par fonction — est côté application.** Elle demande de toucher `MirrorJournal` dans **NuvioTV et NuvioMobile**, les deux ensemble : un format qui changerait d'un seul côté recreuserait exactement la divergence que `FOXA-2959` vient de payer. Le contrat est écrit dans l'outil (`CONTRAT_ATTENDU`) et dans son test. ⚠ **CORRECTION D'UNE MESURE QUE J'AI PUBLIÉE ICI.** La première version de ce corps affirmait que « le banc de NuvioMobile est rouge sur `main` ». **C'est faux — et c'était ma mesure qui était fausse.** J'avais lancé `testAndroidHostTest` **sans** `-Pnuvio.android.distribution=full`, donc sur le flavor `playstore` par défaut, où les greffons-scrapers n'existent pas (`PluginScraperCodeFileStore` vit dans `src/androidFull/kotlin`) : le test, lui, est dans le source set partagé `androidHostTest`, et il ne pouvait pas compiler. Le dépôt dit pourtant la commande canonique, dans `settings.gradle.kts` : ``` ./gradlew :composeApp:testAndroidHostTest -Pnuvio.android.distribution=full ``` Avec elle, le banc est **VERT** — mesuré : `BUILD SUCCESSFUL in 48s`, et les trois classes visées (`MirrorJournalTest`, `MirrorWriteSinkTest`, `PluginScraperCodeFileStoreTest`) compilent et passent. Il n'y a donc **aucun préalable à réparer** : la moitié application de `FOXA-1215` est faisable, et c'est la suite de ce ticket. Rien dans `JOURNAL.md` / `CLAUDE.md` : la référence de porte se remesure sur le banc en vigueur (`RG78`), ce n'est pas à cette PR de choisir le banc.
feat(outils): le journal du miroir devient relevable, par fonction distante
All checks were successful
Porte / porte (pull_request) Successful in 1m0s
281754db9d
`FOXA-1215`. Le mode double ne sert à rien s'il ne dit pas ce que Nuvio Host ne sait pas faire :
le miroir range ses échecs dans un journal local sur l'appareil, personne ne les lit, et la liste
des manques se reconstitue à la main (`FOXA-1103`).

`outils/relever_journal_miroir.py` lit les journaux tirés des appareils et rend LE TABLEAU :

    FONCTION DISTANTE         échecs  refusés        réussis  dernier code  qualification
    progress_push                  2        1     NON MESURÉ             —  sans code
    addons_push                    1        0     NON MESURÉ         42501  droit à ouvrir
    settings_push                  1        0     NON MESURÉ      PGRST202  manque de Host

`PGRST202` est un manque de Host, `42501` un droit à ouvrir, le reste « à qualifier » : le relevé
qualifie, il ne décide rien — aucune promotion, aucune coupure.

CE QUE LE FORMAT v1 NE PERMET PAS DE DIRE, ET QUI N'EST PAS INVENTÉ. Les succès ne sont comptés
que GLOBALEMENT (`okCount`), pas par fonction. Le relevé écrit « NON MESURÉ » et nomme le contrat
qui manque (un compteur par `rpcName`, dans `MirrorJournal`, et les deux copies NuvioTV /
NuvioMobile doivent changer ensemble) au lieu de rendre `0` — un `0` se lirait « cette fonction
n'est jamais appelée », le contraire de la vérité quand le miroir est muet.

TROIS REFUS, hérités de la doctrine du dépôt :

  · **rien lu, rien mesuré** : aucun journal trouvé rend `2`, jamais un tableau vide — un tableau
    vide se lit « le miroir va bien » ;
  · **la liste est BORNÉE** (50 échecs gardés) : quand le compteur global dépasse les lignes
    lisibles, les comptes par fonction sont rendus `≥n`, jamais `n` ;
  · **le relevé redige à son tour** : il sort de l'appareil, donc il ne fait pas confiance au
    fichier, qui peut venir d'un build antérieur ou d'une copie.

LE CAS D'ÉCHEC EST TESTÉ D'ABORD : miroir coupé (`SIGN_IN`) et cible injoignable (`Connection
refused`) rendent un relevé lisible, sans lever, et sans incidence sur la lecture — la règle du
miroir ne change pas.

13 tests. Porte : 2144 passés, 14 sautés, 0 échec (référence `main` : 2131).

CE QUE CETTE PR NE PORTE PAS. La moitié qui manque au critère du ticket — les réussis PAR FONCTION
— demande de toucher `MirrorJournal` dans NuvioTV **et** NuvioMobile, et le banc de NuvioMobile est
rouge sur `main` aujourd'hui (`Unresolved reference 'PluginScraperCodeFileStore'`, dans
`androidHostTest`) : aucune modification d'application ne peut y être prouvée. Le contrat est écrit
dans l'outil et dans son test, prêt pour la passe qui rouvrira le chantier.
foXaCe force-pushed passe-20261018-101500-journalmiroir from 281754db9d
All checks were successful
Porte / porte (pull_request) Successful in 1m0s
to ff4c1d09dc
All checks were successful
Porte / porte (pull_request) Successful in 1m17s
2026-10-18 10:20:50 +02:00
Compare
feat(outils): le relevé lit le format v2 — rejeux tentés, réussis, refusés
All checks were successful
Porte / porte (pull_request) Successful in 1m3s
cca6fea981
Le journal du miroir porte désormais ses compteurs PAR FONCTION distante (`okByRpc`,
`failuresByRpc`), livrés dans NuvioTV et NuvioMobile dans la même passe. Le relevé sait donc
enfin remplir la colonne que le ticket demande, celle qui manquait :

    FONCTION DISTANTE            tentés    réussis  échecs  refusés  dernier code  qualification
    addons_push                       1          0       1        0         42501  droit à ouvrir
    progress_push                   132        131       1        1             —  sans code
    settings_push                     3          2       1        0      PGRST202  manque de Host
    library_push                      8          8       0        0             —  sans code

Deux choses que ce tableau dit et qui n'étaient pas lisibles avant :
`library_push` réussit huit fois sans jamais échouer — donc ce n'est PAS un manque de Host, et
c'est aussi important à savoir que le contraire ; `settings_push` n'a jamais réussi et échoue sur
`PGRST202` — un manque, nommé.

CE QUI NE CHANGE PAS. Les journaux v1 (écrits avant) restent lus, et le relevé y écrit toujours
« NON MESURÉ » plutôt qu'un zéro : le format v1 ne compte pas les réussis par fonction, donc 0
serait une invention — la doctrine du dépôt, un silence n'est pas un zéro.

ET LA DIFFÉRENCE QUI COMPTE, mesurée par un test : en v1, une fonction absente du journal est
« NON MESURÉE » ; en v2, une fonction absente de `okByRpc` a réellement réussi zéro fois — la
carte des compteurs est exhaustive. Le relevé distingue les deux, et le test le verrouille
(`settings_push` affiche `0`, pas `NON MESURÉ`).

Chaque journal dit son format en tête (`format v1 (global seulement)` / `format v2 (par fonction)`),
pour qu'un tableau ne mélange jamais des chiffres exacts et des chiffres inventés sans le dire.

Le reste de la discipline est inchangé : liste bornée → ce qui en dérive reste un minimum (`≥n`),
les compteurs v2 non ; rien lu → code 2 ; le relevé redige les jetons à son tour.

4 tests de plus (17 au total). Porte : 2148 passés, 14 sautés, 0 échec.
foXaCe merged commit 044396ddd7 into main 2026-10-18 10:35:49 +02:00
foXaCe deleted branch passe-20261018-101500-journalmiroir 2026-10-18 10:35:49 +02:00
Sign in to join this conversation.
No reviewers
No labels
montee-majeure
No milestone
No project
No assignees
1 participant
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/backend!113
No description provided.