diff --git a/src/api/routes.ts b/src/api/routes.ts index 4061e37265..8055f95372 100644 --- a/src/api/routes.ts +++ b/src/api/routes.ts @@ -241,7 +241,7 @@ import { generateAndSendReviewRecap } from "../services/review-recap"; import { loadOrComputeIssueQualityResponse } from "../services/issue-quality"; import { loadMaintainerNoiseReport } from "../services/maintainer-noise"; import { buildAmsMinerCohortComparison } from "../review/ams-miner-cohort"; -import { loadOrComputeBurdenForecastResponse } from "../services/burden-forecast"; +import { loadCachedBurdenForecastResponse } from "../services/burden-forecast"; import { buildUnavailableQueueTrendReport } from "../services/queue-trends"; import { loadOrComputeRepoOutcomePatternsResponse } from "../services/repo-outcome-patterns"; import { PREFLIGHT_LIMITS } from "../signals/preflight-limits"; @@ -5719,7 +5719,7 @@ async function buildRepoIntelligenceResponse(env: Env, fullName: string) { ]), ), loadRepoDataQuality(env, fullName), - loadOrComputeBurdenForecastResponse(env, fullName).catch((error) => { + loadCachedBurdenForecastResponse(env, fullName).catch((error) => { burdenForecastError = error; return null; }), diff --git a/src/mcp/server.ts b/src/mcp/server.ts index a8979c8915..f6799b776c 100644 --- a/src/mcp/server.ts +++ b/src/mcp/server.ts @@ -104,7 +104,7 @@ import { buildRemediationPlan } from "../services/remediation-plan"; import { deriveEligibilityPlan } from "../services/eligibility-plan"; import { explainScoreBreakdown } from "../services/score-breakdown"; import { loadOrComputeIssueQualityResponse } from "../services/issue-quality"; -import { loadOrComputeBurdenForecastResponse } from "../services/burden-forecast"; +import { loadCachedBurdenForecastResponse } from "../services/burden-forecast"; import { buildMcpClientTelemetry } from "../services/client-telemetry"; import { loadOrComputeRepoOutcomePatternsResponse } from "../services/repo-outcome-patterns"; import { buildRepoOutcomeCalibration, outcomeCalibrationSummary } from "../services/outcome-calibration"; @@ -3455,7 +3455,7 @@ export class LoopoverMcp { private async getBurdenForecast(input: { owner: string; repo: string }): Promise { const fullName = `${input.owner}/${input.repo}`; await this.requireRepoAccess(fullName); - const response = await loadOrComputeBurdenForecastResponse(this.env, fullName); + const response = await loadCachedBurdenForecastResponse(this.env, fullName); if (!response) { return { summary: `LoopOver has no cached burden forecast for ${fullName}.`, diff --git a/src/services/burden-forecast.ts b/src/services/burden-forecast.ts index c4f850b3cf..4ba32a70ce 100644 --- a/src/services/burden-forecast.ts +++ b/src/services/burden-forecast.ts @@ -7,7 +7,9 @@ export type BurdenForecastFreshness = "fresh" | "stale"; export type BurdenForecastResponse = { status: "ready"; - source: "snapshot" | "computed"; + // Cache-only: the request-time compute path was removed in #906 (moved to the background + // `buildBurdenForecasts` job), so a response is always served from a stored snapshot (#8019). + source: "snapshot"; repoFullName: string; generatedAt: string; ageSeconds: number; @@ -15,7 +17,12 @@ export type BurdenForecastResponse = { report: BurdenForecast; }; -export async function loadOrComputeBurdenForecastResponse(env: Env, fullName: string): Promise { +/** + * Load the stored burden-forecast snapshot for a repo, or null when none is cached. This is cache-only + * (#8019): the inline compute fallback was removed in #906 and now runs as the background + * `buildBurdenForecasts` job (`src/queue/processors.ts`), which is what populates the snapshot read here. + */ +export async function loadCachedBurdenForecastResponse(env: Env, fullName: string): Promise { const repo = await getRepository(env, fullName); if (!repo) return null; diff --git a/test/unit/burden-forecast.test.ts b/test/unit/burden-forecast.test.ts index 54b60fbed8..4f7b7ffc38 100644 --- a/test/unit/burden-forecast.test.ts +++ b/test/unit/burden-forecast.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it, vi } from "vitest"; import { getBurdenForecast, upsertBurdenForecast, upsertRepositoryFromGitHub } from "../../src/db/repositories"; -import { BURDEN_FORECAST_MAX_AGE_MS, loadOrComputeBurdenForecastResponse } from "../../src/services/burden-forecast"; +import { BURDEN_FORECAST_MAX_AGE_MS, loadCachedBurdenForecastResponse } from "../../src/services/burden-forecast"; import { buildBurdenForecast, buildCollisionReport } from "../../src/signals/engine"; import type { IssueRecord, JsonValue, PullRequestRecord, RepositoryRecord } from "../../src/types"; import { createTestEnv } from "../helpers/d1"; @@ -57,10 +57,10 @@ describe("burden forecast builder", () => { }); }); -describe("loadOrComputeBurdenForecastResponse", () => { +describe("loadCachedBurdenForecastResponse", () => { it("returns null when the repo is unknown", async () => { const env = createTestEnv(); - const response = await loadOrComputeBurdenForecastResponse(env, "ghost/missing"); + const response = await loadCachedBurdenForecastResponse(env, "ghost/missing"); expect(response).toBeNull(); }); @@ -76,7 +76,7 @@ describe("loadOrComputeBurdenForecastResponse", () => { generatedAt: new Date(Date.now() - 1000).toISOString(), }); - const response = await loadOrComputeBurdenForecastResponse(env, "ghost/private-repo"); + const response = await loadCachedBurdenForecastResponse(env, "ghost/private-repo"); expect(response).toBeNull(); }); @@ -89,7 +89,7 @@ describe("loadOrComputeBurdenForecastResponse", () => { payload: { repoFullName: "owner/fresh", level: "low", summary: "fresh fixture" } as unknown as Record, generatedAt: new Date(Date.now() - 60_000).toISOString(), }); - const response = await loadOrComputeBurdenForecastResponse(env, "owner/fresh"); + const response = await loadCachedBurdenForecastResponse(env, "owner/fresh"); expect(response).toMatchObject({ status: "ready", source: "snapshot", @@ -110,7 +110,7 @@ describe("loadOrComputeBurdenForecastResponse", () => { payload: { repoFullName: "owner/old", level: "high", summary: "stale fixture" } as unknown as Record, generatedAt, }); - const response = await loadOrComputeBurdenForecastResponse(env, "owner/old"); + const response = await loadCachedBurdenForecastResponse(env, "owner/old"); expect(response).toMatchObject({ status: "ready", source: "snapshot", @@ -135,7 +135,7 @@ describe("loadOrComputeBurdenForecastResponse", () => { generatedAt: "not-a-date", }); - const response = await loadOrComputeBurdenForecastResponse(env, "owner/malformed-time"); + const response = await loadCachedBurdenForecastResponse(env, "owner/malformed-time"); expect(response).toMatchObject({ status: "ready", @@ -150,7 +150,7 @@ describe("loadOrComputeBurdenForecastResponse", () => { const env = createTestEnv(); await upsertRepositoryFromGitHub(env, { name: "uncached", full_name: "owner/uncached", private: false, owner: { login: "owner" }, default_branch: "main" }); - const response = await loadOrComputeBurdenForecastResponse(env, "owner/uncached"); + const response = await loadCachedBurdenForecastResponse(env, "owner/uncached"); expect(response).toBeNull(); }); @@ -169,7 +169,7 @@ describe("loadOrComputeBurdenForecastResponse", () => { vi.spyOn(repositoriesModule, "listOpenPullRequests"), vi.spyOn(repositoriesModule, "listRecentMergedPullRequests"), ]; - await loadOrComputeBurdenForecastResponse(env, "owner/perf"); + await loadCachedBurdenForecastResponse(env, "owner/perf"); for (const spy of spies) { expect(spy).not.toHaveBeenCalled(); spy.mockRestore(); @@ -186,7 +186,7 @@ describe("loadOrComputeBurdenForecastResponse", () => { vi.spyOn(repositoriesModule, "listRecentMergedPullRequests"), ]; - const response = await loadOrComputeBurdenForecastResponse(env, "owner/computed-perf"); + const response = await loadCachedBurdenForecastResponse(env, "owner/computed-perf"); expect(response).toBeNull(); for (const spy of spies) {