fix: require six times P99 for sustained signals (#3204)

This commit is contained in:
Jesse Merhi
2026-07-22 00:14:12 +10:00
committed by GitHub
parent 723f1551e9
commit a09d42484a
4 changed files with 26 additions and 22 deletions
+16 -13
View File
@@ -449,24 +449,27 @@ describe("publisher abuse scoring", () => {
expect(score.reasonCodes).toContain("temporal_download_spike_flat_installs");
});
it("flags sustained high downloads with flat installs", () => {
it("flags sustained flat-install traffic one download above six times platform P99", () => {
const todayDay = 100;
const score = computeCurrentSkillTemporalAbuseScore({
todayDay,
benchmark: temporalBenchmark({
downloads30dP95: 3_000,
downloads30dP99: 3_000,
downloads30dP95: 284,
downloads30dP99: 600,
spikeMultiplier7dP95: 20,
spikeMultiplier7dP99: 50,
}),
dailyStats: dailyRange(71, 30, { downloads: 120, installs: 0 }),
dailyStats: [
...dailyRange(71, 29, { downloads: 120, installs: 0 }),
{ day: 100, downloads: 121, installs: 0 },
],
});
expect(score.spike).toBe(false);
expect(score.sustained).toBe(true);
expect(score.recent30Downloads).toBe(3_600);
expect(score.recent30Downloads).toBe(3_601);
expect(score.recent30Installs).toBe(0);
expect(score.downloadInstallRatio30).toBe(3_600);
expect(score.downloadInstallRatio30).toBe(3_601);
expect(score.downloads30dCohortBand).toBe("p99");
expect(score.reasonCodes).toContain("temporal_sustained_downloads_flat_installs");
});
@@ -486,17 +489,17 @@ describe("publisher abuse scoring", () => {
expect(score.downloads30dCohortBand).toBeUndefined();
});
it("keeps sub-3000 P99 download traffic below the absolute review floor", () => {
it("keeps downloads at exactly six times platform P99 below the sustained threshold", () => {
const score = computeCurrentSkillTemporalAbuseScore({
todayDay: 100,
benchmark: temporalBenchmark({
downloads30dP95: 1_000,
downloads30dP99: 2_000,
downloads30dP95: 284,
downloads30dP99: 600,
}),
dailyStats: dailyRange(71, 30, { downloads: 90, installs: 0 }),
dailyStats: dailyRange(71, 30, { downloads: 120, installs: 0 }),
});
expect(score.recent30Downloads).toBe(2_700);
expect(score.recent30Downloads).toBe(3_600);
expect(score.sustained).toBe(false);
expect(score.downloads30dCohortBand).toBeUndefined();
});
@@ -611,8 +614,8 @@ describe("publisher abuse scoring", () => {
it("finds historical spike and sustained windows for backfill scans", () => {
const score = computeHistoricalSkillTemporalAbuseScore({
benchmark: temporalBenchmark({
downloads30dP95: 3_000,
downloads30dP99: 10_000,
downloads30dP95: 284,
downloads30dP99: 1_000,
spikeMultiplier7dP95: 5,
spikeMultiplier7dP99: 25,
}),
+3 -2
View File
@@ -128,7 +128,7 @@ const TEMPORAL_SUSTAINED_DAYS = 30;
const TEMPORAL_MAX_SPIKE_INSTALLS = 2;
const TEMPORAL_MAX_SUSTAINED_INSTALLS = 5;
const TEMPORAL_MIN_SPIKE_7_DOWNLOADS = 2_000;
const TEMPORAL_MIN_SUSTAINED_30_DOWNLOADS = 3_000;
const TEMPORAL_SUSTAINED_DOWNLOADS_P99_MULTIPLIER = 6;
const TEMPORAL_MIN_BASELINE_7_DOWNLOADS = 100;
const TEMPORAL_MIN_NEAR_CONVERSION_7_DOWNLOADS = 500;
const TEMPORAL_MIN_NEAR_CONVERSION_30_DOWNLOADS = 1_000;
@@ -437,7 +437,8 @@ export function classifySkillTemporalAbuseScore(
score.spikeMultiplier / Math.max(1, benchmark.spikeMultiplier7dP95);
const downloads30dCohortBand =
score.recent30Installs <= TEMPORAL_MAX_SUSTAINED_INSTALLS &&
score.recent30Downloads >= TEMPORAL_MIN_SUSTAINED_30_DOWNLOADS
score.recent30Downloads >
benchmark.downloads30dP99 * TEMPORAL_SUSTAINED_DOWNLOADS_P99_MULTIPLIER
? p99Band({ value: score.recent30Downloads, p99: benchmark.downloads30dP99 })
: undefined;
const spikeMultiplierCohortBand =
+3 -3
View File
@@ -655,8 +655,8 @@ describe("scheduled temporal publisher abuse scan", () => {
sampleSize: 1_000,
downloads30dAverage: 180,
downloads30dMedian: 45,
downloads30dP95: 900,
downloads30dP99: 3_000,
downloads30dP95: 284,
downloads30dP99: 600,
spikeMultiplier7dP95: 4,
spikeMultiplier7dP99: 12,
};
@@ -667,7 +667,7 @@ describe("scheduled temporal publisher abuse scan", () => {
});
const candidate = temporalCandidate(
"skills:anysearch" as Id<"skills">,
temporalScore({ recent30Downloads: 3_370, recent30Installs: 4 }),
temporalScore({ recent30Downloads: 3_700, recent30Installs: 4 }),
);
const runQuery = vi
.fn()
+4 -4
View File
@@ -94,10 +94,10 @@ See also: [acceptable-usage.md](./acceptable-usage.md) for the marketplace polic
Partial scans must not archive signals or present their top-download slice as
a platform percentile.
- Flat-install temporal review signals are deliberately high-confidence:
sustained volume must exceed the platform P99, reach at least 3,000 downloads
in 30 days, and have at most 5 installs; a spike must exceed the platform P99,
reach at least 2,000 downloads in 7 days, and have at most 2 installs. These
signals indicate anomalous traffic for manual review, not publisher attribution.
sustained volume must exceed six times the platform 30-day download P99 and
have at most 5 installs; a spike must exceed the platform P99, reach at least
2,000 downloads in 7 days, and have at most 2 installs. These signals indicate
anomalous traffic for manual review, not publisher attribution.
- Publisher abuse scoring must skip staff-linked and official publishers before
nominations are created. Publisher abuse autoban must process pending
`potential_ban_candidate` pressure nominations without waiting for the score