fix: checklist step 5, secondary-profile migrations and Linux database paths (#390) #394
No reviewers
Labels
No labels
autopilot:pending-human
source:analyste
source:defenseur
source:human
source:medic
status:approved
status:blocked
status:in-progress
status:needs-clarification
status:needs-fix
status:ready
status:review
status:triage
type:bug
type:feature
type:infra
type:refactor
type:schema
type:security
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: maximus/Simpl-Resultat#394
Loading…
Reference in a new issue
No description provided.
Delete branch "issue-390-manual-flag-profile-migrations"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Refs #390
Ce qui change pour l'utilisateur
"true") se restaure correctement.migration-backups/, à côté des bases, puis applique les versions manquantes. La copie est supprimée avec le profil.~/.config/com.simpl.resultat/. Les colonnes de rendement du Bilan affichent les rendements au lieu d'un tiret par cellule, etrepair_migrationsagit enfin.Écarts au corps de l'issue
Décisions de Max du 2026-10-06, prises en cours de
/fix-issue:add_migrations(sqlite:simpl_resultat.db) et retire la liste au premier chargement. Une v18 n'aurait réparé que le profil par défaut. La réparation des drapeaux texte se fait donc à l'ouverture de chaque profil.app_config_dir(), alors que trois commandes les cherchaient sousapp_data_dir(). C'est le même dossier sous Windows et macOS, pas sous Linux. Les trois commandes sont corrigées par le même helper.Technique
TypeScript
utils/sqlFlag.ts:toSqlFlagtransforme un booléen, un nombre ou la chaîne d'une sauvegarde en 0/1.updateTransactionCategory,parseImportedCsvet l'INSERT de restauration l'utilisent.db.ts:connectToProfileinvoqueprepare_profile_dbaprèsrepair_migrationset avantDatabase.load.describePreparationchoisit les lignes de Journal, et un échec n'empêche pas l'ouverture.Rust
lib.rs: la liste sort derun()dansmigrations()sans qu'un octet bouge.git diff -U0 main -- src-tauri/src/lib.rsne montre que les lignes d'enveloppe, l'URL passée parDEFAULT_PROFILE_DB, la commande enregistrée et un commentaire de test hors du bloc. Les SHA-384 de v1 à v17, calculés surmainavant le déplacement par un crate jetable qui recopiait le bloc tel quel, sont épinglés parmigration_checksums_match_the_released_ones.database/profile_db.rs:infer_floordéduit du schéma le plancher d'une base sans historique ;catch_upécrit l'historique en une transaction, puis applique chaque version dans la sienne, avec les clés étrangères actives ;backup_tocopie la base parVACUUM INTOvers un chemin lié, synchronise un.tmp, puis le renomme ;history_sql,repair_manual_flags,prepare_at, etscrubqui retire le dossier des messages.profile_commands.rs:profile_db_pathrésoutapp_config_dir()et valide le nom (simpl_resultat.dbouprofile_<hex>.db) ;prepare_profile_dbest synchrone, donc sérialisée sur le fil principal ;delete_profile_dbemporte aussi-wal,-shm,-journalet les copies ;get_new_profile_init_sqlajoute l'historique complet, si bien qu'un nouveau profil ne passe jamais par l'inférence ;repair_migrationscherche dans le bon dossier.balance_commands.rs:compute_account_returnpasse parprofile_db_path, et son message ne contient plus de chemin.Cargo.toml: tauri avecfeatures = ["test"]en dev-dependency, pourmock_app(). La feature n'ajoute aucune crate etCargo.lockest inchangé.database/fixtures/: les sept états de création publiés, copiés à l'identique de git (v0.3.0 consolidé + seed, seed v0.4.0, consolidés v0.8.4, v0.9.0, v0.9.1, v0.10.0). L'état courant estCONSOLIDATED_SCHEMA.Documentation
architecture.md,CLAUDE.md, règle.claude/rules/sql-migrations.md(une correction de seed se reporte aussi dans le schéma consolidé), guide FR et EN avec les clésdocs.*, CHANGELOG FR et EN (trois entrées sous Corrigé).Preuves
updateTransactionCategoryliait[12, true, 5]au lieu de[12, 1, 5];'true';"true"comme 0.npm run buildvert,cargo checksans avertissement.toSqlFlagoublie"true"parseIntconnectToProfilesaute la préparationWHEREapp_data_dirscrubgarde le dossierhistory_sqln'échappe pas'Non gardé par les tests :
Plan checker : trois passes, chacune refusée.
sync_allsous Windows, déjà réglé dans le code parOpenOptions::new().write(true).Max a validé la suite après la troisième passe.
Post-merge checklist (hence
Refs, not a closing keyword)~/.config/com.simpl.resultat/migration-backups/, et l'import doit fonctionner dans ce profil..dbet ses fichiers annexes doivent disparaître de~/.config/com.simpl.resultat/.🤖 Generated with Claude Code
Revue adversariale — PR #394 (#390)
Verdict : APPROVE
Résumé. Le correctif est correct et solidement gardé : le drapeau est lié en entier et réparé dans tous les profils, les profils secondaires rattrapent leurs migrations à partir d'un plancher déduit d'un ensemble d'états publiés que j'ai vérifié complet, et les trois commandes visent enfin le dossier du plugin. Aucun bloquant. Les suggestions portent sur des épingles manquantes autour d'un code correct, sur le Journal d'un rattrapage arrêté et sur la rétention des copies.
Ce que j'ai rejoué
71147c5, dans une copie hors dépôt (git archive,npm ci, dossier cible cargo séparé) : vitest 1519/1519,cargo test254/254. CI de la tête lue dans les logs : run 493 (frontend) 1519 verts et build vert, run 494 (Rust) 254 verts,cargo checksans avertissement.lib.rs(continuations\comprises) et des fichiersinclude_str!, surmain(8907eef) et sur le tagv0.17.0. Les 17 SHA-384 sont identiques aux valeurs épinglées parmigration_checksums_match_the_released_ones.git diff -U0 main -- src-tauri/src/lib.rsne touche que l'enveloppe.227d3b7,aeba40c,0582449,cb593ec,6341d9d,83a08a9,17f5503). Le relevé des 51 tags qui ont des profils (67 en tout) confirme que l'ensemble est fermé : le seed0582449n'a existé qu'en v0.4.0 (aeba40crevient en v0.4.1), le consolidé227d3b7couvre v0.3.0 à v0.8.3, puis un état par changement (v0.8.4, v0.9.0, v0.9.1, v0.10.0 à v0.14.0, v0.15.0 à v0.17.0). Le nomprofile_${id.split("-")[0]}.dbn'a pas changé depuis v0.3.0, et aucun code TypeScript n'a jamais modifié un schéma à l'exécution (git log -S "ALTER TABLE" -- src/est vide) : pas d'état hors fixtures.app_config_dir()danswrapper.rs, liste retirée au premierloadde la seule URL enregistrée, booléen lié parquery.bind(value)(valeur JSON, donc texte). sqlx-sqlite 0.8.6 active les clés étrangères par défaut et ne fixe pasjournal_mode: les bases sont en journal de retour arrière.pull_refde #394 avecref_action: none, et aucun mot-clé de fermeture n'apparaît dans le corps ni dans les commits. Cohérent avecRefset une fermeture à la main. Commits conventionnels, aucune donnée personnelle dans les lignes ajoutées.Mutations du relecteur
Une à la fois sur la copie, atterrissage vérifié (occurrence unique avant, texte muté présent après), restauration depuis une copie vierge et comparaison octet par octet.
history_sql(nouveau profil) stocke un mauvais checksum-journalcatch_up_errorrendu sans nettoyer le dossiercompute_account_returnrevenu àapp_data_dirDatabase.loadlancé avant la fin deprepare_profile_dbRV8 :
profile_db_pathest épinglé (R9 de l'auteur), son appel danscompute_account_returnne l'est pas ; la vérification n° 4 de la checklist post-merge le couvre.Sonde pour RV6 (test ajouté à la copie seulement) : un vrai échec de
VACUUM INTO, provoqué par un lien symbolique pendant à la place du.tmp, donnebackup_failed: unable to open database: <dossier>/migration-backups/profile_0a1b2c3d-before-v17.db.tmp. Le code de la PR rend<profile folder>/migration-backups/…(vert) ; sous RV6, le chemin complet passe (rouge). Le message de SQLite nomme donc bien le dossier, et seulscrubl'en retire.Suggestions (non bloquantes)
prepares after the checksum repair and before Database.loadcompareinvocationCallOrder: RT1, qui lanceDatabase.loaddès l'appel deprepare_profile_dbpuis attend les deux, le laisse vert, comme toute la suitedb.test.ts. C'est pourtant la propriété qui protège les données : en production la commande est synchrone sur le fil principal etloadasynchrone, ils se chevaucheraient. Unh.preparedifféré (promesse résolue à la main) et l'assertion queDatabase.loadn'est pas appelé avant sa résolution la fermeraient.apply_pending,catch_uprendErr(String)etreport.backup_fileest perdu : la ligne d'erreur ditapplied before stopping: [...]sans le nom de la copie, alors que c'est le cas où on en a besoin, et le corps de la PR annonce que chaque copie est écrite au Journal. Rendre le nom de la copie avec l'erreur suffirait.scrub(RV6).a_failed_backup_writes_nothingprovoque une erreur decreate_dir_all, dont le message ne contient aucun chemin : son!error.contains(dir)ne peut pas échouer. Or un vrai échec deVACUUM INTOnomme le fichier avec son dossier (sonde ci-dessus). La sonde peut entrer telle quelle dans la suite : le lien pendant fait échouerVACUUM INTOmême en root, alors qu'un dossier en lecture seule n'échouerait pas dans le conteneur de la CI, qui tourne en root.-journaln'est pas épinglé (RV5). Comme sqlx 0.8.6 laisse le journal par défaut,-journalest le seul fichier annexe que cette application produit (journal chaud après un plantage), alors que le test crée-walet-shm. Ajouterprofile_0a1b2c3d.db-journalaux fichiers du test suffit.beforevide, elle remettrait à NULL des lignes sans rapport avec la migration). Une violation préexistante dans le profil sans catégorie 31 couvrirait RV4 et RV7 ; une assertion sur la lignepoint to a missing rowcouvrirait RT2.App.tsxarme un délai de 10 s (STARTUP_TIMEOUT_MS) autour deconnectActiveProfile, qui couvre désormais le rattrapage du profil actif. Au-delà,startupErrorreste posé et la page d'erreur s'affiche alors que le rattrapage aboutit. Peu probable avec des bases de taille courante, mais à citer dans les points de vigilance de l'ADR 0021.-before-vN.dben clair par profil secondaire, retiré seulement avec le profil. Et une copie existante est gardée telle quelle : après un rattrapage qui a échoué puis abouti plus tard avec la même première version, elle précède les données saisies entre-temps. Une rétention (garder la dernière) et une phrase au guide relèvent d'une issue de suivi.unique_dirne nettoie pas ses dossiers. Chaque passage de la suite Rust laisse 42 dossiers, soit 16 Mo de bases SQLite, dans/tmp.Recommandation : merger. Les points 1, 3, 4, 5 et 8 sont des épingles sur du code correct, et 2, 6 et 7 des améliorations : les regrouper dans une issue de suivi, ou en tête de branche, plutôt qu'un tour de revue de plus.
Actions faites
/tmp/pr394-review, dossier cible/tmp/pr394-target, copies vierges, scripts et logs/tmp/pr394-*, et les dossiers de test laissés dans/tmppar mes passages : supprimés en fin de revue. Aucun fichier du dépôt touché.status:review→status:approved.Suggestions rapides de la revue appliquées (commit
5910387, tête non relue)À la demande de Max (« appliquer les rapides, puis merger »), les suggestions 1, 3, 4 et 8 de la revue sont appliquées en tête de branche. La nouvelle tête
5910387n'a pas été relue. #390 repasse enstatus:review.Ce qui change (tests seulement, aucun code de production)
opens the database only once the preparation has finisheddansdb.test.ts: la préparation est une promesse résolue à la main, etDatabase.loadne doit pas être appelé avant sa résolution.a_real_backup_failure_names_no_folder(Unix seulement) : un lien symbolique pendant remplace le.tmp, etVACUUM INTOéchoue, même en root. Le test vérifie d'abord que le message brut debackup_tonomme bien le dossier, puis quecatch_up_errordeprepare_aten sort sans le dossier.-journal(suggestion 4, RV5). Le test de suppression crée aussiprofile_0a1b2c3d.db-journal, le seul fichier annexe que l'application produit, et vérifie qu'il disparaît.unique_dirrend un gardeTestDir, qui supprime son dossier à la fin du test, échec compris. Le test de suppression deprofile_commands.rsl'utilise aussi. Après une suite Rust complète,ls -d /tmp/simpl-resultat-*ne trouve plus rien.Mutations (arbre committé, restauration par git)
Database.loadlancé avant la fin deprepare_profile_dbscrubrend le message tel quel-journalTestDir/tmp/simpl-resultat-*vide après la suite Rust entière, y compris après des passes où des tests ont échouéSuites sur
5910387cargo test255 (254 + 1) ;cargo checksans avertissement.Le reste
Les suggestions 2, 5, 6 et 7 sont regroupées dans #395 : copie nommée quand le rattrapage s'arrête, violations préexistantes épinglées (RV4, RV7, RT2), délai de démarrage, rétention des copies.
🤖 Generated with Claude Code
La PR est mergée en avance rapide sur
5910387, par l'API, avec la tête épinglée.Validation de la tête dans un worktree propre :
npm ci;cargo checksans avertissement ;cargo test255/255 ;/tmp.Revue : l'APPROVE portait sur
71147c5. Le commit5910387, qui ne contient que des tests, a été ajouté après la revue et n'a pas été relu, à la demande de Max : appliquer les suggestions rapides, puis merger. Sa CI est verte (runs 495 et 496).Suites :
status:blocked) ;