Skip to content

Commit ad22e6d

Browse files
authored
fix(queue): widen rag-index fan-out to match the regate-sweep candidate set (#5686)
fanOutRagIndexJobs still filtered candidates to isRegistered repos unioned with the LOOPOVER_REVIEW_REPOS allowlist, unlike fanOutAgentRegateSweepJobs which unions ALL listRepositories(). An installed-but-never-registered, non-allowlisted repo (the brokered self-host case) was never a fan-out candidate, so a per-repo features.rag override could never resurface it — the repo was still reviewed via the regate sweep, just without codebase- context retrieval. Drop the isRegistered filter so the candidate set matches the regate sweep exactly; convergedFeatureActive still gates the actual indexing spend, so this only widens eligibility. Closes #5024
1 parent 292e275 commit ad22e6d

2 files changed

Lines changed: 34 additions & 12 deletions

File tree

src/queue/processors.ts

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1159,24 +1159,25 @@ export async function runRagIndexJob(
11591159
await indexRepo(env, project, repo);
11601160
}
11611161

1162-
// Enqueue one per-repo FULL re-index job for every registered + cutover-allowlisted repo (mirrors the
1163-
// signal-snapshot / agent-regate fan-out: a delayed per-repo queue message so each repo's index runs as its own
1164-
// bounded, retryable job rather than one giant tick). Only allowlisted repos are indexed — retrieval is gated the
1165-
// same way, so indexing a non-converged repo would only burn the free-tier vector budget for no benefit.
1162+
// Enqueue one per-repo FULL re-index job for every RAG-active repo, candidates drawn from ALL known repos plus the
1163+
// cutover allowlist (mirrors the agent-regate fan-out: a delayed per-repo queue message so each repo's index runs
1164+
// as its own bounded, retryable job rather than one giant tick). Only RAG-active repos are indexed — retrieval is
1165+
// gated the same way, so indexing a non-converged repo would only burn the free-tier vector budget for no benefit.
11661166
async function fanOutRagIndexJobs(
11671167
env: Env,
11681168
requestedBy: "schedule" | "api" | "webhook" | "test",
11691169
): Promise<void> {
1170-
// Candidate repos = the webhook-REGISTERED repos UNION the maintainer's CONFIGURED repos (LOOPOVER_REVIEW_REPOS).
1171-
// The union is the fix for the brokered self-host: a maintainer's repos are is_registered=0 (never went through the
1172-
// registration webhook), so a registered-only fan-out never indexed them — leaving reviews without codebase context.
1173-
// Deduped case-insensitively (a repo can be both registered AND configured). Each is then filtered by whether RAG is
1174-
// active for it (`features.rag` override → LOOPOVER_REVIEW_REPOS allowlist default), so nothing extra is indexed.
1170+
// Candidate repos = ALL known repos UNION the maintainer's CONFIGURED repos (LOOPOVER_REVIEW_REPOS) — mirrors
1171+
// fanOutAgentRegateSweepJobs's own candidate set exactly (#5024). Filtering to isRegistered-only left an
1172+
// installed-but-never-registered repo (the brokered self-host case: is_registered=0, never went through the
1173+
// registration webhook) out of the candidate pool entirely, so even a per-repo `features.rag` override could never
1174+
// resurface it — the regate sweep still reviewed that repo, just without codebase-context retrieval. Deduped
1175+
// case-insensitively (a repo can be both known AND configured). Each candidate is then filtered by whether RAG is
1176+
// active for it (`features.rag` override → LOOPOVER_REVIEW_REPOS allowlist default) just below, so this widens
1177+
// ELIGIBILITY only — the convergedFeatureActive gate below is what actually controls indexing spend.
11751178
const repositoriesByKey = new Map((await listRepositories(env)).map((repo) => [repo.fullName.toLowerCase(), repo]));
11761179
const byKey = new Map<string, { fullName: string; installationId?: number }>();
1177-
for (const repo of [...repositoriesByKey.values()].filter(
1178-
(r) => r.isRegistered,
1179-
))
1180+
for (const repo of repositoriesByKey.values())
11801181
byKey.set(repo.fullName.toLowerCase(), { fullName: repo.fullName, ...(typeof repo.installationId === "number" ? { installationId: repo.installationId } : {}) });
11811182
for (const fullName of listConvergenceRepos(env)) {
11821183
const repo = repositoriesByKey.get(fullName.toLowerCase());

test/unit/rag-index.test.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -906,6 +906,27 @@ describe("rag-index-repo job dispatch (processors.ts wiring)", () => {
906906
expect(sent.filter((m) => (m as { repoFullName?: string }).repoFullName === "JSONbored/gittensory").length).toBe(1);
907907
});
908908

909+
it("cron fan-out includes an INSTALLED, non-registered, non-allowlisted repo once a features.rag override opts it in (#5024)", async () => {
910+
const sent: import("../../src/types").JobMessage[] = [];
911+
const env = createTestEnv({
912+
LOOPOVER_REVIEW_RAG: "true",
913+
LOOPOVER_REVIEW_REPOS: "", // empty allowlist — this repo must be discoverable without it
914+
JOBS: { async send(message: import("../../src/types").JobMessage) { sent.push(message); } } as unknown as Queue,
915+
});
916+
// Installed via the GitHub App (installationId set ⇒ is_installed=1) but never went through the registration
917+
// webhook — is_registered stays 0 (registerRepo() is deliberately NOT called), and it's not in the allowlist
918+
// either. Before this fix, fanOutRagIndexJobs's candidate pool was isRegistered-repos UNION the static
919+
// allowlist, so this repo was never even a candidate — the per-repo features.rag override below could never
920+
// resurface it, unlike fanOutAgentRegateSweepJobs's candidate set (ALL listRepositories()), which the regate
921+
// sweep already covers this exact repo through.
922+
await upsertRepositoryFromGitHub(env, { name: "widgets", full_name: "owner/widgets", private: false, owner: { login: "owner" } }, 456);
923+
await upsertRepoFocusManifest(env, "owner/widgets", { features: { rag: true } });
924+
925+
await processJob(env, { type: "rag-index-repo", requestedBy: "schedule" });
926+
927+
expect(sent).toEqual([{ type: "rag-index-repo", requestedBy: "schedule", repoFullName: "owner/widgets", installationId: 456 }]);
928+
});
929+
909930
it("FLAG-OFF cron fan-out is a no-op (no per-repo jobs enqueued, no fan-out audit)", async () => {
910931
const sent: import("../../src/types").JobMessage[] = [];
911932
const env = createTestEnv({

0 commit comments

Comments
 (0)