Skip to content

fix(verify-cv-facts): stop flagging ordinary prose as tool claims - #3643

Merged
santifer merged 2 commits into
career-ops-hq:mainfrom
Schlaflied:fix/3639-tool-claim-false-positives
Sep 5, 2026
Merged

santifer merged 2 commits into
career-ops-hq:mainfrom
Schlaflied:fix/3639-tool-claim-false-positives

Conversation

@Schlaflied

@Schlaflied Schlaflied commented Sep 1, 2026 •

Copy link
Copy Markdown
Contributor

Summary

factClaims()'s 'tool' extraction in verify-cv-facts.mjs captured everything after a trigger word (using, built with, worked with, technologies:, tech stack:) up to the next punctuation, split it on commas/and/with/in, and treated any resulting fragment of ≤3 words not in TOOL_PROSE_WORDS as an unverified tool claim. That flagged ordinary prose — "improving on-time submission", "recurring HR", "diagnosing", "feedback", "efficiency" — as fabricated tools, forcing rewrites of completely truthful CV/cover-letter text to dodge the trigger grammar.

Direction chosen (issue asked for a justification since it's ambiguous)

The issue proposed three directions. This PR combines (1)/(3) and (2) rather than picking one exclusively:

  • Positive shape signal (direction 3): a fragment that looks tool-shaped — Title-Cased ("React", "Google Cloud"), or carries a digit/version token ("n8n", "GPT-4") — is accepted outright.
  • Source-backed exemption (direction 3): a fragment that fails the shape test but is already an exact substring of a source file is still accepted, so a real lowercase tool name a user genuinely used and listed in cv.md (e.g. kubernetes) isn't penalized for casing.
  • Prose-suffix drop (targeted version of direction 2, not the reactive whack-a-mole form): a fragment that is neither tool-shaped nor source-backed is dropped only when it carries a common gerund/abstract-noun suffix (-ing/-tion/-sion/-ment/-ency/-ance/-ery/-ity/-ness) — the shape every concrete false positive in the issue shared. This targets the actual failure class instead of adding one stoplist word at a time.
  • Everything else (a lowercase, non-prose-shaped, unbacked fragment like kubernetes or google cloud) falls through to the pre-existing fail-closed behavior, unchanged — so a fabricated tool can't evade detection just by lowercasing it.

One item from the issue's table — the "byte-lossless markdown-to-database round-trip" example — is a distinct root cause (an exact-string mismatch against cv.md's markdown↔DB phrasing) rather than the extraction-breadth problem this issue is about, and is out of scope here; the issue itself flags it as "not a fabrication" separately from the extraction bug.

Changes

  • verify-cv-facts.mjs: added looksToolShaped() and PROSE_SUFFIX_RE; isLikelyTool() now takes an optional sourceNormalized param so a lowercase-but-genuine tool can be recognized against the source; factClaims() threads it through from verifyFacts().
  • tests/nonmetric-fact-gate.test.mjs: added coverage for every concrete false-positive example from verify-cv-facts.mjs: 'using X' tool-claim extraction flags ordinary prose as unverified tool claims #3639, a source-backed lowercase tool passing cleanly, a lowercase fabricated tool still blocking, and a Title-Cased fabricated tool still blocking.

Test plan

Fixes #3639

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Improved fact verification to distinguish genuine technology/tool claims from ordinary prose.
    • Continued blocking fabricated tool claims, including lowercase and title-cased names.
    • Added support for validating lowercase technology names when backed by source content.
  • Tests

    • Added regression coverage for tool-claim extraction and fact verification.
    • Verified handling of prose collisions, source-backed claims, and fabricated tools.

@Scott-Emberson Scott-Emberson 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.

Reviewed at head 38c792fc2a. On a source-of-truth surface like the fact gate, the risk with a false-positive fix is over-correcting into silence, and this one does not. The owned test asserts both directions: the five prose cases that were being wrongly flagged (a gerund or abstract noun after "using") now come back with zero tool claims, but a genuine unbacked tool claim still blocks in both its lowercase form ("using kubernetes and google cloud") and its Title-Cased form ("Kubernetes and Terraform" with no source), and a source-backed lowercase claim ("kubernetes and n8n") passes. The fix only adds a source-backed exemption to isLikelyTool plus a narrow prose-suffix drop that fires only for fragments that are neither Title-Cased/versioned nor source-backed, and the "still blocks" cases guard against that suffix drop silencing real fabrication. The pre-existing "explicit lowercase tool claims fail closed without a whitelist entry" assertion still passes.

