Skip to content

week_1-3: Module C — real 319-row golden dataset from OpenCRE DB#4

Open
PRAteek-singHWY wants to merge 2 commits into
gsocmodule_C_week_1_2from
gsocmodule_C_week_1_3
Open

week_1-3: Module C — real 319-row golden dataset from OpenCRE DB#4
PRAteek-singHWY wants to merge 2 commits into
gsocmodule_C_week_1_2from
gsocmodule_C_week_1_3

Conversation

@PRAteek-singHWY

Copy link
Copy Markdown
Owner

What this PR is

The third and final PR of Module C, Week 1. It replaces the 10-row seed dataset from week_1-2 with the real golden dataset — 319 rows derived from standards_cache.sqlite (the OpenCRE graph the team already curated). This is the actual ruler every later C PR will be measured against in CI.

Stacked on gsocmodule_C_week_1_2. When PR 2 merges, GitHub re-targets this to main.

Why this PR is the most important one in Week 1

W1 PR 1 nailed the contracts. W1 PR 2 nailed the harness scaffolding. But neither has a real measurement. The 10-row seed in PR 2 only proved the wiring; with this PR landed, every later C PR is measured against 319 real rows with real DB-grounded ground truth, and an accuracy drop > 2pts blocks the PR. That's the W1 promise from your golden-dataset-plan.md: "It makes a mapping change behave like a code change: versioned, measured, and revertible."

What's in this PR, step by step

1. The build script — scripts/build_golden_dataset.py

A deterministic derivation from standards_cache.sqlite:

  • Joins node → cre_node_links → cre to pull ground-truth labels. The OpenCRE graph is the ground truth — per golden-dataset-plan.md §3, "Ground truth = the existing OpenCRE DB mappings."
  • Every query has an explicit ORDER BY so output is byte-stable.
  • --check mode re-derives and exits non-zero if the committed JSON has drifted from what the DB now produces. CI runs this to guarantee the JSON tracks live mappings.
  • All curated rows (explicit / update) pin a real ASVS section_id and fetch the real CRE external_id at build time — text is synthesized but ground truth stays real.

2. The dataset — application/tests/librarian/fixtures/golden_dataset.json

319 rows total, by slice:

Slice Count Source
positive (1:1) 277 All 277 ASVS requirements + their single mapped CRE (DB-grounded)
positive (multi-link) 15 OWASP Top 10 2021 + CWE nodes with 2–4 CRE mappings — exercises the Q-D rule with real data
hard_negative 12 ASVS reqs whose text contains do not / does not / shall not / should not, with real DB CRE (cross-encoder must beat cosine without losing the right CRE)
explicit 5 Synthesized text that literally cites a real cre.external_id
update 5 Synthesized before/after rewordings of real ASVS reqs, real CRE id
ambiguous 5 Broad SDLC / governance / culture statements; decision=review, reason_code=BELOW_THRESHOLD / NO_CANDIDATES

Every row carries an honest provenance.ground_truth_source recording whether the ground truth came from the DB (most rows) or was manually synthesized (the broad ambiguous slice).

3. The validation + determinism tests — application/tests/librarian/dataset_test.py

What CI now guarantees, on every PR:

  • Every row validates against both GoldenDatasetRow (Pydantic) and golden_dataset.schema.json (jsonschema).
  • At least 277 positive rows are present (the master-guide §9.3 hard floor).
  • All 5 slices are present and each has ≥5 rows so the harness can stratify.
  • At least one multi-link positive row exists — otherwise the Q-D rule has nothing to operate on and is dead code.
  • Every row carries provenance.ground_truth_source.
  • All row ids are unique.
  • Determinism: build_golden_dataset.py --check against the committed JSON exits 0. (Auto-skipped if the DB file is absent.)

4. Multi-link rule consistency

Per your proposal's Q-D rule (scoring.py from week_1-2):

Jaccard(expected, predicted) ≥ 0.5 AND top-1 in expected.

The dataset honours this by including 15 genuine multi-link positive rows from OWASP Top 10 and CWE, where the expected cre_ids list has 2–4 entries pulled straight from the DB. The hard floor is set so that this rule has real data to exercise, not just a single hand-crafted multi-link example.

How to verify locally

# all librarian tests pass on the real 319-row dataset
python3 -m unittest discover -s application/tests/librarian -p '*_test.py' -t .
# expected: 48 passed

# determinism — re-derive and prove no drift
python3 scripts/build_golden_dataset.py --check
# expected: "OK: golden dataset matches derivation (319 rows)"

