From 3e7bf54c3ec271c2b47cc0270690e1ad6abf3197 Mon Sep 17 00:00:00 2001 From: ghost <49853598+JSONbored@users.noreply.github.com> Date: Thu, 18 Jun 2026 15:47:57 -0700 Subject: [PATCH] fix: keep predicted gate on public manifest --- src/api/routes.ts | 6 +++--- src/signals/focus-manifest-loader.ts | 27 +++++++++++++++++++++++-- test/unit/focus-manifest-loader.test.ts | 27 +++++++++++++++++++++++++ 3 files changed, 55 insertions(+), 5 deletions(-) diff --git a/src/api/routes.ts b/src/api/routes.ts index 8db71ad86c..378949d8bb 100644 --- a/src/api/routes.ts +++ b/src/api/routes.ts @@ -221,7 +221,7 @@ import { loadGatePrecisionReport } from "../services/gate-precision"; import { buildMaintainerQualityDashboard, isMaintainerQualityDataStale } from "../services/maintainer-quality-dashboard"; import { MAX_LOCAL_SCORER_WARNING_CHARS, MAX_LOCAL_SCORER_WARNING_COUNT } from "../signals/local-scorer-diagnostics"; import { compileFocusManifestPolicy } from "../signals/focus-manifest"; -import { loadRepoFocusManifest, upsertRepoFocusManifest } from "../signals/focus-manifest-loader"; +import { loadPublicRepoFocusManifest, loadRepoFocusManifest, upsertRepoFocusManifest } from "../signals/focus-manifest-loader"; import { buildRepoOnboardingPackPreviewForRepo } from "../services/repo-onboarding-pack"; import { generateContributorIssueDrafts } from "../services/contributor-issue-draft"; import { buildRepoSettingsPreview, type PublicSurfaceSkipReason } from "../signals/settings-preview"; @@ -2471,7 +2471,7 @@ export function createApp() { listBountiesByRepo(c.env, parsed.data.repoFullName), getOrCreateScoringModelSnapshot(c.env), loadOrComputeIssueQualityResponse(c.env, parsed.data.repoFullName), - loadRepoFocusManifest(c.env, parsed.data.repoFullName), + loadPublicRepoFocusManifest(c.env, parsed.data.repoFullName), ]); const fit = buildContributorFit(context.profile, context.repositories, [], [], context.syncStates, context.repoStats); const scoringProfile = buildContributorScoringProfile({ login: parsed.data.login, fit, scoringSnapshot: snapshot }); @@ -2546,7 +2546,7 @@ export function createApp() { listBountiesByRepo(c.env, parsed.data.repoFullName), getOrCreateScoringModelSnapshot(c.env), loadOrComputeIssueQualityResponse(c.env, parsed.data.repoFullName), - loadRepoFocusManifest(c.env, parsed.data.repoFullName), + loadPublicRepoFocusManifest(c.env, parsed.data.repoFullName), ]); const fit = buildContributorFit(context.profile, context.repositories, [], [], context.syncStates, context.repoStats); const scoringProfile = buildContributorScoringProfile({ login: parsed.data.login, fit, scoringSnapshot: snapshot }); diff --git a/src/signals/focus-manifest-loader.ts b/src/signals/focus-manifest-loader.ts index 7cab747b21..8b06fd6746 100644 --- a/src/signals/focus-manifest-loader.ts +++ b/src/signals/focus-manifest-loader.ts @@ -55,11 +55,33 @@ export async function loadRepoFocusManifest( env: Env, repoFullName: string, options: { fetcher?: RepoFocusManifestFetcher; maxAgeMs?: number; refresh?: boolean } = {}, +): Promise { + return loadRepoFocusManifestWithCachePolicy(env, repoFullName, options); +} + +/** + * Load only the repo-published focus manifest. This intentionally ignores maintainer/API-backed + * records so contributor-facing previews cannot infer private gate policy while still benefiting + * from fresh public repo-file cache entries. + */ +export async function loadPublicRepoFocusManifest( + env: Env, + repoFullName: string, + options: { fetcher?: RepoFocusManifestFetcher; maxAgeMs?: number; refresh?: boolean } = {}, +): Promise { + return loadRepoFocusManifestWithCachePolicy(env, repoFullName, options, { publicOnly: true }); +} + +async function loadRepoFocusManifestWithCachePolicy( + env: Env, + repoFullName: string, + options: { fetcher?: RepoFocusManifestFetcher; maxAgeMs?: number; refresh?: boolean } = {}, + cachePolicy: { publicOnly?: boolean } = {}, ): Promise { const fetcher = options.fetcher ?? fetchRepoFocusManifestFile; const maxAgeMs = options.maxAgeMs ?? REPO_FOCUS_MANIFEST_MAX_AGE_MS; if (!options.refresh) { - const cached = await readCachedManifest(env, repoFullName, maxAgeMs); + const cached = await readCachedManifest(env, repoFullName, maxAgeMs, cachePolicy); if (cached) return cached; } let manifest: FocusManifest; @@ -145,7 +167,7 @@ export async function upsertRepoFocusManifest(env: Env, repoFullName: string, ra return manifest; } -async function readCachedManifest(env: Env, repoFullName: string, maxAgeMs: number): Promise { +async function readCachedManifest(env: Env, repoFullName: string, maxAgeMs: number, options: { publicOnly?: boolean } = {}): Promise { const [latest] = await listSignalSnapshots(env, REPO_FOCUS_MANIFEST_SIGNAL, repoFullName); if (!latest) return null; const manifest = parseFocusManifest(latest.payload); @@ -153,6 +175,7 @@ async function readCachedManifest(env: Env, repoFullName: string, maxAgeMs: numb latest.payload !== null && typeof latest.payload === "object" && !Array.isArray(latest.payload) ? (latest.payload as Record).source : undefined; + if (options.publicOnly && explicitSource !== "repo_file") return null; if (explicitSource === "api_record") return manifest; if (snapshotAgeMs(latest.generatedAt) > maxAgeMs) return null; return manifest; diff --git a/test/unit/focus-manifest-loader.test.ts b/test/unit/focus-manifest-loader.test.ts index 475635b70b..9daaf8519c 100644 --- a/test/unit/focus-manifest-loader.test.ts +++ b/test/unit/focus-manifest-loader.test.ts @@ -3,6 +3,7 @@ import { createTestEnv } from "../helpers/d1"; import type { JsonValue } from "../../src/types"; import { fetchRepoFocusManifestFile, + loadPublicRepoFocusManifest, loadRepoFocusManifest, loadRepoFocusManifests, upsertRepoFocusManifest, @@ -87,6 +88,32 @@ describe("focus-manifest loader", () => { expect(reloaded.source).toBe("api_record"); }); + it("ignores API-backed records when loading a public-only repo manifest", async () => { + const env = createTestEnv(); + await upsertRepoFocusManifest(env, "owner/public-only", { wantedPaths: ["private/"], gate: { linkedIssue: "block", readinessMinScore: 99 } }); + + const manifest = await loadPublicRepoFocusManifest(env, "owner/public-only", { + fetcher: async () => JSON.stringify({ wantedPaths: ["src/"], gate: { linkedIssue: "advisory" } }), + }); + + expect(manifest.source).toBe("repo_file"); + expect(manifest.wantedPaths).toEqual(["src/"]); + expect(manifest.gate.linkedIssue).toBe("advisory"); + expect(manifest.gate.readinessMinScore).toBeNull(); + }); + + it("falls back to safe public defaults when only an API-backed record exists", async () => { + const env = createTestEnv(); + await upsertRepoFocusManifest(env, "owner/no-public-file", { gate: { linkedIssue: "block", readinessMinScore: 99 } }); + + const manifest = await loadPublicRepoFocusManifest(env, "owner/no-public-file", { fetcher: async () => null }); + + expect(manifest.present).toBe(false); + expect(manifest.source).toBe("none"); + expect(manifest.gate.linkedIssue).toBeNull(); + expect(manifest.gate.readinessMinScore).toBeNull(); + }); + it("bulk-loads manifests for many repos with a concurrency cap", async () => { const env = createTestEnv(); let active = 0;