Skip to content

Commit fb94246

Browse files
committed
feat: add migration for blast radius analysis
Signed-off-by: Umberto Sgueglia <usgueglia@contractor.linuxfoundation.org>
1 parent efec236 commit fb94246

1 file changed

Lines changed: 135 additions & 0 deletions

File tree

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
-- Blast-radius analysis pipeline (CM-1328). Mirrors the 4-stage PoC
2+
-- (linux-foundation/blast-radius): intel -> dependents -> reachability -> report.
3+
-- Everything the PoC kept as JSON files under data/<VULN-ID>/ moves to these
4+
-- tables so runs are resumable, queryable, and inspectable across ecosystems.
5+
-- The aggregated report (naive vs true blast radius, bucket counts) is not
6+
-- materialized here — it's derived from blast_radius_verdicts at read time.
7+
8+
-- One row per submitted job (backend's submitBlastRadiusJob). id matches the
9+
-- analysisId the API already generates before starting the Temporal workflow.
10+
CREATE TABLE IF NOT EXISTS blast_radius_analyses (
11+
id UUID PRIMARY KEY,
12+
-- Raw GHSA/CVE id from the request; the advisory may not be synced from
13+
-- deps.dev BQ yet, so advisory_id is resolved (and filled in) later.
14+
advisory_osv_id TEXT NOT NULL,
15+
advisory_id BIGINT REFERENCES advisories (id),
16+
-- Raw value from the request (purl or bare name); may not resolve to a
17+
-- known package yet. package_id is filled in once/if it resolves.
18+
package_name TEXT,
19+
package_id BIGINT REFERENCES packages (id),
20+
ecosystem TEXT NOT NULL,
21+
status TEXT NOT NULL DEFAULT 'pending'
22+
CHECK (status IN ('pending', 'running', 'done', 'failed')),
23+
force BOOLEAN NOT NULL DEFAULT FALSE,
24+
-- Stage 2 (dependents) run metadata — the per-dependent rows live in
25+
-- blast_radius_dependents; these are the population-level numbers report
26+
-- rendering needs alongside them (dependents_doc.source / candidates_considered).
27+
dependents_source TEXT,
28+
candidates_considered INT,
29+
total_cost_usd NUMERIC(10, 4) NOT NULL DEFAULT 0,
30+
error TEXT,
31+
started_at TIMESTAMPTZ,
32+
completed_at TIMESTAMPTZ,
33+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
34+
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
35+
);
36+
37+
CREATE INDEX IF NOT EXISTS blast_radius_analyses_advisory_id_idx
38+
ON blast_radius_analyses (advisory_id);
39+
CREATE INDEX IF NOT EXISTS blast_radius_analyses_package_id_idx
40+
ON blast_radius_analyses (package_id) WHERE package_id IS NOT NULL;
41+
CREATE INDEX IF NOT EXISTS blast_radius_analyses_status_idx
42+
ON blast_radius_analyses (status);
43+
44+
-- Stage 1 output — one row per analysis (symbol_spec.json equivalent).
45+
CREATE TABLE IF NOT EXISTS blast_radius_symbol_specs (
46+
id BIGSERIAL PRIMARY KEY,
47+
analysis_id UUID NOT NULL UNIQUE REFERENCES blast_radius_analyses (id),
48+
vuln_id TEXT NOT NULL,
49+
aliases TEXT[],
50+
package TEXT NOT NULL,
51+
ecosystem TEXT NOT NULL,
52+
affected_ranges JSONB NOT NULL,
53+
vulnerable_versions TEXT[] NOT NULL,
54+
analyzed_version TEXT NOT NULL,
55+
related_affected_packages TEXT[],
56+
vulnerable_symbols JSONB NOT NULL, -- [{name, kind, defined_in, exported_as[], notes}]
57+
import_signatures JSONB NOT NULL, -- {style: [signature, ...]}
58+
exploit_preconditions TEXT,
59+
reachability_notes TEXT,
60+
confidence NUMERIC(3, 2) NOT NULL CHECK (confidence BETWEEN 0.00 AND 1.00),
61+
sources TEXT[],
62+
summary TEXT,
63+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
64+
);
65+
66+
-- Stage 2 output — one row per candidate dependent, analyzed or excluded.
67+
-- Unifies dependents.json's "analyzed" and "excluded_by_range" lists: the
68+
-- excluded ones never got a resolved version/tarball, hence those columns
69+
-- are nullable.
70+
CREATE TABLE IF NOT EXISTS blast_radius_dependents (
71+
id BIGSERIAL PRIMARY KEY,
72+
analysis_id UUID NOT NULL REFERENCES blast_radius_analyses (id),
73+
package_id BIGINT REFERENCES packages (id),
74+
name TEXT NOT NULL,
75+
version TEXT,
76+
downloads BIGINT,
77+
declared_range TEXT,
78+
dependency_kind TEXT, -- 'dependencies' | 'peerDependencies' | 'optionalDependencies'
79+
range_includes_vuln BOOLEAN,
80+
range_check TEXT, -- 'matched' | 'excluded' | 'unparseable-included'
81+
tarball_url TEXT,
82+
excluded_by_range BOOLEAN NOT NULL DEFAULT FALSE,
83+
exclusion_reason TEXT,
84+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
85+
UNIQUE (analysis_id, name)
86+
);
87+
88+
CREATE INDEX IF NOT EXISTS blast_radius_dependents_analysis_id_idx
89+
ON blast_radius_dependents (analysis_id);
90+
CREATE INDEX IF NOT EXISTS blast_radius_dependents_package_id_idx
91+
ON blast_radius_dependents (package_id) WHERE package_id IS NOT NULL;
92+
93+
-- Stage 3 output — one reachability verdict per analyzed (non-excluded) dependent.
94+
CREATE TABLE IF NOT EXISTS blast_radius_verdicts (
95+
id BIGSERIAL PRIMARY KEY,
96+
analysis_id UUID NOT NULL REFERENCES blast_radius_analyses (id),
97+
dependent_id BIGINT NOT NULL UNIQUE REFERENCES blast_radius_dependents (id),
98+
uses_package BOOLEAN NOT NULL,
99+
imports_vulnerable_symbol BOOLEAN NOT NULL,
100+
import_style TEXT, -- 'main-member' | 'deep-import' | 'standalone-pkg' | 'reexport' | 'none'
101+
reachable_verdict TEXT NOT NULL CHECK (reachable_verdict IN ('affected', 'not_affected', 'unclear')),
102+
confidence NUMERIC(3, 2) NOT NULL CHECK (confidence BETWEEN 0.00 AND 1.00),
103+
evidence JSONB, -- [{file, line, snippet}]
104+
reasoning TEXT,
105+
model TEXT,
106+
turns_used INT,
107+
cost_usd NUMERIC(10, 4) NOT NULL DEFAULT 0,
108+
error TEXT,
109+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
110+
);
111+
112+
CREATE INDEX IF NOT EXISTS blast_radius_verdicts_analysis_id_idx
113+
ON blast_radius_verdicts (analysis_id);
114+
CREATE INDEX IF NOT EXISTS blast_radius_verdicts_analysis_id_verdict_idx
115+
ON blast_radius_verdicts (analysis_id, reachable_verdict);
116+
117+
-- Performance monitoring — one row per (analysis, stage), independent of the
118+
-- ecosystem-specific pipeline tables above so it keeps working unchanged as
119+
-- more ecosystems (and possibly different stage sets) come online.
120+
CREATE TABLE IF NOT EXISTS blast_radius_stage_runs (
121+
id BIGSERIAL PRIMARY KEY,
122+
analysis_id UUID NOT NULL REFERENCES blast_radius_analyses (id),
123+
stage TEXT NOT NULL CHECK (stage IN ('intel', 'dependents', 'reachability', 'report')),
124+
status TEXT NOT NULL DEFAULT 'running' CHECK (status IN ('running', 'succeeded', 'failed')),
125+
model TEXT, -- agent model used by this stage, NULL for deterministic stages
126+
started_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
127+
completed_at TIMESTAMPTZ,
128+
duration_ms BIGINT,
129+
cost_usd NUMERIC(10, 4) NOT NULL DEFAULT 0,
130+
error TEXT,
131+
UNIQUE (analysis_id, stage)
132+
);
133+
134+
CREATE INDEX IF NOT EXISTS blast_radius_stage_runs_analysis_id_idx
135+
ON blast_radius_stage_runs (analysis_id);

0 commit comments

Comments
 (0)