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 arrivent dans les tables de 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 rejoignent le paquet brouillon actif en parallèle de l'écriture de production.

Derrière la page, le framework conserve chaque écriture suivie dans sa propre base — celle du framework, distincte du miroir d'audit —, et le chemin d'application les rejoue sur la cible avec détection des écarts sur l'image avant.

La page Paquet transporte des tranches de configuration (écrans, entrées de menu, tableaux de bord…) ; les Paquets de modifications transportent des données. Les deux se côtoient sous Liberty Next → Déployer.


Ouvrir la page​

  • Liberty Next → Déployer → Modifications.
  • Depuis la page Liberty Next → Construire → Connecteurs, Suivre les modifications dans la carte de l'écran ouvre la page filtrée sur l'application correspondante.

Vue d'ensemble​

Nomaflow · ModificationsModificationsAppliquer🔍 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'écriture de chaque ligne capturée)

Comment un paquet se remplit​

Trois éléments doivent être en place pour qu'une écriture rejoigne 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 (tâches, utilisateurs, les paquets de modifications eux-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 passe par ces états :

DRAFT ─── submit ───▶ PENDING ─── approve ──▶ APPROVED ─── export ──▶ EXPORTED
│
└── reject ──▶ REJECTED
ÉtatSignification
DRAFTLe paquet actif de son application. Les nouvelles écritures suivies s'y rattachent. Les entrées peuvent être exclues / réincluses.
PENDINGSoumis à approbation. Aucune nouvelle écriture ne s'y rattache (un nouveau brouillon s'ouvre pour l'application à la capture suivante). Le relecteur peut approuver ou rejeter.
APPROVEDFigé, prêt à l'export. Les entrées ne peuvent plus être exclues.
EXPORTEDUn paquet JSON a été téléchargé. Le paquet reste côté source la trace de ce qui a été livré ; le fichier JSON est ce qui part vers la cible.
REJECTEDLe relecteur l'a refusé. Il reste rejeté ; la prochaine écriture suivie sur l'application ouvre un nouveau brouillon.

Les quatre boutons de cycle de vie en en-tête du détail — Soumettre, Approuver, Rejeter, Exporter — appellent POST /admin/changesets/<id>/submit (ou …/approve, …/reject, …/export). Les paquets de modifications sont réservés aux superutilisateurs.

Par entrée : exclure / réinclure​

Tant que le paquet est DRAFT, 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 et à l'export. La réinclusion se fait en un clic. Pratique pour trier : capturer un large ensemble de modifications dans la journée, exclure l'essai abandonné, livrer le reste.

La compaction est calculée à l'export, pas à la capture — les exclusions comptent dès la construction du paquet JSON.


L'onglet Changements — relire et agir​

La page a deux onglets : Changements (le catalogue des paquets) et Appliquer (décrit plus bas).

Colonne de gauche — la liste des paquets​

ÉlémentDescription
Zone de filtreRecherche sur le nom ou le statut du paquet.
Paquet actifPrésélectionné à l'ouverture (le brouillon de la dernière application modifiée). Surligné en bleu.
Ligne de paquetNom + ligne d'informations (statut · captured_by · horodatage · nombre d'entrées) + un badge de statut coloré.
Couleurs de statutDRAFT bleu, PENDING orange, APPROVED vert, EXPORTED gris, REJECTED rouge.
CorbeilleSupprime le journal du paquet — les lignes déjà écrites dans les tables ne sont PAS touchées ; seul l'enregistrement du paquet disparaît. Confirmation demandé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 étapes de post-application retenues (quand les écrans contributeurs en définissent).
Entrées groupées par entitéGroupes habituels : ROLES, USERS, RELATIONSHIPS, SECURITY, MENUS, ACTION REPLAYS (les entrées CALL_API / CALL_PLUGIN) — le libellé vient du change_entity de chaque entrée. Un clic sur l'en-tête du groupe le replie ou le déplie.
En-tête de groupeLe libellé + 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 + la clé naturelle + l'action d'origine. Le chevron déplie l'écart ancien → nouveau.
Vue des écartsTableau champ / valeur à trois tons : rouge barré = ancien, vert = nouveau, discret = inchangé. Un switch Show unchanged affiche aussi les colonnes non modifiées.
Boutons par entréeExclure (paquets DRAFT uniquement) passe l'entrée en EXCLUDED et l'estompe. Un nouveau clic la réinclut.

Étapes de post-application en en-tête​

Un écran peut nommer des étapes de post-application (Screen.post_apply) : des actions à exécuter une fois sur la cible après l'écriture des lignes du paquet — refusionner un modèle de sécurité, rafraîchir une vue matérialisée. Elles proviennent d'une bibliothèque d'étapes de post-application commune à l'application, chaque étape étant un appel de plugin (call_plugin), un endpoint d'API (call_api) ou une requête (run_query). Leurs identifiants s'accumulent en en-tête du paquet et, à l'export, s'ajoutent au paquet JSON comme dernières opérations.


