From bbe1af7ca00556f65ac9e9f7124ba2a38287b4f2 Mon Sep 17 00:00:00 2001 From: ghost <49853598+JSONbored@users.noreply.github.com> Date: Sat, 11 Jul 2026 01:34:35 -0700 Subject: [PATCH] fix(stats): scope reuse trend to public repos --- src/services/public-reuse-rate-trend.ts | 19 +++++++------ test/unit/public-reuse-rate-trend.test.ts | 34 ++++++++++++++++++++++- 2 files changed, 44 insertions(+), 9 deletions(-) diff --git a/src/services/public-reuse-rate-trend.ts b/src/services/public-reuse-rate-trend.ts index 8c0008d933..8f41a1f9c5 100644 --- a/src/services/public-reuse-rate-trend.ts +++ b/src/services/public-reuse-rate-trend.ts @@ -8,11 +8,9 @@ // is already durable, so a live weekly re-bucketing of the SAME rows can recompute any historical week correctly // on every request -- no cron-miss gap risk, and no second copy of the number to keep in sync. // -// DELIBERATELY GLOBAL, not scoped to the public-stats repo allowlist: unlike accuracy/handled-PR counts, a -// cache-hit/miss event carries no PR content, author, or repo-specific outcome -- the aggregate reuse rate -// doesn't reveal anything about any one repo's activity, and target_key isn't uniformly shaped across all eight -// capabilities (some key by bare repoFullName, others by repoFullName#prNumber), so allowlist-filtering it would -// need a fragile per-capability parser for no real privacy benefit. +// PUBLIC-SAFE SCOPE: only events whose target_key maps to GITTENSORY_PUBLIC_STATS_REPOS are included. Most +// cache keys are either a bare repoFullName or repoFullName#prNumber; anything outside that allowlist is treated +// as private operational telemetry and deliberately excluded from this unauthenticated payload. // // NAMING CONVENTION, not a hardcoded capability list: every instrumented capability already follows // `github_app._cache_hit` / `github_app._cache_miss` (confirmed via a full-repo grep before writing @@ -20,7 +18,7 @@ // same convention, with zero code change here. ai_review's three additional REUSE variants (frozen/paused/ // one-shot) don't fit that exact suffix -- each is a genuine "skipped a redundant AI call" event, so they're // folded into "hit" alongside the plain ai_review_cache_hit. -import { safeAll } from "../review/public-stats"; +import { publicStatsProjects, safeAll } from "../review/public-stats"; import { isoWeekStart } from "./public-quality-metrics"; export const PUBLIC_REUSE_RATE_TREND_WEEKS = 8; @@ -81,7 +79,9 @@ export function buildPublicReuseRateTrend(dayRows: DayRow[], nowMs: number, week /** Day-bucketed hit/miss counts across every `github_app._cache_hit` / `_cache_miss` event, plus * ai_review's three non-suffix-conforming reuse variants (see file header). Fail-safe: degrades to [] on any * query error (safeAll), yielding under-counted weeks rather than throwing the whole public stats payload. */ -async function loadReuseRateDayRows(env: Env, sinceIso: string): Promise { +async function loadReuseRateDayRows(env: Env, projects: string[], sinceIso: string): Promise { + if (projects.length === 0) return []; + const projectPlaceholders = projects.map(() => "?").join(", "); const reuseTypePlaceholders = AI_REVIEW_REUSE_EVENT_TYPES.map(() => "?").join(", "); const rows = await safeAll<{ day: string; hits: number; misses: number }>( env, @@ -90,10 +90,12 @@ async function loadReuseRateDayRows(env: Env, sinceIso: string): Promise 0 THEN substr(target_key, 1, instr(target_key, '#') - 1) ELSE target_key END) IN (${projectPlaceholders}) AND created_at >= ? GROUP BY day`, ...AI_REVIEW_REUSE_EVENT_TYPES, ...AI_REVIEW_REUSE_EVENT_TYPES, + ...projects, sinceIso, ); /* v8 ignore next -- SUM(CASE WHEN ... THEN 1 ELSE 0 END) over an existing GROUP BY day always yields a @@ -105,7 +107,8 @@ async function loadReuseRateDayRows(env: Env, sinceIso: string): Promise { + const projects = publicStatsProjects(env); const sinceIso = new Date(Date.parse(isoWeekStart(nowMs)) - (PUBLIC_REUSE_RATE_TREND_WEEKS - 1) * MS_PER_WEEK).toISOString(); - const dayRows = await loadReuseRateDayRows(env, sinceIso); + const dayRows = await loadReuseRateDayRows(env, projects, sinceIso); return buildPublicReuseRateTrend(dayRows, nowMs); } diff --git a/test/unit/public-reuse-rate-trend.test.ts b/test/unit/public-reuse-rate-trend.test.ts index 66e936081d..fc57742e65 100644 --- a/test/unit/public-reuse-rate-trend.test.ts +++ b/test/unit/public-reuse-rate-trend.test.ts @@ -70,7 +70,7 @@ describe("buildPublicReuseRateTrend", () => { describe("loadPublicReuseRateTrend — end-to-end over the real live audit_events ledger", () => { it("counts every github_app.*_cache_hit / *_cache_miss event, plus ai_review's three non-suffix reuse variants, as hits/misses", async () => { - const env = createTestEnv(); + const env = createTestEnv({ GITTENSORY_PUBLIC_STATS_REPOS: "owner/repo" }); const thisMonday = isoWeekStart(NOW); const thisWeekIso = `${thisMonday}T09:00:00.000Z`; @@ -94,8 +94,40 @@ describe("loadPublicReuseRateTrend — end-to-end over the real live audit_event }); it("returns all-zero buckets when no instrumented events exist yet", async () => { + const env = createTestEnv({ GITTENSORY_PUBLIC_STATS_REPOS: "owner/repo" }); + const trend = await loadPublicReuseRateTrend(env, NOW); + for (const week of trend) expect(week).toMatchObject({ hits: 0, misses: 0, reuseRatePct: null }); + }); + + it("REGRESSION: excludes cache activity outside the public stats repo allowlist", async () => { + const env = createTestEnv({ GITTENSORY_PUBLIC_STATS_REPOS: "owner/repo" }); + const thisMonday = isoWeekStart(NOW); + const thisWeekIso = `${thisMonday}T09:00:00.000Z`; + + await recordAuditEvent(env, { eventType: "github_app.grounding_cache_hit", targetKey: "owner/repo", outcome: "completed", createdAt: thisWeekIso }); + await recordAuditEvent(env, { eventType: "github_app.impact_map_cache_hit", targetKey: "owner/repo#123", outcome: "completed", createdAt: thisWeekIso }); + await recordAuditEvent(env, { eventType: "github_app.review_memory_cache_miss", targetKey: "owner/repo#123", outcome: "completed", createdAt: thisWeekIso }); + await recordAuditEvent(env, { eventType: "github_app.grounding_cache_hit", targetKey: "secret/private", outcome: "completed", createdAt: thisWeekIso }); + await recordAuditEvent(env, { eventType: "github_app.review_memory_cache_miss", targetKey: "secret/private#7", outcome: "completed", createdAt: thisWeekIso }); + await recordAuditEvent(env, { eventType: "github_app.ai_review_frozen_reuse", targetKey: "secret/private#7", outcome: "completed", createdAt: thisWeekIso }); + + const trend = await loadPublicReuseRateTrend(env, NOW); + const currentWeek = trend[trend.length - 1]; + expect(currentWeek).toMatchObject({ weekStart: thisMonday, hits: 2, misses: 1, reuseRatePct: null }); + }); + + it("returns all-zero buckets when the public stats repo allowlist is empty", async () => { const env = createTestEnv(); + const thisMonday = isoWeekStart(NOW); + await recordAuditEvent(env, { + eventType: "github_app.grounding_cache_hit", + targetKey: "owner/repo", + outcome: "completed", + createdAt: `${thisMonday}T09:00:00.000Z`, + }); + const trend = await loadPublicReuseRateTrend(env, NOW); for (const week of trend) expect(week).toMatchObject({ hits: 0, misses: 0, reuseRatePct: null }); }); + });