Skip to content

week_4: Module C (The Librarian) — C.2 cross-encoder reranker#957

Merged
northdpole merged 5 commits into
OWASP:mainfrom
PRAteek-singHWY:gsocmodule_C_week_4
Jul 9, 2026
Merged

week_4: Module C (The Librarian) — C.2 cross-encoder reranker#957
northdpole merged 5 commits into
OWASP:mainfrom
PRAteek-singHWY:gsocmodule_C_week_4

Conversation

@PRAteek-singHWY

@PRAteek-singHWY PRAteek-singHWY commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Hi @northdpole — Week 4 of Module C. This one adds the cross-encoder that re-reads the shortlist C.1 produced.

Stacked on #937 (Week 3). Based on gsocmodule_C_week_3, so until #922#925#937 land the diff shows the earlier commits too — I'll rebase onto main as they merge, shrinking it to just the W4 files.

The problem it fixes

Week 3's search is fast but rough. The bi-encoder fingerprints the section and each CRE separately, then compares them — great for narrowing hundreds of CREs down to 20, but it never actually reads a section and a CRE together, so the exact ordering inside those 20 is unreliable. The real answer might be sitting at #7, not #1.

What this does

Takes those 20 candidates and reads each one side-by-side with the section as a single combined input, and scores "do these two actually match?" — then re-sorts the 20 and keeps the best 5. It fills the reranked[] slot we deliberately left empty in Week 3, and leaves candidates[] untouched so the pre-rerank shortlist stays auditable.

Two model kinds, one line: the bi-encoder (W3) fingerprints each thing alone — fast, whole-hub, rough. The cross-encoder (W4) reads the pair together — slow, so we only run it on the 20, but much more accurate. W3 skims 20 résumés to make a shortlist; W4 interviews those 20 one-on-one to pick the top 5.

What changed

  • cross_encoder.py (new)CrossEncoderReranker over an injected score_fn, the same DI pattern as C.1's embed_fn, so the module never imports torch and stays hermetically testable. build_cross_encoder_score_fn lazily loads the pinned ms-marco-MiniLM-L-6-v2. Stable sort so ties keep C.1's cosine order; typed errors; a RERANKER_NAME audit tag.
  • cross_encoder_test.py (new) — 9 hermetic tests (re-ordering by cross-encoder score, top-N truncation, tie stability, audit shape, and the missing-text / score-count failure modes).
  • db.pyget_embedding_contents_by_doc_type: the {id → embeddings_content} pair text the reranker scores against (mirrors get_embeddings_by_doc_type).
  • cre_main.pyrun_librarian now reranks C.1's audit and logs the reranked top-N per section.
  • evaluate_librarian.py — v1 full pipeline; reports the live rerank top-1 alongside recall@k. Offline path unchanged.
  • requirements.txt — add sentence-transformers; __init__.py scope note now covers C.2.

Results (positive slice, live)

retrieval recall@20 (C.1): any-hit 285/292 (98%), all-hit 274/292 (94%)
rerank top-1     (C.2): 220/292 (75%)   (target >= 0.80)

Measured live against a populated standards_cache.sqlite (428 CRE embeddings, gemini/gemini-embedding-001, dim 3072), hub-firewall ON, top_n_rerank=5.

Reading it: the retriever puts the right CRE in the top-20 98% of the time, so the shortlist the reranker sees is almost always complete. The cross-encoder then lands the correct CRE at #1 for 75% of rows — solid, but 5 points under the 0.80 target. The gap is the honest state at W4: the reranker sometimes demotes a correct near-tie. Calibration (W5) + the threshold experiment (W7) are the levers to close it; the target is a W6/W8 gate, not a W4 merge blocker. The reranker logic itself is fully covered offline by the hermetic tests above.

Not here (later weeks)

  • Calibration + the confident yes/no decision (C.3–C.4, W5–W6).
  • Graph writes / wiring into the worker (W8) — still dry-run only; the CLI stays opt-in and only costs the embedding API on manual runs.

@coderabbitai

coderabbitai Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: b20aedf2-58d2-4af1-a1fc-aa906d55577e

📥 Commits

Reviewing files that changed from the base of the PR and between 31b6145 and ea53658.

📒 Files selected for processing (13)
  • application/cmd/cre_main.py
  • application/database/db.py
  • application/tests/librarian/config_loader_test.py
  • application/tests/librarian/cross_encoder_test.py
  • application/utils/librarian/__init__.py
  • application/utils/librarian/candidate_retriever.py
  • application/utils/librarian/cross_encoder.py
  • application/utils/librarian/knowledge_source.py
  • application/utils/librarian/schemas.py
  • application/utils/librarian/section_validator.py
  • requirements.txt
  • scripts/build_golden_dataset.py
  • scripts/evaluate_librarian.py