L'onglet Appliquer — importer un paquet​

À lancer sur l'installation cible (en général la production) pour importer un paquet exporté depuis la source (en général le développement).

L'application se fait en deux temps : d'abord un essai à blanc pour voir le rapport des écarts par opération, puis l'application réelle, avec éventuellement certains conflits cochés pour les appliquer quand même.

ÉtapeDescription
1. Choisir le JSONLe paquet exporté. L'en-tête affiche son nom, son application et son nombre d'opérations une fois lu.
2. Essai à blancPOST /admin/changesets/apply { bundle, dry_run: true }. Le serveur parcourt chaque opération, lit la ligne actuelle de la cible avec la read_query capturée, la compare aux old_values capturées et attribue un statut. Aucune écriture.
3. Lire le rapportChaque opération affiche son statut : would_apply (sans écart), conflict (la cible diffère de l'état capturé — voir plus bas), would_force (un conflit que l'opérateur a choisi d'appliquer), unverified (contrôle impossible), skipped (une action capturée pour relecture seulement), error. La ligne de synthèse totalise chaque statut.
4. Cocher ce qui doit s'appliquer quand mêmeChaque opération conflict — et chaque opération unverified, dont l'écart ne peut être contrôlé faute de requête de lecture sur l'écran — a une case forcer. La cocher applique l'opération malgré tout (elle devient would_force) ; une opération non cochée n'est pas appliquée.
5. AppliquerPOST /admin/changesets/apply { bundle, dry_run: false, force: [<index>] }. Chaque opération s'exécute dans sa propre transaction ; le nombre de lignes et le statut de chacune reviennent dans le rapport.
6. ConfirmationUne ligne s'ajoute au journal d'import de la cible (en haut de l'onglet Appliquer), avec la somme de contrôle du paquet. Le paquet source reste EXPORTED.

Promouvoir vers une autre instance​

Un paquet approuvé — ou déjà exporté — peut partir directement vers une autre instance, sans manipuler le JSON : Promouvoir vers… dans l'en-tête du paquet ouvre la fenêtre de promotion. Choisir la Cible et un Commentaire facultatif : la cible effectue aussitôt l'essai à blanc et renvoie son rapport d'écarts, avec les mêmes cases forcer ; Appliquer sur <cible> l'applique sur place et marque le paquet comme exporté. Les cibles se déclarent dans Paquet → Promotion — voir Paquets → Promotion ; les promotions appliquées figurent dans son historique.

Détection des écarts — ce qui est comparé​

Pour chaque opération sur une ligne, l'application lit la ligne actuelle de la cible avec la read_query et la clé capturées, puis :

OpérationLigne cibleStatut
INSERTabsentewould_apply
INSERTdéjà présenteconflict
UPDATE / DELETEidentique à l'état capturéwould_apply
UPDATE / DELETEdifférente, ou absenteconflict
toutepas de requête de lecture ni de clé capturée, ou lecture impossibleunverified — appliquée sans contrôle
CALL_API / CALL_PLUGIN—unverified — relancée à l'application ; skipped quand l'action a été capturée sans change_replay
étape de post-application—unverified — toujours exécutée, après les opérations de modification

Un conflit n'est appliqué que si sa case est cochée. Une opération dont l'écriture échoue est signalée error avec le message de la base.

Réappliquer le même paquet​

Le journal d'import identifie un paquet par sa somme de contrôle (un SHA-256 de ses opérations). Réappliquer le même fichier est détecté avant toute écriture — le rapport d'essai à blanc porte un bloc already_applied avec les applied_at / applied_by précédents. L'opérateur peut confirmer et réappliquer (les lignes s'écrivent de nouveau là où la cible correspond à l'état capturé ; les autres reviennent en conflit) ou fermer le fichier.

Rejeu d'action (CALL_API / CALL_PLUGIN)​

Un écran suivi capture chaque appel d'API ou de plugin qu'il déclenche (pour que le paquet l'affiche en relecture). Le rejeu de l'appel sur la cible dépend de l'option change_replay de l'action :

change_replayCe qui est rejoué sur la cible
true (à activer)L'action est relancée telle quelle avec les paramètres capturés. Sert aux plugins de fusion côté serveur comme nomajde.security:j_remerge_security — refusionner la sécurité sur la cible est correct une fois les lignes écrites.
false (par défaut)La capture est affichée dans le paquet pour relecture mais pas relancée. Sert aux effets ponctuels qui ne doivent pas se produire deux fois (envoi d'e-mails, webhook externe).

Promouvoir la sécurité JD Edwards — le cas d'usage de référence​

Le flux qui a motivé cette fonctionnalité. Sur l'instance de développement :

  1. L'opérateur crée un rôle PRJM_NEW, lui attache trois menus, modifie les permissions de deux rôles existants. Chaque écriture rejoint au fil de l'eau le paquet brouillon de l'application JDE-PROD.
  2. L'opérateur ouvre Liberty Next → Déployer → Modifications, relit le brouillon. Le détail regroupe les trois rôles sous ROLES, les menus attachés sous MENUS, la sécurité touchée sous SECURITY. 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 préfère annuler à la main. Il soumet, puis Approuve, puis Exporte.
  4. Le navigateur télécharge jde-prod-security-2026-06-09.changeset.json.

Sur l'instance de production :

  1. L'opérateur ouvre Liberty Next → Déployer → Modifications → Appliquer, dépose le JSON.
  2. L'essai à blanc s'exécute. Le rapport affiche 11 would_apply et 1 conflict sur le rôle BUYER_R2 (modifié en production depuis la capture). L'opérateur coche cette ligne pour l'appliquer quand même.
  3. L'opérateur clique sur Appliquer. Toutes les lignes s'écrivent ; l'étape de post-application s'exécute en dernier et refusionne la sécurité des rôles parents concernés.
  4. Le journal d'import de la production enregistre le paquet avec sa somme de contrôle.

Pour le détail du SQL de fusion exécuté par l'étape de post-application, 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 réglages :

ChampDescription
change_trackedRéglage principal. Activé, les écritures de l'écran rejoignent le paquet actif de son application.
change_entityLibellé facultatif qui regroupe les entrées dans le détail. Texte libre — role, user, relationship, security par convention. Vide : le nom de la requête.
Screen.post_applyIdentifiants d'étapes de post-application (de la bibliothèque des étapes de post-application) à exécuter une fois sur la cible après l'écriture des lignes du paquet. À utiliser avec parcimonie — ce sont les étapes transverses du type « refusionner la sécurité » ou « rafraîchir les vues matérialisées ».

Pour une action intégrée à l'écran, un réglage supplémentaire dans l'onglet Général de l'action :

ChampDescription
change_replayActivé, un appel CALL_API / CALL_PLUGIN de cette action est rejoué sur la cible. Désactivé par défaut — capture seule.

Contrat d'API​

EndpointMéthodeDescription
/admin/changesetsGETListe les paquets. Filtres : application=<id>, status=<valeur>. Superutilisateur uniquement.
/admin/changesets/<id>GETDétail du paquet — en-tête + chaque entrée avec ses valeurs avant / après.
/admin/changesets/<id>DELETESupprime seulement le journal du paquet (pas les écritures).
/admin/changesets/<id>/submitPOSTPasse DRAFT → PENDING.
/admin/changesets/<id>/approvePOSTPasse PENDING → APPROVED.
/admin/changesets/<id>/rejectPOSTPasse PENDING → REJECTED.
/admin/changesets/<id>/entries/<entry_id>/excludePOSTPasse une entrée en EXCLUDED.
/admin/changesets/<id>/entries/<entry_id>/includePOSTRepasse une entrée en CAPTURED.
/admin/changesets/<id>/exportGETRenvoie le paquet JSON compacté ; passe le paquet en EXPORTED.
/admin/changesets/applyPOSTCorps : { bundle, dry_run, force: [<index>] }. 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/1",
"package": { "id": "...", "name": "...", "application": "JDE-PROD", "approved_by": "...", "approved_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" }
],
"checksum": "9f2c…"
}

Le format reste stable d'une version du framework à l'autre. Le champ format porte la version du paquet (liberty-changeset/1) ; les étapes de post-application voyagent comme dernières entrées de ops.


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 appliquéNon. Le framework enregistre l'application mais ne construit pas de paquet inverse. Pour défaire, capturer sur la source un nouveau paquet qui annule les lignes, puis l'appliquer.
Mises à jour multi-onglets en directNon. Une capture sur un autre onglet ne rafraîchit pas cette page ; cliquer sur Actualiser (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 DRAFT ouvert sur la même application.Au plus un DRAFT existe à la fois ; les écritures du second opérateur s'attachent au même paquet.Se coordonner. 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 réussit ; les lignes existent sur la recette.Capturer un paquet inverse sur la recette, le promouvoir en production — le framework ne synthétise pas cela automatiquement. La discipline de flux compte plus que l'outil ici.
conflict 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 ne s'est jamais exécutée.Le champ Screen.post_apply de l'écran contributeur est vide.Ajouter l'identifiant de l'étape au post_apply de l'écran. Les nouvelles captures l'ajoutent en en-tête du paquet ; les paquets existants ne sont pas complétés.
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 faire qu'en connaissance de cause.

Pour aller plus loin​