18 KiB
30. Chantier des consignes — moteur générique (6 septembre 2026)
Origine. Reprise le soir même de la réflexion stratégique de la nuit précédente (Réflexions stratégiques, section 14) sur le pilotage vocal comme deuxième mode de fonctionnement, et de la session de réflexion du matin du 6 septembre sur la distinction entre règles intrinsèques (non modifiables) et consignes personnelles (modifiables par l'utilisateur) — voir Réflexions stratégiques, section 15, pour le fil conceptuel complet. Ce chantier est le premier cas concret construit sur cette distinction, volontairement circonscrit à la fiche contact (création/modification) pour commencer.
Nouvelle numérotation de version actée en tout début de session. Format à trois niveaux V<majeur>.<code>.<conception> (voir version.php, en-tête). Départ de la session : V1.13.14. <code> et <conception> anticipés dès le début de session (Jean-Marc savait déjà que les deux évolueraient) : V1.14.15.
Deux tables créées par Jean-Marc lui-même (convention habituelle) :
CREATE TABLE `ASP_Fonctions` (
`FON_Incr` int NOT NULL AUTO_INCREMENT,
`FON_Nom` varchar(30) NOT NULL,
`FON_Nature` varchar(12) NOT NULL,
`FON_Description` varchar(200) NOT NULL,
`FON_ParametresAttendus` varchar(200) NULL,
`FON_NomPHP` varchar(50) NOT NULL,
`FON_Active` varchar(1) NOT NULL DEFAULT 'O',
PRIMARY KEY (`FON_Incr`),
UNIQUE KEY (`FON_Nom`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
CREATE TABLE `ASP_Consignes` (
`CNS_Incr` int NOT NULL AUTO_INCREMENT,
`CNS_Contexte` varchar(40) NOT NULL,
`CNS_Portee` varchar(60) NOT NULL,
`CNS_Champ` varchar(40) NULL,
`CNS_Type` varchar(10) NOT NULL,
`CNS_Nature` varchar(12) NOT NULL,
`CNS_Moment` varchar(15) NULL,
`CNS_Visible` varchar(1) NOT NULL DEFAULT 'O',
`CNS_Modifiable` varchar(1) NOT NULL DEFAULT 'O',
`CNS_Agent` varchar(20) NULL,
`CNS_LibelleCourt` varchar(60) NOT NULL,
`CNS_TexteDefinition` TEXT NOT NULL,
`CNS_TexteAffiche` varchar(300) NOT NULL,
`CNS_Fonction` varchar(30) NULL,
`CNS_Parametres` JSON NULL,
`CNS_MessageErreur` varchar(200) NULL,
`CNS_Active` varchar(1) NOT NULL DEFAULT 'O',
`CNS_DateCreation` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`CNS_Utilisateur` varchar(3) NULL,
PRIMARY KEY (`CNS_Incr`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
Conventions retenues pour les valeurs de colonnes (choisies par Jean-Marc, différentes du style sans accent utilisé ailleurs comme CVX_Type) : CNS_Type = Règle/Consigne, CNS_Nature = Traitement/Test, CNS_Moment = Saisie/Validation, booléens en O/N. CNS_Portee = 'Mise à jour' est traité comme un mot-clé signifiant "Création et Modification" par le moteur (voir plus bas), à côté de valeurs plus précises possibles plus tard ('Création' ou 'Modification' seules, pour une future règle d'immuabilité). CNS_Fonction référence ASP_Fonctions.FON_Nom (texte, pas la clé technique FON_Incr) — plus lisible pour une inspection directe en base.
moteur_consignes.php — le moteur générique. Fonction unique appliquerConsignes(PDO $pdo, string $contexte, string $portee, array &$data): ?string. Lit ASP_Consignes (jointure ASP_Fonctions sur FON_Nom = CNS_Fonction et FON_Active='O') pour le contexte/portée donnés, applique tous les Traitements actifs (silencieux, mutent $data par référence, dans l'ordre de CNS_Incr) puis tous les Tests actifs sur les valeurs déjà transformées ; retourne le message du premier test en échec (CNS_MessageErreur), ou null si tout passe. Aucune référence à un champ ou une consigne précise dans ce fichier — tout vient de la base. Limites assumées pour cette première version (documentées en en-tête du fichier) :
- pas de distinction Saisie/Validation à l'exécution (voir plus bas, décision de Jean-Marc) ;
- les consignes transverses à toute la fiche (
CNS_ChampNULL, ex. futur contrôle de doublon) sont ignorées, à reprendre le jour d'un vrai cas ; - un seul message d'erreur renvoyé (le premier test en échec), pas d'accumulation.
consignes_fonctions.php — le catalogue de fonctions. Bibliothèque sans dépendance (pas de session, pas de base), une fonction par entrée de ASP_Fonctions.FON_NomPHP. Convention : un Traitement a la signature (string $valeur): string, un Test (string $valeur): bool. Trois fonctions écrites ce soir :
consigne_majuscule(MAJUSCULE, Traitement) —mb_strtoupperavec encodage explicite (unstrtoupper()simple laisserait les accents français inchangés selon la locale serveur).consigne_non_vide(NON_VIDE, Test) —trim($valeur) !== ''.consigne_premiere_lettre_majuscule(PREMIERE_LETTRE_MAJUSCULE, Traitement) — "georges"→"Georges", "JOSEPH"→"Joseph", "jean-marc"→"Jean-Marc". Écrite à la main plutôt qu'avecucwords(): cette fonction native PHP raisonne en octets, pas en caractères, et corromprait un prénom commençant par une lettre accentuée (ex. "Éric"). Implémentation parpreg_replace_callbackavec\p{L}(Unicode) après minuscule globale.
contacts.php débarrassé de tout contrôle en dur. Avant ce soir : $nom = trim($data['CNT_Nom'] ?? ''); if ($nom === '') { ... 400 ... } dupliqué en POST et PUT, aucun traitement de mise en forme. Après : un seul appel, appliquerConsignes($pdo, 'Contacts', 'Création'|'Modification', $data), suivi d'un retour 400 avec le message tel quel si non-null. Objectif explicite de Jean-Marc, vérifié en fin de session : ajouter ou modifier une ligne dans ASP_Consignes/ASP_Fonctions change le comportement réel de l'application sans qu'aucun fichier PHP ne soit retouché, sauf écriture d'une fonction nouvelle ou bug.
Détour et correction en cours de route : le temps réel, tenté puis abandonné. Premier réflexe de Claude (avant clarification par Jean-Marc) : coder en dur un contrôle "nom obligatoire" réactif au blur du champ côté écran, en plus du contrôle serveur — mauvaise cible, Jean-Marc voulait tester le moteur générique, pas un nouveau code écrit à la main par champ. Après clarification, Jean-Marc a lui-même arbitré l'abandon du temps réel pour cette version (latence non significative pour un seul champ, mais complexité de construction jugée disproportionnée par rapport au gain — priorité donnée à la robustesse). Tout le code de validation côté client (liste_contacts.html) a été retiré ; validerFormulaireContact() se contente d'envoyer et d'afficher tel quel le message renvoyé par le serveur.
Bug découvert (et corrigé) précisément à cause de ce retrait. En enlevant l'ancien contrôle client ("Le nom est obligatoire.", codé en dur avant ce chantier), on a révélé que le gestionnaire d'erreur de validerFormulaireContact() affichait déjà, de longue date, un message générique fixe ("Erreur : impossible de créer ce contact.") au lieu du message précis renvoyé par le serveur (data.error était lu pour la console, jamais pour le toast). Corrigé dans les trois points d'échec (PUT, POST, catch réseau) : afficherToast((data && data.error) || <message générique>, true).
Ajustements d'interface demandés par Jean-Marc après premiers tests réels :
- Toast d'erreur centré sur le panneau de saisie (
top:50%, comme#panneau-detail) et en rouge (var(--rouge-suppression), déjà utilisée pour le mode suppression) — nouvelle classe.toast-erreur, activée via un deuxième paramètre deafficherToast(message, erreur). Limité aux erreurs devaliderFormulaireContact(PUT/POST/catch), les autres toasts du fichier restent inchangés. white-space:pre-lineajouté à#toast(tous les toasts, pas seulement l'erreur) : unCNS_MessageErreurcontenant des retours à la ligne saisis en base ne s'affichait pas correctement (HTML fond les retours à la ligne en un espace par défaut).
Test réel réussi en fin de session, sans aucune modification de code : Jean-Marc a ajouté trois nouvelles lignes dans ASP_Consignes (NON_VIDE sur CNT_Prenom et CNT_AdrVille, réutilisant la fonction déjà cataloguée) — comportement immédiatement actif après dépôt des fichiers déjà livrés, aucun fichier PHP/JS retouché. Preuve de concept validée par Jean-Marc : "On peut dire qu'on n'est pas mauvais !"
Reste à faire, noté en fin de session du 6 septembre :
- Concevoir un nouveau type de consigne pour les cas nécessitant un appel IA en cours de traitement (ex. retrouver le code postal d'une ville saisie sans code postal) — contrat de retour plus riche que Traitement/Test actuel (silencieux / besoin de précision / question à poser), gestion d'échec réseau, coût et latence d'un appel externe. Session dédiée à prévoir, pas un ajout mineur au catalogue existant. (Premier incrément construit dès le lendemain, voir 7 septembre 2026 ci-dessous — pas encore le contrat complet à trois issues.)
- Écran de gestion des consignes par le boss (au lieu de
INSERT/UPDATESQL à la main) — à construire après stabilisation du contrat ci-dessus, pour ne pas le refaire. - Réflexion sur un catalogue restreint de consignes/fonctions avancées à proposer aux futurs utilisateurs de l'agence, sans leur ouvrir la création libre de A à Z — argument commercial identifié par Jean-Marc ("Possibilité de nouvelles consignes pour vos assistants").
- Accumulation de plusieurs messages d'erreur à la fois (le moteur ne remonte aujourd'hui que le premier test en échec).
- Gestion des consignes transverses à toute la fiche (
CNS_ChampNULL, ex. contrôle de doublon) — non traitée par le moteur actuel. - Distinction Saisie/Validation réellement exploitée un jour (temps réel), si le besoin redevient assez fort pour justifier la complexité écartée ce soir.
Suite — 7 septembre 2026 : hiérarchisation, traçabilité, et premier appel IA réel dans une consigne
Contexte stratégique complet : Réflexions stratégiques, section 17 ([Évolutions majeures]). Ce qui suit est le compte rendu technique correspondant.
1. Badge de chargement IA générique. Nouvel endpoint consignes_apercu.php et convention de fonction jumelle : tout Traitement marqué FON_VisuelAttenteIA='O' dans ASP_Fonctions doit être accompagné d'une fonction <FON_NomPHP>_seraitDeclenchee(array $valeurs): bool, sans aucun appel réseau, qui répond seule à "ce traitement se déclencherait-il réellement sur ces valeurs". auMoinsUneConsigneIaSeraitDeclenchee() (moteur_consignes.php) interroge ces fonctions jumelles pour toutes les consignes actives du contexte/portée, avant même une tentative d'enregistrement — liste_contacts.html n'affiche le badge #badge-ia-actif que si la réponse est positive, jamais de façon systématique. Rendu : rond, centré à l'écran comme un toast d'erreur, 65px de côté après plusieurs essais (130 → 50 → 25 → 65), vérifiés au rendu réel via Playwright plutôt qu'en pixels devinés. pointer-events:none (jamais un obstacle au clic, même principe que les autres éléments purement informatifs de l'écran de fond).
2. Hiérarchisation des trois phases d'appliquerConsignes(). Bug réel découvert par Jean-Marc : une soumission vouée à échouer sur un Test déclenchait quand même, avant l'échec, un Traitement IA coûteux — appel réseau gaspillé, sans même laisser de trace puisque l'enregistrement entier était rejeté ensuite. Le moteur exécute désormais trois phases strictement séquentielles, distinguées par le seul attribut FON_VisuelAttenteIA déjà présent : (1) tous les Traitements de cohérence (FON_VisuelAttenteIA != 'O'), (2) tous les Tests (le premier échec arrête tout, avant d'atteindre la moindre étape IA), (3) seulement si tous les Tests ont réussi, les Traitements IA (FON_VisuelAttenteIA = 'O'). Nouvelle fonction interne executerTraitements(array $consignesTraitement, array &$data, array &$journalisations, array &$avertissements): void, appelée deux fois (phase 1 et phase 3) avec un sous-ensemble différent de consignes.
3. Traçabilité de l'agent réel dans une réalisation journalisée. executerTraitements() ne fait plus confiance à un nom d'agent proposé par la fonction de traitement elle-même (source d'un bug déjà corrigé : un prénom "Mat" resté codé en dur sans aucune source) — le nom journalisé dans _realisation.agent vient désormais systématiquement de $c['CNS_Agent'] (la consigne active elle-même), tronqué à 15 caractères UTF-8, avec un repli sur la valeur de la fonction puis sur 'IA' seulement si CNS_Agent est vide. Même principe déjà en place côté $agentEchec pour un Test en échec.
4. REA_NomAffiche — instantané du nom affiché, jamais recalculé à la lecture. Nouvelle colonne sur ASP_Realisations, remplie une seule fois à la création de chaque ligne : côté humain, $_SESSION['USR_Prenom'] (désormais chargé et stocké en session dès aspic.php, à la connexion — requête étendue SELECT USR_Nom, USR_Prenom FROM ASP_Users) ; côté IA, le même nom d'agent que celui journalisé dans _realisation.agent (voir point 3). Une première approche par jointure ASP_Users recalculée à la lecture a été écartée (complexité de la requête, et surtout incompatibilité avec un futur agent hébergé dans une autre base MIRADOR) au profit de cet instantané écrit une seule fois — zéro requête supplémentaire à la lecture, zéro risque de base croisée. realisations.php (GET et POST) et contacts.php (journaliserRealisationsIA()) mis à jour en conséquence ; liste_contacts.html affiche r.REA_NomAffiche || r.REA_CodeUser || "" et la colonne "Qui ?" a été élargie de 40 à 90px pour ne plus tronquer "Jean-Marc"/"Matilda".
5. Renommage ASP_Agents.AGN_Nom → AGN_Prenom (à l'initiative de Jean-Marc), et une régression corrigée. Fait pour homogénéiser avec USR_Prenom. Claude a validé ce renommage en affirmant "aucun impact côté code" sans avoir grep le nom de colonne dans l'ensemble du projet — cinq fichiers pré-existants du pilotage vocal (agents.php, micro.js, contact_ia_panel.js, contact_ia_interprete.php, agent_notes.php) référençaient encore AGN_Nom, cassant la liste des agents ("la liste des agents n'est pas disponible !"). Tous corrigés le même soir. Leçon actée pour la suite (voir Réflexions stratégiques, section 17) : grep systématiquement le nom d'une colonne dans tout le code avant d'affirmer "aucun impact" sur un changement de schéma.
6. consigne_sans_chiffre() — nouveau Test générique. consignes_fonctions.php : return trim($valeur) === '' || !preg_match('/\d/', $valeur);. Ajoutée au catalogue (ASP_Fonctions, code SANS_CHIFFRE) puis appliquée par Jean-Marc lui-même via trois nouvelles lignes ASP_Consignes (Nom, Prénom, Ville) — aucun fichier PHP/JS retouché pour l'étendre à un troisième champ, confirmant une fois de plus le principe du chantier.
7. consigne_completer_code_postal() — premier vrai appel IA dans une consigne, avec trace honnête en cas d'échec. Reprend le contrat existant (_realisation, clé réservée déjà en place) et introduit une nouvelle clé réservée symétrique, _avertissement — une simple chaîne, affichée immédiatement à l'écran par le front-end sans qu'il sache jamais d'où elle vient. Quand l'IA ne peut rien déduire d'exploitable (ville ambiguë comme Paris, découpée en arrondissements) : une _realisation est journalisée dans tous les cas (même en échec — corrige l'anomalie signalée par Jean-Marc : un appel IA sans la moindre trace), et _avertissement est en plus renvoyé au front. moteur_consignes.php (executerTraitements()) extrait les deux clés en parallèle dans un nouveau tableau $avertissements, remonté par appliquerConsignes() (nouveau paramètre ?array &$avertissements = null) puis par contacts.php dans la réponse JSON ('avertissements' => $avertissementsIA).
8. Affichage de l'avertissement côté écran — trois itérations dans la soirée. D'abord un toast neutre, identique aux autres. Puis rouge et centré comme un vrai message d'erreur (afficherToast(message, true)), et le panneau reste en mode édition au lieu de repasser en mode vue après l'enregistrement (afficherDetailPourId(idContact, avertissements.length ? "edition" : "vue")) — pour que l'utilisateur puisse corriger tout de suite le champ resté incomplet, contactCourant étant malgré tout rafraîchi pour éviter un double POST en cas de nouvelle tentative d'enregistrement. Puis, une fois testé, le message disparaissait encore trop vite et la fiche restait visuellement "non validée" alors qu'elle était bien enregistrée : afficherToast() accepte désormais un 4ᵉ paramètre optionnel (onValider) qui, s'il est fourni, supprime la disparition automatique et ajoute un bouton "Valider" (nouvelle classe .toast-bouton) — le clic ferme le message et exécute onValider, ici un simple basculerModeDetail("vue") (aucune revalidation, aucun nouvel enregistrement déclenché, le contact étant déjà enregistré depuis le début de la séquence). Le texte de l'avertissement, côté consignes_fonctions.php, a été rendu plus impératif ("Saisissez le code postal vous-même.") en cohérence avec ce fonctionnement bloquant.
Version. <code> déjà avancé en tout début de cette session de travail (V1.14.15 → V1.15.15, avant même le badge IA du point 1). <conception> avancé en fin de session à 17 (Réflexions stratégiques, nouvelle section 17) — version finale de la session : V1.15.17.
Reste à faire, noté en fin de session du 7 septembre :
- Le contrat de retour à trois issues (silencieux / besoin de précision / question à poser), envisagé le 6 septembre, reste à concevoir dans son ensemble — ce qui a été construit ce 7 septembre n'en couvre que deux (succès silencieux, échec avec avertissement), volontairement, en attendant un second cas concret qui justifierait la généralisation.
- Tous les points laissés ouverts le 6 septembre (écran de gestion des consignes, catalogue commercial restreint, accumulation de plusieurs messages d'erreur, consignes transverses
CNS_ChampNULL, distinction Saisie/Validation) restent entiers, aucun n'a été traité ce 7 septembre.