Owned test verified sound, the no-fabrication gate still catches real unbacked claims. Holding formal approval until the branch is finalized, nothing blocking from the tests/ side.

@Scott-Emberson Scott-Emberson 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.

Approving from the tests/ side. The owned test is verified sound at this head (evidence in my review note above), CI is green, and the branch is mergeable. Good to go from our end. As always this routes the code-owner review, it does not merge.

…reer-ops-hq#3639)

factClaims()'s 'tool' extraction pattern was too broad: everything after a
trigger word (using/built with/worked with/technologies:/tech stack:) up to
the next punctuation was captured, split on commas/and/with/in, and any
resulting fragment of <=3 words not in a small stoplist was treated as an
unverified tool claim. That flagged ordinary prose like "improving on-time
submission", "recurring HR", "diagnosing", "feedback", and "efficiency" as
fabricated tools, forcing repeated rewrites of truthful CV/cover-letter text.

Fix combines two of the issue's three suggested directions rather than
picking one exclusively:

- A fragment that looks tool-shaped (Title-Cased, or carries a digit/version
  token like "n8n" or "GPT-4") is accepted outright — this is direction 3's
  positive signal, and it also protects real lowercase tool names that are
  already listed in a source file (verified with a new test).
- A fragment that is neither tool-shaped nor source-backed is now dropped
  only when it carries a common English gerund/abstract-noun suffix
  (-ing/-tion/-sion/-ment/-ency/-ance/-ery/-ity/-ness), the shape shared by
  every concrete false positive in the issue. This targets the actual
  failure class instead of hand-listing more stoplist words one at a time
  (direction 2's whack-a-mole problem).
- Anything else (e.g. a lowercase, non-prose-shaped fragment like
  "kubernetes" or "google cloud" with no source backing) falls through to
  the pre-existing fail-closed behavior unchanged, so a fabricated tool
  cannot evade detection just by typing it in lowercase.

Added tests/nonmetric-fact-gate.test.mjs coverage for: every concrete
false-positive example from career-ops-hq#3639, a source-backed lowercase tool passing
cleanly, a lowercase fabricated tool still blocking, and a Title-Cased
fabricated tool still blocking.

Fixes career-ops-hq#3639

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@Schlaflied
Schlaflied force-pushed the fix/3639-tool-claim-false-positives branch from 38c792f to 6acc7fd Compare September 2, 2026 23:19

@Scott-Emberson Scott-Emberson 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.

Re-reviewed at head 6acc7fd (moved past the earlier approval on a large merge). The subject test tests/nonmetric-fact-gate.test.mjs did genuinely change, but the change is additive and strengthening, and it is not made redundant by the similarly-titled #3639 that landed in main: it adds the gerund and abstract-noun false-positive cases, keeps the lowercase and Title-Case fabrication-still-blocks guards, and adds a distinct delegatedAuthorshipClaims block for vendor and contractor relative-clause escalation, importing an export that is this PR's own subject. No existing assertion was removed or loosened, the embedded-substring boundary test and the prior cases are all retained.

The substance is verify-cv-facts.mjs, a source-of-truth surface outside our owned paths, so this is a test-axis review: the owned test is sound and the anti-fabrication coverage is extended, the scope and merge of the change are the maintainer's call. Nothing blocking from the tests/ side.

@luochen211 luochen211 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The suffix heuristic opens a fail-open path for real lowercase technology names. PROSE_SUFFIX_RE drops any unbacked lowercase fragment ending in ing|tion|sion|ment|ency|ance|ery|ity|ness; direct calls such as factClaims('Built this using spring.'), ... unity., and ... processing. all return no tool claim. Those are real technologies, so a fabricated lowercase claim now bypasses the gate instead of blocking.

The new tests prove only names outside the suffix set (kubernetes, google cloud). Please avoid using a blanket English suffix as the deciding signal, or add an evidence-backed technology recognition path, and add regressions for lowercase product names that collide with these suffixes.

@coderabbitai

coderabbitai Bot commented Sep 3, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Advanced

Run ID: 165fcc7b-7aa2-44e1-b827-cc9dc048d43f

📥 Commits

Reviewing files that changed from the base of the PR and between 455bf37 and fc22a4d.

📒 Files selected for processing (2)
  • tests/nonmetric-fact-gate.test.mjs
  • verify-cv-facts.mjs

Included review availability: Your plan provides up to 10 included reviews per hour; 7 remain after this review.


📝 Walkthrough

Walkthrough

The tool-claim filter now rejects prose-shaped fragments, preserves blocking for unsupported tools, and permits lowercase technology claims when normalized source text contains exact matches. Regression tests cover these cases.

Changes

Tool claim validation

Layer / File(s) Summary
Tool claim filtering
verify-cv-facts.mjs
The filter expands prose-word exclusions, identifies versioned or Title-Cased tool fragments, and permits lowercase fragments only when normalized source text contains an exact match.
Tool claim verification and regression coverage
verify-cv-facts.mjs, tests/nonmetric-fact-gate.test.mjs
factClaims receives normalized source content through verifyFacts. Tests cover prose false positives, unsupported lowercase and Title-Cased tools, fabricated multiword tools, and source-backed lowercase tools.

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

Merge Risk: ⚪ Minimal · up to fc22a

Tool-claim extraction now avoids prose false positives while retaining unsupported-tool blocking, with regression coverage for accepted and rejected patterns. No current merge-blocking risk remains.

Suggested reviewers: luochen211, scott-emberson, abankar1

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: preventing ordinary prose from being incorrectly flagged as tool claims.
Linked Issues check ✅ Passed The changes satisfy issue #3639. They reduce prose false positives through tool-shape detection and targeted filtering, allow source-backed lowercase tools, preserve fail-closed handling for fabricate…
Out of Scope Changes check ✅ Passed The changes remain within issue #3639. The implementation and regression tests address tool-claim extraction and verification. The separate markdown-to-database phrase mismatch remains appropriately o…
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 5 functions across 2 files.
Full details: Linked Issues check

Explanation

The changes satisfy issue #3639. They reduce prose false positives through tool-shape detection and targeted filtering, allow source-backed lowercase tools, preserve fail-closed handling for fabricated tools, and add regression tests.

Full details: Out of Scope Changes check

Explanation

The changes remain within issue #3639. The implementation and regression tests address tool-claim extraction and verification. The separate markdown-to-database phrase mismatch remains appropriately out of scope.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

@Scott-Emberson Scott-Emberson 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.

Re-reviewed at head fc22a4d. The rebase strengthened the fact gate in both directions: lowercase tech names (spring, unity, processing) stay fail-closed and block, and source evidence now overrides an exact prose-word collision so there is no over-block. The named-vendor escalation-blocks case and the source-of-truth behavior are intact. Nothing earlier was weakened, and the anti-fabrication direction is hardened.

The substance is verify-cv-facts.mjs, a source-of-truth surface outside our owned paths, so this is a test-axis review: the owned test is sound and the coverage is extended, the scope and merge are the maintainer's call. The branch is behind main, so a rebase before merge would help, but nothing is blocking from the tests/ side.

@luochen211 luochen211 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Re-reviewed current head fc22a4d. The blanket suffix exemption is gone; unbacked lowercase Spring, Unity, and Processing claims remain fail-closed, while the prose false positives are handled by bounded exact words and source-backed collisions still pass. Focused nonmetric fact-gate suite passes locally (30/30). My prior blocking finding is resolved.

@santifer
santifer merged commit 6043ef8 into career-ops-hq:main Sep 5, 2026
12 checks passed
@santifer

santifer commented Sep 5, 2026

Copy link
Copy Markdown
Member

Another solid one, @Schlaflied. Keeping the stoplist to exact words instead of suffixes, so Spring, Unity and Processing stay fail-closed, is the distinction that makes this safe, and the source-backed exemption means a real lowercase kubernetes listed in cv.md is not penalised for its casing. Merged, closes #3639.

@santifer santifer mentioned this pull request Sep 16, 2026
rubicon added a commit to rubicon/career-ops that referenced this pull request Sep 23, 2026
…l claim

`isLikelyTool()` accepted any short lowercase fragment by default, so an
ordinary "using" sentence was asserted as a technology name and
`assertFacts()` blocked the render of a document that claimed nothing
false. career-ops-hq#3643 added a shape test and a source-backed exemption, but both
only add accept paths, which left `TOOL_PROSE_WORDS` as the only thing
standing between ordinary prose and a tool claim: a list holding the
words that happened to appear in career-ops-hq#3639.

Drop a fragment whose every word already occurs in the source. That is
the document's own vocabulary reworded, which is exactly what tailoring
does to a "using" sentence. A name the source never mentions is
untouched, so a lowercase fabrication still blocks.

Widen the existing leading-article drop to the rest of the determiner
class, so "using that campaign" is not read as a product name whatever
the source happens to contain.

Closes career-ops-hq#4004
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

verify-cv-facts.mjs: 'using X' tool-claim extraction flags ordinary prose as unverified tool claims

4 participants