From 551a7f12b54c4684b49765c837e5cad53e332765 Mon Sep 17 00:00:00 2001 From: hsiegeln <37154749+hsiegeln@users.noreply.github.com> Date: Sun, 26 Apr 2026 10:21:51 +0200 Subject: [PATCH] refactor(license): remove dead Feature enum and isEnabled scaffolding MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Spec §9 — feature flags are out of scope for license enforcement. Drops Feature.java, LicenseGate.isEnabled, LicenseInfo.hasFeature, and the corresponding test cases. LicenseValidator now silently ignores any features array on the wire (no error). Co-Authored-By: Claude Opus 4.7 (1M context) --- .../server/core/license/LicenseGateTest.java | 21 ++++++------------- .../core/license/LicenseValidatorTest.java | 8 +++---- .../cameleer/server/core/license/Feature.java | 9 -------- .../server/core/license/LicenseGate.java | 8 ++----- .../server/core/license/LicenseInfo.java | 9 +------- .../server/core/license/LicenseValidator.java | 13 +----------- 6 files changed, 13 insertions(+), 55 deletions(-) delete mode 100644 cameleer-server-core/src/main/java/com/cameleer/server/core/license/Feature.java diff --git a/cameleer-server-app/src/test/java/com/cameleer/server/core/license/LicenseGateTest.java b/cameleer-server-app/src/test/java/com/cameleer/server/core/license/LicenseGateTest.java index 95186fdd..45772531 100644 --- a/cameleer-server-app/src/test/java/com/cameleer/server/core/license/LicenseGateTest.java +++ b/cameleer-server-app/src/test/java/com/cameleer/server/core/license/LicenseGateTest.java @@ -5,37 +5,28 @@ import org.junit.jupiter.api.Test; import java.time.Instant; import java.time.temporal.ChronoUnit; import java.util.Map; -import java.util.Set; import static org.assertj.core.api.Assertions.assertThat; class LicenseGateTest { @Test - void noLicense_allFeaturesEnabled() { + void noLicense_returnsOpenTier() { LicenseGate gate = new LicenseGate(); - // No license loaded -> open mode - - assertThat(gate.isEnabled(Feature.debugger)).isTrue(); - assertThat(gate.isEnabled(Feature.replay)).isTrue(); - assertThat(gate.isEnabled(Feature.lineage)).isTrue(); assertThat(gate.getTier()).isEqualTo("open"); + assertThat(gate.getLimit("max_apps", 99)).isEqualTo(99); } @Test - void withLicense_onlyLicensedFeaturesEnabled() { + void loaded_exposesLimits() { LicenseGate gate = new LicenseGate(); - LicenseInfo license = new LicenseInfo("MID", - Set.of(Feature.topology, Feature.lineage, Feature.correlation), + LicenseInfo info = new LicenseInfo("MID", Map.of("max_agents", 10, "retention_days", 30), Instant.now(), Instant.now().plus(365, ChronoUnit.DAYS)); - gate.load(license); + gate.load(info); - assertThat(gate.isEnabled(Feature.topology)).isTrue(); - assertThat(gate.isEnabled(Feature.lineage)).isTrue(); - assertThat(gate.isEnabled(Feature.debugger)).isFalse(); - assertThat(gate.isEnabled(Feature.replay)).isFalse(); assertThat(gate.getTier()).isEqualTo("MID"); assertThat(gate.getLimit("max_agents", 0)).isEqualTo(10); + assertThat(gate.getLimit("missing", 7)).isEqualTo(7); } } diff --git a/cameleer-server-app/src/test/java/com/cameleer/server/core/license/LicenseValidatorTest.java b/cameleer-server-app/src/test/java/com/cameleer/server/core/license/LicenseValidatorTest.java index d321a2b0..00c698e7 100644 --- a/cameleer-server-app/src/test/java/com/cameleer/server/core/license/LicenseValidatorTest.java +++ b/cameleer-server-app/src/test/java/com/cameleer/server/core/license/LicenseValidatorTest.java @@ -33,7 +33,7 @@ class LicenseValidatorTest { Instant expires = Instant.now().plus(365, ChronoUnit.DAYS); String payload = """ - {"tier":"HIGH","features":["topology","lineage","debugger"],"limits":{"max_agents":50,"retention_days":90},"iat":%d,"exp":%d} + {"tier":"HIGH","limits":{"max_agents":50,"retention_days":90},"iat":%d,"exp":%d} """.formatted(Instant.now().getEpochSecond(), expires.getEpochSecond()).trim(); String signature = sign(kp.getPrivate(), payload); String token = Base64.getEncoder().encodeToString(payload.getBytes()) + "." + signature; @@ -41,8 +41,6 @@ class LicenseValidatorTest { LicenseInfo info = validator.validate(token); assertThat(info.tier()).isEqualTo("HIGH"); - assertThat(info.hasFeature(Feature.debugger)).isTrue(); - assertThat(info.hasFeature(Feature.replay)).isFalse(); assertThat(info.getLimit("max_agents", 0)).isEqualTo(50); assertThat(info.isExpired()).isFalse(); } @@ -55,7 +53,7 @@ class LicenseValidatorTest { Instant past = Instant.now().minus(1, ChronoUnit.DAYS); String payload = """ - {"tier":"LOW","features":["topology"],"limits":{},"iat":%d,"exp":%d} + {"tier":"LOW","limits":{},"iat":%d,"exp":%d} """.formatted(past.minus(30, ChronoUnit.DAYS).getEpochSecond(), past.getEpochSecond()).trim(); String signature = sign(kp.getPrivate(), payload); String token = Base64.getEncoder().encodeToString(payload.getBytes()) + "." + signature; @@ -72,7 +70,7 @@ class LicenseValidatorTest { LicenseValidator validator = new LicenseValidator(publicKeyBase64); String payload = """ - {"tier":"LOW","features":["topology"],"limits":{},"iat":0,"exp":9999999999} + {"tier":"LOW","limits":{},"iat":0,"exp":9999999999} """.trim(); String signature = sign(kp.getPrivate(), payload); diff --git a/cameleer-server-core/src/main/java/com/cameleer/server/core/license/Feature.java b/cameleer-server-core/src/main/java/com/cameleer/server/core/license/Feature.java deleted file mode 100644 index 72e74df4..00000000 --- a/cameleer-server-core/src/main/java/com/cameleer/server/core/license/Feature.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.cameleer.server.core.license; - -public enum Feature { - topology, - lineage, - correlation, - debugger, - replay -} diff --git a/cameleer-server-core/src/main/java/com/cameleer/server/core/license/LicenseGate.java b/cameleer-server-core/src/main/java/com/cameleer/server/core/license/LicenseGate.java index b6ca83fa..245c8be0 100644 --- a/cameleer-server-core/src/main/java/com/cameleer/server/core/license/LicenseGate.java +++ b/cameleer-server-core/src/main/java/com/cameleer/server/core/license/LicenseGate.java @@ -13,12 +13,8 @@ public class LicenseGate { public void load(LicenseInfo license) { current.set(license); - log.info("License loaded: tier={}, features={}, expires={}", - license.tier(), license.features(), license.expiresAt()); - } - - public boolean isEnabled(Feature feature) { - return current.get().hasFeature(feature); + log.info("License loaded: tier={}, limits={}, expires={}", + license.tier(), license.limits(), license.expiresAt()); } public String getTier() { diff --git a/cameleer-server-core/src/main/java/com/cameleer/server/core/license/LicenseInfo.java b/cameleer-server-core/src/main/java/com/cameleer/server/core/license/LicenseInfo.java index 2940e3e6..ead03616 100644 --- a/cameleer-server-core/src/main/java/com/cameleer/server/core/license/LicenseInfo.java +++ b/cameleer-server-core/src/main/java/com/cameleer/server/core/license/LicenseInfo.java @@ -2,11 +2,9 @@ package com.cameleer.server.core.license; import java.time.Instant; import java.util.Map; -import java.util.Set; public record LicenseInfo( String tier, - Set features, Map limits, Instant issuedAt, Instant expiresAt @@ -15,16 +13,11 @@ public record LicenseInfo( return expiresAt != null && Instant.now().isAfter(expiresAt); } - public boolean hasFeature(Feature feature) { - return features.contains(feature); - } - public int getLimit(String key, int defaultValue) { return limits.getOrDefault(key, defaultValue); } - /** Open license — all features enabled, no limits. Used when no license is configured. */ public static LicenseInfo open() { - return new LicenseInfo("open", Set.of(Feature.values()), Map.of(), Instant.now(), null); + return new LicenseInfo("open", Map.of(), Instant.now(), null); } } diff --git a/cameleer-server-core/src/main/java/com/cameleer/server/core/license/LicenseValidator.java b/cameleer-server-core/src/main/java/com/cameleer/server/core/license/LicenseValidator.java index 07e29043..7436f941 100644 --- a/cameleer-server-core/src/main/java/com/cameleer/server/core/license/LicenseValidator.java +++ b/cameleer-server-core/src/main/java/com/cameleer/server/core/license/LicenseValidator.java @@ -56,17 +56,6 @@ public class LicenseValidator { String tier = root.get("tier").asText(); - Set features = new HashSet<>(); - if (root.has("features")) { - for (JsonNode f : root.get("features")) { - try { - features.add(Feature.valueOf(f.asText())); - } catch (IllegalArgumentException e) { - log.warn("Unknown feature in license: {}", f.asText()); - } - } - } - Map limits = new HashMap<>(); if (root.has("limits")) { root.get("limits").fields().forEachRemaining(entry -> @@ -76,7 +65,7 @@ public class LicenseValidator { Instant issuedAt = root.has("iat") ? Instant.ofEpochSecond(root.get("iat").asLong()) : Instant.now(); Instant expiresAt = root.has("exp") ? Instant.ofEpochSecond(root.get("exp").asLong()) : null; - LicenseInfo info = new LicenseInfo(tier, features, limits, issuedAt, expiresAt); + LicenseInfo info = new LicenseInfo(tier, limits, issuedAt, expiresAt); if (info.isExpired()) { throw new IllegalArgumentException("License expired at " + expiresAt);