From 6610529acb9b6bdf41c1f7d5d491dbf00fdfef7e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 12 Aug 2026 18:21:29 +0900 Subject: [PATCH 1/3] test(analytics): reproduce invalid KPI replay acceptance --- .../viewer/analytics/KpiSnapshotLedgerTest.java | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/test/java/com/clearfolio/viewer/analytics/KpiSnapshotLedgerTest.java b/src/test/java/com/clearfolio/viewer/analytics/KpiSnapshotLedgerTest.java index d42349fd..de5e8993 100644 --- a/src/test/java/com/clearfolio/viewer/analytics/KpiSnapshotLedgerTest.java +++ b/src/test/java/com/clearfolio/viewer/analytics/KpiSnapshotLedgerTest.java @@ -86,6 +86,16 @@ void rejectsInvalidPersistedLedgerLines() throws Exception { assertInvalidLedger(snapshotLine().replace(encoded("tenant-a"), "!")); } + @Test + void rejectsNonFiniteOrOutOfRangePersistedKpiEvidence() throws Exception { + assertInvalidLedger(snapshotLine().replace("\t0.5\t", "\tNaN\t")); + assertInvalidLedger(snapshotLine().replace("\t0.5\t", "\tInfinity\t")); + assertInvalidLedger(snapshotLine().replace("\t0.5\t", "\t-Infinity\t")); + assertInvalidLedger(snapshotLine().replace("\t0.5\t", "\t-0.01\t")); + assertInvalidLedger(snapshotLine().replace("\t0.5\t", "\t1.01\t")); + assertInvalidLedger(snapshotLine().replace("\t123", "\t-1")); + } + @Test void reportsLoadAndWriteFailures() throws Exception { Path directory = tempDir.resolve("directory-ledger"); @@ -107,7 +117,11 @@ private void assertInvalidLedger(String line) throws Exception { StandardCharsets.UTF_8 ); - assertThrows(IllegalStateException.class, () -> new KpiSnapshotLedger(ledgerPath, CLOCK)); + IllegalStateException error = assertThrows( + IllegalStateException.class, + () -> new KpiSnapshotLedger(ledgerPath, CLOCK) + ); + assertEquals("kpi snapshot ledger contains an invalid line", error.getMessage()); } private static TenantContext context(String tenantId) { From c21bd4a66403d1a6994acda2d0218192f5d021fc Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 12 Aug 2026 18:31:21 +0900 Subject: [PATCH 2/3] fix(analytics): reject invalid KPI numeric evidence --- .../viewer/analytics/KpiSnapshotLedger.java | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/clearfolio/viewer/analytics/KpiSnapshotLedger.java b/src/main/java/com/clearfolio/viewer/analytics/KpiSnapshotLedger.java index f5cbde5d..6793afe4 100644 --- a/src/main/java/com/clearfolio/viewer/analytics/KpiSnapshotLedger.java +++ b/src/main/java/com/clearfolio/viewer/analytics/KpiSnapshotLedger.java @@ -213,7 +213,11 @@ private static int integer(String field) { private static double rate(String field) { try { - return Double.parseDouble(field); + double parsed = Double.parseDouble(field); + if (!Double.isFinite(parsed) || parsed < 0.0 || parsed > 1.0) { + throw invalidLine(); + } + return parsed; } catch (NumberFormatException ex) { throw invalidLine(ex); } @@ -224,7 +228,11 @@ private static Long nullableLong(String field) { return null; } try { - return Long.parseLong(field); + long parsed = Long.parseLong(field); + if (parsed < 0L) { + throw invalidLine(); + } + return parsed; } catch (NumberFormatException ex) { throw invalidLine(ex); } From 7e41b46112b4795ede9809cdf5a54ab4ec5f2438 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 12 Aug 2026 19:00:47 +0900 Subject: [PATCH 3/3] test(analytics): cover KPI numeric boundaries --- .../analytics/KpiSnapshotLedgerTest.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/test/java/com/clearfolio/viewer/analytics/KpiSnapshotLedgerTest.java b/src/test/java/com/clearfolio/viewer/analytics/KpiSnapshotLedgerTest.java index de5e8993..61b64962 100644 --- a/src/test/java/com/clearfolio/viewer/analytics/KpiSnapshotLedgerTest.java +++ b/src/test/java/com/clearfolio/viewer/analytics/KpiSnapshotLedgerTest.java @@ -96,6 +96,13 @@ void rejectsNonFiniteOrOutOfRangePersistedKpiEvidence() throws Exception { assertInvalidLedger(snapshotLine().replace("\t123", "\t-1")); } + @Test + void acceptsInclusiveKpiNumericBoundaries() throws Exception { + assertValidLedger(snapshotLine().replace("\t0.5\t", "\t0.0\t"), 0.0, 123L); + assertValidLedger(snapshotLine().replace("\t0.5\t", "\t1.0\t"), 1.0, 123L); + assertValidLedger(snapshotLine().replace("\t123", "\t0"), 0.5, 0L); + } + @Test void reportsLoadAndWriteFailures() throws Exception { Path directory = tempDir.resolve("directory-ledger"); @@ -124,6 +131,19 @@ private void assertInvalidLedger(String line) throws Exception { assertEquals("kpi snapshot ledger contains an invalid line", error.getMessage()); } + private void assertValidLedger(String line, double expectedRate, Long expectedP95) throws Exception { + Path ledgerPath = Files.writeString( + tempDir.resolve(UUID.randomUUID() + ".log"), + line + System.lineSeparator(), + StandardCharsets.UTF_8 + ); + + KpiSnapshotLedger ledger = new KpiSnapshotLedger(ledgerPath, CLOCK); + var snapshot = ledger.snapshotsFor("tenant-a").getFirst(); + assertEquals(expectedRate, snapshot.conversionSuccessRate()); + assertEquals(expectedP95, snapshot.p95TimeToPreviewMs()); + } + private static TenantContext context(String tenantId) { return new TenantContext(tenantId, "subject-a", java.util.Set.of(TenantPermissions.ANALYTICS_READ)); }