✅ Files skipped from review due to trivial changes (2)
  • application/utils/librarian/init.py
  • application/tests/librarian/config_loader_test.py
🚧 Files skipped from review as they are similar to previous changes (5)
  • requirements.txt
  • application/database/db.py
  • application/utils/librarian/candidate_retriever.py
  • application/tests/librarian/cross_encoder_test.py
  • application/utils/librarian/cross_encoder.py

Summary by CodeRabbit

  • New Features

    • Added an optional librarian workflow with semantic retrieval and reranking for improved result ordering.
    • Introduced live evaluation support that reports both retrieval recall and rerank top-1 accuracy.
    • Added support for cross-encoder-based reranking with stable handling of tied scores.
  • Bug Fixes

    • Improved ordering consistency when multiple results have the same similarity score.
    • Better handling of malformed input and ambiguous matches, avoiding silent incorrect selections.

Walkthrough

This PR adds a cross-encoder reranker (C.2) to the Librarian pipeline, integrating it into candidate retrieval, the CLI's dry-run pipeline, and the evaluation harness with rerank top-1 metrics. It also refactors schema-version validation and Pydantic coercion helpers, adds a database embedding-lookup method, and updates dependency pins.

Changes

Librarian cross-encoder reranking pipeline

Layer / File(s) Summary
Embedding lookup and stable retrieval ordering
application/database/db.py, application/utils/librarian/candidate_retriever.py
Adds get_embedding_contents_by_doc_type returning {id -> embeddings_content} and switches top-K selection to a stable descending sort for deterministic tie ordering.
Cross-encoder reranker implementation and tests
application/utils/librarian/cross_encoder.py, application/utils/librarian/__init__.py, application/tests/librarian/cross_encoder_test.py
Adds CrossEncoderReranker, RerankFn, error types, and build_cross_encoder_score_fn; updates scope docstring; adds hermetic unit tests for ordering, truncation, ties, and failure modes.
CLI librarian run wired to reranker
application/cmd/cre_main.py
Constructs the reranker from CRE embedding contents and reworks the semantic path to call retrieve() then rerank(), logging score_rerank shortlist details, with rejection on failure.
Schema and validation refactors
application/utils/librarian/schemas.py, application/utils/librarian/section_validator.py, application/utils/librarian/knowledge_source.py, application/tests/librarian/config_loader_test.py, requirements.txt
Centralizes schema_version validation via _require_schema_version, adds generic _validate_or_raise helper, logs structured validation errors, types a test class variable, and pins pydantic/adds sentence-transformers.
Evaluation harness rerank metrics and dataset ambiguity check
scripts/evaluate_librarian.py, scripts/build_golden_dataset.py
Extends report_retrieval_recall with reranking and top-1 hit tracking, removes --dry_run, updates help text; changes _fetch_asvs_cre to raise on ambiguous ASVS-to-CRE mappings.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Possibly related issues

Suggested reviewers: Pa04rth

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 42.11% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately names the main change: the Week 4 Module C.2 cross-encoder reranker.
Description check ✅ Passed The description is directly about the C.2 cross-encoder reranker and its supporting changes.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (7)
application/utils/librarian/cross_encoder.py (1)

101-104: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Add strict=True to zip() per static analysis hint.

Ruff flags this zip() call (B905). Lengths are already validated above, so this is purely defensive/lint hygiene.

🔧 Proposed fix
         reranked = [
             c.model_copy(update={"score_rerank": float(s)})
-            for c, s in zip(candidates, scores)
+            for c, s in zip(candidates, scores, strict=True)
         ]
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@application/utils/librarian/cross_encoder.py` around lines 101 - 104, The
reranking comprehension in cross_encoder.py uses zip() without an explicit
strict setting, which Ruff flags with B905. Update the zip(candidates, scores)
call inside the reranked list construction to use strict=True, keeping the
existing length validation intact, so the CrossEncoder rerank path remains
lint-clean and defensive.

Source: Linters/SAST tools

application/utils/librarian/candidate_retriever.py (1)

156-160: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Non-stable tie-breaking in top-K selection.

np.argsort defaults to quicksort (not stable), so cosine ties can be ordered arbitrarily/nondeterministically between runs. This directly contradicts the reproducibility goal the audit trail exists for, and is inconsistent with cross_encoder.py's explicit stable-sort guarantee for reranked[].

🔧 Proposed fix
-        top_idx = np.argsort(scores)[-k:][::-1]
+        top_idx = np.argsort(scores, kind="stable")[-k:][::-1]
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@application/utils/librarian/candidate_retriever.py` around lines 156 - 160,
The top-K selection in candidate_retriever.py is using np.argsort without a
stable sort, so tied cosine scores can return in a nondeterministic order.
Update the ranking logic around the query cosine_similarity and top_idx
computation to use a stable descending sort, and keep the existing k cap
behavior. Make the tie-breaking deterministic in the same spirit as
cross_encoder.py’s stable reranked[] handling so repeated runs produce the same
CRE ordering.
application/utils/librarian/section_validator.py (1)