# harness runs end-to-end on the real dataset
python3 scripts/evaluate_librarian.py --dataset application/tests/librarian/fixtures/golden_dataset.json
# expected:
#   loaded 319 golden rows from .../golden_dataset.json
#     ambiguous      5
#     explicit       5
#     hard_negative  12
#     positive       292
#     update         5
#   hub-firewall: ON; stripped 319 leaking hub entries
#   correct (W1 stub, no predictions): 5/319

# regenerate the dataset (idempotent against the same DB)
python3 scripts/build_golden_dataset.py

The 319 stripped leaking hub entries is exactly the point of the firewall — the W1 stub hub is seeded from the rows' own text, so without the firewall every row would leak. 5/319 correct is the W1 baseline: the 5 ambiguous rows have no expected cre_ids so empty predictions match; the 314 linked-decision rows correctly show as not-yet-predicted (real predictions arrive when the pipeline lands W3–W6).

What this PR does NOT claim

  • Does not re-validate or republish the locked pre-code +33.3% / +28.6% negative top-1 numbers. Those are your separate locked experiment; this is the ongoing CI ruler.
  • Does not include any retriever / ranker / embedding / linking code — that's W3 onwards.
  • Does not modify any RFC-side schema. Internal GoldenDatasetRow was nailed down in week_1-1.

Why some slices are smaller than others

  • WSTG and most other standards have empty description fields in the DB — no usable text. That's why multi-link rows come from OWASP Top 10 + CWE (the standards with both multi-CRE mappings and real text), not WSTG.
  • ASVS uses positive phrasing ("Verify that X is not Y") rather than imperative negation ("Do not Y"), so the 12 negation hits found are exactly the rows that legitimately matched. Expanding this slice synthetically would dilute the signal — better 12 real rows than 30 fabricated ones.

That closes Week 1: contracts (PR 1) → harness (PR 2) → real ruler (PR 3). Mentor confirmation of Q-D at the Friday call unblocks merging PR 2 and the multi-link reasoning in this PR. After that, week 2 starts the deterministic stage — the SectionNormalizer (RFC contract → internal Section, no ML yet).

PRAteek-singHWY and others added 2 commits May 30, 2026 19:31
Replaces the 10-row seed fixture with the real DB-derived golden set, sized
to clear the master-guide §9.3 / golden-dataset-plan §3 bar of "at least the
277 ASVS-core". Final breakdown:

  positive       292 (277 ASVS 1:1 + 15 multi-link from OWASP Top 10 + CWE)
  hard_negative   12 (ASVS reqs with "do not / does not / shall not /
                     should not" phrasing, real DB CRE; this is the
                     cross-encoder vs cosine negation case)
  explicit         5 (synthesized text literally citing real cre.external_id)
  update           5 (synthesized before/after of real ASVS reqs, real CRE)
  ambiguous        5 (broad SDLC/governance statements, decision=review)

Ground truth comes from standards_cache.sqlite via the join
node -> cre_node_links -> cre, so labels are the OpenCRE graph the team
already curated. Multi-link rows exercise the Q-D rule (Jaccard >= 0.5 of
expected CRE set AND top-1 in set) with real data.

Adds scripts/build_golden_dataset.py to make the derivation reproducible.
The script's --check mode re-derives from the DB and exits non-zero on any
drift, so CI can guarantee the committed JSON tracks the live mappings.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds dataset_test.py with:

- Every row validates against both the Pydantic GoldenDatasetRow and the
  canonical gsoc-notes/bonding/golden_dataset.schema.json (jsonschema).
