From 662982b9836d3602a9da3b518a7a8d16ec1cc7af Mon Sep 17 00:00:00 2001 From: ghost <49853598+JSONbored@users.noreply.github.com> Date: Tue, 9 Jun 2026 12:22:26 -0600 Subject: [PATCH 1/2] fix(signals): validate repo before burden forecast cache --- src/services/burden-forecast.ts | 20 +++++++++++--------- test/unit/burden-forecast.test.ts | 24 ++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 9 deletions(-) diff --git a/src/services/burden-forecast.ts b/src/services/burden-forecast.ts index 09feb056fa..659373f02d 100644 --- a/src/services/burden-forecast.ts +++ b/src/services/burden-forecast.ts @@ -22,32 +22,34 @@ export type BurdenForecastResponse = { }; export async function loadOrComputeBurdenForecastResponse(env: Env, fullName: string): Promise { - const cached = await getBurdenForecast(env, fullName); + const repo = await getRepository(env, fullName); + if (!repo) return null; + + const repoFullName = repo.fullName; + const cached = await getBurdenForecast(env, repoFullName); if (cached) { const ageMs = forecastAgeMs(cached.generatedAt); return { status: "ready", source: "snapshot", - repoFullName: fullName, + repoFullName, generatedAt: cached.generatedAt, ageSeconds: Math.max(0, Math.floor(ageMs / 1000)), freshness: ageMs > BURDEN_FORECAST_MAX_AGE_MS ? "stale" : "fresh", report: cached.payload as unknown as BurdenForecast, }; } - const repo = await getRepository(env, fullName); - if (!repo) return null; const [issues, pullRequests, recentMergedPullRequests] = await Promise.all([ - listIssueSignalSample(env, fullName), - listOpenPullRequests(env, fullName), - listRecentMergedPullRequests(env, fullName), + listIssueSignalSample(env, repoFullName), + listOpenPullRequests(env, repoFullName), + listRecentMergedPullRequests(env, repoFullName), ]); - const collisions = buildCollisionReport(fullName, issues, pullRequests, recentMergedPullRequests); + const collisions = buildCollisionReport(repoFullName, issues, pullRequests, recentMergedPullRequests); const report = buildBurdenForecast(repo, issues, pullRequests, collisions, 30); return { status: "ready", source: "computed", - repoFullName: fullName, + repoFullName, generatedAt: report.generatedAt, ageSeconds: 0, freshness: "fresh", diff --git a/test/unit/burden-forecast.test.ts b/test/unit/burden-forecast.test.ts index 34f09b9690..4cebe4257f 100644 --- a/test/unit/burden-forecast.test.ts +++ b/test/unit/burden-forecast.test.ts @@ -64,6 +64,23 @@ describe("loadOrComputeBurdenForecastResponse", () => { expect(response).toBeNull(); }); + it("does not expose an orphaned cached forecast for an unknown repo", async () => { + const env = createTestEnv(); + await upsertBurdenForecast(env, { + repoFullName: "ghost/private-repo", + payload: { + repoFullName: "ghost/private-repo", + level: "critical", + summary: "orphaned private queue fixture", + } as unknown as Record, + generatedAt: new Date(Date.now() - 1000).toISOString(), + }); + + const response = await loadOrComputeBurdenForecastResponse(env, "ghost/private-repo"); + + expect(response).toBeNull(); + }); + it("returns a snapshot envelope with freshness:fresh for a recently persisted forecast", async () => { const env = createTestEnv(); await upsertRepositoryFromGitHub(env, { name: "fresh", full_name: "owner/fresh", private: false, owner: { login: "owner" }, default_branch: "main" }); @@ -105,6 +122,13 @@ describe("loadOrComputeBurdenForecastResponse", () => { it("treats malformed cached forecast timestamps as stale", async () => { const env = createTestEnv(); + await upsertRepositoryFromGitHub(env, { + name: "malformed-time", + full_name: "owner/malformed-time", + private: false, + owner: { login: "owner" }, + default_branch: "main", + }); await upsertBurdenForecast(env, { repoFullName: "owner/malformed-time", payload: { repoFullName: "owner/malformed-time", level: "medium", summary: "bad timestamp fixture" } as unknown as Record, From df2af0a5e927cb4a68d6c502d7a0d72633969187 Mon Sep 17 00:00:00 2001 From: JSONbored <49853598+JSONbored@users.noreply.github.com> Date: Tue, 9 Jun 2026 22:45:15 -0700 Subject: [PATCH 2/2] test(signals): keep pending PR fixtures fresh --- test/unit/contributor-open-pr-monitor.test.ts | 8 ++++++-- test/unit/pending-pr-scenarios.test.ts | 8 ++++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/test/unit/contributor-open-pr-monitor.test.ts b/test/unit/contributor-open-pr-monitor.test.ts index 81ba03fb3b..eb3d16703e 100644 --- a/test/unit/contributor-open-pr-monitor.test.ts +++ b/test/unit/contributor-open-pr-monitor.test.ts @@ -34,6 +34,10 @@ const maintainerRole: RoleContext = { guidance: "maintainer", }; +function daysAgo(days: number): string { + return new Date(Date.now() - days * 86_400_000).toISOString(); +} + function pr(overrides: Partial & Pick): PullRequestRecord { return { repoFullName: "entrius/allways-ui", @@ -42,8 +46,8 @@ function pr(overrides: Partial & Pick & Pick): PullRequestRecord { return { repoFullName: "entrius/allways-ui", @@ -42,8 +46,8 @@ function pr(overrides: Partial & Pick