110-114: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Minor duplication in the validate-and-wrap pattern.

Both entry points repeat the same isinstance check + model_validate + except ValidationError → MalformedKnowledgeItemError block. Could be factored into a small private helper (e.g. _validate_or_raise(model_cls, raw)), but with only two call sites the current duplication is minor.

Also applies to: 153-157

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@application/utils/librarian/section_validator.py` around lines 110 - 114, The
same validate-and-wrap pattern is duplicated in the section validator flow,
where raw items are checked with isinstance and then passed through
model_validate with ValidationError converted to MalformedKnowledgeItemError.
Refactor this shared logic in the relevant validator methods, likely around the
KnowledgeQueueItem handling and the other call site mentioned in the comment,
into a small private helper such as _validate_or_raise(model_cls, raw), and
update both entry points to call it.
scripts/evaluate_librarian.py (1)

178-207: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

--dry_run flag is parsed but never used.

args.dry_run (Line 187-189) is defined but not referenced anywhere else in main(). The harness never writes regardless of this flag (per the docstring: "no writes (always true pre-W8)"), so the flag is currently a no-op that may mislead users into thinking it toggles behavior.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/evaluate_librarian.py` around lines 178 - 207, The --dry_run option
in main() is currently a no-op, so either wire args.dry_run into the harness
flow where writes would occur or remove the flag and its help text if dry-run is
permanently always enabled. Update the argument parsing in
evaluate_librarian.main and any downstream write path to consistently respect
this flag, using args.dry_run as the deciding signal.
application/tests/librarian/config_loader_test.py (1)

30-39: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Annotate OVERRIDES as ClassVar to satisfy Ruff RUF012.

Static analysis flags the mutable dict class attribute. Since it's never mutated, adding a ClassVar annotation resolves the lint warning.

🧹 Proposed fix
+from typing import ClassVar
+
 class TestConfigLoaderOverrides(unittest.TestCase):
