Skip to content

Commit d22a0ba

Browse files
authored
Merge branch 'main' into fix/cm-1216-email-domain-affiliations
2 parents ab901cb + 36c8078 commit d22a0ba

70 files changed

Lines changed: 3713 additions & 285 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

backend/.env.dist.local

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -185,9 +185,9 @@ ENRICHER_BATCH_SIZE=100
185185
ENRICHER_REPO_UPDATE_INTERVAL_HOURS=24
186186
ENRICHER_IDLE_SLEEP_SEC=60
187187

188-
OSSPCKGS_GCP_PROJECT=
189-
OSSPCKGS_GCS_BUCKET=
190-
OSSPCKGS_GCP_CREDENTIALS_B64=
188+
OSSPCKGS_GCP_PROJECT=local-dev
189+
OSSPCKGS_GCS_BUCKET=local-dev
190+
OSSPCKGS_GCP_CREDENTIALS_B64=e30=
191191

192192
# osv-sync (Temporal-scheduled; see services/apps/packages_worker/src/osv/schedule.ts)
193193
# OSV_ECOSYSTEMS uses OSV's canonical bucket case (npm lowercase, Maven titlecase) because
@@ -199,3 +199,14 @@ OSV_ECOSYSTEMS=npm,Maven
199199
OSV_TMP_DIR=/tmp/osv
200200
OSV_BATCH_SIZE=500
201201
OSV_DERIVE_BATCH_SIZE=1000
202+
203+
# maven enricher
204+
205+
MAVEN_FETCHER_BATCH_SIZE=2000
206+
MAVEN_FETCHER_CONCURRENCY=10
207+
MAVEN_FETCHER_NON_CRITICAL_BATCH_SIZE=500
208+
MAVEN_FETCHER_NON_CRITICAL_CONCURRENCY=20
209+
MAVEN_FETCHER_REFRESH_DAYS=1
210+
MAVEN_FETCHER_GROUP_DELAY_MS=100
211+
MAVEN_FETCHER_BASE_URL_BACKFILL=https://maven-central.storage-download.googleapis.com/maven2
212+
MAVEN_FETCHER_BASE_URL_INCREMENTAL=https://repo1.maven.org/maven2
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
-- Add created_at / updated_at to all packages-db tables for Tinybird sync watermarking.
2+
--
3+
-- Excluded tables and why:
4+
-- package_name_history — event log; has changed_at
5+
-- audit_field_changes — audit log; has logged_at
6+
-- osspckgs_ingest_jobs — job tracking; has started_at / finished_at
7+
-- npm_worker_state — state machine; has updated_at
8+
-- npm_package_state — watermark columns serve the same purpose
9+
-- npm_package_universe_state — watermark columns serve the same purpose
10+
-- package_criticality_spotlight — override config; has added_at
11+
--
12+
-- repos: created_at already exists (stores the GitHub repository creation date).
13+
-- last_synced_at is nullable (not set until the GitHub enricher runs), so updated_at
14+
-- is added as a non-nullable watermark for Tinybird sync. last_synced_at is kept as
15+
-- a separate enrichment-freshness signal.
16+
--
17+
-- packages, versions, repo_docker: last_synced_at already serves as updated_at.
18+
-- Only created_at is added to avoid duplicate semantics.
19+
--
20+
-- package_repos: verified_at already serves as updated_at. Only created_at is added.
21+
--
22+
-- Partitioned tables (versions, package_dependencies, downloads_daily, downloads_last_30d):
23+
-- PostgreSQL 12+ propagates new columns to all child partitions automatically —
24+
-- no per-partition ALTER needed.
25+
26+
ALTER TABLE repos
27+
ADD COLUMN IF NOT EXISTS updated_at timestamptz NOT NULL DEFAULT NOW();
28+
29+
ALTER TABLE packages
30+
ADD COLUMN IF NOT EXISTS created_at timestamptz NOT NULL DEFAULT NOW();
31+
32+
ALTER TABLE package_funding_links
33+
ADD COLUMN IF NOT EXISTS created_at timestamptz NOT NULL DEFAULT NOW(),
34+
ADD COLUMN IF NOT EXISTS updated_at timestamptz NOT NULL DEFAULT NOW();
35+
36+
ALTER TABLE versions
37+
ADD COLUMN IF NOT EXISTS created_at timestamptz NOT NULL DEFAULT NOW();
38+
39+
ALTER TABLE package_dependencies
40+
ADD COLUMN IF NOT EXISTS created_at timestamptz NOT NULL DEFAULT NOW(),
41+
ADD COLUMN IF NOT EXISTS updated_at timestamptz NOT NULL DEFAULT NOW();
42+
43+
ALTER TABLE repo_scorecard_checks
44+
ADD COLUMN IF NOT EXISTS created_at timestamptz NOT NULL DEFAULT NOW(),
45+
ADD COLUMN IF NOT EXISTS updated_at timestamptz NOT NULL DEFAULT NOW();
46+
47+
ALTER TABLE repo_docker
48+
ADD COLUMN IF NOT EXISTS created_at timestamptz NOT NULL DEFAULT NOW();
49+
50+
ALTER TABLE package_repos
51+
ADD COLUMN IF NOT EXISTS created_at timestamptz NOT NULL DEFAULT NOW();
52+
53+
ALTER TABLE advisories
54+
ADD COLUMN IF NOT EXISTS created_at timestamptz NOT NULL DEFAULT NOW(),
55+
ADD COLUMN IF NOT EXISTS updated_at timestamptz NOT NULL DEFAULT NOW();
56+
57+
ALTER TABLE advisory_packages
58+
ADD COLUMN IF NOT EXISTS created_at timestamptz NOT NULL DEFAULT NOW(),
59+
ADD COLUMN IF NOT EXISTS updated_at timestamptz NOT NULL DEFAULT NOW();
60+
61+
ALTER TABLE advisory_affected_ranges
62+
ADD COLUMN IF NOT EXISTS created_at timestamptz NOT NULL DEFAULT NOW(),
63+
ADD COLUMN IF NOT EXISTS updated_at timestamptz NOT NULL DEFAULT NOW();
64+
65+
ALTER TABLE maintainers
66+
ADD COLUMN IF NOT EXISTS created_at timestamptz NOT NULL DEFAULT NOW(),
67+
ADD COLUMN IF NOT EXISTS updated_at timestamptz NOT NULL DEFAULT NOW();
68+
69+
ALTER TABLE package_maintainers
70+
ADD COLUMN IF NOT EXISTS created_at timestamptz NOT NULL DEFAULT NOW(),
71+
ADD COLUMN IF NOT EXISTS updated_at timestamptz NOT NULL DEFAULT NOW();
72+
73+
ALTER TABLE downloads_daily
74+
ADD COLUMN IF NOT EXISTS created_at timestamptz NOT NULL DEFAULT NOW(),
75+
ADD COLUMN IF NOT EXISTS updated_at timestamptz NOT NULL DEFAULT NOW();
76+
77+
ALTER TABLE downloads_last_30d
78+
ADD COLUMN IF NOT EXISTS created_at timestamptz NOT NULL DEFAULT NOW(),
79+
ADD COLUMN IF NOT EXISTS updated_at timestamptz NOT NULL DEFAULT NOW();
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
-- Retire packages_universe (Tier 3 workspace). All signals are migrated onto
2+
-- packages, rank_packages_universe() is replaced by rank_packages() which operates on packages directly,
3+
-- and the table is dropped.
4+
--
5+
-- Columns migrated from packages_universe → packages:
6+
-- downloads_last_30d bigint (npm 30-day window cache for ranking)
7+
-- centrality_score numeric(10,8) (PageRank, stored for future formula use)
8+
-- rank_in_ecosystem int (computed by rank_packages)
9+
--
10+
-- rank_packages_universe() → rank_packages() changes:
11+
-- - Operates on packages directly; no more TRUNCATE/INSERT workspace.
12+
-- - Scope limited to ecosystems present in critical_top_n_by_ecosystem JSONB
13+
-- (dynamic — add an ecosystem to the JSONB to include it in ranking).
14+
-- - "Propagate to packages" step removed (packages IS the target now).
15+
-- - Return column propagated_rows removed (no propagation step).
16+
17+
-- ── 1. Add missing columns to packages ────────────────────────────────────────
18+
19+
ALTER TABLE packages
20+
ADD COLUMN IF NOT EXISTS downloads_last_30d bigint,
21+
ADD COLUMN IF NOT EXISTS centrality_score numeric(10, 8),
22+
ADD COLUMN IF NOT EXISTS rank_in_ecosystem int;
23+
24+
-- ── 2. Back-fill from packages_universe ───────────────────────────────────────
25+
26+
UPDATE packages p
27+
SET downloads_last_30d = pu.downloads_last_30d,
28+
centrality_score = pu.centrality_score,
29+
rank_in_ecosystem = pu.rank_in_ecosystem
30+
FROM packages_universe pu
31+
WHERE p.purl = pu.purl
32+
AND (
33+
p.downloads_last_30d IS DISTINCT FROM pu.downloads_last_30d
34+
OR p.centrality_score IS DISTINCT FROM pu.centrality_score
35+
OR p.rank_in_ecosystem IS DISTINCT FROM pu.rank_in_ecosystem
36+
);
37+
38+
-- ── 3. Replace rank_packages_universe() ───────────────────────────────────────
39+
-- Two overloads exist in the schema:
40+
-- V1779710880 created (numeric, numeric, numeric, numeric, jsonb) — 5 params
41+
-- V1780589607 CREATE OR REPLACE'd with (numeric, numeric, numeric, jsonb) — different
42+
-- signature, so it added a second overload rather than replacing the first.
43+
-- Both must be dropped; only one new 4-param version is created.
44+
45+
DROP FUNCTION IF EXISTS rank_packages_universe(numeric, numeric, numeric, numeric, jsonb);
46+
DROP FUNCTION IF EXISTS rank_packages_universe(numeric, numeric, numeric, jsonb);
47+
48+
-- Usage:
49+
-- -- with defaults (weights 0.25/0.25/0.50, built-in top-N budget)
50+
-- SELECT * FROM rank_packages();
51+
--
52+
-- -- with custom weights and/or a different top-N budget
53+
-- SELECT * FROM rank_packages(
54+
-- 0.20, 0.30, 0.50,
55+
-- '{"npm": 400000, "maven": 200000, "cargo": 75000}'::jsonb
56+
-- );
57+
58+
-- rank_packages() — score, rank, and flag packages in one pass.
59+
--
60+
-- Formula:
61+
-- impact = w_downloads * pct_rank( LOG(1 + downloads_last_30d) ) within ecosystem
62+
-- + w_dep_pkgs * pct_rank( LOG(1 + dependent_count) ) within ecosystem
63+
-- + w_transitive * pct_rank( LOG(1 + transitive_dependent_count) ) within ecosystem
64+
--
65+
-- Steps:
66+
-- 1. Score — compute impact via weighted PERCENT_RANK() (scoped to JSONB ecosystems)
67+
-- 2. Rank — ROW_NUMBER() per ecosystem, flag top-N as is_critical (scoped to JSONB ecosystems)
68+
-- 2.5 Spotlight — force is_critical = TRUE for rows in package_criticality_spotlight
69+
-- 3. Stamp — unconditionally set last_rank_pass_at on all scored rows (schema contract)
70+
--
71+
-- All weights and the top-N budget are call-time parameters.
72+
-- ROW_NUMBER() (not RANK()) keeps each ecosystem's critical set exactly at top-N.
73+
-- Only ecosystems present as keys in critical_top_n_by_ecosystem are scored/ranked;
74+
-- packages from other ecosystems are not touched.
75+
76+
CREATE OR REPLACE FUNCTION rank_packages(
77+
weight_downloads numeric DEFAULT 0.25,
78+
weight_dependent_packages numeric DEFAULT 0.25,
79+
weight_transitive numeric DEFAULT 0.50,
80+
critical_top_n_by_ecosystem jsonb DEFAULT '{"npm":400000,"go":100000,"maven":200000,"pypi":100000,"nuget":50000,"cargo":75000}'::jsonb
81+
)
82+
RETURNS TABLE(scored_rows int, ranked_rows int)
83+
LANGUAGE plpgsql AS $$
84+
DECLARE
85+
n_scored int;
86+
n_ranked int;
87+
BEGIN
88+
-- ── Step 1: score ──────────────────────────────────────────────────────────
89+
WITH percentile_scores AS (
90+
SELECT
91+
id,
92+
(
93+
weight_downloads * PERCENT_RANK() OVER (
94+
PARTITION BY ecosystem ORDER BY LOG(1 + COALESCE(downloads_last_30d, 0)))
95+
96+
+ weight_dependent_packages * PERCENT_RANK() OVER (
97+
PARTITION BY ecosystem ORDER BY LOG(1 + COALESCE(dependent_count, 0)))
98+
99+
+ weight_transitive * PERCENT_RANK() OVER (
100+
PARTITION BY ecosystem ORDER BY LOG(1 + COALESCE(transitive_dependent_count, 0)))
101+
)::numeric(10, 4) AS new_impact
102+
FROM packages
103+
WHERE ecosystem IN (SELECT jsonb_object_keys(critical_top_n_by_ecosystem))
104+
)
105+
UPDATE packages p
106+
SET impact = ps.new_impact
107+
FROM percentile_scores ps
108+
WHERE p.id = ps.id
109+
AND p.impact IS DISTINCT FROM ps.new_impact;
110+
111+
GET DIAGNOSTICS n_scored = ROW_COUNT;
112+
113+
-- ── Step 2: rank + flag ────────────────────────────────────────────────────
114+
WITH ranked AS (
115+
SELECT
116+
id, ecosystem,
117+
ROW_NUMBER() OVER (
118+
PARTITION BY ecosystem
119+
ORDER BY impact DESC NULLS LAST, id
120+
) AS r
121+
FROM packages
122+
WHERE purl IS NOT NULL
123+
AND ecosystem IN (SELECT jsonb_object_keys(critical_top_n_by_ecosystem))
124+
),
125+
flagged AS (
126+
SELECT
127+
id, r,
128+
COALESCE(
129+
r <= (critical_top_n_by_ecosystem ->> ecosystem)::int,
130+
FALSE
131+
) AS new_is_critical
132+
FROM ranked
133+
)
134+
UPDATE packages p
135+
SET rank_in_ecosystem = f.r,
136+
is_critical = f.new_is_critical
137+
FROM flagged f
138+
WHERE p.id = f.id
139+
AND (
140+
p.rank_in_ecosystem IS DISTINCT FROM f.r
141+
OR p.is_critical IS DISTINCT FROM f.new_is_critical
142+
);
143+
144+
GET DIAGNOSTICS n_ranked = ROW_COUNT;
145+
146+
-- ── Step 2.5: spotlight overrides ─────────────────────────────────────────
147+
UPDATE packages p
148+
SET is_critical = TRUE
149+
FROM package_criticality_spotlight s
150+
WHERE p.ecosystem = s.ecosystem
151+
AND (p.namespace IS NOT DISTINCT FROM s.namespace)
152+
AND p.name = s.name
153+
AND p.is_critical = FALSE;
154+
155+
-- ── Step 3: stamp last_rank_pass_at unconditionally ───────────────────────
156+
-- Schema contract: must be updated on every pass (not only when scores change)
157+
-- so stale-detection queries (last_rank_pass_at < NOW() - INTERVAL '8 days') work.
158+
UPDATE packages
159+
SET last_rank_pass_at = NOW()
160+
WHERE ecosystem IN (SELECT jsonb_object_keys(critical_top_n_by_ecosystem));
161+
162+
RETURN QUERY SELECT n_scored, n_ranked;
163+
END;
164+
$$;
165+
166+
-- ── 4. Drop packages_universe ─────────────────────────────────────────────────
167+
-- No FK constraints reference this table (npm_package_universe_state and
168+
-- downloads_last_30d use purl text, not FK).
169+
170+
DROP TABLE packages_universe;
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
ALTER TABLE repos ADD COLUMN IF NOT EXISTS security_policy_enabled boolean;
2+
ALTER TABLE repos ADD COLUMN IF NOT EXISTS security_file_enabled boolean;
3+
ALTER TABLE repos ADD COLUMN IF NOT EXISTS snapshot_at timestamptz;
4+
5+
CREATE TABLE IF NOT EXISTS repo_activity_snapshot (
6+
repo_id bigint PRIMARY KEY REFERENCES repos(id) ON DELETE CASCADE,
7+
snapshot_at timestamptz NOT NULL,
8+
window_months int NOT NULL DEFAULT 12,
9+
-- commit activity
10+
commits_last_12m int,
11+
commits_last_6m int,
12+
commits_prior_6m int,
13+
-- PR health
14+
prs_opened_last_12m int,
15+
prs_merged_last_12m int,
16+
prs_closed_unmerged_12m int,
17+
pr_median_time_to_merge_hours int,
18+
pr_median_time_to_first_response_hours int,
19+
-- issue health
20+
issues_opened_last_12m int,
21+
issues_closed_last_12m int,
22+
issues_opened_last_6m int,
23+
issues_opened_prior_6m int,
24+
issues_open_now int,
25+
issue_median_time_to_close_hours int,
26+
issue_median_time_to_first_response_hours int
27+
);
28+
29+
CREATE INDEX IF NOT EXISTS repo_activity_snapshot_snapshot_at_idx
30+
ON repo_activity_snapshot (snapshot_at);
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
-- ── 1. Add the 30d watermark columns to npm_package_state ──────────────────────
2+
ALTER TABLE npm_package_state
3+
ADD COLUMN IF NOT EXISTS downloads_30d_last_run_at timestamptz, -- breadth watermark: latest 30d window refreshed
4+
ADD COLUMN IF NOT EXISTS downloads_30d_history_backfilled_at timestamptz, -- depth watermark: NULL until full older history filled
5+
ADD COLUMN IF NOT EXISTS downloads_30d_run_result jsonb; -- { status, httpStatus?, errorKind?, message? }
6+
7+
-- Recreate the two indexes the old table had — both due-selection queries
8+
-- filter/order on these columns.
9+
CREATE INDEX IF NOT EXISTS npm_package_state_downloads_30d_last_run_at_idx
10+
ON npm_package_state (downloads_30d_last_run_at);
11+
CREATE INDEX IF NOT EXISTS npm_package_state_downloads_30d_history_backfilled_at_idx
12+
ON npm_package_state (downloads_30d_history_backfilled_at);
13+
14+
-- ── 2. Migrate existing rows ───────────────────────────────────────────────────
15+
INSERT INTO npm_package_state
16+
(purl, downloads_30d_last_run_at, downloads_30d_history_backfilled_at, downloads_30d_run_result)
17+
SELECT purl, downloads_30d_last_run_at, downloads_30d_history_backfilled_at, downloads_30d_run_result
18+
FROM npm_package_universe_state
19+
ON CONFLICT (purl) DO UPDATE SET
20+
downloads_30d_last_run_at = EXCLUDED.downloads_30d_last_run_at,
21+
downloads_30d_history_backfilled_at = EXCLUDED.downloads_30d_history_backfilled_at,
22+
downloads_30d_run_result = EXCLUDED.downloads_30d_run_result;
23+
24+
-- ── 3. Drop the retired table ──────────────────────────────────────────────────
25+
DROP TABLE npm_package_universe_state;

0 commit comments

Comments
 (0)