Skip to content

Commit a62f998

Browse files
committed
fix: remove zombies
Signed-off-by: Umberto Sgueglia <usgueglia@contractor.linuxfoundation.org>
1 parent dd15ccd commit a62f998

3 files changed

Lines changed: 59 additions & 3 deletions

File tree

services/apps/packages_worker/src/maven/backfillRepositoryUrl.ts

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
import {
2+
deleteMavenPackageRepoLinks,
23
listMavenPackagesForRepoUrlRecompute,
34
updateMavenRepositoryUrls,
45
} from '@crowd/data-access-layer'
56
import { QueryExecutor } from '@crowd/data-access-layer/src/queryExecutor'
67
import { getServiceChildLogger } from '@crowd/logging'
78

89
import { normalizeScmUrl } from './extract'
10+
import { writeRepoLink } from './runMavenEnrichmentLoop'
911

1012
const log = getServiceChildLogger('maven-repo-url-backfill')
1113

@@ -15,6 +17,8 @@ export type RepoUrlBackfillTotals = {
1517
cleared: number // Gap C: non-repo value → NULL
1618
rewritten: number // non-canonical value → different canonical value
1719
unchanged: number
20+
linked: number // repos/package_repos link (re)written for a fill or rewrite
21+
pruned: number // stale 'declared' link removed for a clear or rewrite
1822
}
1923

2024
/**
@@ -23,6 +27,15 @@ export type RepoUrlBackfillTotals = {
2327
* fetched — the raw SCM value is already in the DB. Fills recoverable NULLs
2428
* (Gap B) and clears non-repository values (Gap C) via direct UPDATE.
2529
*
30+
* Link tables: package_repos is kept consistent with the recomputed
31+
* repository_url. For rows that had a value and now change (rewrites) or are
32+
* cleared, the stale source='declared' link is deleted; for rows that gain a
33+
* canonical URL (fills and rewrites) the correct link is (re)written via the
34+
* same `writeRepoLink` the enrichment loop uses. This matters because consumers
35+
* such as security-contacts read the repo through repos ⋈ package_repos, not
36+
* packages.repository_url. Note this is stricter than the incremental
37+
* enrichment path, which is upsert-only and never prunes.
38+
*
2639
* Idempotent and resumable: the id cursor is derived from the scan, so a
2740
* re-run after an interrupt simply reprocesses from the start and skips rows
2841
* that already match.
@@ -38,6 +51,8 @@ export async function backfillMavenRepositoryUrls(
3851
cleared: 0,
3952
rewritten: 0,
4053
unchanged: 0,
54+
linked: 0,
55+
pruned: 0,
4156
}
4257

4358
let afterId = 0
@@ -51,6 +66,10 @@ export async function backfillMavenRepositoryUrls(
5166
if (rows.length === 0) break
5267

5368
const updates: { id: number; repositoryUrl: string | null }[] = []
69+
// Rows that had a link and now change/clear — their stale 'declared' link is pruned.
70+
const pruneTargets: number[] = []
71+
// Rows that gained a canonical URL — their repo link is (re)written after the update.
72+
const linkTargets: { id: number; repositoryUrl: string }[] = []
5473
for (const row of rows) {
5574
totals.scanned++
5675
const desired = normalizeScmUrl(row.declaredRepositoryUrl)
@@ -62,10 +81,27 @@ export async function backfillMavenRepositoryUrls(
6281
else if (desired === null) totals.cleared++
6382
else totals.rewritten++
6483
updates.push({ id: row.id, repositoryUrl: desired })
84+
// A 'declared' link only exists when the row already had a value.
85+
if (row.repositoryUrl !== null) pruneTargets.push(row.id)
86+
if (desired !== null) linkTargets.push({ id: row.id, repositoryUrl: desired })
6587
}
6688

6789
if (updates.length > 0 && !dryRun) {
68-
await updateMavenRepositoryUrls(qx, updates)
90+
// Atomic per batch: the repository_url UPDATE, the stale-link prune, and the
91+
// relink must commit together. Otherwise an interrupt between them leaves
92+
// packages.repository_url updated but package_repos out of sync — and on a
93+
// re-run the row is skipped (its repository_url already matches `desired`),
94+
// so the inconsistency would never be repaired. On rollback the row stays
95+
// unchanged and is reprocessed on the next run.
96+
await qx.tx(async (t) => {
97+
await updateMavenRepositoryUrls(t, updates)
98+
await deleteMavenPackageRepoLinks(t, pruneTargets)
99+
for (const target of linkTargets) {
100+
await writeRepoLink(t, target.id, target.repositoryUrl)
101+
}
102+
})
103+
totals.pruned += pruneTargets.length
104+
totals.linked += linkTargets.length
69105
}
70106

71107
afterId = rows[rows.length - 1].id

services/apps/packages_worker/src/maven/runMavenEnrichmentLoop.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ type PackageRow = MavenPackageToSync
5353
// ─── Helpers ──────────────────────────────────────────────────────────────────
5454

5555
// prettier-ignore
56-
async function writeRepoLink(qx: QueryExecutor, packageId: number, repositoryUrl: string | null, changed: Set<string>): Promise<void> {
56+
export async function writeRepoLink(qx: QueryExecutor, packageId: number, repositoryUrl: string | null, changed?: Set<string>): Promise<void> {
5757
if (!repositoryUrl) return
5858
const parsed = parseRepoUrl(repositoryUrl)
5959
if (!parsed) return
@@ -64,7 +64,7 @@ async function writeRepoLink(qx: QueryExecutor, packageId: number, repositoryUrl
6464
source: 'declared',
6565
confidence: 0.8,
6666
})
67-
repoChanged.forEach((f) => changed.add(f))
67+
repoChanged.forEach((f) => changed?.add(f))
6868
}
6969

7070
// Postgres deadlock (40P01) is transient: concurrent transactions upserting the same shared

services/libs/data-access-layer/src/osspckgs/repos.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,26 @@ export async function upsertRepo(qx: QueryExecutor, item: IDbRepoUpsert): Promis
3636
return row.id as number
3737
}
3838

39+
/**
40+
* Removes the `declared` repo links for the given packages. Used by the
41+
* repository_url backfill to drop stale links when a package's canonical URL
42+
* changes or is cleared, keeping package_repos consistent with
43+
* packages.repository_url. Only touches source='declared' (the link Maven
44+
* owns) — links from other enrichers (deps.dev, heuristic, manual) are left
45+
* untouched. The shared `repos` rows are never deleted.
46+
*/
47+
export async function deleteMavenPackageRepoLinks(
48+
qx: QueryExecutor,
49+
packageIds: number[],
50+
): Promise<void> {
51+
if (packageIds.length === 0) return
52+
await qx.result(
53+
`DELETE FROM package_repos
54+
WHERE package_id = ANY($(packageIds)::bigint[]) AND source = 'declared'`,
55+
{ packageIds },
56+
)
57+
}
58+
3959
/**
4060
* Links a package to a repo with provenance metadata.
4161
* On conflict keeps the higher confidence value and refreshes verified_at.

0 commit comments

Comments
 (0)