[ADD] analyse: ausculter une base qui n'est pas ici
Les analyses existaient ; le chemin d'AVANT manquait. La base d'un client
est dans un zip, derrière une URL, ou vivante sur un serveur.
« Restant de migration » a dû être écrit : check_migration_quality compare
les bases de PALIER et exige le journal de progression — devant une
sauvegarde isolée, ni l'un ni l'autre n'existe.
Ses compteurs évidents ont été écartés après mesure. Comparés à la base
d'ORIGINE : champs sans colonne 25 → 72, modèles sans table 90 → 158.
Vingt-cinq et quatre-vingt-dix AVANT toute migration : du bruit. Ne
restent que les constats faux en eux-mêmes, 0 avant, non nuls après —
9 langues au drapeau NULL, 68 tables m2m absentes, 414 index doublés.
Le passe-plat RPC n'accepte que la lecture. psql l'obtient du serveur ;
une session RPC n'a rien d'équivalent, et la liste blanche est donc
appliquée dans le passe-plat, pas chez l'appelant.
--- EN ---
The analyses existed; the path BEFORE them did not. A customer database
sits in a zip, behind a URL, or live on a server.
« Migration leftovers » had to be written: check_migration_quality
compares STEP databases and needs the progression log — facing a lone
backup, neither exists.
Its obvious counters were dropped after measuring. Against the ORIGINAL
database: fields with no column 25 → 72, models with no table 90 → 158.
Twenty-five and ninety BEFORE any migration: noise. Only what is wrong in
itself remains, 0 before and non-zero after — 9 languages with a NULL
flag, 68 missing m2m tables, 414 duplicated indexes.
The RPC proxy only reads. psql gets that from the server; an RPC session
has no equivalent, so the allowlist lives in the proxy, not the caller.
Assisted-by: Claude Opus 5
2026-08-24 07:21:37 -04:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
|
# © 2021-2026 TechnoLibre (http://www.technolibre.ca)
|
|
|
|
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
|
|
|
|
|
|
|
|
|
|
"""Ce qu'une migration a laissé derrière elle, lu dans UNE seule base.
|
|
|
|
|
|
|
|
|
|
`check_migration_quality` répond à une autre question : il compare les
|
|
|
|
|
bases de PALIER qu'une migration locale a laissées, et il lui faut le
|
|
|
|
|
journal de progression. Devant la sauvegarde d'un client, ni l'une ni
|
|
|
|
|
l'autre n'existe. Il fallait donc un outil qui n'ait besoin que de la
|
|
|
|
|
base qu'on a sous la main.
|
|
|
|
|
|
|
|
|
|
Le piège, et pourquoi la moitié des contrôles évidents ont été écartés
|
|
|
|
|
--------------------------------------------------------------------
|
|
|
|
|
Un compteur absolu ne prouve rien. Mesuré sur une chaîne 12 → 18 réelle,
|
|
|
|
|
en comparant la base d'ORIGINE à la migrée :
|
|
|
|
|
|
|
|
|
|
champs stockés sans colonne 25 → 72 ← 25 AVANT toute migration
|
|
|
|
|
modèles sans table 90 → 158 ← 90 AVANT
|
|
|
|
|
contraintes orphelines 155 → 530 ← 155 AVANT
|
|
|
|
|
|
|
|
|
|
Ces trois-là paraissent accablants et ne le sont pas : un modèle abstrait
|
|
|
|
|
n'a jamais de table, un champ hérité n'a jamais sa colonne à lui. Affichés
|
|
|
|
|
bruts, ils font peur pour rien — et un rapport qui fait peur pour rien
|
|
|
|
|
finit par être ignoré en entier.
|
|
|
|
|
|
|
|
|
|
Ne restent ici que les constats qui se jugent SANS point de comparaison,
|
|
|
|
|
parce qu'ils sont faux en eux-mêmes. Les mêmes bases, mêmes mesures :
|
|
|
|
|
|
|
|
|
|
ir_model_relation sans table 0 → 68 la table m2m est nommée,
|
|
|
|
|
elle n'existe pas
|
|
|
|
|
index doublés convention 17 0 → 414 Odoo 17 renomme, sans
|
|
|
|
|
supprimer l'ancien
|
|
|
|
|
liste de prix par défaut absente 0 → 1 `product` installé, son
|
|
|
|
|
xmlid pas là
|
|
|
|
|
|
[REM] résidus : retirer le contrôle res_lang.active à NULL
Le contrôle jugeait cassée une langue dont `active` est NULL : listée
nulle part, plus réactivable. Faux dans la source 18 — un domaine
('active','=',False) compile en (IS NULL OR = FALSE) (models.py:3217),
l'action du menu Langues porte active_test: False, le tri passe par
COALESCE(active, FALSE) et la lecture rend bool(value).
Odoo écrit ce NULL : active = fields.Boolean() sans défaut
(res_lang.py:64) et un res.lang.csv sans la colonne, soit un NULL par
langue ajoutée au catalogue. « Zéro avant, non nul après » ne suffit
donc pas. Un test refuse désormais une clé que plus rien ne définit.
--- EN ---
The check called a language broken when `active` is NULL: listed
nowhere, no longer re-enablable. False in the 18 source — an
('active','=',False) domain compiles to (IS NULL OR = FALSE)
(models.py:3217), the Languages menu action carries active_test: False,
sorting goes through COALESCE(active, FALSE), reading returns bool(value).
Odoo writes that NULL itself: active = fields.Boolean() with no default
(res_lang.py:64) and a res.lang.csv without the column, one NULL per
language added to the catalogue. So "zero before, nonzero after" is not
enough. A test now refuses a key that nothing defines any more.
Assisted-by: Claude Opus 5
(cherry picked from commit ee932334cfde63c1af831316549a432c3d382721)
2026-08-27 02:15:30 -04:00
|
|
|
Zéro avant, non nul après : aucun de ceux-là ne peut s'expliquer
|
[ADD] analyse: ausculter une base qui n'est pas ici
Les analyses existaient ; le chemin d'AVANT manquait. La base d'un client
est dans un zip, derrière une URL, ou vivante sur un serveur.
« Restant de migration » a dû être écrit : check_migration_quality compare
les bases de PALIER et exige le journal de progression — devant une
sauvegarde isolée, ni l'un ni l'autre n'existe.
Ses compteurs évidents ont été écartés après mesure. Comparés à la base
d'ORIGINE : champs sans colonne 25 → 72, modèles sans table 90 → 158.
Vingt-cinq et quatre-vingt-dix AVANT toute migration : du bruit. Ne
restent que les constats faux en eux-mêmes, 0 avant, non nuls après —
9 langues au drapeau NULL, 68 tables m2m absentes, 414 index doublés.
Le passe-plat RPC n'accepte que la lecture. psql l'obtient du serveur ;
une session RPC n'a rien d'équivalent, et la liste blanche est donc
appliquée dans le passe-plat, pas chez l'appelant.
--- EN ---
The analyses existed; the path BEFORE them did not. A customer database
sits in a zip, behind a URL, or live on a server.
« Migration leftovers » had to be written: check_migration_quality
compares STEP databases and needs the progression log — facing a lone
backup, neither exists.
Its obvious counters were dropped after measuring. Against the ORIGINAL
database: fields with no column 25 → 72, models with no table 90 → 158.
Twenty-five and ninety BEFORE any migration: noise. Only what is wrong in
itself remains, 0 before and non-zero after — 9 languages with a NULL
flag, 68 missing m2m tables, 414 duplicated indexes.
The RPC proxy only reads. psql gets that from the server; an RPC session
has no equivalent, so the allowlist lives in the proxy, not the caller.
Assisted-by: Claude Opus 5
2026-08-24 07:21:37 -04:00
|
|
|
autrement que par la migration.
|
|
|
|
|
|
[REM] résidus : retirer le contrôle res_lang.active à NULL
Le contrôle jugeait cassée une langue dont `active` est NULL : listée
nulle part, plus réactivable. Faux dans la source 18 — un domaine
('active','=',False) compile en (IS NULL OR = FALSE) (models.py:3217),
l'action du menu Langues porte active_test: False, le tri passe par
COALESCE(active, FALSE) et la lecture rend bool(value).
Odoo écrit ce NULL : active = fields.Boolean() sans défaut
(res_lang.py:64) et un res.lang.csv sans la colonne, soit un NULL par
langue ajoutée au catalogue. « Zéro avant, non nul après » ne suffit
donc pas. Un test refuse désormais une clé que plus rien ne définit.
--- EN ---
The check called a language broken when `active` is NULL: listed
nowhere, no longer re-enablable. False in the 18 source — an
('active','=',False) domain compiles to (IS NULL OR = FALSE)
(models.py:3217), the Languages menu action carries active_test: False,
sorting goes through COALESCE(active, FALSE), reading returns bool(value).
Odoo writes that NULL itself: active = fields.Boolean() with no default
(res_lang.py:64) and a res.lang.csv without the column, one NULL per
language added to the catalogue. So "zero before, nonzero after" is not
enough. A test now refuses a key that nothing defines any more.
Assisted-by: Claude Opus 5
(cherry picked from commit ee932334cfde63c1af831316549a432c3d382721)
2026-08-27 02:15:30 -04:00
|
|
|
« Zéro avant, non nul après » ne suffit pourtant pas
|
|
|
|
|
----------------------------------------------------
|
|
|
|
|
Un quatrième contrôle a vécu ici et n'y est plus : `res_lang.active` à
|
|
|
|
|
NULL, 0 avant et 9 après. Le chiffre était juste, la conclusion fausse.
|
|
|
|
|
|
|
|
|
|
Mesuré palier par palier : 0, 1, 2, 3, 5, 8, 9 — les NULL arrivent avec
|
|
|
|
|
les langues que CHAQUE version ajoute au catalogue. Et c'est Odoo
|
|
|
|
|
lui-même qui les écrit : `active = fields.Boolean()` sans défaut
|
|
|
|
|
(res_lang.py:64) et un `res.lang.csv` sans colonne `active` — l'INSERT ne
|
|
|
|
|
porte pas la colonne, PostgreSQL y met NULL.
|
|
|
|
|
|
|
|
|
|
Aucune conséquence, vérifiée dans la source de la 18 : un domaine
|
|
|
|
|
`('active','=',False)` compile en `(IS NULL OR = FALSE)`
|
|
|
|
|
(models.py:3217-3222), l'action du menu Langues porte `active_test: False`
|
|
|
|
|
(res_lang_views.xml:136), le tri passe par `COALESCE(active, FALSE)`
|
|
|
|
|
(models.py:5692) et la lecture rend `bool(value)` (fields.py:1515). NULL
|
|
|
|
|
et FALSE sont indiscernables partout.
|
|
|
|
|
|
|
|
|
|
La leçon : la croissance mesurée doit AUSSI être inexplicable autrement.
|
|
|
|
|
Ici elle s'expliquait très bien.
|
|
|
|
|
|
[FIX] analyse: retirer un constat qui faisait peur pour rien
« ir_model_relation nomme une table absente » : 0 avant la migration, 68
après. Le profil idéal — et aucune conséquence.
Son unique consommateur, _module_data_uninstall dans
base/models/ir_model.py, teste sql.table_exists() AVANT de supprimer : la
ligne périmée est ignorée, puis effacée. J'écrivais « la prochaine mise à
jour de module tente de la modifier et échoue » : c'est faux.
Les 68 appartiennent de plus à des modules INSTALLÉS, et database_cleanup
ne touche que les désinstallés — la réparation désignée n'en aurait
réparé aucune.
Un constat sans conséquence et sans geste possible est du bruit, quelle
que soit la netteté du signal. Restent trois constats, tous réels.
--- EN ---
« ir_model_relation names a missing table »: 0 before the migration, 68
after. The ideal profile — and no consequence whatsoever.
Its only consumer, _module_data_uninstall in base/models/ir_model.py,
tests sql.table_exists() BEFORE dropping: the stale row is skipped, then
unlinked. I wrote "the next module update tries to alter it and fails":
that is false.
The 68 also belong to INSTALLED modules, and database_cleanup only
touches uninstalled ones — the repair I named would have fixed none.
A finding with no consequence and no possible action is noise, however
clean the signal. Three findings remain, all real.
Assisted-by: Claude Opus 5
2026-08-24 23:28:23 -04:00
|
|
|
Un quatrième a été RETIRÉ après vérification
|
|
|
|
|
--------------------------------------------
|
|
|
|
|
« ir_model_relation nomme une table absente » : 0 avant, 68 après, le
|
|
|
|
|
profil idéal. Et sans la moindre conséquence. Son unique consommateur,
|
|
|
|
|
`_module_data_uninstall` dans `base/models/ir_model.py`, teste
|
|
|
|
|
`sql.table_exists(...)` AVANT de supprimer : la ligne périmée est
|
|
|
|
|
ignorée, puis effacée. Les 68 appartiennent en outre à des modules
|
|
|
|
|
INSTALLÉS, que `database_cleanup` ne touche pas — la réparation que ce
|
|
|
|
|
fichier désignait n'en aurait réparé aucune.
|
|
|
|
|
|
|
|
|
|
Un constat sans conséquence et sans geste possible est du bruit, quelle
|
|
|
|
|
que soit la netteté du signal. Il est parti.
|
|
|
|
|
|
[ADD] analyse: ausculter une base qui n'est pas ici
Les analyses existaient ; le chemin d'AVANT manquait. La base d'un client
est dans un zip, derrière une URL, ou vivante sur un serveur.
« Restant de migration » a dû être écrit : check_migration_quality compare
les bases de PALIER et exige le journal de progression — devant une
sauvegarde isolée, ni l'un ni l'autre n'existe.
Ses compteurs évidents ont été écartés après mesure. Comparés à la base
d'ORIGINE : champs sans colonne 25 → 72, modèles sans table 90 → 158.
Vingt-cinq et quatre-vingt-dix AVANT toute migration : du bruit. Ne
restent que les constats faux en eux-mêmes, 0 avant, non nuls après —
9 langues au drapeau NULL, 68 tables m2m absentes, 414 index doublés.
Le passe-plat RPC n'accepte que la lecture. psql l'obtient du serveur ;
une session RPC n'a rien d'équivalent, et la liste blanche est donc
appliquée dans le passe-plat, pas chez l'appelant.
--- EN ---
The analyses existed; the path BEFORE them did not. A customer database
sits in a zip, behind a URL, or live on a server.
« Migration leftovers » had to be written: check_migration_quality
compares STEP databases and needs the progression log — facing a lone
backup, neither exists.
Its obvious counters were dropped after measuring. Against the ORIGINAL
database: fields with no column 25 → 72, models with no table 90 → 158.
Twenty-five and ninety BEFORE any migration: noise. Only what is wrong in
itself remains, 0 before and non-zero after — 9 languages with a NULL
flag, 68 missing m2m tables, 414 duplicated indexes.
The RPC proxy only reads. psql gets that from the server; an RPC session
has no equivalent, so the allowlist lives in the proxy, not the caller.
Assisted-by: Claude Opus 5
2026-08-24 07:21:37 -04:00
|
|
|
Chaque constat nomme l'outil qui le répare. Un rapport qui montre un
|
|
|
|
|
dégât sans dire quoi lancer oblige à chercher, et on ne cherche pas.
|
|
|
|
|
|
|
|
|
|
Lecture seule : `default_transaction_read_only=on`, imposé par le serveur.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
import os
|
|
|
|
|
import sys
|
|
|
|
|
|
|
|
|
|
sys.path.append(
|
|
|
|
|
os.path.normpath(os.path.join(os.path.dirname(__file__), "..", ".."))
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
from script.todo.todo_i18n import t
|
|
|
|
|
except Exception: # pragma: no cover - repli si i18n indisponible
|
|
|
|
|
|
|
|
|
|
def t(key: str) -> str:
|
|
|
|
|
return key
|
|
|
|
|
|
|
|
|
|
|
[ADD] qualité de migration : verdicts, sources, revue
Le rapport comparait les paliers sans dire si la migration avait réussi,
alors que les verdicts dorment déjà dans lst_event du journal de
progression : des contrôles en échec y restent sans remonter nulle part.
Trois sections s'ajoutent sous les paliers : les verdicts, rattachés au
palier ODOO et non au compteur du pilote, décalé d'un rang ; où vivent les
traces, car config.conf laisse logfile= vide et la sortie d'Odoo meurt avec
le terminal ; et la revue, six étapes lançables par « r ». Le contrôle de
résidus porte la même section sans toucher son code de sortie : un verdict
vient du fichier, pas de la base.
--- EN ---
The report compared the tiers without saying whether the migration had
succeeded, while the verdicts already sit in lst_event of the progression
file: failed checks stay there and surface nowhere.
Three sections are added below the tiers: the verdicts, tied to the ODOO
tier and not to the driver counter, which is off by one; where the traces
live, since config.conf leaves logfile= empty and Odoo's output dies with
the terminal; and the review, six steps runnable with "r". The residue
check carries the same section without touching its exit code: a verdict
comes from the file, not from the database.
Assisted-by: Claude Opus 5
(cherry picked from commit b05e0333c4b94d58eb794f09a2459e41d826c655)
2026-08-26 07:46:32 -04:00
|
|
|
from script.analyse import check_migration_quality as quality # noqa: E402
|
[ADD] analyse: ausculter une base qui n'est pas ici
Les analyses existaient ; le chemin d'AVANT manquait. La base d'un client
est dans un zip, derrière une URL, ou vivante sur un serveur.
« Restant de migration » a dû être écrit : check_migration_quality compare
les bases de PALIER et exige le journal de progression — devant une
sauvegarde isolée, ni l'un ni l'autre n'existe.
Ses compteurs évidents ont été écartés après mesure. Comparés à la base
d'ORIGINE : champs sans colonne 25 → 72, modèles sans table 90 → 158.
Vingt-cinq et quatre-vingt-dix AVANT toute migration : du bruit. Ne
restent que les constats faux en eux-mêmes, 0 avant, non nuls après —
9 langues au drapeau NULL, 68 tables m2m absentes, 414 index doublés.
Le passe-plat RPC n'accepte que la lecture. psql l'obtient du serveur ;
une session RPC n'a rien d'équivalent, et la liste blanche est donc
appliquée dans le passe-plat, pas chez l'appelant.
--- EN ---
The analyses existed; the path BEFORE them did not. A customer database
sits in a zip, behind a URL, or live on a server.
« Migration leftovers » had to be written: check_migration_quality
compares STEP databases and needs the progression log — facing a lone
backup, neither exists.
Its obvious counters were dropped after measuring. Against the ORIGINAL
database: fields with no column 25 → 72, models with no table 90 → 158.
Twenty-five and ninety BEFORE any migration: noise. Only what is wrong in
itself remains, 0 before and non-zero after — 9 languages with a NULL
flag, 68 missing m2m tables, 414 duplicated indexes.
The RPC proxy only reads. psql gets that from the server; an RPC session
has no equivalent, so the allowlist lives in the proxy, not the caller.
Assisted-by: Claude Opus 5
2026-08-24 07:21:37 -04:00
|
|
|
from script.analyse import lib_analyse # noqa: E402
|
[ADD] verdicts : tous les paliers, leur journal, et la bascule
L'écran de qualité ne listait que les échecs, quand la question devant
une base migrée est « qu'a-t-on vérifié » : les quatorze verdicts
s'affichent, de la 12 à la 18, seule façon de voir qu'un échec a été
rattrapé à un palier plus haut. Le panneau ne portait que la commande ;
il montre le passage du journal d'étape qui l'entoure, garde par tee ce
qu'il lance lui-même et le relit sans relancer. La sortie de l'outil,
elle, part sur le terminal : un tube ferait renoncer les pleins écrans.
Relancer un test d'un autre palier ouvrait la base avec la mauvaise
version, qui y écrit avant d'échouer ; l'écran demande avant de basculer.
--- EN ---
The quality screen listed failures only, when the question in front of a
migrated database is "what did we check": all fourteen verdicts now show,
12 through 18, the only way to see that a failure at one tier was
recovered higher up. The panel carried only the command; it shows the
step-log passage around it, keeps by tee what it runs itself and re-reads
that without rerunning. The tool output goes to the terminal: a pipe
would make full-screen tools give up. Replaying a test from another tier
opened the database with the wrong version, which writes before it
fails; the screen asks before switching the checkout.
Assisted-by: Claude Opus 5
(cherry picked from commit 2d460b7c777d39a887dabfe7cf5405864c6c3f8c)
2026-08-27 04:36:03 -04:00
|
|
|
from script.todo import migration_status as status # noqa: E402
|
[ADD] analyse: ausculter une base qui n'est pas ici
Les analyses existaient ; le chemin d'AVANT manquait. La base d'un client
est dans un zip, derrière une URL, ou vivante sur un serveur.
« Restant de migration » a dû être écrit : check_migration_quality compare
les bases de PALIER et exige le journal de progression — devant une
sauvegarde isolée, ni l'un ni l'autre n'existe.
Ses compteurs évidents ont été écartés après mesure. Comparés à la base
d'ORIGINE : champs sans colonne 25 → 72, modèles sans table 90 → 158.
Vingt-cinq et quatre-vingt-dix AVANT toute migration : du bruit. Ne
restent que les constats faux en eux-mêmes, 0 avant, non nuls après —
9 langues au drapeau NULL, 68 tables m2m absentes, 414 index doublés.
Le passe-plat RPC n'accepte que la lecture. psql l'obtient du serveur ;
une session RPC n'a rien d'équivalent, et la liste blanche est donc
appliquée dans le passe-plat, pas chez l'appelant.
--- EN ---
The analyses existed; the path BEFORE them did not. A customer database
sits in a zip, behind a URL, or live on a server.
« Migration leftovers » had to be written: check_migration_quality
compares STEP databases and needs the progression log — facing a lone
backup, neither exists.
Its obvious counters were dropped after measuring. Against the ORIGINAL
database: fields with no column 25 → 72, models with no table 90 → 158.
Twenty-five and ninety BEFORE any migration: noise. Only what is wrong in
itself remains, 0 before and non-zero after — 9 languages with a NULL
flag, 68 missing m2m tables, 414 duplicated indexes.
The RPC proxy only reads. psql gets that from the server; an RPC session
has no equivalent, so the allowlist lives in the proxy, not the caller.
Assisted-by: Claude Opus 5
2026-08-24 07:21:37 -04:00
|
|
|
|
|
|
|
|
COULEURS = {
|
|
|
|
|
"broken": "\033[31m",
|
[ADD] qualité de migration : verdicts, sources, revue
Le rapport comparait les paliers sans dire si la migration avait réussi,
alors que les verdicts dorment déjà dans lst_event du journal de
progression : des contrôles en échec y restent sans remonter nulle part.
Trois sections s'ajoutent sous les paliers : les verdicts, rattachés au
palier ODOO et non au compteur du pilote, décalé d'un rang ; où vivent les
traces, car config.conf laisse logfile= vide et la sortie d'Odoo meurt avec
le terminal ; et la revue, six étapes lançables par « r ». Le contrôle de
résidus porte la même section sans toucher son code de sortie : un verdict
vient du fichier, pas de la base.
--- EN ---
The report compared the tiers without saying whether the migration had
succeeded, while the verdicts already sit in lst_event of the progression
file: failed checks stay there and surface nowhere.
Three sections are added below the tiers: the verdicts, tied to the ODOO
tier and not to the driver counter, which is off by one; where the traces
live, since config.conf leaves logfile= empty and Odoo's output dies with
the terminal; and the review, six steps runnable with "r". The residue
check carries the same section without touching its exit code: a verdict
comes from the file, not from the database.
Assisted-by: Claude Opus 5
(cherry picked from commit b05e0333c4b94d58eb794f09a2459e41d826c655)
2026-08-26 07:46:32 -04:00
|
|
|
"step": "\033[36m",
|
[ADD] analyse: ausculter une base qui n'est pas ici
Les analyses existaient ; le chemin d'AVANT manquait. La base d'un client
est dans un zip, derrière une URL, ou vivante sur un serveur.
« Restant de migration » a dû être écrit : check_migration_quality compare
les bases de PALIER et exige le journal de progression — devant une
sauvegarde isolée, ni l'un ni l'autre n'existe.
Ses compteurs évidents ont été écartés après mesure. Comparés à la base
d'ORIGINE : champs sans colonne 25 → 72, modèles sans table 90 → 158.
Vingt-cinq et quatre-vingt-dix AVANT toute migration : du bruit. Ne
restent que les constats faux en eux-mêmes, 0 avant, non nuls après —
9 langues au drapeau NULL, 68 tables m2m absentes, 414 index doublés.
Le passe-plat RPC n'accepte que la lecture. psql l'obtient du serveur ;
une session RPC n'a rien d'équivalent, et la liste blanche est donc
appliquée dans le passe-plat, pas chez l'appelant.
--- EN ---
The analyses existed; the path BEFORE them did not. A customer database
sits in a zip, behind a URL, or live on a server.
« Migration leftovers » had to be written: check_migration_quality
compares STEP databases and needs the progression log — facing a lone
backup, neither exists.
Its obvious counters were dropped after measuring. Against the ORIGINAL
database: fields with no column 25 → 72, models with no table 90 → 158.
Twenty-five and ninety BEFORE any migration: noise. Only what is wrong in
itself remains, 0 before and non-zero after — 9 languages with a NULL
flag, 68 missing m2m tables, 414 duplicated indexes.
The RPC proxy only reads. psql gets that from the server; an RPC session
has no equivalent, so the allowlist lives in the proxy, not the caller.
Assisted-by: Claude Opus 5
2026-08-24 07:21:37 -04:00
|
|
|
"watch": "\033[33m",
|
|
|
|
|
"ok": "\033[32m",
|
|
|
|
|
"dim": "\033[90m",
|
|
|
|
|
}
|
|
|
|
|
RESET = "\033[0m"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def paint(texte, genre, colour):
|
|
|
|
|
"""Teinter, ou rendre le texte tel quel quand la couleur est coupée."""
|
|
|
|
|
if not colour:
|
|
|
|
|
return texte
|
|
|
|
|
return f"{COULEURS.get(genre, '')}{texte}{RESET}"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# `sql` doit rendre UN nombre. `repair` nomme l'outil qui corrige, ou None
|
|
|
|
|
# quand il n'y en a pas encore — le dire vaut mieux que de laisser croire.
|
|
|
|
|
CONTROLES = (
|
|
|
|
|
{
|
|
|
|
|
"key": "stuck_modules",
|
|
|
|
|
"title": "Modules stuck between two states",
|
|
|
|
|
"why": "A migration that stopped mid-flight leaves them there;"
|
|
|
|
|
" Odoo will retry the transition at every start.",
|
|
|
|
|
"sql": "SELECT count(*) FROM ir_module_module"
|
|
|
|
|
" WHERE state IN ('to install','to upgrade','to remove')",
|
|
|
|
|
"gravity": "broken",
|
|
|
|
|
"repair": "script/todo/todo_upgrade.py (uninstall_one_by_one)",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"key": "duplicate_index",
|
|
|
|
|
"title": "Indexes duplicated by the Odoo 17 renaming",
|
|
|
|
|
"why": "Odoo 17 changed the naming convention without dropping the"
|
[ADD] migration: brancher les deux réparations qui ne tournaient jamais
fix_duplicate_index et restore_config_defaults étaient écrits, éprouvés,
et absents du pilote. Chaque migration refabriquait donc ses index
redondants et reperdait sa liste de prix.
Mesuré sur DEUX chaînes 12 → 18 indépendantes, même base source : 68
relations orphelines, 9 langues au drapeau NULL, une liste de prix
absente, 376 paires d'index en double — les mêmes nombres des deux côtés.
Ce n'est pas un accident d'exécution, c'est le chemin lui-même.
Les index à partir du palier 17 : avant, la convention n'a pas changé.
Les réglages au DERNIER palier : l'outil charge le registre, et seul
l'état final compte. Les deux en wait_at_error=False — avec --apply, le
code 1 dit « il en reste », pas « je suis tombé », et cela n'arrête pas
six paliers.
--- EN ---
fix_duplicate_index and restore_config_defaults were written, proven, and
absent from the driver. Every migration therefore rebuilt its redundant
indexes and lost its default pricelist again.
Measured on TWO independent 12 → 18 chains from the same source: 68
orphan relations, 9 languages with a NULL flag, one missing pricelist,
376 duplicate index pairs — the same numbers on both. Not a fluke of one
run: the path itself.
Indexes from step 17 onward: before that the convention had not changed.
Config defaults at the LAST step: the tool loads the registry, and only
the final state matters. Both with wait_at_error=False — with --apply,
exit 1 means "some remain", not "I crashed", and that must not halt six
steps.
Assisted-by: Claude Opus 5
2026-08-24 22:55:21 -04:00
|
|
|
" old index: both are maintained on every write. This count is a"
|
|
|
|
|
" cheap signal — the repair tool compares columns and"
|
|
|
|
|
" uniqueness, and is the one to trust.",
|
[ADD] analyse: ausculter une base qui n'est pas ici
Les analyses existaient ; le chemin d'AVANT manquait. La base d'un client
est dans un zip, derrière une URL, ou vivante sur un serveur.
« Restant de migration » a dû être écrit : check_migration_quality compare
les bases de PALIER et exige le journal de progression — devant une
sauvegarde isolée, ni l'un ni l'autre n'existe.
Ses compteurs évidents ont été écartés après mesure. Comparés à la base
d'ORIGINE : champs sans colonne 25 → 72, modèles sans table 90 → 158.
Vingt-cinq et quatre-vingt-dix AVANT toute migration : du bruit. Ne
restent que les constats faux en eux-mêmes, 0 avant, non nuls après —
9 langues au drapeau NULL, 68 tables m2m absentes, 414 index doublés.
Le passe-plat RPC n'accepte que la lecture. psql l'obtient du serveur ;
une session RPC n'a rien d'équivalent, et la liste blanche est donc
appliquée dans le passe-plat, pas chez l'appelant.
--- EN ---
The analyses existed; the path BEFORE them did not. A customer database
sits in a zip, behind a URL, or live on a server.
« Migration leftovers » had to be written: check_migration_quality
compares STEP databases and needs the progression log — facing a lone
backup, neither exists.
Its obvious counters were dropped after measuring. Against the ORIGINAL
database: fields with no column 25 → 72, models with no table 90 → 158.
Twenty-five and ninety BEFORE any migration: noise. Only what is wrong in
itself remains, 0 before and non-zero after — 9 languages with a NULL
flag, 68 missing m2m tables, 414 duplicated indexes.
The RPC proxy only reads. psql gets that from the server; an RPC session
has no equivalent, so the allowlist lives in the proxy, not the caller.
Assisted-by: Claude Opus 5
2026-08-24 07:21:37 -04:00
|
|
|
"sql": "SELECT count(*) FROM pg_indexes a WHERE a.schemaname='public'"
|
|
|
|
|
" AND a.indexname ~ '__[a-z0-9_]+_index$'"
|
|
|
|
|
" AND EXISTS (SELECT 1 FROM pg_indexes b WHERE b.schemaname='public'"
|
|
|
|
|
" AND b.tablename = a.tablename"
|
|
|
|
|
" AND b.indexname = replace(a.indexname, '__', '_'))",
|
|
|
|
|
"gravity": "watch",
|
|
|
|
|
"repair": "script/odoo/migration/fix_duplicate_index.py --apply",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"key": "missing_pricelist",
|
|
|
|
|
"title": "Default pricelist missing while product is installed",
|
|
|
|
|
"why": "product.list0 was declared up to Odoo 16 only; nothing"
|
|
|
|
|
" recreates it, and a quotation has no price list to pick.",
|
2026-08-25 00:58:14 -04:00
|
|
|
# On cherche une LISTE, pas son xmlid. Mesuré : la réparation
|
|
|
|
|
# laisse Odoo créer « Par défaut » sans poser `product.list0` —
|
|
|
|
|
# chercher l'xmlid signalait donc une base parfaitement saine, et
|
|
|
|
|
# aurait signalé de même celle d'un client qui a créé la sienne à
|
|
|
|
|
# la main.
|
[FIX] liste de prix : décider sur la fonctionnalité, pas sur l'exécutant
`env.user.has_group()` répond « oui » dès que l'exécutant est membre du
groupe, et la migration l'y ajoute en cours de route. Or la case des
réglages lit tout autre chose : ce que `base.group_user` IMPLIQUE
(res_config.py, « which groups are implied by the group Employee »).
Décider sur l'exécutant créait une liste de prix dans une base dont la
fonctionnalité est éteinte, et Odoo prévenait à chaque ouverture des
réglages qu'il allait l'archiver. Le contrôle « restant de migration »
posait la même mauvaise question ; les deux lisent désormais l'implication
du groupe. Vérifié sur copie jetable, dans les deux sens : fonctionnalité
éteinte, rien n'est signalé ; activée, constat et réparation reviennent.
--- EN ---
`env.user.has_group()` says yes as soon as the caller belongs to the
group, and the migration adds it along the way. But the settings checkbox
reads something else: what `base.group_user` IMPLIES (res_config.py,
"which groups are implied by the group Employee"). Deciding on the caller
created a pricelist in a database whose feature is off, and Odoo warned on
every opening of the settings that it would archive it. The
migration-residue check asked the same wrong question; both now read the
group implication. Verified on a throwaway copy, both ways: feature off,
nothing is reported; feature on, finding and repair come back.
Assisted-by: Claude Opus 5
(cherry picked from commit 38ba25e01894f45fb956bcbb08cc3d96a125e648)
2026-08-26 03:58:45 -04:00
|
|
|
#
|
|
|
|
|
# Et seulement si la FONCTIONNALITÉ est active, c'est-à-dire si
|
|
|
|
|
# `base.group_user` implique `product.group_product_pricelist` —
|
|
|
|
|
# la question exacte que pose la case des réglages. Sans elle,
|
|
|
|
|
# l'absence de liste est normale ; signaler quand même menait à
|
|
|
|
|
# créer une liste dans une base qui n'en veut pas, et Odoo
|
|
|
|
|
# prévenait alors à chaque ouverture des réglages qu'il allait
|
|
|
|
|
# l'archiver.
|
[ADD] analyse: ausculter une base qui n'est pas ici
Les analyses existaient ; le chemin d'AVANT manquait. La base d'un client
est dans un zip, derrière une URL, ou vivante sur un serveur.
« Restant de migration » a dû être écrit : check_migration_quality compare
les bases de PALIER et exige le journal de progression — devant une
sauvegarde isolée, ni l'un ni l'autre n'existe.
Ses compteurs évidents ont été écartés après mesure. Comparés à la base
d'ORIGINE : champs sans colonne 25 → 72, modèles sans table 90 → 158.
Vingt-cinq et quatre-vingt-dix AVANT toute migration : du bruit. Ne
restent que les constats faux en eux-mêmes, 0 avant, non nuls après —
9 langues au drapeau NULL, 68 tables m2m absentes, 414 index doublés.
Le passe-plat RPC n'accepte que la lecture. psql l'obtient du serveur ;
une session RPC n'a rien d'équivalent, et la liste blanche est donc
appliquée dans le passe-plat, pas chez l'appelant.
--- EN ---
The analyses existed; the path BEFORE them did not. A customer database
sits in a zip, behind a URL, or live on a server.
« Migration leftovers » had to be written: check_migration_quality
compares STEP databases and needs the progression log — facing a lone
backup, neither exists.
Its obvious counters were dropped after measuring. Against the ORIGINAL
database: fields with no column 25 → 72, models with no table 90 → 158.
Twenty-five and ninety BEFORE any migration: noise. Only what is wrong in
itself remains, 0 before and non-zero after — 9 languages with a NULL
flag, 68 missing m2m tables, 414 duplicated indexes.
The RPC proxy only reads. psql gets that from the server; an RPC session
has no equivalent, so the allowlist lives in the proxy, not the caller.
Assisted-by: Claude Opus 5
2026-08-24 07:21:37 -04:00
|
|
|
"sql": "SELECT CASE WHEN EXISTS (SELECT 1 FROM ir_module_module"
|
|
|
|
|
" WHERE name='product' AND state='installed')"
|
2026-08-25 00:58:14 -04:00
|
|
|
" AND to_regclass('public.product_pricelist') IS NOT NULL"
|
[FIX] liste de prix : décider sur la fonctionnalité, pas sur l'exécutant
`env.user.has_group()` répond « oui » dès que l'exécutant est membre du
groupe, et la migration l'y ajoute en cours de route. Or la case des
réglages lit tout autre chose : ce que `base.group_user` IMPLIQUE
(res_config.py, « which groups are implied by the group Employee »).
Décider sur l'exécutant créait une liste de prix dans une base dont la
fonctionnalité est éteinte, et Odoo prévenait à chaque ouverture des
réglages qu'il allait l'archiver. Le contrôle « restant de migration »
posait la même mauvaise question ; les deux lisent désormais l'implication
du groupe. Vérifié sur copie jetable, dans les deux sens : fonctionnalité
éteinte, rien n'est signalé ; activée, constat et réparation reviennent.
--- EN ---
`env.user.has_group()` says yes as soon as the caller belongs to the
group, and the migration adds it along the way. But the settings checkbox
reads something else: what `base.group_user` IMPLIES (res_config.py,
"which groups are implied by the group Employee"). Deciding on the caller
created a pricelist in a database whose feature is off, and Odoo warned on
every opening of the settings that it would archive it. The
migration-residue check asked the same wrong question; both now read the
group implication. Verified on a throwaway copy, both ways: feature off,
nothing is reported; feature on, finding and repair come back.
Assisted-by: Claude Opus 5
(cherry picked from commit 38ba25e01894f45fb956bcbb08cc3d96a125e648)
2026-08-26 03:58:45 -04:00
|
|
|
" AND EXISTS (SELECT 1 FROM res_groups_implied_rel r"
|
|
|
|
|
" JOIN ir_model_data u ON u.model='res.groups' AND u.res_id=r.gid"
|
|
|
|
|
" AND u.module='base' AND u.name='group_user'"
|
|
|
|
|
" JOIN ir_model_data g ON g.model='res.groups' AND g.res_id=r.hid"
|
|
|
|
|
" AND g.module='product' AND g.name='group_product_pricelist')"
|
2026-08-25 00:58:14 -04:00
|
|
|
" AND NOT EXISTS (SELECT 1 FROM product_pricelist)"
|
[ADD] analyse: ausculter une base qui n'est pas ici
Les analyses existaient ; le chemin d'AVANT manquait. La base d'un client
est dans un zip, derrière une URL, ou vivante sur un serveur.
« Restant de migration » a dû être écrit : check_migration_quality compare
les bases de PALIER et exige le journal de progression — devant une
sauvegarde isolée, ni l'un ni l'autre n'existe.
Ses compteurs évidents ont été écartés après mesure. Comparés à la base
d'ORIGINE : champs sans colonne 25 → 72, modèles sans table 90 → 158.
Vingt-cinq et quatre-vingt-dix AVANT toute migration : du bruit. Ne
restent que les constats faux en eux-mêmes, 0 avant, non nuls après —
9 langues au drapeau NULL, 68 tables m2m absentes, 414 index doublés.
Le passe-plat RPC n'accepte que la lecture. psql l'obtient du serveur ;
une session RPC n'a rien d'équivalent, et la liste blanche est donc
appliquée dans le passe-plat, pas chez l'appelant.
--- EN ---
The analyses existed; the path BEFORE them did not. A customer database
sits in a zip, behind a URL, or live on a server.
« Migration leftovers » had to be written: check_migration_quality
compares STEP databases and needs the progression log — facing a lone
backup, neither exists.
Its obvious counters were dropped after measuring. Against the ORIGINAL
database: fields with no column 25 → 72, models with no table 90 → 158.
Twenty-five and ninety BEFORE any migration: noise. Only what is wrong in
itself remains, 0 before and non-zero after — 9 languages with a NULL
flag, 68 missing m2m tables, 414 duplicated indexes.
The RPC proxy only reads. psql gets that from the server; an RPC session
has no equivalent, so the allowlist lives in the proxy, not the caller.
Assisted-by: Claude Opus 5
2026-08-24 07:21:37 -04:00
|
|
|
" THEN 1 ELSE 0 END",
|
|
|
|
|
"gravity": "broken",
|
|
|
|
|
"repair": "script/odoo/migration/restore_config_defaults.py --apply",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"key": "view_model_gone",
|
|
|
|
|
"title": "Views bound to a model that no longer exists",
|
|
|
|
|
"why": "Opening one raises; the menu that leads to it is a dead end.",
|
|
|
|
|
"sql": "SELECT count(*) FROM ir_ui_view v"
|
|
|
|
|
" WHERE v.model IS NOT NULL AND v.model <> ''"
|
|
|
|
|
" AND NOT EXISTS (SELECT 1 FROM ir_model m WHERE m.model = v.model)",
|
|
|
|
|
"gravity": "broken",
|
|
|
|
|
"repair": None,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"key": "view_parent_gone",
|
|
|
|
|
"title": "Views inheriting a view that is gone",
|
|
|
|
|
"why": "The whole inheritance chain below them stops rendering.",
|
|
|
|
|
"sql": "SELECT count(*) FROM ir_ui_view v WHERE v.inherit_id IS NOT"
|
|
|
|
|
" NULL AND NOT EXISTS"
|
|
|
|
|
" (SELECT 1 FROM ir_ui_view p WHERE p.id = v.inherit_id)",
|
|
|
|
|
"gravity": "broken",
|
|
|
|
|
"repair": "script/odoo/migration/fix_cow_render.py --apply",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"key": "xmlid_model_gone",
|
|
|
|
|
"title": "External ids pointing at a model that is gone",
|
|
|
|
|
"why": "Every module update that resolves one of them fails.",
|
|
|
|
|
"sql": "SELECT count(*) FROM ir_model_data d WHERE NOT EXISTS"
|
|
|
|
|
" (SELECT 1 FROM ir_model m WHERE m.model = d.model)",
|
|
|
|
|
"gravity": "broken",
|
[REM] résidus : retirer le contrôle res_lang.active à NULL
Le contrôle jugeait cassée une langue dont `active` est NULL : listée
nulle part, plus réactivable. Faux dans la source 18 — un domaine
('active','=',False) compile en (IS NULL OR = FALSE) (models.py:3217),
l'action du menu Langues porte active_test: False, le tri passe par
COALESCE(active, FALSE) et la lecture rend bool(value).
Odoo écrit ce NULL : active = fields.Boolean() sans défaut
(res_lang.py:64) et un res.lang.csv sans la colonne, soit un NULL par
langue ajoutée au catalogue. « Zéro avant, non nul après » ne suffit
donc pas. Un test refuse désormais une clé que plus rien ne définit.
--- EN ---
The check called a language broken when `active` is NULL: listed
nowhere, no longer re-enablable. False in the 18 source — an
('active','=',False) domain compiles to (IS NULL OR = FALSE)
(models.py:3217), the Languages menu action carries active_test: False,
sorting goes through COALESCE(active, FALSE), reading returns bool(value).
Odoo writes that NULL itself: active = fields.Boolean() with no default
(res_lang.py:64) and a res.lang.csv without the column, one NULL per
language added to the catalogue. So "zero before, nonzero after" is not
enough. A test now refuses a key that nothing defines any more.
Assisted-by: Claude Opus 5
(cherry picked from commit ee932334cfde63c1af831316549a432c3d382721)
2026-08-27 02:15:30 -04:00
|
|
|
"repair": "script/odoo/migration/database_cleanup.py",
|
[ADD] analyse: ausculter une base qui n'est pas ici
Les analyses existaient ; le chemin d'AVANT manquait. La base d'un client
est dans un zip, derrière une URL, ou vivante sur un serveur.
« Restant de migration » a dû être écrit : check_migration_quality compare
les bases de PALIER et exige le journal de progression — devant une
sauvegarde isolée, ni l'un ni l'autre n'existe.
Ses compteurs évidents ont été écartés après mesure. Comparés à la base
d'ORIGINE : champs sans colonne 25 → 72, modèles sans table 90 → 158.
Vingt-cinq et quatre-vingt-dix AVANT toute migration : du bruit. Ne
restent que les constats faux en eux-mêmes, 0 avant, non nuls après —
9 langues au drapeau NULL, 68 tables m2m absentes, 414 index doublés.
Le passe-plat RPC n'accepte que la lecture. psql l'obtient du serveur ;
une session RPC n'a rien d'équivalent, et la liste blanche est donc
appliquée dans le passe-plat, pas chez l'appelant.
--- EN ---
The analyses existed; the path BEFORE them did not. A customer database
sits in a zip, behind a URL, or live on a server.
« Migration leftovers » had to be written: check_migration_quality
compares STEP databases and needs the progression log — facing a lone
backup, neither exists.
Its obvious counters were dropped after measuring. Against the ORIGINAL
database: fields with no column 25 → 72, models with no table 90 → 158.
Twenty-five and ninety BEFORE any migration: noise. Only what is wrong in
itself remains, 0 before and non-zero after — 9 languages with a NULL
flag, 68 missing m2m tables, 414 duplicated indexes.
The RPC proxy only reads. psql gets that from the server; an RPC session
has no equivalent, so the allowlist lives in the proxy, not the caller.
Assisted-by: Claude Opus 5
2026-08-24 07:21:37 -04:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"key": "attachment_field_gone",
|
|
|
|
|
"title": "Attachments whose carrying field is gone",
|
|
|
|
|
"why": "Odoo raises a KeyError merely checking them, and nothing"
|
|
|
|
|
" will ever read them again.",
|
|
|
|
|
"sql": "SELECT count(*) FROM ir_attachment a"
|
|
|
|
|
" WHERE a.res_field IS NOT NULL AND a.res_field <> ''"
|
|
|
|
|
" AND NOT EXISTS (SELECT 1 FROM ir_model_fields f"
|
|
|
|
|
" WHERE f.model = a.res_model AND f.name = a.res_field)",
|
|
|
|
|
"gravity": "watch",
|
|
|
|
|
"repair": "script/analyse/check_filestore.py",
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def inspect(database, config_path=None):
|
|
|
|
|
"""Passer chaque contrôle, et rendre son nombre.
|
|
|
|
|
|
|
|
|
|
Un contrôle qui ÉCHOUE n'est pas un contrôle qui rend zéro : une table
|
|
|
|
|
absente parce que le module n'est pas installé n'est pas un dégât. On
|
|
|
|
|
garde l'erreur telle quelle, et le rendu la distingue.
|
|
|
|
|
"""
|
|
|
|
|
resultats = {}
|
|
|
|
|
for controle in CONTROLES:
|
|
|
|
|
try:
|
|
|
|
|
brut = lib_analyse.run_psql(
|
|
|
|
|
database, controle["sql"], config_path=config_path
|
|
|
|
|
).strip()
|
|
|
|
|
resultats[controle["key"]] = int(brut.splitlines()[0])
|
|
|
|
|
except Exception as exc: # noqa: BLE001 - on rapporte, on ne meurt pas
|
|
|
|
|
resultats[controle["key"]] = {
|
|
|
|
|
"error": str(exc).splitlines()[0][:120]
|
|
|
|
|
}
|
|
|
|
|
return resultats
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def judge(resultats):
|
|
|
|
|
"""Ne garder que ce qui compte, le plus grave d'abord.
|
|
|
|
|
|
|
|
|
|
Un contrôle illisible remonte AVEC les autres : ne pas avoir pu
|
|
|
|
|
regarder n'est pas la même chose que n'avoir rien trouvé, et taire la
|
|
|
|
|
différence est exactement ce qui fait prendre un rapport pour une
|
|
|
|
|
garantie.
|
|
|
|
|
"""
|
|
|
|
|
trouve, illisibles = [], []
|
|
|
|
|
for controle in CONTROLES:
|
|
|
|
|
valeur = resultats.get(controle["key"])
|
|
|
|
|
if isinstance(valeur, dict):
|
|
|
|
|
illisibles.append((controle, valeur.get("error", "")))
|
|
|
|
|
elif valeur:
|
|
|
|
|
trouve.append((controle, valeur))
|
|
|
|
|
trouve.sort(key=lambda pair: (pair[0]["gravity"] != "broken", -pair[1]))
|
|
|
|
|
return trouve, illisibles
|
|
|
|
|
|
|
|
|
|
|
[ADD] qualité de migration : verdicts, sources, revue
Le rapport comparait les paliers sans dire si la migration avait réussi,
alors que les verdicts dorment déjà dans lst_event du journal de
progression : des contrôles en échec y restent sans remonter nulle part.
Trois sections s'ajoutent sous les paliers : les verdicts, rattachés au
palier ODOO et non au compteur du pilote, décalé d'un rang ; où vivent les
traces, car config.conf laisse logfile= vide et la sortie d'Odoo meurt avec
le terminal ; et la revue, six étapes lançables par « r ». Le contrôle de
résidus porte la même section sans toucher son code de sortie : un verdict
vient du fichier, pas de la base.
--- EN ---
The report compared the tiers without saying whether the migration had
succeeded, while the verdicts already sit in lst_event of the progression
file: failed checks stay there and surface nowhere.
Three sections are added below the tiers: the verdicts, tied to the ODOO
tier and not to the driver counter, which is off by one; where the traces
live, since config.conf leaves logfile= empty and Odoo's output dies with
the terminal; and the review, six steps runnable with "r". The residue
check carries the same section without touching its exit code: a verdict
comes from the file, not from the database.
Assisted-by: Claude Opus 5
(cherry picked from commit b05e0333c4b94d58eb794f09a2459e41d826c655)
2026-08-26 07:46:32 -04:00
|
|
|
def famille(nom):
|
|
|
|
|
"""La base d'origine dont ce nom est un palier.
|
|
|
|
|
|
|
|
|
|
« test_neutralize_upgrade_14 » et « …_upgrade_18 » sont deux paliers
|
|
|
|
|
de la MÊME migration. Interroger la base 18 doit montrer l'échec du
|
|
|
|
|
palier 14 : c'est le seul endroit où il subsiste.
|
|
|
|
|
"""
|
|
|
|
|
return nom.rsplit("_upgrade_", 1)[0] if "_upgrade_" in nom else nom
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def verdicts(database, path=None):
|
|
|
|
|
"""(tous, ratés) pour cette base — lus dans le FICHIER, pas en SQL.
|
|
|
|
|
|
|
|
|
|
Un test de fumée qui échoue ne laisse aucune trace en base : rien
|
|
|
|
|
n'est écrit, rien n'est cassé, la requête suivante répond. Les
|
|
|
|
|
contrôles ci-dessus sont donc structurellement aveugles à ce type
|
|
|
|
|
d'échec, et c'était la moitié de ce qu'une migration peut rater.
|
|
|
|
|
"""
|
|
|
|
|
dct = quality.read_progression(path or quality.DEFAULT_PROGRESSION)
|
|
|
|
|
lignee = famille(database)
|
|
|
|
|
tous = [
|
|
|
|
|
e
|
|
|
|
|
for e in quality.read_events(dct)
|
|
|
|
|
if famille(quality.event_database(e)) == lignee
|
|
|
|
|
]
|
|
|
|
|
return tous, quality.failures(tous)
|
|
|
|
|
|
|
|
|
|
|
[ADD] verdicts : tous les paliers, leur journal, et la bascule
L'écran de qualité ne listait que les échecs, quand la question devant
une base migrée est « qu'a-t-on vérifié » : les quatorze verdicts
s'affichent, de la 12 à la 18, seule façon de voir qu'un échec a été
rattrapé à un palier plus haut. Le panneau ne portait que la commande ;
il montre le passage du journal d'étape qui l'entoure, garde par tee ce
qu'il lance lui-même et le relit sans relancer. La sortie de l'outil,
elle, part sur le terminal : un tube ferait renoncer les pleins écrans.
Relancer un test d'un autre palier ouvrait la base avec la mauvaise
version, qui y écrit avant d'échouer ; l'écran demande avant de basculer.
--- EN ---
The quality screen listed failures only, when the question in front of a
migrated database is "what did we check": all fourteen verdicts now show,
12 through 18, the only way to see that a failure at one tier was
recovered higher up. The panel carried only the command; it shows the
step-log passage around it, keeps by tee what it runs itself and re-reads
that without rerunning. The tool output goes to the terminal: a pipe
would make full-screen tools give up. Replaying a test from another tier
opened the database with the wrong version, which writes before it
fails; the screen asks before switching the checkout.
Assisted-by: Claude Opus 5
(cherry picked from commit 2d460b7c777d39a887dabfe7cf5405864c6c3f8c)
2026-08-27 04:36:03 -04:00
|
|
|
def extrait_du_journal(dct, event, colour=True, avant=6):
|
[ADD] migration : garder la sortie des tests, par pseudo-terminal
Le journal d'étape notait la commande et son code de retour, jamais ce
qu'elle avait écrit : l'écran d'analyse ne pouvait rien montrer d'un
échec. Un tube aurait capturé et changé le programme — smoke_public_url
appelle can_ask(), qui exige stdin ET stdout sur un terminal, et derrière
un tube il cesse en silence d'offrir la réparation des vues COW. Un
pseudo-terminal lève le dilemme : l'enfant voit un vrai terminal, la
réponse tapée lui parvient, le code de retour survit. Neuf exécutions y
passent, dont check_hidden_models, qui tournait sans verdict retenu ;
deux restent dehors, pty.spawn naît en 0×0 et un plein écran s'y perdrait.
--- EN ---
The step log recorded the command and its exit code, never what it
wrote: the analysis screen could show nothing of a failure. A pipe would
have captured and changed the program — smoke_public_url calls can_ask(),
which requires stdin AND stdout to be terminals, and behind a pipe it
silently stops offering the COW view repair. A pty settles it: the child
sees a real terminal, a typed answer reaches it, the exit code survives.
Nine runs go through it, including check_hidden_models, which ran with no
verdict recorded; two stay out, pty.spawn starts at 0×0 and a full-screen
app would lay out on nothing.
Assisted-by: Claude Opus 5
(cherry picked from commit 81e9227502a0d16e6409338cf958495a5f8e3827)
2026-08-27 05:54:24 -04:00
|
|
|
"""(lignes, la sortie de l'outil y est-elle) autour de ce verdict.
|
[ADD] verdicts : tous les paliers, leur journal, et la bascule
L'écran de qualité ne listait que les échecs, quand la question devant
une base migrée est « qu'a-t-on vérifié » : les quatorze verdicts
s'affichent, de la 12 à la 18, seule façon de voir qu'un échec a été
rattrapé à un palier plus haut. Le panneau ne portait que la commande ;
il montre le passage du journal d'étape qui l'entoure, garde par tee ce
qu'il lance lui-même et le relit sans relancer. La sortie de l'outil,
elle, part sur le terminal : un tube ferait renoncer les pleins écrans.
Relancer un test d'un autre palier ouvrait la base avec la mauvaise
version, qui y écrit avant d'échouer ; l'écran demande avant de basculer.
--- EN ---
The quality screen listed failures only, when the question in front of a
migrated database is "what did we check": all fourteen verdicts now show,
12 through 18, the only way to see that a failure at one tier was
recovered higher up. The panel carried only the command; it shows the
step-log passage around it, keeps by tee what it runs itself and re-reads
that without rerunning. The tool output goes to the terminal: a pipe
would make full-screen tools give up. Replaying a test from another tier
opened the database with the wrong version, which writes before it
fails; the screen asks before switching the checkout.
Assisted-by: Claude Opus 5
(cherry picked from commit 2d460b7c777d39a887dabfe7cf5405864c6c3f8c)
2026-08-27 04:36:03 -04:00
|
|
|
|
|
|
|
|
La commande seule ne dit pas POURQUOI. Le journal, lui, garde ce
|
|
|
|
|
qu'Odoo écrivait au moment du test — et c'est tout ce qu'on a : la
|
|
|
|
|
sortie de l'outil n'y est pas, parce qu'elle passe par le terminal,
|
|
|
|
|
qu'un tube ferait renoncer aux outils en plein écran. Le pilote le
|
|
|
|
|
documente à l'endroit où il l'écrit.
|
|
|
|
|
|
|
|
|
|
Silencieux quand il n'y a rien à montrer : une ligne « pas de
|
|
|
|
|
journal » par verdict noierait les quatre qui comptent.
|
|
|
|
|
"""
|
|
|
|
|
chemin = status.step_log_path(dct, event.get("step"))
|
|
|
|
|
if not chemin or avant <= 0:
|
[ADD] migration : garder la sortie des tests, par pseudo-terminal
Le journal d'étape notait la commande et son code de retour, jamais ce
qu'elle avait écrit : l'écran d'analyse ne pouvait rien montrer d'un
échec. Un tube aurait capturé et changé le programme — smoke_public_url
appelle can_ask(), qui exige stdin ET stdout sur un terminal, et derrière
un tube il cesse en silence d'offrir la réparation des vues COW. Un
pseudo-terminal lève le dilemme : l'enfant voit un vrai terminal, la
réponse tapée lui parvient, le code de retour survit. Neuf exécutions y
passent, dont check_hidden_models, qui tournait sans verdict retenu ;
deux restent dehors, pty.spawn naît en 0×0 et un plein écran s'y perdrait.
--- EN ---
The step log recorded the command and its exit code, never what it
wrote: the analysis screen could show nothing of a failure. A pipe would
have captured and changed the program — smoke_public_url calls can_ask(),
which requires stdin AND stdout to be terminals, and behind a pipe it
silently stops offering the COW view repair. A pty settles it: the child
sees a real terminal, a typed answer reaches it, the exit code survives.
Nine runs go through it, including check_hidden_models, which ran with no
verdict recorded; two stay out, pty.spawn starts at 0×0 and a full-screen
app would lay out on nothing.
Assisted-by: Claude Opus 5
(cherry picked from commit 81e9227502a0d16e6409338cf958495a5f8e3827)
2026-08-27 05:54:24 -04:00
|
|
|
return [], False
|
[ADD] verdicts : tous les paliers, leur journal, et la bascule
L'écran de qualité ne listait que les échecs, quand la question devant
une base migrée est « qu'a-t-on vérifié » : les quatorze verdicts
s'affichent, de la 12 à la 18, seule façon de voir qu'un échec a été
rattrapé à un palier plus haut. Le panneau ne portait que la commande ;
il montre le passage du journal d'étape qui l'entoure, garde par tee ce
qu'il lance lui-même et le relit sans relancer. La sortie de l'outil,
elle, part sur le terminal : un tube ferait renoncer les pleins écrans.
Relancer un test d'un autre palier ouvrait la base avec la mauvaise
version, qui y écrit avant d'échouer ; l'écran demande avant de basculer.
--- EN ---
The quality screen listed failures only, when the question in front of a
migrated database is "what did we check": all fourteen verdicts now show,
12 through 18, the only way to see that a failure at one tier was
recovered higher up. The panel carried only the command; it shows the
step-log passage around it, keeps by tee what it runs itself and re-reads
that without rerunning. The tool output goes to the terminal: a pipe
would make full-screen tools give up. Replaying a test from another tier
opened the database with the wrong version, which writes before it
fails; the screen asks before switching the checkout.
Assisted-by: Claude Opus 5
(cherry picked from commit 2d460b7c777d39a887dabfe7cf5405864c6c3f8c)
2026-08-27 04:36:03 -04:00
|
|
|
try:
|
|
|
|
|
with open(chemin, "r", encoding="utf-8", errors="replace") as handle:
|
|
|
|
|
brut = handle.read().splitlines()
|
|
|
|
|
except OSError:
|
[ADD] migration : garder la sortie des tests, par pseudo-terminal
Le journal d'étape notait la commande et son code de retour, jamais ce
qu'elle avait écrit : l'écran d'analyse ne pouvait rien montrer d'un
échec. Un tube aurait capturé et changé le programme — smoke_public_url
appelle can_ask(), qui exige stdin ET stdout sur un terminal, et derrière
un tube il cesse en silence d'offrir la réparation des vues COW. Un
pseudo-terminal lève le dilemme : l'enfant voit un vrai terminal, la
réponse tapée lui parvient, le code de retour survit. Neuf exécutions y
passent, dont check_hidden_models, qui tournait sans verdict retenu ;
deux restent dehors, pty.spawn naît en 0×0 et un plein écran s'y perdrait.
--- EN ---
The step log recorded the command and its exit code, never what it
wrote: the analysis screen could show nothing of a failure. A pipe would
have captured and changed the program — smoke_public_url calls can_ask(),
which requires stdin AND stdout to be terminals, and behind a pipe it
silently stops offering the COW view repair. A pty settles it: the child
sees a real terminal, a typed answer reaches it, the exit code survives.
Nine runs go through it, including check_hidden_models, which ran with no
verdict recorded; two stay out, pty.spawn starts at 0×0 and a full-screen
app would lay out on nothing.
Assisted-by: Claude Opus 5
(cherry picked from commit 81e9227502a0d16e6409338cf958495a5f8e3827)
2026-08-27 05:54:24 -04:00
|
|
|
return [], False
|
|
|
|
|
extrait, rang = quality.event_excerpt(brut, event, avant=avant)
|
[ADD] verdicts : tous les paliers, leur journal, et la bascule
L'écran de qualité ne listait que les échecs, quand la question devant
une base migrée est « qu'a-t-on vérifié » : les quatorze verdicts
s'affichent, de la 12 à la 18, seule façon de voir qu'un échec a été
rattrapé à un palier plus haut. Le panneau ne portait que la commande ;
il montre le passage du journal d'étape qui l'entoure, garde par tee ce
qu'il lance lui-même et le relit sans relancer. La sortie de l'outil,
elle, part sur le terminal : un tube ferait renoncer les pleins écrans.
Relancer un test d'un autre palier ouvrait la base avec la mauvaise
version, qui y écrit avant d'échouer ; l'écran demande avant de basculer.
--- EN ---
The quality screen listed failures only, when the question in front of a
migrated database is "what did we check": all fourteen verdicts now show,
12 through 18, the only way to see that a failure at one tier was
recovered higher up. The panel carried only the command; it shows the
step-log passage around it, keeps by tee what it runs itself and re-reads
that without rerunning. The tool output goes to the terminal: a pipe
would make full-screen tools give up. Replaying a test from another tier
opened the database with the wrong version, which writes before it
fails; the screen asks before switching the checkout.
Assisted-by: Claude Opus 5
(cherry picked from commit 2d460b7c777d39a887dabfe7cf5405864c6c3f8c)
2026-08-27 04:36:03 -04:00
|
|
|
if not extrait:
|
[ADD] migration : garder la sortie des tests, par pseudo-terminal
Le journal d'étape notait la commande et son code de retour, jamais ce
qu'elle avait écrit : l'écran d'analyse ne pouvait rien montrer d'un
échec. Un tube aurait capturé et changé le programme — smoke_public_url
appelle can_ask(), qui exige stdin ET stdout sur un terminal, et derrière
un tube il cesse en silence d'offrir la réparation des vues COW. Un
pseudo-terminal lève le dilemme : l'enfant voit un vrai terminal, la
réponse tapée lui parvient, le code de retour survit. Neuf exécutions y
passent, dont check_hidden_models, qui tournait sans verdict retenu ;
deux restent dehors, pty.spawn naît en 0×0 et un plein écran s'y perdrait.
--- EN ---
The step log recorded the command and its exit code, never what it
wrote: the analysis screen could show nothing of a failure. A pipe would
have captured and changed the program — smoke_public_url calls can_ask(),
which requires stdin AND stdout to be terminals, and behind a pipe it
silently stops offering the COW view repair. A pty settles it: the child
sees a real terminal, a typed answer reaches it, the exit code survives.
Nine runs go through it, including check_hidden_models, which ran with no
verdict recorded; two stay out, pty.spawn starts at 0×0 and a full-screen
app would lay out on nothing.
Assisted-by: Claude Opus 5
(cherry picked from commit 81e9227502a0d16e6409338cf958495a5f8e3827)
2026-08-27 05:54:24 -04:00
|
|
|
return [], False
|
[ADD] verdicts : tous les paliers, leur journal, et la bascule
L'écran de qualité ne listait que les échecs, quand la question devant
une base migrée est « qu'a-t-on vérifié » : les quatorze verdicts
s'affichent, de la 12 à la 18, seule façon de voir qu'un échec a été
rattrapé à un palier plus haut. Le panneau ne portait que la commande ;
il montre le passage du journal d'étape qui l'entoure, garde par tee ce
qu'il lance lui-même et le relit sans relancer. La sortie de l'outil,
elle, part sur le terminal : un tube ferait renoncer les pleins écrans.
Relancer un test d'un autre palier ouvrait la base avec la mauvaise
version, qui y écrit avant d'échouer ; l'écran demande avant de basculer.
--- EN ---
The quality screen listed failures only, when the question in front of a
migrated database is "what did we check": all fourteen verdicts now show,
12 through 18, the only way to see that a failure at one tier was
recovered higher up. The panel carried only the command; it shows the
step-log passage around it, keeps by tee what it runs itself and re-reads
that without rerunning. The tool output goes to the terminal: a pipe
would make full-screen tools give up. Replaying a test from another tier
opened the database with the wrong version, which writes before it
fails; the screen asks before switching the checkout.
Assisted-by: Claude Opus 5
(cherry picked from commit 2d460b7c777d39a887dabfe7cf5405864c6c3f8c)
2026-08-27 04:36:03 -04:00
|
|
|
lignes = [paint(f" {chemin}", "dim", colour)]
|
|
|
|
|
for ligne in extrait:
|
|
|
|
|
lignes.append(paint(f" {ligne[:150]}", "dim", colour))
|
[ADD] migration : garder la sortie des tests, par pseudo-terminal
Le journal d'étape notait la commande et son code de retour, jamais ce
qu'elle avait écrit : l'écran d'analyse ne pouvait rien montrer d'un
échec. Un tube aurait capturé et changé le programme — smoke_public_url
appelle can_ask(), qui exige stdin ET stdout sur un terminal, et derrière
un tube il cesse en silence d'offrir la réparation des vues COW. Un
pseudo-terminal lève le dilemme : l'enfant voit un vrai terminal, la
réponse tapée lui parvient, le code de retour survit. Neuf exécutions y
passent, dont check_hidden_models, qui tournait sans verdict retenu ;
deux restent dehors, pty.spawn naît en 0×0 et un plein écran s'y perdrait.
--- EN ---
The step log recorded the command and its exit code, never what it
wrote: the analysis screen could show nothing of a failure. A pipe would
have captured and changed the program — smoke_public_url calls can_ask(),
which requires stdin AND stdout to be terminals, and behind a pipe it
silently stops offering the COW view repair. A pty settles it: the child
sees a real terminal, a typed answer reaches it, the exit code survives.
Nine runs go through it, including check_hidden_models, which ran with no
verdict recorded; two stay out, pty.spawn starts at 0×0 and a full-screen
app would lay out on nothing.
Assisted-by: Claude Opus 5
(cherry picked from commit 81e9227502a0d16e6409338cf958495a5f8e3827)
2026-08-27 05:54:24 -04:00
|
|
|
debut = max(0, rang - avant) if rang is not None else 0
|
|
|
|
|
avec_sortie = rang is not None and quality.excerpt_has_output(
|
|
|
|
|
extrait, rang - debut
|
|
|
|
|
)
|
|
|
|
|
return lignes, avec_sortie
|
[ADD] verdicts : tous les paliers, leur journal, et la bascule
L'écran de qualité ne listait que les échecs, quand la question devant
une base migrée est « qu'a-t-on vérifié » : les quatorze verdicts
s'affichent, de la 12 à la 18, seule façon de voir qu'un échec a été
rattrapé à un palier plus haut. Le panneau ne portait que la commande ;
il montre le passage du journal d'étape qui l'entoure, garde par tee ce
qu'il lance lui-même et le relit sans relancer. La sortie de l'outil,
elle, part sur le terminal : un tube ferait renoncer les pleins écrans.
Relancer un test d'un autre palier ouvrait la base avec la mauvaise
version, qui y écrit avant d'échouer ; l'écran demande avant de basculer.
--- EN ---
The quality screen listed failures only, when the question in front of a
migrated database is "what did we check": all fourteen verdicts now show,
12 through 18, the only way to see that a failure at one tier was
recovered higher up. The panel carried only the command; it shows the
step-log passage around it, keeps by tee what it runs itself and re-reads
that without rerunning. The tool output goes to the terminal: a pipe
would make full-screen tools give up. Replaying a test from another tier
opened the database with the wrong version, which writes before it
fails; the screen asks before switching the checkout.
Assisted-by: Claude Opus 5
(cherry picked from commit 2d460b7c777d39a887dabfe7cf5405864c6c3f8c)
2026-08-27 04:36:03 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def verdicts_block(database, colour=True, path=None, lignes_avant=6):
|
[ADD] qualité de migration : verdicts, sources, revue
Le rapport comparait les paliers sans dire si la migration avait réussi,
alors que les verdicts dorment déjà dans lst_event du journal de
progression : des contrôles en échec y restent sans remonter nulle part.
Trois sections s'ajoutent sous les paliers : les verdicts, rattachés au
palier ODOO et non au compteur du pilote, décalé d'un rang ; où vivent les
traces, car config.conf laisse logfile= vide et la sortie d'Odoo meurt avec
le terminal ; et la revue, six étapes lançables par « r ». Le contrôle de
résidus porte la même section sans toucher son code de sortie : un verdict
vient du fichier, pas de la base.
--- EN ---
The report compared the tiers without saying whether the migration had
succeeded, while the verdicts already sit in lst_event of the progression
file: failed checks stay there and surface nowhere.
Three sections are added below the tiers: the verdicts, tied to the ODOO
tier and not to the driver counter, which is off by one; where the traces
live, since config.conf leaves logfile= empty and Odoo's output dies with
the terminal; and the review, six steps runnable with "r". The residue
check carries the same section without touching its exit code: a verdict
comes from the file, not from the database.
Assisted-by: Claude Opus 5
(cherry picked from commit b05e0333c4b94d58eb794f09a2459e41d826c655)
2026-08-26 07:46:32 -04:00
|
|
|
"""La section « Verdicts », ou rien du tout s'il n'y en a pas.
|
|
|
|
|
|
|
|
|
|
Silencieuse quand le fichier n'existe pas : devant la sauvegarde d'un
|
|
|
|
|
client, il n'y a jamais eu de migration locale, et annoncer l'absence
|
|
|
|
|
d'un fichier qu'on n'attendait pas ne renseigne personne.
|
|
|
|
|
"""
|
|
|
|
|
chemin = path or quality.DEFAULT_PROGRESSION
|
|
|
|
|
tous, ratés = verdicts(database, chemin)
|
|
|
|
|
if not tous:
|
|
|
|
|
return []
|
|
|
|
|
lignes = [
|
|
|
|
|
"",
|
|
|
|
|
paint(f"🚦 {t('Verdicts the migration recorded')}", "step", colour),
|
|
|
|
|
]
|
|
|
|
|
lignes.append(paint(f" {t('recorded in')} {chemin}", "dim", colour))
|
|
|
|
|
lignes.append("")
|
|
|
|
|
if not ratés:
|
|
|
|
|
lignes.append(
|
|
|
|
|
paint(
|
|
|
|
|
f"✅ {str(len(tous)).rjust(6)} {t('checks, all passed')}",
|
|
|
|
|
"ok",
|
|
|
|
|
colour,
|
|
|
|
|
)
|
|
|
|
|
)
|
[ADD] verdicts : tous les paliers, leur journal, et la bascule
L'écran de qualité ne listait que les échecs, quand la question devant
une base migrée est « qu'a-t-on vérifié » : les quatorze verdicts
s'affichent, de la 12 à la 18, seule façon de voir qu'un échec a été
rattrapé à un palier plus haut. Le panneau ne portait que la commande ;
il montre le passage du journal d'étape qui l'entoure, garde par tee ce
qu'il lance lui-même et le relit sans relancer. La sortie de l'outil,
elle, part sur le terminal : un tube ferait renoncer les pleins écrans.
Relancer un test d'un autre palier ouvrait la base avec la mauvaise
version, qui y écrit avant d'échouer ; l'écran demande avant de basculer.
--- EN ---
The quality screen listed failures only, when the question in front of a
migrated database is "what did we check": all fourteen verdicts now show,
12 through 18, the only way to see that a failure at one tier was
recovered higher up. The panel carried only the command; it shows the
step-log passage around it, keeps by tee what it runs itself and re-reads
that without rerunning. The tool output goes to the terminal: a pipe
would make full-screen tools give up. Replaying a test from another tier
opened the database with the wrong version, which writes before it
fails; the screen asks before switching the checkout.
Assisted-by: Claude Opus 5
(cherry picked from commit 2d460b7c777d39a887dabfe7cf5405864c6c3f8c)
2026-08-27 04:36:03 -04:00
|
|
|
dct = quality.read_progression(chemin)
|
[ADD] migration : garder la sortie des tests, par pseudo-terminal
Le journal d'étape notait la commande et son code de retour, jamais ce
qu'elle avait écrit : l'écran d'analyse ne pouvait rien montrer d'un
échec. Un tube aurait capturé et changé le programme — smoke_public_url
appelle can_ask(), qui exige stdin ET stdout sur un terminal, et derrière
un tube il cesse en silence d'offrir la réparation des vues COW. Un
pseudo-terminal lève le dilemme : l'enfant voit un vrai terminal, la
réponse tapée lui parvient, le code de retour survit. Neuf exécutions y
passent, dont check_hidden_models, qui tournait sans verdict retenu ;
deux restent dehors, pty.spawn naît en 0×0 et un plein écran s'y perdrait.
--- EN ---
The step log recorded the command and its exit code, never what it
wrote: the analysis screen could show nothing of a failure. A pipe would
have captured and changed the program — smoke_public_url calls can_ask(),
which requires stdin AND stdout to be terminals, and behind a pipe it
silently stops offering the COW view repair. A pty settles it: the child
sees a real terminal, a typed answer reaches it, the exit code survives.
Nine runs go through it, including check_hidden_models, which ran with no
verdict recorded; two stay out, pty.spawn starts at 0×0 and a full-screen
app would lay out on nothing.
Assisted-by: Claude Opus 5
(cherry picked from commit 81e9227502a0d16e6409338cf958495a5f8e3827)
2026-08-27 05:54:24 -04:00
|
|
|
sortie_presente = False
|
[ADD] qualité de migration : verdicts, sources, revue
Le rapport comparait les paliers sans dire si la migration avait réussi,
alors que les verdicts dorment déjà dans lst_event du journal de
progression : des contrôles en échec y restent sans remonter nulle part.
Trois sections s'ajoutent sous les paliers : les verdicts, rattachés au
palier ODOO et non au compteur du pilote, décalé d'un rang ; où vivent les
traces, car config.conf laisse logfile= vide et la sortie d'Odoo meurt avec
le terminal ; et la revue, six étapes lançables par « r ». Le contrôle de
résidus porte la même section sans toucher son code de sortie : un verdict
vient du fichier, pas de la base.
--- EN ---
The report compared the tiers without saying whether the migration had
succeeded, while the verdicts already sit in lst_event of the progression
file: failed checks stay there and surface nowhere.
Three sections are added below the tiers: the verdicts, tied to the ODOO
tier and not to the driver counter, which is off by one; where the traces
live, since config.conf leaves logfile= empty and Odoo's output dies with
the terminal; and the review, six steps runnable with "r". The residue
check carries the same section without touching its exit code: a verdict
comes from the file, not from the database.
Assisted-by: Claude Opus 5
(cherry picked from commit b05e0333c4b94d58eb794f09a2459e41d826c655)
2026-08-26 07:46:32 -04:00
|
|
|
for event in ratés:
|
[ADD] verdicts : tous les paliers, leur journal, et la bascule
L'écran de qualité ne listait que les échecs, quand la question devant
une base migrée est « qu'a-t-on vérifié » : les quatorze verdicts
s'affichent, de la 12 à la 18, seule façon de voir qu'un échec a été
rattrapé à un palier plus haut. Le panneau ne portait que la commande ;
il montre le passage du journal d'étape qui l'entoure, garde par tee ce
qu'il lance lui-même et le relit sans relancer. La sortie de l'outil,
elle, part sur le terminal : un tube ferait renoncer les pleins écrans.
Relancer un test d'un autre palier ouvrait la base avec la mauvaise
version, qui y écrit avant d'échouer ; l'écran demande avant de basculer.
--- EN ---
The quality screen listed failures only, when the question in front of a
migrated database is "what did we check": all fourteen verdicts now show,
12 through 18, the only way to see that a failure at one tier was
recovered higher up. The panel carried only the command; it shows the
step-log passage around it, keeps by tee what it runs itself and re-reads
that without rerunning. The tool output goes to the terminal: a pipe
would make full-screen tools give up. Replaying a test from another tier
opened the database with the wrong version, which writes before it
fails; the screen asks before switching the checkout.
Assisted-by: Claude Opus 5
(cherry picked from commit 2d460b7c777d39a887dabfe7cf5405864c6c3f8c)
2026-08-27 04:36:03 -04:00
|
|
|
version = quality.version_of(quality.event_database(event), dct)
|
|
|
|
|
palier = str(version) if version else quality.event_step(event)
|
[FIX] verdicts de migration : distinguer trouvaille et échec d'outil
L'écran peignait en échec tout code non nul, quand la convention écrite
dans todo_upgrade.run_tool dit : 0 rien à signaler, 1 des trouvailles, 2
l'outil a échoué. database_cleanup imprime lui-même « This is a warning,
not a failure » avant de rendre 1.
Un écran qui contredit l'outil apprend à ignorer les deux : du rouge
signalait une migration en échec là où rien n'avait échoué.
L'icône rejoint la couleur dans migration_status, et le panneau écrit le
sens du chiffre à côté de lui : « statut 1 (des trouvailles) ». Testé.
--- EN ---
The screen painted every non-zero code as a failure, where the convention
written in todo_upgrade.run_tool says: 0 nothing to report, 1 findings, 2
the tool failed. database_cleanup itself prints "This is a warning, not a
failure" before returning 1.
A screen that contradicts the tool teaches you to ignore both: red marked
a migration as failed where nothing had failed.
The icon now lives beside the colour in migration_status, and the panel
spells the number out: "status 1 (findings)". Covered by tests.
Assisted-by: Claude Opus 5
(cherry picked from commit 39d122965e7a09d710255fff061a7526a4077aaa)
2026-08-28 01:09:24 -04:00
|
|
|
icone, teinte = status.verdict_mark(event["status"])
|
[ADD] qualité de migration : verdicts, sources, revue
Le rapport comparait les paliers sans dire si la migration avait réussi,
alors que les verdicts dorment déjà dans lst_event du journal de
progression : des contrôles en échec y restent sans remonter nulle part.
Trois sections s'ajoutent sous les paliers : les verdicts, rattachés au
palier ODOO et non au compteur du pilote, décalé d'un rang ; où vivent les
traces, car config.conf laisse logfile= vide et la sortie d'Odoo meurt avec
le terminal ; et la revue, six étapes lançables par « r ». Le contrôle de
résidus porte la même section sans toucher son code de sortie : un verdict
vient du fichier, pas de la base.
--- EN ---
The report compared the tiers without saying whether the migration had
succeeded, while the verdicts already sit in lst_event of the progression
file: failed checks stay there and surface nowhere.
Three sections are added below the tiers: the verdicts, tied to the ODOO
tier and not to the driver counter, which is off by one; where the traces
live, since config.conf leaves logfile= empty and Odoo's output dies with
the terminal; and the review, six steps runnable with "r". The residue
check carries the same section without touching its exit code: a verdict
comes from the file, not from the database.
Assisted-by: Claude Opus 5
(cherry picked from commit b05e0333c4b94d58eb794f09a2459e41d826c655)
2026-08-26 07:46:32 -04:00
|
|
|
lignes.append(
|
|
|
|
|
paint(
|
[FIX] verdicts de migration : distinguer trouvaille et échec d'outil
L'écran peignait en échec tout code non nul, quand la convention écrite
dans todo_upgrade.run_tool dit : 0 rien à signaler, 1 des trouvailles, 2
l'outil a échoué. database_cleanup imprime lui-même « This is a warning,
not a failure » avant de rendre 1.
Un écran qui contredit l'outil apprend à ignorer les deux : du rouge
signalait une migration en échec là où rien n'avait échoué.
L'icône rejoint la couleur dans migration_status, et le panneau écrit le
sens du chiffre à côté de lui : « statut 1 (des trouvailles) ». Testé.
--- EN ---
The screen painted every non-zero code as a failure, where the convention
written in todo_upgrade.run_tool says: 0 nothing to report, 1 findings, 2
the tool failed. database_cleanup itself prints "This is a warning, not a
failure" before returning 1.
A screen that contradicts the tool teaches you to ignore both: red marked
a migration as failed where nothing had failed.
The icon now lives beside the colour in migration_status, and the panel
spells the number out: "status 1 (findings)". Covered by tests.
Assisted-by: Claude Opus 5
(cherry picked from commit 39d122965e7a09d710255fff061a7526a4077aaa)
2026-08-28 01:09:24 -04:00
|
|
|
f"{icone} {palier.rjust(6)} {event['name']}",
|
|
|
|
|
"broken" if teinte == "fail" else "watch",
|
[ADD] qualité de migration : verdicts, sources, revue
Le rapport comparait les paliers sans dire si la migration avait réussi,
alors que les verdicts dorment déjà dans lst_event du journal de
progression : des contrôles en échec y restent sans remonter nulle part.
Trois sections s'ajoutent sous les paliers : les verdicts, rattachés au
palier ODOO et non au compteur du pilote, décalé d'un rang ; où vivent les
traces, car config.conf laisse logfile= vide et la sortie d'Odoo meurt avec
le terminal ; et la revue, six étapes lançables par « r ». Le contrôle de
résidus porte la même section sans toucher son code de sortie : un verdict
vient du fichier, pas de la base.
--- EN ---
The report compared the tiers without saying whether the migration had
succeeded, while the verdicts already sit in lst_event of the progression
file: failed checks stay there and surface nowhere.
Three sections are added below the tiers: the verdicts, tied to the ODOO
tier and not to the driver counter, which is off by one; where the traces
live, since config.conf leaves logfile= empty and Odoo's output dies with
the terminal; and the review, six steps runnable with "r". The residue
check carries the same section without touching its exit code: a verdict
comes from the file, not from the database.
Assisted-by: Claude Opus 5
(cherry picked from commit b05e0333c4b94d58eb794f09a2459e41d826c655)
2026-08-26 07:46:32 -04:00
|
|
|
colour,
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
lignes.append(
|
|
|
|
|
paint(f" {event['detail'][:120]}", "dim", colour)
|
|
|
|
|
)
|
[ADD] migration : garder la sortie des tests, par pseudo-terminal
Le journal d'étape notait la commande et son code de retour, jamais ce
qu'elle avait écrit : l'écran d'analyse ne pouvait rien montrer d'un
échec. Un tube aurait capturé et changé le programme — smoke_public_url
appelle can_ask(), qui exige stdin ET stdout sur un terminal, et derrière
un tube il cesse en silence d'offrir la réparation des vues COW. Un
pseudo-terminal lève le dilemme : l'enfant voit un vrai terminal, la
réponse tapée lui parvient, le code de retour survit. Neuf exécutions y
passent, dont check_hidden_models, qui tournait sans verdict retenu ;
deux restent dehors, pty.spawn naît en 0×0 et un plein écran s'y perdrait.
--- EN ---
The step log recorded the command and its exit code, never what it
wrote: the analysis screen could show nothing of a failure. A pipe would
have captured and changed the program — smoke_public_url calls can_ask(),
which requires stdin AND stdout to be terminals, and behind a pipe it
silently stops offering the COW view repair. A pty settles it: the child
sees a real terminal, a typed answer reaches it, the exit code survives.
Nine runs go through it, including check_hidden_models, which ran with no
verdict recorded; two stay out, pty.spawn starts at 0×0 and a full-screen
app would lay out on nothing.
Assisted-by: Claude Opus 5
(cherry picked from commit 81e9227502a0d16e6409338cf958495a5f8e3827)
2026-08-27 05:54:24 -04:00
|
|
|
bloc, avec_sortie = extrait_du_journal(
|
|
|
|
|
dct, event, colour, lignes_avant
|
|
|
|
|
)
|
|
|
|
|
sortie_presente = sortie_presente or avec_sortie
|
|
|
|
|
lignes.extend(bloc)
|
[ADD] qualité de migration : verdicts, sources, revue
Le rapport comparait les paliers sans dire si la migration avait réussi,
alors que les verdicts dorment déjà dans lst_event du journal de
progression : des contrôles en échec y restent sans remonter nulle part.
Trois sections s'ajoutent sous les paliers : les verdicts, rattachés au
palier ODOO et non au compteur du pilote, décalé d'un rang ; où vivent les
traces, car config.conf laisse logfile= vide et la sortie d'Odoo meurt avec
le terminal ; et la revue, six étapes lançables par « r ». Le contrôle de
résidus porte la même section sans toucher son code de sortie : un verdict
vient du fichier, pas de la base.
--- EN ---
The report compared the tiers without saying whether the migration had
succeeded, while the verdicts already sit in lst_event of the progression
file: failed checks stay there and surface nowhere.
Three sections are added below the tiers: the verdicts, tied to the ODOO
tier and not to the driver counter, which is off by one; where the traces
live, since config.conf leaves logfile= empty and Odoo's output dies with
the terminal; and the review, six steps runnable with "r". The residue
check carries the same section without touching its exit code: a verdict
comes from the file, not from the database.
Assisted-by: Claude Opus 5
(cherry picked from commit b05e0333c4b94d58eb794f09a2459e41d826c655)
2026-08-26 07:46:32 -04:00
|
|
|
lignes.append("")
|
|
|
|
|
lignes.append(
|
|
|
|
|
paint(
|
|
|
|
|
f" {t('These come from the file, not the database:')}"
|
|
|
|
|
f" {t('the exit code ignores them.')}",
|
|
|
|
|
"dim",
|
|
|
|
|
colour,
|
|
|
|
|
)
|
|
|
|
|
)
|
[ADD] migration : garder la sortie des tests, par pseudo-terminal
Le journal d'étape notait la commande et son code de retour, jamais ce
qu'elle avait écrit : l'écran d'analyse ne pouvait rien montrer d'un
échec. Un tube aurait capturé et changé le programme — smoke_public_url
appelle can_ask(), qui exige stdin ET stdout sur un terminal, et derrière
un tube il cesse en silence d'offrir la réparation des vues COW. Un
pseudo-terminal lève le dilemme : l'enfant voit un vrai terminal, la
réponse tapée lui parvient, le code de retour survit. Neuf exécutions y
passent, dont check_hidden_models, qui tournait sans verdict retenu ;
deux restent dehors, pty.spawn naît en 0×0 et un plein écran s'y perdrait.
--- EN ---
The step log recorded the command and its exit code, never what it
wrote: the analysis screen could show nothing of a failure. A pipe would
have captured and changed the program — smoke_public_url calls can_ask(),
which requires stdin AND stdout to be terminals, and behind a pipe it
silently stops offering the COW view repair. A pty settles it: the child
sees a real terminal, a typed answer reaches it, the exit code survives.
Nine runs go through it, including check_hidden_models, which ran with no
verdict recorded; two stay out, pty.spawn starts at 0×0 and a full-screen
app would lay out on nothing.
Assisted-by: Claude Opus 5
(cherry picked from commit 81e9227502a0d16e6409338cf958495a5f8e3827)
2026-08-27 05:54:24 -04:00
|
|
|
if ratés and lignes_avant > 0 and not sortie_presente:
|
[ADD] verdicts : tous les paliers, leur journal, et la bascule
L'écran de qualité ne listait que les échecs, quand la question devant
une base migrée est « qu'a-t-on vérifié » : les quatorze verdicts
s'affichent, de la 12 à la 18, seule façon de voir qu'un échec a été
rattrapé à un palier plus haut. Le panneau ne portait que la commande ;
il montre le passage du journal d'étape qui l'entoure, garde par tee ce
qu'il lance lui-même et le relit sans relancer. La sortie de l'outil,
elle, part sur le terminal : un tube ferait renoncer les pleins écrans.
Relancer un test d'un autre palier ouvrait la base avec la mauvaise
version, qui y écrit avant d'échouer ; l'écran demande avant de basculer.
--- EN ---
The quality screen listed failures only, when the question in front of a
migrated database is "what did we check": all fourteen verdicts now show,
12 through 18, the only way to see that a failure at one tier was
recovered higher up. The panel carried only the command; it shows the
step-log passage around it, keeps by tee what it runs itself and re-reads
that without rerunning. The tool output goes to the terminal: a pipe
would make full-screen tools give up. Replaying a test from another tier
opened the database with the wrong version, which writes before it
fails; the screen asks before switching the checkout.
Assisted-by: Claude Opus 5
(cherry picked from commit 2d460b7c777d39a887dabfe7cf5405864c6c3f8c)
2026-08-27 04:36:03 -04:00
|
|
|
lignes.append(
|
|
|
|
|
paint(
|
|
|
|
|
f" {t('the tool output is not in the step log: it goes')}"
|
|
|
|
|
f" {t('to the terminal and dies with it.')}",
|
|
|
|
|
"dim",
|
|
|
|
|
colour,
|
|
|
|
|
)
|
|
|
|
|
)
|
[ADD] qualité de migration : verdicts, sources, revue
Le rapport comparait les paliers sans dire si la migration avait réussi,
alors que les verdicts dorment déjà dans lst_event du journal de
progression : des contrôles en échec y restent sans remonter nulle part.
Trois sections s'ajoutent sous les paliers : les verdicts, rattachés au
palier ODOO et non au compteur du pilote, décalé d'un rang ; où vivent les
traces, car config.conf laisse logfile= vide et la sortie d'Odoo meurt avec
le terminal ; et la revue, six étapes lançables par « r ». Le contrôle de
résidus porte la même section sans toucher son code de sortie : un verdict
vient du fichier, pas de la base.
--- EN ---
The report compared the tiers without saying whether the migration had
succeeded, while the verdicts already sit in lst_event of the progression
file: failed checks stay there and surface nowhere.
Three sections are added below the tiers: the verdicts, tied to the ODOO
tier and not to the driver counter, which is off by one; where the traces
live, since config.conf leaves logfile= empty and Odoo's output dies with
the terminal; and the review, six steps runnable with "r". The residue
check carries the same section without touching its exit code: a verdict
comes from the file, not from the database.
Assisted-by: Claude Opus 5
(cherry picked from commit b05e0333c4b94d58eb794f09a2459e41d826c655)
2026-08-26 07:46:32 -04:00
|
|
|
return lignes
|
|
|
|
|
|
|
|
|
|
|
[ADD] analyse: ausculter une base qui n'est pas ici
Les analyses existaient ; le chemin d'AVANT manquait. La base d'un client
est dans un zip, derrière une URL, ou vivante sur un serveur.
« Restant de migration » a dû être écrit : check_migration_quality compare
les bases de PALIER et exige le journal de progression — devant une
sauvegarde isolée, ni l'un ni l'autre n'existe.
Ses compteurs évidents ont été écartés après mesure. Comparés à la base
d'ORIGINE : champs sans colonne 25 → 72, modèles sans table 90 → 158.
Vingt-cinq et quatre-vingt-dix AVANT toute migration : du bruit. Ne
restent que les constats faux en eux-mêmes, 0 avant, non nuls après —
9 langues au drapeau NULL, 68 tables m2m absentes, 414 index doublés.
Le passe-plat RPC n'accepte que la lecture. psql l'obtient du serveur ;
une session RPC n'a rien d'équivalent, et la liste blanche est donc
appliquée dans le passe-plat, pas chez l'appelant.
--- EN ---
The analyses existed; the path BEFORE them did not. A customer database
sits in a zip, behind a URL, or live on a server.
« Migration leftovers » had to be written: check_migration_quality
compares STEP databases and needs the progression log — facing a lone
backup, neither exists.
Its obvious counters were dropped after measuring. Against the ORIGINAL
database: fields with no column 25 → 72, models with no table 90 → 158.
Twenty-five and ninety BEFORE any migration: noise. Only what is wrong in
itself remains, 0 before and non-zero after — 9 languages with a NULL
flag, 68 missing m2m tables, 414 duplicated indexes.
The RPC proxy only reads. psql gets that from the server; an RPC session
has no equivalent, so the allowlist lives in the proxy, not the caller.
Assisted-by: Claude Opus 5
2026-08-24 07:21:37 -04:00
|
|
|
def render(database, resultats, version=None, colour=True):
|
|
|
|
|
"""Le rapport lisible. Chaque constat dit quoi lancer pour le réparer."""
|
|
|
|
|
trouve, illisibles = judge(resultats)
|
|
|
|
|
lignes = [f"🚚 {t('Migration residue in')} {database}"]
|
|
|
|
|
if version:
|
|
|
|
|
lignes.append(f" {t('base module version:')} {version}")
|
|
|
|
|
lignes.append("")
|
|
|
|
|
|
|
|
|
|
if not trouve and not illisibles:
|
|
|
|
|
lignes.append(
|
|
|
|
|
paint(
|
|
|
|
|
f"✅ {t('None of the checks found anything.')}", "ok", colour
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
lignes.append(
|
|
|
|
|
paint(
|
|
|
|
|
f" {t('This reads one database on its own — it cannot see')}"
|
|
|
|
|
f" {t('what an earlier step silently dropped.')}",
|
|
|
|
|
"dim",
|
|
|
|
|
colour,
|
|
|
|
|
)
|
|
|
|
|
)
|
[ADD] qualité de migration : verdicts, sources, revue
Le rapport comparait les paliers sans dire si la migration avait réussi,
alors que les verdicts dorment déjà dans lst_event du journal de
progression : des contrôles en échec y restent sans remonter nulle part.
Trois sections s'ajoutent sous les paliers : les verdicts, rattachés au
palier ODOO et non au compteur du pilote, décalé d'un rang ; où vivent les
traces, car config.conf laisse logfile= vide et la sortie d'Odoo meurt avec
le terminal ; et la revue, six étapes lançables par « r ». Le contrôle de
résidus porte la même section sans toucher son code de sortie : un verdict
vient du fichier, pas de la base.
--- EN ---
The report compared the tiers without saying whether the migration had
succeeded, while the verdicts already sit in lst_event of the progression
file: failed checks stay there and surface nowhere.
Three sections are added below the tiers: the verdicts, tied to the ODOO
tier and not to the driver counter, which is off by one; where the traces
live, since config.conf leaves logfile= empty and Odoo's output dies with
the terminal; and the review, six steps runnable with "r". The residue
check carries the same section without touching its exit code: a verdict
comes from the file, not from the database.
Assisted-by: Claude Opus 5
(cherry picked from commit b05e0333c4b94d58eb794f09a2459e41d826c655)
2026-08-26 07:46:32 -04:00
|
|
|
return "\n".join(lignes + verdicts_block(database, colour)).rstrip()
|
[ADD] analyse: ausculter une base qui n'est pas ici
Les analyses existaient ; le chemin d'AVANT manquait. La base d'un client
est dans un zip, derrière une URL, ou vivante sur un serveur.
« Restant de migration » a dû être écrit : check_migration_quality compare
les bases de PALIER et exige le journal de progression — devant une
sauvegarde isolée, ni l'un ni l'autre n'existe.
Ses compteurs évidents ont été écartés après mesure. Comparés à la base
d'ORIGINE : champs sans colonne 25 → 72, modèles sans table 90 → 158.
Vingt-cinq et quatre-vingt-dix AVANT toute migration : du bruit. Ne
restent que les constats faux en eux-mêmes, 0 avant, non nuls après —
9 langues au drapeau NULL, 68 tables m2m absentes, 414 index doublés.
Le passe-plat RPC n'accepte que la lecture. psql l'obtient du serveur ;
une session RPC n'a rien d'équivalent, et la liste blanche est donc
appliquée dans le passe-plat, pas chez l'appelant.
--- EN ---
The analyses existed; the path BEFORE them did not. A customer database
sits in a zip, behind a URL, or live on a server.
« Migration leftovers » had to be written: check_migration_quality
compares STEP databases and needs the progression log — facing a lone
backup, neither exists.
Its obvious counters were dropped after measuring. Against the ORIGINAL
database: fields with no column 25 → 72, models with no table 90 → 158.
Twenty-five and ninety BEFORE any migration: noise. Only what is wrong in
itself remains, 0 before and non-zero after — 9 languages with a NULL
flag, 68 missing m2m tables, 414 duplicated indexes.
The RPC proxy only reads. psql gets that from the server; an RPC session
has no equivalent, so the allowlist lives in the proxy, not the caller.
Assisted-by: Claude Opus 5
2026-08-24 07:21:37 -04:00
|
|
|
|
|
|
|
|
for controle, combien in trouve:
|
|
|
|
|
icone = "❌" if controle["gravity"] == "broken" else "⚠"
|
|
|
|
|
lignes.append(
|
|
|
|
|
paint(
|
|
|
|
|
f"{icone} {str(combien).rjust(6)} {t(controle['title'])}",
|
|
|
|
|
controle["gravity"],
|
|
|
|
|
colour,
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
lignes.append(paint(f" {t(controle['why'])}", "dim", colour))
|
|
|
|
|
if controle["repair"]:
|
|
|
|
|
lignes.append(f" → {controle['repair']}")
|
|
|
|
|
else:
|
|
|
|
|
lignes.append(
|
|
|
|
|
paint(f" → {t('no repair tool yet')}", "dim", colour)
|
|
|
|
|
)
|
|
|
|
|
lignes.append("")
|
|
|
|
|
|
|
|
|
|
for controle, erreur in illisibles:
|
|
|
|
|
lignes.append(
|
|
|
|
|
paint(
|
|
|
|
|
f"❔ {t(controle['title'])} — {t('could not read')}",
|
|
|
|
|
"watch",
|
|
|
|
|
colour,
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
lignes.append(paint(f" {erreur}", "dim", colour))
|
[ADD] qualité de migration : verdicts, sources, revue
Le rapport comparait les paliers sans dire si la migration avait réussi,
alors que les verdicts dorment déjà dans lst_event du journal de
progression : des contrôles en échec y restent sans remonter nulle part.
Trois sections s'ajoutent sous les paliers : les verdicts, rattachés au
palier ODOO et non au compteur du pilote, décalé d'un rang ; où vivent les
traces, car config.conf laisse logfile= vide et la sortie d'Odoo meurt avec
le terminal ; et la revue, six étapes lançables par « r ». Le contrôle de
résidus porte la même section sans toucher son code de sortie : un verdict
vient du fichier, pas de la base.
--- EN ---
The report compared the tiers without saying whether the migration had
succeeded, while the verdicts already sit in lst_event of the progression
file: failed checks stay there and surface nowhere.
Three sections are added below the tiers: the verdicts, tied to the ODOO
tier and not to the driver counter, which is off by one; where the traces
live, since config.conf leaves logfile= empty and Odoo's output dies with
the terminal; and the review, six steps runnable with "r". The residue
check carries the same section without touching its exit code: a verdict
comes from the file, not from the database.
Assisted-by: Claude Opus 5
(cherry picked from commit b05e0333c4b94d58eb794f09a2459e41d826c655)
2026-08-26 07:46:32 -04:00
|
|
|
return "\n".join(lignes + verdicts_block(database, colour)).rstrip()
|
[ADD] analyse: ausculter une base qui n'est pas ici
Les analyses existaient ; le chemin d'AVANT manquait. La base d'un client
est dans un zip, derrière une URL, ou vivante sur un serveur.
« Restant de migration » a dû être écrit : check_migration_quality compare
les bases de PALIER et exige le journal de progression — devant une
sauvegarde isolée, ni l'un ni l'autre n'existe.
Ses compteurs évidents ont été écartés après mesure. Comparés à la base
d'ORIGINE : champs sans colonne 25 → 72, modèles sans table 90 → 158.
Vingt-cinq et quatre-vingt-dix AVANT toute migration : du bruit. Ne
restent que les constats faux en eux-mêmes, 0 avant, non nuls après —
9 langues au drapeau NULL, 68 tables m2m absentes, 414 index doublés.
Le passe-plat RPC n'accepte que la lecture. psql l'obtient du serveur ;
une session RPC n'a rien d'équivalent, et la liste blanche est donc
appliquée dans le passe-plat, pas chez l'appelant.
--- EN ---
The analyses existed; the path BEFORE them did not. A customer database
sits in a zip, behind a URL, or live on a server.
« Migration leftovers » had to be written: check_migration_quality
compares STEP databases and needs the progression log — facing a lone
backup, neither exists.
Its obvious counters were dropped after measuring. Against the ORIGINAL
database: fields with no column 25 → 72, models with no table 90 → 158.
Twenty-five and ninety BEFORE any migration: noise. Only what is wrong in
itself remains, 0 before and non-zero after — 9 languages with a NULL
flag, 68 missing m2m tables, 414 duplicated indexes.
The RPC proxy only reads. psql gets that from the server; an RPC session
has no equivalent, so the allowlist lives in the proxy, not the caller.
Assisted-by: Claude Opus 5
2026-08-24 07:21:37 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def main(argv=None):
|
|
|
|
|
import argparse
|
|
|
|
|
import json
|
|
|
|
|
|
|
|
|
|
parser = argparse.ArgumentParser(
|
|
|
|
|
description=t("What a migration left behind, read in one database."),
|
|
|
|
|
)
|
|
|
|
|
parser.add_argument("-d", "--database", required=True)
|
|
|
|
|
parser.add_argument("-c", "--config", help="odoo config file")
|
|
|
|
|
parser.add_argument("--json", action="store_true")
|
|
|
|
|
parser.add_argument("--no-color", action="store_true")
|
|
|
|
|
args = parser.parse_args(argv)
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
lib_analyse.require_odoo_database(
|
|
|
|
|
args.database, config_path=args.config
|
|
|
|
|
)
|
|
|
|
|
except Exception as exc: # noqa: BLE001
|
|
|
|
|
print(f"❌ {exc}", file=sys.stderr)
|
|
|
|
|
return 2
|
|
|
|
|
|
|
|
|
|
resultats = inspect(args.database, args.config)
|
|
|
|
|
try:
|
|
|
|
|
version = lib_analyse.database_version(
|
|
|
|
|
args.database, config_path=args.config
|
|
|
|
|
)
|
|
|
|
|
except Exception: # noqa: BLE001 - la version est un confort, pas le sujet
|
|
|
|
|
version = None
|
|
|
|
|
trouve, illisibles = judge(resultats)
|
|
|
|
|
|
|
|
|
|
if args.json:
|
[ADD] qualité de migration : verdicts, sources, revue
Le rapport comparait les paliers sans dire si la migration avait réussi,
alors que les verdicts dorment déjà dans lst_event du journal de
progression : des contrôles en échec y restent sans remonter nulle part.
Trois sections s'ajoutent sous les paliers : les verdicts, rattachés au
palier ODOO et non au compteur du pilote, décalé d'un rang ; où vivent les
traces, car config.conf laisse logfile= vide et la sortie d'Odoo meurt avec
le terminal ; et la revue, six étapes lançables par « r ». Le contrôle de
résidus porte la même section sans toucher son code de sortie : un verdict
vient du fichier, pas de la base.
--- EN ---
The report compared the tiers without saying whether the migration had
succeeded, while the verdicts already sit in lst_event of the progression
file: failed checks stay there and surface nowhere.
Three sections are added below the tiers: the verdicts, tied to the ODOO
tier and not to the driver counter, which is off by one; where the traces
live, since config.conf leaves logfile= empty and Odoo's output dies with
the terminal; and the review, six steps runnable with "r". The residue
check carries the same section without touching its exit code: a verdict
comes from the file, not from the database.
Assisted-by: Claude Opus 5
(cherry picked from commit b05e0333c4b94d58eb794f09a2459e41d826c655)
2026-08-26 07:46:32 -04:00
|
|
|
tous_verdicts, verdicts_ratés = verdicts(args.database)
|
[ADD] analyse: ausculter une base qui n'est pas ici
Les analyses existaient ; le chemin d'AVANT manquait. La base d'un client
est dans un zip, derrière une URL, ou vivante sur un serveur.
« Restant de migration » a dû être écrit : check_migration_quality compare
les bases de PALIER et exige le journal de progression — devant une
sauvegarde isolée, ni l'un ni l'autre n'existe.
Ses compteurs évidents ont été écartés après mesure. Comparés à la base
d'ORIGINE : champs sans colonne 25 → 72, modèles sans table 90 → 158.
Vingt-cinq et quatre-vingt-dix AVANT toute migration : du bruit. Ne
restent que les constats faux en eux-mêmes, 0 avant, non nuls après —
9 langues au drapeau NULL, 68 tables m2m absentes, 414 index doublés.
Le passe-plat RPC n'accepte que la lecture. psql l'obtient du serveur ;
une session RPC n'a rien d'équivalent, et la liste blanche est donc
appliquée dans le passe-plat, pas chez l'appelant.
--- EN ---
The analyses existed; the path BEFORE them did not. A customer database
sits in a zip, behind a URL, or live on a server.
« Migration leftovers » had to be written: check_migration_quality
compares STEP databases and needs the progression log — facing a lone
backup, neither exists.
Its obvious counters were dropped after measuring. Against the ORIGINAL
database: fields with no column 25 → 72, models with no table 90 → 158.
Twenty-five and ninety BEFORE any migration: noise. Only what is wrong in
itself remains, 0 before and non-zero after — 9 languages with a NULL
flag, 68 missing m2m tables, 414 duplicated indexes.
The RPC proxy only reads. psql gets that from the server; an RPC session
has no equivalent, so the allowlist lives in the proxy, not the caller.
Assisted-by: Claude Opus 5
2026-08-24 07:21:37 -04:00
|
|
|
print(
|
|
|
|
|
json.dumps(
|
|
|
|
|
{
|
|
|
|
|
"database": args.database,
|
|
|
|
|
"base_version": version,
|
|
|
|
|
"checks": resultats,
|
|
|
|
|
"found": [c["key"] for c, _ in trouve],
|
|
|
|
|
"unreadable": [c["key"] for c, _ in illisibles],
|
[ADD] qualité de migration : verdicts, sources, revue
Le rapport comparait les paliers sans dire si la migration avait réussi,
alors que les verdicts dorment déjà dans lst_event du journal de
progression : des contrôles en échec y restent sans remonter nulle part.
Trois sections s'ajoutent sous les paliers : les verdicts, rattachés au
palier ODOO et non au compteur du pilote, décalé d'un rang ; où vivent les
traces, car config.conf laisse logfile= vide et la sortie d'Odoo meurt avec
le terminal ; et la revue, six étapes lançables par « r ». Le contrôle de
résidus porte la même section sans toucher son code de sortie : un verdict
vient du fichier, pas de la base.
--- EN ---
The report compared the tiers without saying whether the migration had
succeeded, while the verdicts already sit in lst_event of the progression
file: failed checks stay there and surface nowhere.
Three sections are added below the tiers: the verdicts, tied to the ODOO
tier and not to the driver counter, which is off by one; where the traces
live, since config.conf leaves logfile= empty and Odoo's output dies with
the terminal; and the review, six steps runnable with "r". The residue
check carries the same section without touching its exit code: a verdict
comes from the file, not from the database.
Assisted-by: Claude Opus 5
(cherry picked from commit b05e0333c4b94d58eb794f09a2459e41d826c655)
2026-08-26 07:46:32 -04:00
|
|
|
"verdicts": tous_verdicts,
|
|
|
|
|
"verdicts_failed": [
|
|
|
|
|
{"step": quality.event_step(e), "name": e["name"]}
|
|
|
|
|
for e in verdicts_ratés
|
|
|
|
|
],
|
[ADD] analyse: ausculter une base qui n'est pas ici
Les analyses existaient ; le chemin d'AVANT manquait. La base d'un client
est dans un zip, derrière une URL, ou vivante sur un serveur.
« Restant de migration » a dû être écrit : check_migration_quality compare
les bases de PALIER et exige le journal de progression — devant une
sauvegarde isolée, ni l'un ni l'autre n'existe.
Ses compteurs évidents ont été écartés après mesure. Comparés à la base
d'ORIGINE : champs sans colonne 25 → 72, modèles sans table 90 → 158.
Vingt-cinq et quatre-vingt-dix AVANT toute migration : du bruit. Ne
restent que les constats faux en eux-mêmes, 0 avant, non nuls après —
9 langues au drapeau NULL, 68 tables m2m absentes, 414 index doublés.
Le passe-plat RPC n'accepte que la lecture. psql l'obtient du serveur ;
une session RPC n'a rien d'équivalent, et la liste blanche est donc
appliquée dans le passe-plat, pas chez l'appelant.
--- EN ---
The analyses existed; the path BEFORE them did not. A customer database
sits in a zip, behind a URL, or live on a server.
« Migration leftovers » had to be written: check_migration_quality
compares STEP databases and needs the progression log — facing a lone
backup, neither exists.
Its obvious counters were dropped after measuring. Against the ORIGINAL
database: fields with no column 25 → 72, models with no table 90 → 158.
Twenty-five and ninety BEFORE any migration: noise. Only what is wrong in
itself remains, 0 before and non-zero after — 9 languages with a NULL
flag, 68 missing m2m tables, 414 duplicated indexes.
The RPC proxy only reads. psql gets that from the server; an RPC session
has no equivalent, so the allowlist lives in the proxy, not the caller.
Assisted-by: Claude Opus 5
2026-08-24 07:21:37 -04:00
|
|
|
},
|
|
|
|
|
indent=2,
|
|
|
|
|
ensure_ascii=False,
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
else:
|
|
|
|
|
colour = sys.stdout.isatty() and not args.no_color
|
|
|
|
|
print(render(args.database, resultats, version, colour))
|
|
|
|
|
return 1 if trouve else 0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
sys.exit(main())
|