diff --git a/ordinis-cuod-bundle/src/main/java/cloud/nstart/terravault/ordinis/cuod/CuodBundleImporter.java b/ordinis-cuod-bundle/src/main/java/cloud/nstart/terravault/ordinis/cuod/CuodBundleImporter.java index 5517f8d..40cb7ad 100644 --- a/ordinis-cuod-bundle/src/main/java/cloud/nstart/terravault/ordinis/cuod/CuodBundleImporter.java +++ b/ordinis-cuod-bundle/src/main/java/cloud/nstart/terravault/ordinis/cuod/CuodBundleImporter.java @@ -103,7 +103,14 @@ public class CuodBundleImporter { Optional existing = repository.findByName(entry.name()); if (existing.isPresent()) { DictionaryDefinition def = existing.get(); - if (props.updateExistingSchema() && !entry.schemaVersion().equals(def.getSchemaVersion())) { + // Semver-aware update: применяем bundle только если он СТРОГО НОВЕЕ + // чем то что в БД. Иначе bundle с 1.0.0 откатывал бы user-published + // 1.1.0 на каждом рестарте writer'а (regression наблюдалась на staging + // 2026-06-02 — ground_station). User-published > bundle-shipped по + // умолчанию: dev опубликовал — это новее manifest.json. + boolean bundleNewer = props.updateExistingSchema() + && isStrictlyNewer(entry.schemaVersion(), def.getSchemaVersion()); + if (bundleNewer) { JsonNode prevSchema = def.getSchemaJson(); def.setSchemaJson(schema); def.setSchemaVersion(entry.schemaVersion()); @@ -304,4 +311,44 @@ public class CuodBundleImporter { } return null; } + + /** + * True если {@code bundle} строго новее {@code db} по semver (X.Y.Z). + * Используется чтобы bundle-import не откатывал user-published schema: + * bundle ship'ит 1.0.0, dev опубликовал 1.1.0 → bundle skip. + * Bundle ship'ит 1.2.0 → bundle wins. + * + *

Non-semver формат: fallback на string equals (т.е. update только + * если "разные"). Не худшее поведение — раньше так и было всегда. + */ + static boolean isStrictlyNewer(String bundleVersion, String dbVersion) { + if (bundleVersion == null) return false; + if (dbVersion == null) return true; + int[] bundle = parseSemver(bundleVersion); + int[] db = parseSemver(dbVersion); + if (bundle == null || db == null) { + // Fallback: non-semver — обновим если "разные" чтобы сохранить + // прежнее поведение для custom версионных схем. + return !bundleVersion.equals(dbVersion); + } + for (int i = 0; i < 3; i++) { + if (bundle[i] > db[i]) return true; + if (bundle[i] < db[i]) return false; + } + return false; // equal + } + + private static int[] parseSemver(String s) { + String[] parts = s.split("\\."); + if (parts.length != 3) return null; + try { + return new int[] { + Integer.parseInt(parts[0]), + Integer.parseInt(parts[1]), + Integer.parseInt(parts[2]), + }; + } catch (NumberFormatException e) { + return null; + } + } }