From 003095f2d93bff31b1029f1c21a1650024e24b53 Mon Sep 17 00:00:00 2001 From: joaovictor91123 Date: Tue, 30 Jun 2026 16:04:38 -0400 Subject: [PATCH 1/2] fix(upstream): look up unmodeled scoring drift by fingerprint, not capped list syncUnmodeledScoringConstantDrift scanned only the newest 50 drift reports, so an older unmodeled-constants row could be missed (duplicate opens, lost issue metadata, resolve no-ops). Query by the stable fingerprint directly. Co-authored-by: Cursor --- src/db/repositories.ts | 7 ++++ src/upstream/unmodeled-scoring-drift.ts | 7 +++- test/unit/unmodeled-scoring-drift.test.ts | 47 ++++++++++++++++++++++- 3 files changed, 58 insertions(+), 3 deletions(-) diff --git a/src/db/repositories.ts b/src/db/repositories.ts index 501a25edd5..9534cb3d21 100644 --- a/src/db/repositories.ts +++ b/src/db/repositories.ts @@ -1279,6 +1279,13 @@ export async function getOpenUpstreamDriftReportByFingerprint(env: Env, fingerpr return row ? toUpstreamDriftReportRecord(row) : null; } +/** Lookup a drift report by its stable fingerprint regardless of status (resolved reports included). */ +export async function getUpstreamDriftReportByFingerprint(env: Env, fingerprint: string): Promise { + const db = getDb(env.DB); + const [row] = await db.select().from(upstreamDriftReports).where(eq(upstreamDriftReports.fingerprint, fingerprint)).limit(1); + return row ? toUpstreamDriftReportRecord(row) : null; +} + export async function persistScorePreview(env: Env, preview: ScorePreviewRecord): Promise { const db = getDb(env.DB); await db.insert(scorePreviews).values({ diff --git a/src/upstream/unmodeled-scoring-drift.ts b/src/upstream/unmodeled-scoring-drift.ts index 71005c8fac..b9c52f7576 100644 --- a/src/upstream/unmodeled-scoring-drift.ts +++ b/src/upstream/unmodeled-scoring-drift.ts @@ -1,6 +1,6 @@ import { getLatestUpstreamRulesetSnapshot, - listUpstreamDriftReports, + getUpstreamDriftReportByFingerprint, upsertUpstreamDriftReport, } from "../db/repositories"; import type { UpstreamDriftArea, UpstreamDriftReportRecord, UpstreamDriftSeverity } from "../types"; @@ -23,7 +23,10 @@ export async function syncUnmodeledScoringConstantDrift( }, ): Promise { const fingerprint = await unmodeledScoringConstantsFingerprint(); - const existing = (await listUpstreamDriftReports(env, 50)).find((report) => report.fingerprint === fingerprint) ?? null; + // Key by fingerprint directly — `listUpstreamDriftReports` is capped and ordered by `updatedAt`, so an older + // unmodeled-constants report can fall off the newest-50 window and be treated as missing (duplicate open reports, + // lost issue metadata, resolve no-ops). + const existing = (await getUpstreamDriftReportByFingerprint(env, fingerprint)) ?? null; const now = nowIso(); if (args.unmodeledConstants.length === 0) { diff --git a/test/unit/unmodeled-scoring-drift.test.ts b/test/unit/unmodeled-scoring-drift.test.ts index 8d24e5977e..63bf357014 100644 --- a/test/unit/unmodeled-scoring-drift.test.ts +++ b/test/unit/unmodeled-scoring-drift.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from "vitest"; -import { listUpstreamDriftReports, updateUpstreamDriftReportIssue } from "../../src/db/repositories"; +import { listUpstreamDriftReports, updateUpstreamDriftReportIssue, upsertUpstreamDriftReport } from "../../src/db/repositories"; import { syncUnmodeledScoringConstantDrift, unmodeledScoringConstantsFingerprint } from "../../src/upstream/unmodeled-scoring-drift"; import { createTestEnv } from "../helpers/d1"; @@ -102,4 +102,49 @@ describe("unmodeled scoring constant drift", () => { expect(report?.summary).toMatch(/, …$/); expect(report?.severity).toBe("high"); }); + + it("looks up the stable unmodeled-constants fingerprint even when it falls off the newest-50 drift list", async () => { + const env = createTestEnv(); + const fingerprint = await unmodeledScoringConstantsFingerprint(); + const opened = await syncUnmodeledScoringConstantDrift(env, { unmodeledConstants: ["ALPHA"] }); + expect(opened?.fingerprint).toBe(fingerprint); + + // Push the unmodeled report out of `listUpstreamDriftReports(env, 50)`'s recency window. + await upsertUpstreamDriftReport(env, { ...opened!, updatedAt: "2020-01-01T00:00:00.000Z" }); + for (let index = 0; index < 51; index++) { + await upsertUpstreamDriftReport(env, { + id: `newer-${index}`, + fingerprint: `newer-drift-${index}`, + severity: "low", + status: "open", + summary: `newer drift ${index}`, + affectedAreas: ["registry"], + previousRulesetId: null, + currentRulesetId: null, + issueNumber: null, + issueUrl: null, + payload: { changes: ["noop"] }, + generatedAt: "2026-06-30T00:00:00.000Z", + updatedAt: `2026-06-30T${String(index).padStart(2, "0")}:00:00.000Z`, + }); + } + expect((await listUpstreamDriftReports(env, 50)).some((report) => report.fingerprint === fingerprint)).toBe(false); + + await updateUpstreamDriftReportIssue(env, fingerprint, { + number: 811, + url: "https://github.com/JSONbored/gittensory/issues/811", + }); + const updated = await syncUnmodeledScoringConstantDrift(env, { unmodeledConstants: ["ALPHA", "BETA"] }); + expect(updated).toMatchObject({ + id: opened!.id, + fingerprint, + status: "open", + issueNumber: 811, + issueUrl: "https://github.com/JSONbored/gittensory/issues/811", + payload: expect.objectContaining({ unmodeledUpstreamConstants: ["ALPHA", "BETA"] }), + }); + + const resolved = await syncUnmodeledScoringConstantDrift(env, { unmodeledConstants: [] }); + expect(resolved).toMatchObject({ id: opened!.id, fingerprint, status: "resolved" }); + }); }); From 0521be6ba5a209db373b1a34bd114b656d694079 Mon Sep 17 00:00:00 2001 From: joaovictor91123 Date: Tue, 30 Jun 2026 16:11:57 -0400 Subject: [PATCH 2/2] test(upstream): prove aged-out drift lookup without bumping updatedAt first updateUpstreamDriftReportIssue rewrites updatedAt, which put the row back inside listUpstreamDriftReports(50) and invalidated the regression. Seed issue metadata on upsert and re-age only after the update sync under test. Co-authored-by: Cursor --- test/unit/unmodeled-scoring-drift.test.ts | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/test/unit/unmodeled-scoring-drift.test.ts b/test/unit/unmodeled-scoring-drift.test.ts index 63bf357014..d3a5f6dd40 100644 --- a/test/unit/unmodeled-scoring-drift.test.ts +++ b/test/unit/unmodeled-scoring-drift.test.ts @@ -109,8 +109,15 @@ describe("unmodeled scoring constant drift", () => { const opened = await syncUnmodeledScoringConstantDrift(env, { unmodeledConstants: ["ALPHA"] }); expect(opened?.fingerprint).toBe(fingerprint); - // Push the unmodeled report out of `listUpstreamDriftReports(env, 50)`'s recency window. - await upsertUpstreamDriftReport(env, { ...opened!, updatedAt: "2020-01-01T00:00:00.000Z" }); + // Push the unmodeled report out of `listUpstreamDriftReports(env, 50)`'s recency window and seed linked-issue + // metadata directly on the row — do NOT call `updateUpstreamDriftReportIssue` here; that helper rewrites + // `updatedAt` to now and would put the row back inside the capped list, defeating the regression. + await upsertUpstreamDriftReport(env, { + ...opened!, + updatedAt: "2020-01-01T00:00:00.000Z", + issueNumber: 811, + issueUrl: "https://github.com/JSONbored/gittensory/issues/811", + }); for (let index = 0; index < 51; index++) { await upsertUpstreamDriftReport(env, { id: `newer-${index}`, @@ -130,10 +137,6 @@ describe("unmodeled scoring constant drift", () => { } expect((await listUpstreamDriftReports(env, 50)).some((report) => report.fingerprint === fingerprint)).toBe(false); - await updateUpstreamDriftReportIssue(env, fingerprint, { - number: 811, - url: "https://github.com/JSONbored/gittensory/issues/811", - }); const updated = await syncUnmodeledScoringConstantDrift(env, { unmodeledConstants: ["ALPHA", "BETA"] }); expect(updated).toMatchObject({ id: opened!.id, @@ -144,6 +147,7 @@ describe("unmodeled scoring constant drift", () => { payload: expect.objectContaining({ unmodeledUpstreamConstants: ["ALPHA", "BETA"] }), }); + await upsertUpstreamDriftReport(env, { ...updated!, updatedAt: "2020-01-01T00:00:00.000Z" }); const resolved = await syncUnmodeledScoringConstantDrift(env, { unmodeledConstants: [] }); expect(resolved).toMatchObject({ id: opened!.id, fingerprint, status: "resolved" }); });