Aller au contenu principal

Paquets de modifications

La page Paquets de modifications capture chaque écriture de donnée suivie dans un ensemble groupé, revu et promouvable. Le cas d'usage qui a motivé la fonctionnalité est la promotion de la sécurité JD Edwards d'un environnement de développement vers la production — copier un rôle, attacher quelques menus, enregistrer → les écritures atterrissent dans les tables de production en développement, ET dans un paquet de modifications que l'opérateur peut revoir, approuver et expédier vers la production sous forme de paquet JSON.

Le même schéma convient à tout autre flux « faire les modifications ici, les rejouer là-bas » : un rafraîchissement de catalogue UDC curé, un lot de nouvelles permissions sur un écran spécifique, une mise à jour de règle de routage sur une table de configuration. Quand un écran est change_tracked, ses insertions / mises à jour / suppressions atterrissent dans le paquet brouillon actif en parallèle de l'écriture de production.

C'est le pendant côté opérateur d'un moteur plus profond : le framework enregistre chaque écriture suivie dans les tables ly_change_packages + ly_change_entries (la base change-set — même pool que le framework, distincte du miroir d'audit), et le chemin d'application les rejoue sur la cible avec détection de dérive sur l'image avant.

Nouveauté du framework 2026.06

La page se trouve sous Nomaflow parce que le flux — capture → revue → soumission → approbation → export → application — est un flux, pas un réglage. L'écran Paramètres → Package existe toujours pour les tranches de configuration (écrans / éléments de menu / tableaux de bord) ; les Paquets de modifications sont l'équivalent données. Les deux coexistent.


Ouvrir la page

  • Barre latérale → Nomaflow → Modifications.
  • Depuis la page Paramètres → Connecteurs, Suivre les modifications dans la carte de l'écran ouvre la page filtrée sur l'application correspondante.

Vue d'ensemble

Nomaflow · ModificationsModificationsAppliquer un paquet🔍 Filtrer les paquets…JDE-PROD · Sécurité 2026-06-09soumis · franck · 09/06 09:42 · 12 entréesEN ATTENTEJDE-PROD · Rafraîchissement UDCbrouillon · franck · 09/06 08:15 · 4 entréesBROUILLONJDE-PROD · Permissions T2approuvé · valeria · 06/06 17:10 · 47 entréesAPPROUVÉJDE-PROD · Permissions T1exporté · valeria · 04/06 12:01 · 23 entréesEXPORTÉSécurité 2026-06-09application = JDE-PROD · 12 entréesApprouverRejeterExporter▾ RÔLES (3)PRJM_NEW · UDC_ADM · BUYER_R2PRJM_NEW · INSERT · src : import sécuritéINSERTRLDF · ∅ → PRJM_NEW | RLDC · ∅ → 1 | +6 autres▾ RELATIONS (5)F95921 · F00926RLDF=PRJM_NEW · YRLE=A03 · INSERTINSERT▾ SÉCURITÉ (4)F00950RLUSER=PRJM_NEW · RLAT1=8 · UPDATEUPDATE▾ REJEUX D'ACTION (1)call_plugin · nomajde.security:j_remerge_securityCALL_PLUGIN · child_role=PRJM_NEW · rejeu = trueCALL_PLUGINPost-application : 1 étape · nomajde-remerge-security (s'exécute une fois sur la cible après l'atterrissage de chaque ligne capturée)

Comment un paquet se remplit

Trois éléments doivent être en place pour qu'une écriture atterrisse dans un paquet.

RequisDescription
L'écran est change_tracked = trueUn drapeau dans l'onglet Général de l'écran (ou son TOML). Sans lui, les écritures vont uniquement dans la table sous-jacente — pas de capture.
Un brouillon actif existe pour l'applicationLe connecteur de l'écran → son application. Le framework garde au plus un BROUILLON par application (garanti par un index unique partiel). La première écriture suivie sur cette application en crée un s'il n'en existe pas.
L'utilisateur est connectéLe paquet enregistre captured_by depuis la session. Les écritures anonymes ne sont pas suivies.

Quand ces conditions sont réunies, chaque écriture suivie produit une entrée de modification :

OpérationCe qui est capturé
INSERTnew_values — les colonnes d'écriture liées.
UPDATEold_values (l'image avant récupérée via le read_query de l'écran avant l'écriture) + new_values. L'image avant est ce qui pilote la détection de dérive à l'application.
DELETEold_values uniquement.
CALL_APILes paramètres d'appel résolus + le drapeau change_replay configuré — réémis tels quels à l'application quand replay = true.
CALL_PLUGINPareil — l'id du plugin + les kwargs résolus, conditionnés par change_replay.

Chaque entrée enregistre aussi :

  • connector + query — le framework peut router le rejeu vers le connecteur correspondant sur la cible.
  • entity — le change_entity optionnel de l'écran (role, user, relationship, security, …). L'interface groupe les entrées par cette étiquette sur la page de détail ; un écran sans change_entity capture quand même, simplement groupé par query.
  • entity_key — la clé naturelle de la ligne (résolue depuis les key_columns de l'écran). Pilote la compaction à l'export — INSERT puis UPDATE puis UPDATE sur la même clé se réduit à une seule entrée nette ; INSERT puis DELETE s'annule entièrement.
  • source_action — l'étiquette de l'action qui a déclenché l'écriture, transmise depuis le contexte d'origine. Permet à la vue de détail de grouper un lot comme « importé par import sécurité » au lieu d'un mur d'insertions sans attribution.
  • read_query — la requête de lecture de l'écran, utilisée par le chemin d'application pour récupérer la ligne cible actuelle et la comparer à old_values pour la détection de dérive.
  • seq — monotone par paquet, pour que le rejeu préserve l'ordre de capture. Un rôle doit exister avant d'être référencé ; le seq le garantit.

Ce que change_tracked ne suit PAS

Non suiviPourquoi
Les écritures d'un écran avec change_tracked = false.Comportement par défaut. Les tables d'audit (journal de notifications, statistiques de relance) ne doivent pas polluer les paquets de promotion.
Les écritures d'un job Nomaflow, d'un import en masse, du CLI.Le suivi est par écran et passe par le chemin d'écriture de l'écran. Les jobs utilisent les appelables j_* directement sur le connecteur.
Les lectures — aucun SELECT n'est jamais capturé.Le paquet est un journal de modifications.
Les écritures de métadonnées propres au framework (ly_jobs, ly_users, les tables change-set elles-mêmes).La boucle de capture vérifie change_tracked sur l'écran du connecteur, pas sur la table.

Le cycle de vie

Un paquet traverse jusqu'à six états :

DRAFT ─── submit ───▶ PENDING ─── approve ──▶ APPROVED ─── export ──▶ EXPORTED ─── apply ──▶ PROMOTED
│ │
└── reject ──▶ REJECTED (rouvre à DRAFT à la prochaine capture)
ÉtatSens
DRAFTLe paquet actif sur son application. Les nouvelles écritures suivies s'y attachent. Les entrées peuvent être exclues / réincluses.
PENDINGSoumis pour approbation. Aucune nouvelle écriture ne s'y attache (un nouveau DRAFT s'ouvre pour l'application à la prochaine capture). Le relecteur peut approuver ou rejeter.
APPROVEDFigé, prêt à exporter. Les entrées ne peuvent plus être exclues.
EXPORTEDUn paquet JSON a été téléchargé. Le paquet est désormais l'enregistrement côté source de ce qui a été expédié ; le paquet JSON est ce qui atteint la cible.
PROMOTEDLe chemin d'application de la cible a signalé un succès et a marqué la date promoted_at de ce paquet.
REJECTEDLe relecteur l'a renvoyé. Rouvre à DRAFT à la prochaine écriture capturée, pour que l'opérateur puisse amender et resoumettre.

Les quatre boutons de cycle de vie en en-tête de la vue de détail — Soumettre, Approuver, Rejeter, Exporter — déclenchent POST /admin/changesets/<id>/submit (ou …/approve, …/reject, …/export). Le flux est soumis à permissions : les rôles package:submit, package:approve, package:export contrôlent les boutons individuellement (par défaut : superutilisateur).

Par entrée : exclure / réinclure

Tant que le paquet est DRAFT ou PENDING, chaque entrée capturée a un bouton Exclure. Les entrées exclues ne sont pas supprimées (aucune perte d'audit) ; elles sont marquées status = excluded et ignorées à la compaction + à l'export. La réinclusion se fait en un clic. Utile pour le tri à la carte : capturer un large ensemble de modifications dans la journée, exclure l'expérimentation qui n'a pas abouti, expédier le reste.

La compaction est calculée à l'export, pas à la capture — les exclusions prennent effet immédiatement au moment où le paquet est construit.


L'onglet Modifications — relire et agir

La page a deux onglets : Modifications (le catalogue de paquets) et Appliquer un paquet (traité plus bas).

Colonne de gauche — la liste des paquets

ÉlémentDescription
Boîte de filtreCorrespondance par sous-chaîne sur le nom du paquet + l'application. Côté serveur.
Paquet actifPrésélectionné à l'ouverture (le brouillon de l'application que l'opérateur a touchée en dernier). Surligné en bleu.
Ligne de paquetNom + ligne méta (statut · captured_by · horodatage · nombre_entrées) + un badge de statut coloré.
Couleurs de statutDRAFT bleu, PENDING orange, APPROVED vert, EXPORTED / PROMOTED gris, REJECTED rouge.
Icône corbeilleSupprime le journal du paquet — les lignes déjà écrites dans les tables sous-jacentes NE sont PAS affectées ; seul l'enregistrement du paquet de modifications est retiré. Confirmé via la boîte de dialogue thématisée.

Colonne de droite — détail du paquet

ÉlémentDescription
En-têteNom du paquet + application + nombre d'entrées + boutons de cycle de vie (Soumettre / Approuver / Rejeter / Exporter) + les ids des étapes de post-application résolues (quand elles sont définies sur les écrans contributeurs).
Entrées groupées par entitéGroupes par défaut : RÔLES, UTILISATEURS, RELATIONS, SÉCURITÉ, MENUS, REJEUX D'ACTION (les entrées CALL_API / CALL_PLUGIN) — l'étiquette de groupe vient du change_entity de chaque entrée. Cliquer sur l'en-tête du groupe pour plier / déplier.
En-tête de groupeL'étiquette + le nombre d'entrées + un court échantillon des clés naturelles (par ex. PRJM_NEW · UDC_ADM · BUYER_R2).
Ligne d'entréeBadge d'opération + clé naturelle + étiquette de l'action déclenchante. Cliquer sur le chevron pour développer le diff ancien → nouveau.
Vue diffTableau à deux colonnes champ / valeur avec trois tons : barré rouge = ancien, vert = nouveau, secondaire = inchangé. Un petit interrupteur Voir les inchangés dévoile le bruit des colonnes intactes quand on relit une entrée renommée mais non modifiée.
Boutons par entréeExclure (uniquement sur les paquets DRAFT / PENDING) bascule l'entrée en EXCLUDED et la grise. Cliquer à nouveau pour réinclure.

Ids d'étapes post-application en en-tête

Quand les écrans qui ont contribué au paquet ont des ids d'étapes Screen.post_apply définis (une liste d'ids d'étapes Nomaflow à exécuter une fois sur la cible après l'atterrissage du paquet), ces ids s'accumulent en en-tête du paquet. À l'export ils sont cousus dans une section [changesets] post_apply du paquet JSON ; le chemin d'application de la cible les exécute après la dernière écriture de ligne. Exemple d'usage : une promotion de sécurité JDE qui demande à nomajde-remerge-security de s'exécuter sur la cible une fois que chaque ligne capturée (rôle + affectation) a atterri.

L'éditeur des ids d'étapes — quoi enregistrer sur un écran, quoi y câbler — se trouve sur la sous-page Paramètres → Post-application à côté de l'éditeur de paquets de modifications. Définir une étape prend un id de job Nomaflow ; le job est ensuite exécutable sur la cible.


L'onglet Appliquer — importer un paquet

À exécuter sur l'installation cible (typiquement la prod) pour importer un paquet exporté depuis la source (typiquement le dev).

Le flux d'application se fait en deux passes : essai à blanc d'abord pour voir le rapport de dérive par opération, puis application réelle, avec éventuellement Forcer coché sur les lignes que l'on veut écraser.

ÉtapeDescription
1. Choisir le JSONLe fichier .changeset.json exporté. L'en-tête affiche son nom, son application et le nombre d'opérations une fois parsé.
2. Essai à blancPOST /admin/changesets/apply { bundle, dry_run: true }. Le serveur parcourt chaque opération, récupère la ligne cible actuelle via le read_query capturé, la compare à l'old_values capturé et tague l'opération avec un statut. Aucune écriture n'a lieu.
3. Lire le rapportChaque opération affiche son badge de statut : would_apply (propre), would_force (dérive sur UPDATE / DELETE — exige le coche Forcer), unverified (pas de read_query dans le paquet — l'application atterrira mais sans contrôle de dérive), conflict (insertion sur une ligne déjà existante, ou suppression sur une ligne absente), error (échec par opération). La ligne de résumé totalise chaque statut.
4. Cocher Forcer sur les lignes à écraserChaque opération would_force a une case à cocher. Confirme l'intention de l'opérateur d'écraser une ligne dérivée. Les opérations conflict reçoivent aussi une case ; la cocher les ignore à l'application réelle.
5. AppliquerPOST /admin/changesets/apply { bundle, dry_run: false, force: [<indexes>] }. Chaque opération s'exécute dans sa propre transaction ; les nombres de lignes + le statut par opération reviennent dans le rapport.
6. ConfirmationUne ligne atterrit dans le journal AppliedBundle de la cible (le journal d'import sur cette même page → onglet Appliquer un paquet → haut du panneau). Le statut du paquet côté source bascule en PROMOTED uniquement après que le chemin d'application de la cible a fait son retour à la source — pas automatique dans le flux Appliquer autonome.

Détection de dérive — ce qui est comparé

Pour chaque opération de ligne (INSERT / UPDATE / DELETE), le chemin d'application :

  1. Lit le read_query capturé dans le paquet, l'exécute sur la cible avec l'entity_key capturé → la ligne actuelle de la cible.
  2. Compare champ par champ à l'old_values du paquet.
  3. S'ils correspondent → would_apply (l'image avant côté source est cohérente avec l'état actuel de la cible).
  4. S'ils diffèrent → would_force pour UPDATE / DELETE (la ligne a changé sur la cible depuis la capture du paquet ; l'opérateur décide d'écraser ou non) ou conflict pour INSERT (une ligne avec cette clé existe déjà sur la cible).
  5. Si le paquet n'a pas de read_queryunverified. L'application atterrit mais aucun contrôle de dérive n'a tourné. Les paquets compacts créés avec --no-read-query ou depuis des écrans qui ne fournissent pas de read query atterrissent ici.
StatutSensCe que fait l'application
would_apply / appliedPropre — la ligne actuelle de la cible correspond à l'image avant du paquet.L'application exécute l'écriture de ligne.
would_force / applied après ForcerDérive détectée ; l'opérateur a choisi d'écraser.L'application exécute l'écriture de ligne.
unverifiedPas de read query → pas de contrôle de dérive.L'application exécute l'écriture de ligne (le coche simple est applied).
conflictINSERT sur une clé existante, DELETE sur une ligne absente.Ignoré sauf si l'opérateur coche la case du conflit.
errorL'écriture elle-même a levé une erreur (violation FK, contrainte, incompatibilité de dialecte).Ignoré ; la ligne de détail porte l'erreur SQL.

Réappliquer le même paquet

Le journal AppliedBundle utilise la somme de contrôle du paquet (sha256 sur ses opérations) comme identité. Réappliquer le même fichier est détecté avant la moindre écriture — le rapport d'essai à blanc porte un bloc already_applied avec l'applied_at / applied_by précédents. L'opérateur peut soit confirmer et réappliquer (chaque ligne atterrira en applied si la cible est revenue à l'image avant, ou en would_force / conflict si elle a dérivé) soit fermer le fichier.

Rejeu d'action (CALL_API / CALL_PLUGIN)

Un écran suivi capture chaque appel API / plugin qu'il déclenche (pour que le paquet l'affiche en relecture). Le fait que l'appel soit rejoué sur la cible est conditionné par le drapeau change_replay de l'action :

change_replayCe qui est rejoué sur la cible
true (opt-in)L'action est réémise telle quelle avec les paramètres résolus capturés. Utilisé pour les plugins de fusion côté serveur comme nomajde.security:j_remerge_security — refusionner la sécurité sur la cible est juste après l'atterrissage des écritures de lignes.
false (par défaut)La capture est affichée dans le paquet pour relecture mais pas rejouée. Utilisé pour les effets de bord ponctuels qui ne doivent pas s'exécuter deux fois (envoi d'e-mails, webhook externe).

Une action rejouée tourne dans la même transaction que les écritures de lignes environnantes quand le dialecte le permet ; en pratique cela signifie qu'une exécution réussie du plugin de fusion de sécurité atterrit atomiquement avec les écritures de rôle / d'affectation qui l'ont déclenchée.


Promouvoir la sécurité JD Edwards — le cas d'usage canonique

Le flux qui a motivé cette fonctionnalité. Sur l'instance dev :

  1. L'opérateur crée un rôle PRJM_NEW, attache trois menus, modifie les permissions de deux rôles existants. Chaque écriture atterrit dans le paquet brouillon de l'application JDE-PROD au fil de l'eau.
  2. L'opérateur ouvre Nomaflow → Modifications, relit le brouillon. La vue de détail montre les trois rôles groupés sous RÔLES, les attachements de menus sous MENUS, la sécurité touchée sous SÉCURITÉ. Une entrée CALL_PLUGIN vers nomajde.security:j_remerge_security est aussi capturée.
  3. L'opérateur exclut une entrée — une correction de coquille qu'il veut annuler manuellement à la place. Soumet, puis Approuve, puis Exporte.
  4. Le navigateur télécharge jde-prod-security-2026-06-09.changeset.json.

Sur l'instance prod :

  1. L'opérateur ouvre Nomaflow → Modifications → Appliquer un paquet, dépose le JSON.
  2. L'essai à blanc s'exécute. Le rapport affiche 11 would_apply et 1 would_force sur le rôle BUYER_R2 (quelqu'un l'a touché en prod depuis la capture du paquet). L'opérateur coche la case Forcer pour cette ligne.
  3. L'opérateur clique Appliquer. Chaque ligne atterrit ; l'étape de post-application nomajde-remerge-security se déclenche, refusionnant la sécurité pour les rôles parents concernés en prod.
  4. Le paquet source côté dev bascule en PROMOTED une fois que la cible a fait son retour. Le panneau Paquets appliqués côté prod enregistre la nouvelle ligne avec sa somme de contrôle.

Pour la référence détaillée du SQL de fusion que l'étape de post-application exécute, voir Nomaflow → Jobs intégrés → nomajde-remerge-security.


Configurer un écran pour le suivi des modifications

L'onglet Général de l'écran porte trois bascules :

ChampDescription
change_trackedBascule maîtresse. Quand elle est active, les écritures de l'écran s'attachent au paquet actif de son application.
change_entityÉtiquette optionnelle qui groupe les entrées sur la page de détail. Texte libre — role, user, relationship, security sont des conventions. Vide, on retombe sur le nom de la requête.
Screen.post_applyListe d'ids d'étapes Nomaflow (depuis la sous-page Post-application) à exécuter une fois sur la cible après l'atterrissage de chaque ligne de chaque paquet contribué par cet écran. À utiliser avec parcimonie — ce sont les étapes transverses « refusionner la sécurité maintenant » / « rafraîchir les vues matérialisées maintenant ».

Pour une action embarquée dans l'écran, une bascule supplémentaire sur l'onglet Général de l'action :

ChampDescription
change_replayQuand elle est active, une invocation CALL_API / CALL_PLUGIN de cette action est rejouée sur la cible. Désactivée par défaut — capture seulement.

Contrat d'API

EndpointMéthodeDescription
/admin/changesetsGETListe les paquets. Filtres : application=<id>, status=<une-valeur>. Superutilisateur uniquement.
/admin/changesets/<id>GETDétail du paquet — en-tête + chaque entrée avec ses valeurs anciennes / nouvelles.
/admin/changesets/<id>DELETESupprime uniquement le journal du paquet (pas les écritures sous-jacentes).
/admin/changesets/<id>/submitPOSTPasse DRAFTPENDING.
/admin/changesets/<id>/approvePOSTPasse PENDINGAPPROVED.
/admin/changesets/<id>/rejectPOSTPasse PENDINGREJECTED (rouvre à DRAFT à la prochaine capture).
/admin/changesets/<id>/entries/<entry_id>/excludePOSTBascule le statut d'une entrée vers EXCLUDED.
/admin/changesets/<id>/entries/<entry_id>/includePOSTBascule le statut d'une entrée vers CAPTURED.
/admin/changesets/<id>/exportGETRenvoie le paquet JSON compacté ; bascule le paquet en EXPORTED.
/admin/changesets/applyPOSTCorps : { bundle, dry_run, force: [<indexes>] }. Renvoie le rapport par opération.
/admin/changesets/appliedGETLe journal d'import de la cible (chaque paquet appliqué).

La forme du paquet :

{
"format": "liberty.changeset.v1",
"package": { "id": "...", "name": "...", "application": "JDE-PROD", "exported_at": "..." },
"op_count": 12,
"ops": [
{ "seq": 1, "connector": "jdedwards", "query": "roles", "read_query": "roles_by_key",
"operation": "INSERT", "entity": "role", "entity_key": {"RLDF": "PRJM_NEW"},
"new_values": {"RLDF": "PRJM_NEW", "RLDC": "1", ...},
"source_action": "import security" }
],
"post_apply": ["nomajde-remerge-security"],
"checksum": "sha256:..."
}

Le format est stable entre les montées de version du framework — un paquet construit sur 2026.06 s'applique sans souci sur 2026.07. La compatibilité ascendante est garantie par l'étiquette de version (format = "liberty.changeset.v1") ; un paquet produit par un hypothétique v2 serait rejeté par une cible v1 avec une erreur claire.


Ce qui n'est PAS dans cette page

Le framework livre le moteur pour ce qui suit, mais l'interface aujourd'hui est volontairement minimale :

SurfaceÉtat
Planifier une soumission / approbation automatiqueNon. Cycle de vie manuel par choix de conception (un paquet approuvé part en prod — la validation humaine est précisément l'objectif).
Diff entre deux paquetsNon. Comparez sur la cible via le journal des paquets appliqués + la vue de détail côté source.
Annuler un paquet promuNon. Le framework enregistre l'application mais ne synthétise pas un paquet inverse. Pour défaire, capturez un nouveau paquet sur la source qui annule les lignes + promouvez-le.
Mises à jour multi-onglets en directNon. Une capture sur un autre onglet ne rafraîchit pas cette page ; cliquez Rafraîchir (en haut à droite) pour recharger.

Pièges courants

ErreurSymptômeCorrectif
L'écran n'a pas été marqué change_tracked.Les écritures passent ; aucune entrée n'apparaît dans le paquet.Ouvrir l'onglet Général de l'écran, activer la bascule, enregistrer. La modification ne s'applique qu'aux écritures suivantes — les écritures antérieures ne sont pas rétro-remplies.
Deux opérateurs ont un paquet BROUILLON ouvert sur la même application.Au plus un BROUILLON existe à la fois ; les écritures du second opérateur s'attachent au même paquet.Coordonnez-vous. Le created_by du paquet enregistre l'auteur d'origine ; les captures suivantes marquent captured_by sur chaque entrée, l'attribution est donc conservée.
Paquet appliqué sur la mauvaise cible (recette au lieu de prod).L'application atterrit ; les lignes existent sur la recette.Capturez un paquet inverse sur la recette, promouvez-le en prod — le framework ne synthétise pas cela automatiquement. La discipline de flux compte plus que l'outil ici.
would_force sur chaque UPDATE à l'essai à blanc.L'image avant de la cible a divergé largement — typiquement parce que la source et la cible n'étaient pas synchronisées au départ.Auditer la divergence d'abord. Tout forcer propage l'état de la source en bloc ; parfois c'est juste, parfois c'est la source qui est obsolète.
CALL_PLUGIN capturé mais non déclenché à l'application.change_replay est désactivé sur l'action.Ouvrir l'onglet Général de l'action, activer change_replay, enregistrer. Les captures futures seront rejouées ; les précédentes dans des paquets déjà exportés restent telles quelles.
L'étape de post-application n'a jamais tourné.Le champ Screen.post_apply de l'écran contributeur est vide.Câblez l'id d'étape (le id d'un job Nomaflow) dans le post_apply de l'écran. Les nouvelles captures l'accumuleront en en-tête du paquet ; les paquets existants ne seront pas rétro-remplis.
Paquet réappliqué par accident.L'essai à blanc affiche already_applied avec l'horodatage précédent.Lire le rapport de l'application existante. Réappliquer est permis et ré-exécutable sans danger sur les lignes propres ; ne le faites qu'en pleine conscience.

Pour aller plus loin