- At least 277 positive rows derived from the OpenCRE DB are present
  (the master guide's hard floor).
- All 5 slices are present and each has >=5 rows so the harness stratifies.
- At least one multi-link positive row exists so the Q-D rule has data to
  operate on.
- Every row carries provenance.ground_truth_source (no anonymous rows).
- All row ids are unique.
- Determinism: running build_golden_dataset.py --check against the
  committed JSON exits 0 (skipped if standards_cache.sqlite is absent).

48/48 librarian tests green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
PRAteek-singHWY pushed a commit that referenced this pull request Jun 25, 2026
OWASP#928)

* feat(module-b): add Pydantic v2 schemas + hashing for Module A input contract

Establishes the data contract Module B consumes from Module A. ChangeRecord
is a Pydantic v2 model matching A's actual emission shape: nested source
(discriminated union on type for github/rss), span (chunk position +
heading_path + char/line offsets), and locator (addressing scheme). Internal
models ClassifyResult and QueuePayload prep for later stages.

hashing.py provides normalize_text + compute_content_hash since Module A
does not emit content_hash; B computes its own (SHA-256 of normalized text)
for use as the knowledge_queue dedup key.

22 unittest cases cover the round-trip, the discriminated union, hash
determinism, normalization rules, code-fence preservation, and idempotency.
Full make test: 271 passing, no regressions.

Part of GSoC 2026 OpenCRE Scraper & Indexer (Project OIE) Module B.

* feat(module-b): add Module A mock fixture + generated JSON Schema artifact

module_a_mock.jsonl: Module A's canonical 20-record mock shared 2026-05-29,
saved as JSONL (one record per line per the contract). Becomes a permanent
integration-test fixture for B's parser and a reference shape for the
Module A contributor.

module_a_contract.schema.json: JSON Schema generated from B's Pydantic
ChangeRecord model via model_json_schema(). 246 lines covering all four
nested types (ChangeRecord, GithubSource, RssSource, Span, Locator).
Source of truth for cross-module CI validation.

Part of GSoC 2026 OpenCRE Scraper & Indexer (Project OIE) Module B.

* feat(module-b): add OWASP harvester, labeling TUI, and labeled dataset

build_labeled_dataset.py: PyGithub-based harvester that acts as Module A's
stand-in for producing benchmark data. Fetches recent commits from 4 OWASP
repos (WSTG, ASVS, CheatSheetSeries, SAMM), applies the contract's
normalization rules, splits into chunks at markdown heading boundaries
with a fence-aware stack-based walker that tracks heading_path + char/line
offsets, and emits records in Module A's actual nested shape. Pluggable
via GITHUB_TOKEN env var. Reproducible: python scripts/build_labeled_dataset.py
regenerates the candidate set.

label_dataset.py: resumable interactive TUI for manual classification.
Atomic-writes labeled_data.json after every keystroke; lookup by chunk_id
for resume. Embeds the recall-first definition (agreed with maintainer
2026-06-01) so labelers see the rule front-of-mind: KNOWLEDGE for any
chunk with security signal, NOISE only for pure organizational content.

candidate_commits.json: 100 records, 25 per repo, all Pydantic-valid
against ChangeRecord. 90/100 have non-empty heading_path; 10 multi-chunk
artifacts captured.

labeled_data.json: 100/100 labeled by hand under the recall-first rule.
Distribution 55 KNOWLEDGE / 40 NOISE / 5 UNCERTAIN. Per-repo skew is
visible: CheatSheetSeries 92% K, SAMM 0% K (the SAMM commits sampled
landed entirely on Website/Sponsorship/meetings paths -- empirical input
for Week 2's noise_patterns.yaml).

Part of GSoC 2026 OpenCRE Scraper & Indexer (Project OIE) Module B.

* style(module-b): apply Black formatting to Week 1 files

Super-Linter (Black 24.4.2) flagged 4 files in the previous push.
Applied `black` (same pinned version) to bring them in line with the
repo's formatting standard. Cosmetic changes only: blank lines around
section-separator comments, one multi-line dict join. No behavior or
test changes -- `make test` remains 271 passing, 1 skip.

* chore(module-b): address CodeRabbit Week 1 review comments

- Sort __all__ lists in hashing.py and schemas.py to satisfy
  Ruff RUF022.
- Declare JSON Schema dialect ($schema = draft 2020-12, which is
  what Pydantic v2 model_json_schema() emits) on the contract artifact.
- Wrap load_labeled() in scripts/label_dataset.py with try/except so a
  corrupted labeled_data.json prints an actionable hint instead of a
  raw JSONDecodeError stack trace.

Deferred to Week 2 (will be addressed when we touch the harvester):
- chunker should also track <pre> open/close, not just ``` fences
- _split_chunk_by_size cursor arithmetic assumes \\n\\n separator even
  on hard-split sub-chunks

Tests: 271 passing, 1 skip (unchanged). Black: clean.

* feat(module-b): add Stage 1.5 sanitize.py vendored from TRACT

Defensive text cleanup (PDF ligatures, zero-width chars, HTML, hyphenation).
Vendored from rocklambros/TRACT under CC0; drops their whitespace-collapse
step so structure (newlines, paragraphs) is preserved for Module B's LLM.

26 unit tests, all passing.

* feat(module-b): add Stage 1 regex_filter + noise_patterns.yaml

Path-based filter with extension/filename/glob deny rules and allow_overrides.
Patterns are deliberately conservative under the recall-first labeling rule.

15 unit tests including >=90% rejection / 0% false-positive acceptance criteria.

* fix(module-b): chunker tracks <pre> blocks; correct hard-split cursor math

Addresses CodeRabbit comments #4 and OWASP#5 on the Week 1 PR.

* chore(module-b): address CodeRabbit Week 2 review comments

* chore(module-b): address Week 2 maintainer review on noise_patterns.yaml

---------

Signed-off-by: Manshu Saini <149303743+manshusainishab@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant