Skip to content

Commit 8efd5e4

Browse files
refactor: remove redundant pipeline stages (merge_cve + normalize)
Two redundant phases eliminated after verifying output equivalence: - merge_cve.py was a strict subset of merge_crawl_csvs.py (same CSV glob, source->source_platform normalize, md5 id, dedup, append). CVE CSVs share the crawl schema, so they now ride the single merge_crawl_csvs --src-dirs pass. Deleted merge_cve.py (-106 lines). Side benefit: CVE rows now get the same severity normalization as every other source (6 correctly promoted to the authoritative tier instead of defaulting to Info). - The `normalize` stage (inline python heredoc filling stride/cwe) is unnecessary: build_security_dataset now canonicalizes the ''/nan/Other sentinels itself. Verified curate-from-crossref == curate-after-normalize (identical rows + tiers). Stage 8 is now a plain copy of the crossref parquet. Pipeline: 9 stages -> cleaner 8 (merge is one pass; publish is a copy). Curated 1878 -> 1880, tests 7/7 green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 53794bb commit 8efd5e4

7 files changed

Lines changed: 31 additions & 138 deletions

File tree

collection/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ The curated dataset is derived from the raw snapshot **offline** by
1010
| Per-client repo crawl | `crawl_eth_past_fixes.py`, `grep_eth_commits.py`, `mine_eth_releases.py`, `mine_stealth_prs.py`, `mine_direct_pulls.py`, `parse_eth_changelogs.py`, `extract_nimbus_urgency.py` |
1111
| Advisory databases | `crawl_cve.py`, `crawl_osv.py`, `crawl_rustsec.py`, `crawl_govulncheck.py`, `crawl_teku_jira_refs.py` |
1212
| Cross-client / specs | `crawl_cross_client.py`, `crawl_specs_divergence.py` |
13-
| Merge + classify + enrich | `merge_crawl_csvs.py`, `merge_cve.py`, `build_derived.py`, `classify_stride_cwe.py`, `classify_stride_cwe_sdk.py`, `cross_reference.py`, `blame_walk.py` |
13+
| Merge + classify + enrich | `merge_crawl_csvs.py`, `build_derived.py`, `classify_stride_cwe.py`, `classify_stride_cwe_sdk.py`, `cross_reference.py`, `blame_walk.py` |
1414

1515
Collection methodology (per-client security-label taxonomies, a body-keyword path
1616
filter for unlabeled "stealth" fixes, and the rule that a severity from a release

collection/merge_cve.py

Lines changed: 0 additions & 105 deletions
This file was deleted.

collection/run_pipeline.sh

Lines changed: 10 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@
88
# Stage 2 supplementary crawlers -> $WORK/supp/*.csv
99
# Stage 3 advisory DBs (CVE) -> $WORK/cve/<client>.cve.csv
1010
# Stage 4 build_derived (canonical) -> $WORK/derived/ethereum/train.parquet
11-
# Stage 5 merge supp + cve -> train.parquet (in place)
11+
# Stage 5 merge supp + cve (one pass) -> train.parquet (in place)
1212
# Stage 6 cross_reference (dedup) -> train.parquet
1313
# Stage 7 blame_walk (FULL only) -> enrich introduced_in_commit
14-
# Stage 8 normalize stride/cwe -> data/raw/train.classified.parquet
14+
# Stage 8 publish raw snapshot (copy) -> data/raw/train.classified.parquet
1515
# Stage 9 curate -> data/ethereum_vulns.parquet (+manifest)
1616
#
1717
# LLM STRIDE/CWE classification is intentionally skipped (stride=Other, cwe=N/A);
@@ -123,9 +123,10 @@ stage build_derived PY collection/build_derived.py --domain ethereum \
123123
--filter-platforms "" --out-dir "$DERIVED" "${SRC_ARGS[@]}"
124124
[ -f "$TRAIN" ] || { echo "FATAL: $TRAIN not produced"; exit 1; }
125125

126-
# --- Stage 5: merge supplementary + CVE CSVs -------------------------------
127-
stage merge_supp PY collection/merge_crawl_csvs.py --src-dirs "$SUPP" --parquet "$TRAIN" --out "$TRAIN"
128-
stage merge_cve PY collection/merge_cve.py --cve-dir "$CVE" --parquet "$TRAIN" --out "$TRAIN"
126+
# --- Stage 5: merge supplementary + CVE CSVs (one pass) --------------------
127+
# CVE CSVs share the crawl schema, so merge_crawl_csvs ingests them alongside
128+
# the supplementary dir — no separate merge_cve step needed.
129+
stage merge PY collection/merge_crawl_csvs.py --src-dirs "$SUPP" "$CVE" --parquet "$TRAIN" --out "$TRAIN"
129130

130131
# --- Stage 6: cross_reference (de-dup GHSA/PR/CVE) -------------------------
131132
stage cross_ref PY collection/cross_reference.py --in "$TRAIN" --out "$DERIVED/ethereum/train.crossref.parquet" --quiet
@@ -137,19 +138,11 @@ if [ "$MODE" = "full" ] && [ "${SKIP_BLAME:-0}" != "1" ]; then
137138
--manifest "$DERIVED/ethereum/blame_walk_manifest.json"
138139
fi
139140

140-
# --- Stage 8: normalize stride/cwe (classification skipped) ----------------
141+
# --- Stage 8: publish the raw snapshot -------------------------------------
142+
# No stride/cwe munging here: build_security_dataset canonicalizes the
143+
# unclassified sentinels itself, so this is just a copy of the crossref output.
141144
mkdir -p data/raw
142-
stage normalize PY - "$TRAIN" data/raw/train.classified.parquet <<'PYEOF'
143-
import sys, pandas as pd
144-
src, dst = sys.argv[1], sys.argv[2]
145-
df = pd.read_parquet(src)
146-
if "stride" not in df.columns: df["stride"] = "Other"
147-
if "cwe_top25" not in df.columns: df["cwe_top25"] = "N/A"
148-
df["stride"] = df["stride"].fillna("Other").replace("", "Other")
149-
df["cwe_top25"] = df["cwe_top25"].fillna("N/A").replace("", "N/A")
150-
df.to_parquet(dst, index=False)
151-
print(f"normalized {len(df)} rows -> {dst}")
152-
PYEOF
145+
stage publish_raw cp "$TRAIN" data/raw/train.classified.parquet
153146

154147
# --- Stage 9: curate --------------------------------------------------------
155148
stage curate PY pipeline/build_security_dataset.py \

data/ethereum_vulns.parquet

63 Bytes
Binary file not shown.

data/manifest.json

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"domain": "ethereum",
3-
"n_rows": 1878,
3+
"n_rows": 1880,
44
"schema": [
55
"id",
66
"source_platform",
@@ -32,27 +32,27 @@
3232
"t2_noise_dropped": 1417,
3333
"t2b_nvd_fp_dropped": 49,
3434
"after_t1": 16998,
35-
"security_rows": 1878,
36-
"low_signal_dropped": 15120,
35+
"security_rows": 1880,
36+
"low_signal_dropped": 15118,
3737
"by_confidence": {
38-
"medium": 1544,
39-
"high": 333,
38+
"medium": 1542,
39+
"high": 337,
4040
"low": 1
4141
},
4242
"by_authority_tier": {
43-
"B_corroborated": 1134,
43+
"B_corroborated": 1130,
4444
"C_candidate": 511,
45-
"A_authoritative": 233
45+
"A_authoritative": 239
4646
},
4747
"by_n_signals": {
4848
"1": 556,
4949
"2": 907,
50-
"3": 345,
51-
"4": 65,
50+
"3": 343,
51+
"4": 69,
5252
"5": 5
5353
},
5454
"by_source": {
55-
"geth": 374,
55+
"geth": 376,
5656
"erigon": 358,
5757
"nimbus": 198,
5858
"lodestar": 194,
@@ -66,20 +66,20 @@
6666
"consensus-specs": 2
6767
},
6868
"by_severity": {
69-
"Unrated": 964,
69+
"Unrated": 960,
7070
"Info": 773,
7171
"High": 63,
72-
"Medium": 54,
72+
"Medium": 60,
7373
"Low": 21,
7474
"Critical": 3
7575
},
7676
"by_score": {
7777
"0.0": 35,
7878
"0.3": 4,
7979
"0.5": 1208,
80-
"0.8": 298,
81-
"0.9": 172,
82-
"1.0": 161
80+
"0.8": 296,
81+
"0.9": 170,
82+
"1.0": 167
8383
},
8484
"residual_boilerplate_fp": 0
8585
},

data/raw/train.classified.parquet

-402 Bytes
Binary file not shown.

pipeline/build_security_dataset.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,11 @@ def build(df: pd.DataFrame) -> tuple[pd.DataFrame, dict]:
282282
for col in ("title", "description", "severity", "stride", "cwe_top25", "source_platform"):
283283
if col not in df.columns:
284284
df[col] = ""
285+
# Canonicalize the "unclassified" sentinels once, so the gate/signal logic
286+
# can treat NaN / "" / "nan" identically to Other/N/A. (Makes the former
287+
# external `normalize` collection step unnecessary — see run_pipeline.sh.)
288+
df["stride"] = df["stride"].fillna("Other").replace({"": "Other", "nan": "Other"})
289+
df["cwe_top25"] = df["cwe_top25"].fillna("N/A").replace({"": "N/A", "nan": "N/A"})
285290
blob = df["title"].fillna("").astype(str) + " " + df["description"].fillna("").astype(str)
286291

287292
# T1

0 commit comments

Comments
 (0)