Quatre jours. Quatre sessions. Une surface de fonctionnalités massive (serveurs de base de données autonomes avec 5 moteurs, interfaces admin, gestion de domaines, sauvegardes, utilisateurs, contrôle d'accès). Et maintenant, le round de consolidation.
Le problème du travail incrémental
Quand vous construisez une fonctionnalité sur plusieurs sessions, chaque session optimise localement. La session 1 construit le noyau. La session 2 audite et corrige les bugs. La session 3 ajoute les pièces manquantes (correctif de race au provisioning, détection de collision de domaine admin). La session 4 ajoute la parité de la vue d'ensemble et la sécurité de l'interface admin.
Chaque session est individuellement solide. Mais après 4 jours : - Un fichier handler monolithique de 3 194 lignes a grossi organiquement - L'exécuteur de sauvegardes programmées a un bug qui est le miroir d'un correctif déjà appliqué sur le chemin de déclenchement manuel - Les domaines des serveurs de base de données vivent dans des colonnes séparées, invisibles pour la page globale des domaines - L'architecture a dérivé par rapport aux patterns originaux des stack-app
C'est le problème du « fonctionne individuellement, ne forme pas un tout cohérent ». Et c'est pourquoi nous avons un round de consolidation dédié.
Ce que le Round 4 a trouvé
L'échec silencieux des sauvegardes
La découverte critique était dans sh0-backup/src/scheduler.rs. Le handler de trigger_backup manuel avait été corrigé le 8 avril pour essayer DatabaseServer::find_by_id avant App::find_by_id dans la branche de sauvegarde de volume. Mais l'exécuteur de sauvegardes programmées — un chemin de code complètement séparé — ne cherchait toujours que les apps. Chaque sauvegarde Redis programmée par volume échouait silencieusement.
C'est une classe de bug qui ne se révèle que par un audit cross-path. Le correctif avait été appliqué le soir du 8 avril, mais seulement sur l'un des deux chemins de code identiques. Le prompt d'audit avait spécifiquement signalé cela comme l'item 20, et le vérifier prenait 30 secondes de lecture.
La table unifiée des domaines
Le changement architectural le plus important a été de rendre domains.app_id nullable et d'ajouter db_server_id. C'était l'item reporté le plus impactant sur l'ensemble des 4 sessions.
La stratégie de migration pour SQLite (qui ne supporte pas ALTER COLUMN ... DROP NOT NULL) :
1. Renommer la table existante en _domains_old
2. Créer une nouvelle table avec des contraintes assouplies + CHECK
3. Copier toutes les lignes existantes
4. Supprimer l'ancienne table
5. Remplir rétroactivement depuis les colonnes database_servers.server_domain / admin_domain existantes
La contrainte CHECK ((app_id IS NOT NULL AND db_server_id IS NULL) OR (app_id IS NULL AND db_server_id IS NOT NULL)) garantit qu'exactement un propriétaire est défini.
L'effet de cascade a touché 14 fichiers dans 6 crates. Chaque site de construction Domain { app_id: x, ... } devait passer à Some(x) et db_server_id: None. Chaque comparaison domain.app_id != app_id devait utiliser .as_deref(). Le type DomainResponse a gagné les deux champs optionnels. Et l'endpoint global des domaines est passé de list_all_with_app_name à list_all_with_owner_name (une requête UNION à travers apps et database_servers).
Le refactoring du handler
Un fichier handler de 3 194 lignes est un code smell, mais c'est aussi un résultat naturel de 4 jours de travail incrémental sur des fonctionnalités. La découpe en 9 sous-modules était mécanique mais importante :
database_servers/
mod.rs (293 lignes) -- helpers partagés
crud.rs (586 lignes) -- create/get/list/update/delete
lifecycle.rs (218 lignes) -- start/stop/connection-info
databases.rs (198 lignes) -- bases de données gérées
users.rs (332 lignes) -- utilisateurs de bases de données + ACL
access.rs (589 lignes) -- grants + accès externe
admin_ui.rs (673 lignes) -- interface admin + domaine admin
domains.rs (393 lignes) -- domaine serveur
logs.rs (68 lignes) -- streaming de logs WebSocketLa décision clé a été la visibilité pub(super) pour les fonctions inter-modules. assign_admin_domain vit dans admin_ui.rs mais est appelé depuis crud.rs (pendant la création du serveur). Le rendre pub(super) le garde interne au module tout en permettant l'accès par les modules frères.
La méthodologie en action
La méthodologie build-audit-audit-decide vise la convergence à travers des perspectives diversifiées :
- Session 1 construit rapidement et avec optimisme
- Sessions 2-3 attrapent les bugs et ajoutent les pièces manquantes
- Session 4 (celle-ci) prend du recul et demande : « est-ce que l'ensemble forme un tout cohérent ? »
La réponse était : essentiellement oui, avec deux correctifs critiques (exécuteur de sauvegardes, unification des domaines) et une amélioration structurelle (découpage du handler). Chacun de ces points était explicitement identifié dans le prompt d'audit, qui était lui-même le produit d'observations accumulées sur 4 sessions.
Ce qui reste
Le Round 4 a explicitement reporté 10 items au Round 5, chacun avec une justification spécifique : - Refonte visuelle des onglets Sauvegardes/Logs/Paramètres (cosmétique, pas fonctionnel) - Correctifs d'accessibilité (important mais non bloquant) - Bannière de détection de conteneur admin UI non sécurisé (nécessite Docker inspect à l'exécution) - Interface de domaine personnalisé pour les db-servers (architecturalement débloqué par ce round)
La distinction compte : ce sont des reports conscients avec une justification documentée, pas des éléments oubliés. La prochaine session sait exactement par où commencer.
Conclusion
Le round de consolidation coûte du temps en amont mais empêche l'accumulation lente d'incohérences qui rend un logiciel difficile à maintenir. Après le Round 4, la surface des serveurs de base de données est : - Architecturalement intégrée (table unifiée des domaines) - Structurellement organisée (9 sous-modules ciblés) - Fonctionnellement complète pour tous les chemins audités - Prête pour une vérification manuelle contre une checklist de test en 25 points
C'est la différence entre « ça marche » et « c'est livrable ».