From c87421193fc11f0b1ee10e8542643a72112d8a23 Mon Sep 17 00:00:00 2001 From: Jerry Combs Date: Mon, 2 Nov 2026 15:12:49 -0600 Subject: [PATCH] Write only the package stamps that differ on a changed packaged object A deploy that changed a packaged driver resource (here, the AD driver's NOVLADDCFG-GCVs after a one-value GCV override) planned the aux class and every package stamp again although the vault had the same values: five steps where one was needed. The plan now compares the stamps with the vault object's, writes only those that differ, and adds DirXML-PkgItemAux only when the vault object has no stamps, as it already did for Identity Applications objects. A customized object with no recorded checksum still gets a content-derived one. Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 9 +++++ .../com/pointblue/dirxml/dev/deploy/Plan.java | 31 +++++++++++++-- .../pointblue/dirxml/dev/deploy/PlanTest.java | 39 +++++++++++++++++++ 3 files changed, 75 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 72334988..37909e9c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,15 @@ and this project uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html) ## [Unreleased] +### Fixed + +- A deploy that changes a packaged object (a GCV object, policy or other driver resource that a package + installed) no longer rewrites its package stamps when the vault already has them. Before, it added the + `DirXML-PkgItemAux` class and wrote `DirXML-pkgGUID`, `DirXML-pkgAssociationId` and `DirXML-pkgChecksum` + again with the same values, so a one-value GCV change planned five steps. It now writes only the stamps that + differ, and adds the class only when the vault object has no stamps. Identity Applications objects already + worked this way. + ## [0.62.0] - 2026-11-02 ### Added diff --git a/src/main/java/com/pointblue/dirxml/dev/deploy/Plan.java b/src/main/java/com/pointblue/dirxml/dev/deploy/Plan.java index edd508e8..2b58be7e 100644 --- a/src/main/java/com/pointblue/dirxml/dev/deploy/Plan.java +++ b/src/main/java/com/pointblue/dirxml/dev/deploy/Plan.java @@ -258,10 +258,13 @@ public static Plan of(ModelDiff diff, DriverSet to, String dsDn, Secrets secrets Map> attrs = "package-stamps".equals(c.what) ? new LinkedHashMap<>() : VaultMapping.attributes(a); Map> stamps = VaultMapping.packageAttributes(tree, a, p.packageIndex); + Artifact was = c.kind == ModelDiff.Kind.ARTIFACT_CHANGED ? diff.from().resolve(c.path) : null; if (!stamps.isEmpty()) { - attrs.putAll(stamps); // an installed object: Designer's stamps, the installed checksum included + // an installed object: Designer's stamps, the installed checksum included; on a changed + // object only those that differ from the vault's + attrs.putAll(changedStamps(stamps, was, p.packageIndex)); } - if (Packages.isPackaged(a) && Packages.isCustomized(a) && !attrs.containsKey(VaultMapping.PKG_CHECKSUM)) { + if (Packages.isPackaged(a) && Packages.isCustomized(a) && !stamps.containsKey(VaultMapping.PKG_CHECKSUM)) { // customized, and no checksum recorded (a tree that named the package but never // refreshed its stamp): a content-derived one, so the vault's pair differs from the baseline byte[] content = VaultMapping.contentBytes(a); @@ -276,7 +279,8 @@ public static Plan of(ModelDiff diff, DriverSet to, String dsDn, Secrets secrets bucket.add(new Step(Op.ADD, dn, null, classes, attrs, dn + " " + VaultMapping.objectClass(a) + " (" + size(attrs) + ")", c.path, a.driver)); } else { - if (!stamps.isEmpty()) { + if (!stamps.isEmpty() && (was == null || !VaultMapping.isStamped(was))) { + // a vault object with stamps already has the class that holds them bucket.add(new Step(Op.AUX_CLASS, dn, null, List.of(VaultMapping.PKG_ITEM_AUX), null, dn + " objectClass += " + VaultMapping.PKG_ITEM_AUX, c.path, a.driver)); } @@ -284,7 +288,6 @@ public static Plan of(ModelDiff diff, DriverSet to, String dsDn, Secrets secrets bucket.add(new Step(Op.MODIFY, dn, e.getKey(), null, Map.of(e.getKey(), e.getValue()), dn + " " + e.getKey() + " (" + size(Map.of(e.getKey(), e.getValue())) + ")", c.path, a.driver)); } - Artifact was = diff.from().resolve(c.path); if (stamps.isEmpty() && was != null && stripSteps(p, to, bucket, dn, a.meta, was.meta, c.path, a.driver)) { p.notes.add(c.path + ": package stamps removed (the driver was stripped of its packages)"); } @@ -851,6 +854,26 @@ private static boolean stripSteps(Plan p, DriverSet to, List bucket, Strin return true; } + /** + * The stamp attributes of a changed artifact that differ from the vault object's ({@code was}, null when + * unknown: all of them). A content change used to rewrite every stamp with the value already there. + * {@code DirXML-pkgInitialState} is not in the vault's model, so it is always kept. + */ + static Map> changedStamps(Map> stamps, Artifact was, com.pointblue.dirxml.dev.model.PackageStamps.Index index) { + if (was == null) { + return stamps; + } + Map> live = VaultMapping.packageAttributes(null, was, index); + Map> out = new LinkedHashMap<>(); + for (Map.Entry> e : stamps.entrySet()) { + List v = live.get(e.getKey()); + if (v == null || !sameBytes(v, e.getValue())) { + out.put(e.getKey(), e.getValue()); + } + } + return out; + } + private static boolean sameBytes(List a, List b) { if (a.size() != b.size()) { return false; diff --git a/src/test/java/com/pointblue/dirxml/dev/deploy/PlanTest.java b/src/test/java/com/pointblue/dirxml/dev/deploy/PlanTest.java index 86812080..ec617703 100644 --- a/src/test/java/com/pointblue/dirxml/dev/deploy/PlanTest.java +++ b/src/test/java/com/pointblue/dirxml/dev/deploy/PlanTest.java @@ -192,6 +192,45 @@ public void customizedStampedArtifactCarriesTheRecomputedChecksumNotTheOldOne() assertNotEquals(staleChecksum, checksum); } + private static void stamp(Policy p, String checksum) { + p.meta.put("dirxml-pkgguid", "PKGGUID_1;com.example.pkg;1.0.0;Example;PKG"); + p.meta.put("dirxml-pkgassociationid", "ASSOC1"); + p.meta.put("dirxml-pkgchecksum", checksum); + } + + /** A changed packaged object whose stamps the vault already has: only its content is written (edir3, 2026-11-02). */ + @Test + public void changedPackagedObjectDoesNotRewriteTheVaultsStamps() { + DriverSet from = VaultMappingTest.model(); + DriverSet to = VaultMappingTest.model(); + stamp((Policy) from.resolve("drivers/AD/subscriber/sub-ctp"), "4234957497"); + Policy ctp = (Policy) to.resolve("drivers/AD/subscriber/sub-ctp"); + stamp(ctp, "4234957497"); + ctp.content = ValidatorTest.xml("edited"); + + Plan p = plan(from, to, Secrets.none(), "none"); + for (Plan.Step s : p.steps) { + assertNotEquals(p.text("stg", DS), Plan.Op.AUX_CLASS, s.op); + assertTrue(p.text("stg", DS), s.attr == null || !s.attr.startsWith("DirXML-pkg")); + } + assertTrue(p.text("stg", DS), p.steps.stream().anyMatch(s -> VaultMapping.XML_DATA.equals(s.attr))); + } + + @Test + public void changedPackagedObjectWritesOnlyTheStampThatDiffers() { + DriverSet from = VaultMappingTest.model(); + DriverSet to = VaultMappingTest.model(); + stamp((Policy) from.resolve("drivers/AD/subscriber/sub-ctp"), "111111111"); + Policy ctp = (Policy) to.resolve("drivers/AD/subscriber/sub-ctp"); + stamp(ctp, "222222222"); + ctp.content = ValidatorTest.xml("edited"); + + Plan p = plan(from, to, Secrets.none(), "none"); + List pkg = p.steps.stream().filter(s -> s.attr != null && s.attr.startsWith("DirXML-pkg")).map(s -> s.attr).toList(); + assertEquals(p.text("stg", DS), List.of(VaultMapping.PKG_CHECKSUM), pkg); + assertTrue(p.text("stg", DS), p.steps.stream().noneMatch(s -> s.op == Plan.Op.AUX_CLASS)); + } + @Test public void newDriverIsCreatedStoppedWithItsSecrets() throws IOException { DriverSet from = VaultMappingTest.model();