From 9b9b56043c0a3061283b254f66a1110faede6f12 Mon Sep 17 00:00:00 2001 From: hsiegeln <37154749+hsiegeln@users.noreply.github.com> Date: Sun, 26 Apr 2026 12:43:27 +0200 Subject: [PATCH] fix(license): explicit @Autowired ctor + tolerate audit failures Two follow-ups to LicenseEnforcer review: - Add @Autowired to the 3-arg ctor so Spring picks it unambiguously (the 2-arg test ctor is otherwise an equally-greedy candidate). - Wrap audit.log() in try/catch + log.warn so a degraded audit DB cannot mask a cap rejection: callers still see HTTP 403 even when audit storage is unhealthy. - Extract counter name to private static final. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../server/app/license/LicenseEnforcer.java | 28 +++++++++++++------ 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/cameleer-server-app/src/main/java/com/cameleer/server/app/license/LicenseEnforcer.java b/cameleer-server-app/src/main/java/com/cameleer/server/app/license/LicenseEnforcer.java index 30482877..14cf71c4 100644 --- a/cameleer-server-app/src/main/java/com/cameleer/server/app/license/LicenseEnforcer.java +++ b/cameleer-server-app/src/main/java/com/cameleer/server/app/license/LicenseEnforcer.java @@ -8,6 +8,9 @@ import com.cameleer.server.core.license.LicenseLimits; import io.micrometer.core.instrument.Counter; import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.simple.SimpleMeterRegistry; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.util.LinkedHashMap; @@ -30,11 +33,15 @@ import java.util.concurrent.ConcurrentMap; @Component public class LicenseEnforcer { + private static final Logger log = LoggerFactory.getLogger(LicenseEnforcer.class); + private static final String COUNTER_NAME = "cameleer_license_cap_rejections_total"; + private final LicenseGate gate; private final MeterRegistry meters; private final AuditService audit; private final ConcurrentMap rejectionCounters = new ConcurrentHashMap<>(); + @Autowired public LicenseEnforcer(LicenseGate gate, MeterRegistry meters, AuditService audit) { this.gate = gate; this.meters = meters; @@ -51,16 +58,21 @@ public class LicenseEnforcer { int cap = effective.get(limitKey); // throws IllegalArgumentException if unknown key long projected = currentUsage + requestedDelta; if (projected > cap) { - rejectionCounters.computeIfAbsent(limitKey, k -> Counter.builder("cameleer_license_cap_rejections_total") + rejectionCounters.computeIfAbsent(limitKey, k -> Counter.builder(COUNTER_NAME) .tag("limit", k).register(meters)).increment(); if (audit != null) { - Map detail = new LinkedHashMap<>(); - detail.put("limit", limitKey); - detail.put("current", currentUsage); - detail.put("requested", requestedDelta); - detail.put("cap", cap); - detail.put("state", gate.getState().name()); - audit.log("system", "cap_exceeded", AuditCategory.LICENSE, limitKey, detail, AuditResult.FAILURE, null); + try { + Map detail = new LinkedHashMap<>(); + detail.put("limit", limitKey); + detail.put("current", currentUsage); + detail.put("requested", requestedDelta); + detail.put("cap", cap); + detail.put("state", gate.getState().name()); + audit.log("system", "cap_exceeded", AuditCategory.LICENSE, limitKey, detail, AuditResult.FAILURE, null); + } catch (RuntimeException e) { + // Audit storage degraded; log and continue so the cap rejection still surfaces as 403. + log.warn("Failed to write cap_exceeded audit row for limit={}: {}", limitKey, e.toString()); + } } throw new LicenseCapExceededException(limitKey, projected, cap); }