-    OVERRIDES = {
+    OVERRIDES: ClassVar[dict] = {
         "CRE_LIBRARIAN_RETRIEVER_BACKEND": "pgvector",
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@application/tests/librarian/config_loader_test.py` around lines 30 - 39, The
class attribute OVERRIDES in config_loader_test is a mutable dict that Ruff
flags as a non-instance field; annotate it as ClassVar to make its intent
explicit. Update the test class definition where OVERRIDES is declared so the
attribute is marked as a ClassVar while keeping the existing constant values
unchanged, which will satisfy RUF012 and clarify it is not meant to be mutated
per instance.

Source: Linters/SAST tools

requirements.txt (1)

95-95: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Pin sentence-transformers to a tested release range. The Module C.2 cross-encoder depends on it directly, and leaving it unpinned makes installs non-reproducible and leaves you open to breaking transformers/torch upgrades.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@requirements.txt` at line 95, The dependency entry for sentence-transformers
is unpinned, which makes installs non-reproducible and can break the Module C.2
cross-encoder path. Update the requirements list to constrain
sentence-transformers to a tested version range that is compatible with the
existing transformers and torch stack, and keep the dependency definition in the
same requirements entry so future installs resolve consistently.
application/utils/librarian/schemas.py (1)

213-221: 📐 Maintainability & Code Quality | 🔵 Trivial

Duplicated schema_version pattern validation across three envelopes.

KnowledgeItem._rfc_rules, LinkProposal._schema_version_pattern, and ReviewItem._schema_version_pattern all independently re-check _SCHEMA_VERSION_RE.match(self.schema_version). Consider extracting a shared mixin/base class with this validator to avoid drift if the pattern or error message changes later.

♻️ Proposed refactor
+class _SchemaVersioned(BaseModel):
+    schema_version: str
+
+    `@model_validator`(mode="after")
+    def _schema_version_pattern(self):
+        if not _SCHEMA_VERSION_RE.match(self.schema_version):
+            raise ValueError(r"schema_version must match ^0\.\d+\.\d+$")
+        return self
+
+
-class KnowledgeItem(BaseModel):
+class KnowledgeItem(_SchemaVersioned):
     ...
-    schema_version: str
     ...
-class LinkProposal(BaseModel):
+class LinkProposal(_SchemaVersioned):
     ...
-    schema_version: str
     ...
-class ReviewItem(BaseModel):
+class ReviewItem(_SchemaVersioned):
     ...
-    schema_version: str

Also applies to: 239-243, 263-267

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@application/utils/librarian/schemas.py` around lines 213 - 221, The
schema_version regex check is duplicated in KnowledgeItem._rfc_rules,
LinkProposal._schema_version_pattern, and ReviewItem._schema_version_pattern, so
extract that validation into a shared base class or mixin and have all three
envelopes reuse it. Keep the existing status-specific checks in KnowledgeItem,
but centralize the schema_version match and error message to avoid drift and
make future changes consistent.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@application/cmd/cre_main.py`:
- Around line 1078-1088: In cre_main.py, the pgvector backend check in the
retriever setup only emits a warning when the database dialect is not
postgresql, but this mismatch should fail fast; update the backend selection
logic around RetrieverBackend.pgvector to raise a clear configuration error
immediately after detecting the non-Postgres dialect, rather than continuing to
build the retriever and proceed into the batch. Use the existing mismatch
handling style from build_retriever and make the error message explicit about
pgvector requiring Postgres with the embedding_vec column.
- Around line 1022-1047: Update the run_librarian docstring so it matches the
implementation: it currently says C.2 cross-encoder rerank is not built yet, but
the function creates CrossEncoderReranker and calls rerank. Revise the pipeline
description to mention reranking is implemented, and keep the Ops note accurate
about manual CLI use and embedding API cost.
- Around line 1137-1149: The semantic retrieval/rerank block in the section loop
is not protected by the existing section-level guard, so exceptions from
retriever.retrieve() or reranker.rerank() can abort the whole dry-run. Wrap this
path in the same per-section try/catch used elsewhere in cre_main.py, using the
local section processing around semantic, audit, and logger.info. On failure,
log a warning with the section identifier and error details, then mark the
section as rejected/skipped so later sections and the summary still run.

In `@application/utils/librarian/knowledge_source.py`:
- Around line 39-47: The warning in the knowledge source parsing path is logging
the full ValidationError, which can expose raw row contents. Update the
exception handling in the KnowledgeQueueItem.model_validate_json block to avoid
passing exc directly to logger.warning; instead log
exc.errors(include_input=False) or only the error locations/types so malformed
data fields are not emitted.

In `@scripts/build_golden_dataset.py`:
- Around line 197-210: The `_fetch_asvs_cre` helper is currently masking
ambiguous mappings by using `ORDER BY c.external_id` with `LIMIT 1`, so it can
silently return the wrong CRE when a section maps to multiple entries. Update
`_fetch_asvs_cre` in `build_golden_dataset.py` to detect when `section_id`
resolves to more than one CRE and raise a `ValueError` instead of picking the
first result, matching the fail-loud behavior already used by `build_explicit`
and `build_update` when `cre` is missing.

---

Nitpick comments:
In `@application/tests/librarian/config_loader_test.py`:
- Around line 30-39: The class attribute OVERRIDES in config_loader_test is a
mutable dict that Ruff flags as a non-instance field; annotate it as ClassVar to
make its intent explicit. Update the test class definition where OVERRIDES is
declared so the attribute is marked as a ClassVar while keeping the existing
constant values unchanged, which will satisfy RUF012 and clarify it is not meant
to be mutated per instance.

In `@application/utils/librarian/candidate_retriever.py`:
- Around line 156-160: The top-K selection in candidate_retriever.py is using
np.argsort without a stable sort, so tied cosine scores can return in a
nondeterministic order. Update the ranking logic around the query
cosine_similarity and top_idx computation to use a stable descending sort, and
keep the existing k cap behavior. Make the tie-breaking deterministic in the
same spirit as cross_encoder.py’s stable reranked[] handling so repeated runs
produce the same CRE ordering.

In `@application/utils/librarian/cross_encoder.py`:
- Around line 101-104: The reranking comprehension in cross_encoder.py uses
zip() without an explicit strict setting, which Ruff flags with B905. Update the
zip(candidates, scores) call inside the reranked list construction to use
strict=True, keeping the existing length validation intact, so the CrossEncoder
rerank path remains lint-clean and defensive.

In `@application/utils/librarian/schemas.py`:
- Around line 213-221: The schema_version regex check is duplicated in
KnowledgeItem._rfc_rules, LinkProposal._schema_version_pattern, and
ReviewItem._schema_version_pattern, so extract that validation into a shared
base class or mixin and have all three envelopes reuse it. Keep the existing
status-specific checks in KnowledgeItem, but centralize the schema_version match
and error message to avoid drift and make future changes consistent.

In `@application/utils/librarian/section_validator.py`:
- Around line 110-114: The same validate-and-wrap pattern is duplicated in the
section validator flow, where raw items are checked with isinstance and then
passed through model_validate with ValidationError converted to
MalformedKnowledgeItemError. Refactor this shared logic in the relevant
validator methods, likely around the KnowledgeQueueItem handling and the other
call site mentioned in the comment, into a small private helper such as
_validate_or_raise(model_cls, raw), and update both entry points to call it.

In `@requirements.txt`:
- Line 95: The dependency entry for sentence-transformers is unpinned, which
makes installs non-reproducible and can break the Module C.2 cross-encoder path.
Update the requirements list to constrain sentence-transformers to a tested
version range that is compatible with the existing transformers and torch stack,
and keep the dependency definition in the same requirements entry so future
installs resolve consistently.

In `@scripts/evaluate_librarian.py`:
- Around line 178-207: The --dry_run option in main() is currently a no-op, so
either wire args.dry_run into the harness flow where writes would occur or
remove the flag and its help text if dry-run is permanently always enabled.
Update the argument parsing in evaluate_librarian.main and any downstream write
path to consistently respect this flag, using args.dry_run as the deciding
signal.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: 0a523fa1-7511-47d6-8c56-2cf37ae03123

📥 Commits

Reviewing files that changed from the base of the PR and between 0e16c2e and aabd1d0.

📒 Files selected for processing (37)
  • .env.example
  • application/cmd/cre_main.py
  • application/database/db.py
  • application/tests/librarian/__init__.py
  • application/tests/librarian/candidate_retriever_test.py
  • application/tests/librarian/config_loader_test.py
  • application/tests/librarian/cross_encoder_test.py
  • application/tests/librarian/dataset_test.py
  • application/tests/librarian/explicit_link_resolver_test.py
  • application/tests/librarian/fixtures/golden_dataset.json
  • application/tests/librarian/fixtures/golden_dataset.schema.json
  • application/tests/librarian/fixtures/sample_knowledge_queue.jsonl
  • application/tests/librarian/hub_firewall_test.py
  • application/tests/librarian/schemas_test.py
  • application/tests/librarian/scoring_test.py
  • application/tests/librarian/section_validator_test.py
  • application/utils/librarian/__init__.py
  • application/utils/librarian/_rfc_schemas/knowledge-item.json
  • application/utils/librarian/_rfc_schemas/link-proposal.json
  • application/utils/librarian/_rfc_schemas/locator.json
  • application/utils/librarian/_rfc_schemas/proposed-link.json
  • application/utils/librarian/_rfc_schemas/review-item.json
  • application/utils/librarian/_rfc_schemas/source-ref.json
  • application/utils/librarian/candidate_retriever.py
  • application/utils/librarian/config_loader.py
  • application/utils/librarian/cross_encoder.py
  • application/utils/librarian/explicit_link_resolver.py
  • application/utils/librarian/hub_firewall.py
  • application/utils/librarian/knowledge_source.py
  • application/utils/librarian/schemas.py
  • application/utils/librarian/scoring.py
  • application/utils/librarian/section_validator.py
  • cre.py
  • requirements.txt
  • scripts/benchmark_retriever.py
  • scripts/build_golden_dataset.py
  • scripts/evaluate_librarian.py

Comment thread application/cmd/cre_main.py
Comment thread application/cmd/cre_main.py
Comment thread application/cmd/cre_main.py
Comment thread application/utils/librarian/knowledge_source.py
Comment thread scripts/build_golden_dataset.py Outdated
PRAteek-singHWY added a commit to PRAteek-singHWY/OpenCRE that referenced this pull request Jul 3, 2026
- run_librarian: fix stale docstring (C.2 rerank is built), guard the
  semantic retrieve/rerank per-section so one bad section can't abort the
  dry-run batch
- knowledge_source: log ValidationError.errors(include_input=False), never
  the raw queue row (no content leak)
- build_golden_dataset: fail loud on ambiguous ASVS->CRE mappings instead of
  silently picking one (matches build_explicit/build_update)
- candidate_retriever: stable descending sort so tied cosine scores are
  deterministic
- cross_encoder: zip(..., strict=True) (B905)
- config_loader_test: annotate OVERRIDES as ClassVar (RUF012)
- schemas: centralize the schema_version check across the three envelopes
- section_validator: extract _validate_or_raise helper for both call sites
- requirements: pin sentence-transformers>=5.0,<6.0 (tested with 5.6)
- evaluate_librarian: drop the no-op --dry_run flag (harness never writes)

@northdpole northdpole left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Strong W4 — cross-encoder reranker integrates cleanly with C.1 shortlist, dry-run CLI path is well-scoped (no writes until W8), and config/tests cover the new surface. Ready to merge after #925 and #937 land.

@PRAteek-singHWY

PRAteek-singHWY commented Jul 9, 2026

Copy link
Copy Markdown
Contributor Author

Strong W4 — cross-encoder reranker integrates cleanly with C.1 shortlist, dry-run CLI path is well-scoped (no writes until W8), and config/tests cover the new surface. Ready to merge after #925 and #937 land.

Thank you @northdpole , will resolve these conflicts altogether once #937 gets merged.
Okay this got merged, on it will notify you once these are resolved.

The C.1 bi-encoder (W3) fingerprints the section and each CRE separately, so
the ordering inside the top-K shortlist is rough — the right CRE can sit at OWASP#7.
C.2 reads each (section, candidate-CRE) pair together, re-sorts the shortlist by
that score, and keeps the best N; it fills RetrievalAudit.reranked[] (the slot
C.1 left empty) and leaves candidates[] untouched for audit.

- cross_encoder.py: CrossEncoderReranker over an injected score_fn (mirrors
  C.1's embed_fn DI seam — the module never imports torch), plus a lazy
  build_cross_encoder_score_fn wiring ms-marco-MiniLM-L-6-v2. Typed errors,
  RERANKER_NAME audit tag, stable sort so ties keep cosine order.
- cross_encoder_test.py: 9 hermetic tests (reorder, top-N, tie stability,
  audit shape, missing-text and score-count failure modes).
- db.py: get_embedding_contents_by_doc_type — {id -> embeddings_content},
  the pair text the reranker scores against (mirrors get_embeddings_by_doc_type).
- cre_main.run_librarian: rerank C.1's audit and log the reranked top-N.
- evaluate_librarian.py v1: full C.1 -> C.2 pipeline; report live rerank top-1
  alongside recall@k (W4 target >= 0.80). Offline path unchanged.
- requirements.txt: add sentence-transformers.
- __init__.py: scope note now covers C.2.
- run_librarian: fix stale docstring (C.2 rerank is built), guard the
  semantic retrieve/rerank per-section so one bad section can't abort the
  dry-run batch
- knowledge_source: log ValidationError.errors(include_input=False), never
  the raw queue row (no content leak)
- build_golden_dataset: fail loud on ambiguous ASVS->CRE mappings instead of
  silently picking one (matches build_explicit/build_update)
- candidate_retriever: stable descending sort so tied cosine scores are
  deterministic
- cross_encoder: zip(..., strict=True) (B905)
- config_loader_test: annotate OVERRIDES as ClassVar (RUF012)
- schemas: centralize the schema_version check across the three envelopes
- section_validator: extract _validate_or_raise helper for both call sites
- requirements: pin sentence-transformers>=5.0,<6.0 (tested with 5.6)
- evaluate_librarian: drop the no-op --dry_run flag (harness never writes)
…k top-1

The embeddings pool and cross-encoder cre_texts are keyed by the CRE
internal UUID, but the golden dataset expects external_ids (e.g. 616-305).
Without translating, every comparison missed (recall@20 and top-1 read 0%).
Map both via cre.id->external_id (pass-through for DBs already keyed by
external_id) so the live numbers are measurable and reproducible.
@northdpole northdpole force-pushed the gsocmodule_C_week_4 branch from 31b6145 to ea53658 Compare July 9, 2026 13:31

@northdpole northdpole left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-approved after W4-only rebase onto main (#937 merged). C.2 cross-encoder + live eval id-space fix look good.

@northdpole northdpole merged commit 374d066 into OWASP:main Jul 9, 2026
5 of 6 checks passed
northdpole pushed a commit that referenced this pull request Jul 9, 2026
- run_librarian: fix stale docstring (C.2 rerank is built), guard the
  semantic retrieve/rerank per-section so one bad section can't abort the
  dry-run batch
- knowledge_source: log ValidationError.errors(include_input=False), never
  the raw queue row (no content leak)
- build_golden_dataset: fail loud on ambiguous ASVS->CRE mappings instead of
  silently picking one (matches build_explicit/build_update)
- candidate_retriever: stable descending sort so tied cosine scores are
  deterministic
- cross_encoder: zip(..., strict=True) (B905)
- config_loader_test: annotate OVERRIDES as ClassVar (RUF012)
- schemas: centralize the schema_version check across the three envelopes
- section_validator: extract _validate_or_raise helper for both call sites
- requirements: pin sentence-transformers>=5.0,<6.0 (tested with 5.6)
- evaluate_librarian: drop the no-op --dry_run flag (harness never writes)

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
scripts/build_golden_dataset.py (1)

271-273: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Dead branch: std is already "OTHER" before the if.

The if "Top 10" in name: branch reassigns std to the same value it already holds, making the conditional a no-op.

♻️ Simplify the assignment
 std = "OTHER"
-if "Top 10" in name:
-    std = "OTHER"  # closest enum; not strictly ASVS/WSTG/NIST
 prefix = "top10" if "Top 10" in name else "cwe"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/build_golden_dataset.py` around lines 271 - 273, The `std` assignment
in `build_golden_dataset.py` has a dead conditional because `std` is already set
to "OTHER" before the `if "Top 10" in name:` check. Simplify the logic around
this `std` initialization by removing the no-op branch or making the conditional
assign a different value only when needed, and keep the behavior in the
surrounding dataset-building code unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@application/cmd/cre_main.py`:
- Around line 1130-1131: The fast-path lookup in resolve() is using CRE row UUID
keys from get_embeddings_by_doc_type(), but it needs external CRE IDs to match
inputs like 616-305. Update the known_ids setup in cre_main.py by translating
the embedding map keys from internal UUIDs to cre.external_id first, following
the same approach used in scripts/evaluate_librarian.py, so the resolver can hit
the explicit match path before falling back to semantic retrieval.

---

Nitpick comments:
In `@scripts/build_golden_dataset.py`:
- Around line 271-273: The `std` assignment in `build_golden_dataset.py` has a
dead conditional because `std` is already set to "OTHER" before the `if "Top 10"
in name:` check. Simplify the logic around this `std` initialization by removing
the no-op branch or making the conditional assign a different value only when
needed, and keep the behavior in the surrounding dataset-building code
unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: b20aedf2-58d2-4af1-a1fc-aa906d55577e

📥 Commits

Reviewing files that changed from the base of the PR and between 31b6145 and ea53658.

📒 Files selected for processing (13)
  • application/cmd/cre_main.py
  • application/database/db.py
  • application/tests/librarian/config_loader_test.py
  • application/tests/librarian/cross_encoder_test.py
  • application/utils/librarian/__init__.py
  • application/utils/librarian/candidate_retriever.py
  • application/utils/librarian/cross_encoder.py
  • application/utils/librarian/knowledge_source.py
  • application/utils/librarian/schemas.py
  • application/utils/librarian/section_validator.py
  • requirements.txt
  • scripts/build_golden_dataset.py
  • scripts/evaluate_librarian.py
✅ Files skipped from review due to trivial changes (2)
  • application/utils/librarian/init.py
  • application/tests/librarian/config_loader_test.py
🚧 Files skipped from review as they are similar to previous changes (5)
  • requirements.txt
  • application/database/db.py
  • application/utils/librarian/candidate_retriever.py
  • application/tests/librarian/cross_encoder_test.py
  • application/utils/librarian/cross_encoder.py

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 1

🧹 Nitpick comments (1)
scripts/build_golden_dataset.py (1)

271-273: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Dead branch: std is already "OTHER" before the if.

The if "Top 10" in name: branch reassigns std to the same value it already holds, making the conditional a no-op.

♻️ Simplify the assignment
 std = "OTHER"
-if "Top 10" in name:
-    std = "OTHER"  # closest enum; not strictly ASVS/WSTG/NIST
 prefix = "top10" if "Top 10" in name else "cwe"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/build_golden_dataset.py` around lines 271 - 273, The `std` assignment
in `build_golden_dataset.py` has a dead conditional because `std` is already set
to "OTHER" before the `if "Top 10" in name:` check. Simplify the logic around
this `std` initialization by removing the no-op branch or making the conditional
assign a different value only when needed, and keep the behavior in the
surrounding dataset-building code unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@application/cmd/cre_main.py`:
- Around line 1130-1131: The fast-path lookup in resolve() is using CRE row UUID
keys from get_embeddings_by_doc_type(), but it needs external CRE IDs to match
inputs like 616-305. Update the known_ids setup in cre_main.py by translating
the embedding map keys from internal UUIDs to cre.external_id first, following
the same approach used in scripts/evaluate_librarian.py, so the resolver can hit
the explicit match path before falling back to semantic retrieval.

---

Nitpick comments:
In `@scripts/build_golden_dataset.py`:
- Around line 271-273: The `std` assignment in `build_golden_dataset.py` has a
dead conditional because `std` is already set to "OTHER" before the `if "Top 10"
in name:` check. Simplify the logic around this `std` initialization by removing
the no-op branch or making the conditional assign a different value only when
needed, and keep the behavior in the surrounding dataset-building code
unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: b20aedf2-58d2-4af1-a1fc-aa906d55577e

📥 Commits

Reviewing files that changed from the base of the PR and between 31b6145 and ea53658.

📒 Files selected for processing (13)
  • application/cmd/cre_main.py
  • application/database/db.py
  • application/tests/librarian/config_loader_test.py
  • application/tests/librarian/cross_encoder_test.py
  • application/utils/librarian/__init__.py
  • application/utils/librarian/candidate_retriever.py
  • application/utils/librarian/cross_encoder.py
  • application/utils/librarian/knowledge_source.py
  • application/utils/librarian/schemas.py
  • application/utils/librarian/section_validator.py
  • requirements.txt
  • scripts/build_golden_dataset.py
  • scripts/evaluate_librarian.py
✅ Files skipped from review due to trivial changes (2)
  • application/utils/librarian/init.py
  • application/tests/librarian/config_loader_test.py
🚧 Files skipped from review as they are similar to previous changes (5)
  • requirements.txt
  • application/database/db.py
  • application/utils/librarian/candidate_retriever.py
  • application/tests/librarian/cross_encoder_test.py
  • application/utils/librarian/cross_encoder.py
🛑 Comments failed to post (1)
application/cmd/cre_main.py (1)

1130-1131: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 1. Check what extract_cre_refs extracts (external IDs vs UUIDs)
rg -n "def extract_cre_refs" application/utils/librarian/ -A 15

# 2. Check the Embeddings model — what does cre_id store?
rg -n "class Embeddings" application/database/db.py -A 20

# 3. Check how embeddings are stored (what cre_id is set to)
rg -n "def add_embedding" application/database/db.py -A 15

# 4. Confirm the evaluation harness translates IDs but run_librarian does not
rg -n "id_to_ext\|_to_ext\|known_ids" scripts/evaluate_librarian.py application/cmd/cre_main.py -C 3

Repository: OWASP/OpenCRE

Length of output: 3270


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the explicit resolver and its ID format
sed -n '1,140p' application/utils/librarian/explicit_link_resolver.py

# Inspect the embeddings model and how embeddings are written/retrieved
sed -n '173,230p' application/database/db.py
sed -n '2461,2525p' application/database/db.py
sed -n '2550,2615p' application/database/db.py

# Inspect the caller that builds known_ids
sed -n '1110,1160p' application/cmd/cre_main.py

# Inspect the evaluation harness ID translation helpers and usage
sed -n '1,220p' scripts/evaluate_librarian.py

Repository: OWASP/OpenCRE

Length of output: 21424


Translate embedding keys to external CRE IDs before building known_ids

get_embeddings_by_doc_type() is keyed by the CRE row UUID (cre_id=db_object.id), but resolve() only matches external IDs like 616-305, so the explicit fast path never hits and everything falls back to semantic retrieval. scripts/evaluate_librarian.py already maps UUIDs to cre.external_id before using the resolver; application/cmd/cre_main.py needs the same translation here.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@application/cmd/cre_main.py` around lines 1130 - 1131, The fast-path lookup
in resolve() is using CRE row UUID keys from get_embeddings_by_doc_type(), but
it needs external CRE IDs to match inputs like 616-305. Update the known_ids
setup in cre_main.py by translating the embedding map keys from internal UUIDs
to cre.external_id first, following the same approach used in
scripts/evaluate_librarian.py, so the resolver can hit the explicit match path
before falling back to semantic retrieval.

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.

2 participants