Skip to content

Commit fcd142f

Browse files
Juanpacolclaude
andcommitted
fix(consistency): honest caveat for ambiguous symbol misses, no verdict change
ADR-0018 flagged backtick-quoted local variable names (with_tax, days_overdue) as a nuisance false-positive source: real names, just not graph-indexed, but flagged CONTRADICTED same as a hallucinated function name. Investigating a fix surfaced a harder fact: a real local variable name and a real hallucinated function name are lexically identical -- plain snake_case, no dot, no parens, no capital letter. All 14 invented names the pilot caught had exactly that shape. Any heuristic that softened the verdict or confidence for that shape would have silently cost the 14/14 recall already measured and published. Fix: check_symbol_exists now adds an honest caveat to the explanation text when a missed symbol has no marker distinguishing it from an ordinary variable name (new looks_like_bare_name() in claims.py: no dot, no call parens, no capital) -- status and confidence stay exactly as before, CONTRADICTED at 1.0 either way. The caveat necessarily appears on both with_tax and get_tax_rate, since nothing in the text tells them apart -- stated plainly in the explanation and in ADR-0018, not hidden. A clearly code-shaped miss (TotallyMadeUpClass, CamelCase) gets no caveat, since there the evidence really is as strong as the confidence claims. 6 new regression tests (test_claims.py's TestLooksLikeBareName, test_consistency_check.py's two new TestSymbolExistence cases). ADR-0018 updated to reflect the fix and the lexical-ambiguity finding behind it. 518 tests passing (512 + 6 new). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
1 parent 1aae097 commit fcd142f

5 files changed

Lines changed: 87 additions & 10 deletions

File tree

docs/adr/0018-consistency-engine-first-measurement.md

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -112,13 +112,23 @@ synthetic-fixture trap's blind spots.
112112
here. The extractor's other named gap (tolerating explanatory text
113113
between the relation verb and its arguments, e.g. "likely calls a helper
114114
in") remains open, deliberately out of scope for this pass.
115-
- **Backtick-quoted local variable names are a genuine nuisance-false-
116-
positive source** in real usage — anyone writing a normal technical
117-
summary with `` `some_var` `` for readability will trigger a false
118-
contradiction today. Worth a narrower fix (distinguishing "this claims a
119-
codebase symbol" from "this is emphasis") in a future pass; not attempted
120-
here to keep this pilot's scope to measurement plus the one clear,
121-
high-confidence bug it found.
115+
- **Backtick-quoted local variable names — addressed, deliberately without
116+
changing any verdict** (same-day follow-up). Investigating a fix
117+
surfaced a harder fact: a real local variable name (`with_tax`) and a
118+
real hallucinated function name (`get_tax_rate`) are lexically
119+
identical — plain snake_case, no dot, no parens, no capital letter. All
120+
14 invented names this pilot caught had exactly that shape. Any
121+
heuristic that softened the verdict or confidence for that shape would
122+
have silently cost the 14/14 recall this ADR reports. Instead,
123+
`check_symbol_exists` now adds an honest caveat to the *explanation*
124+
when a missed symbol has no marker distinguishing it from a plain
125+
variable name (`looks_like_bare_name` in `claims.py`) — status and
126+
confidence are unchanged, `CONTRADICTED` at 1.0 either way. The caveat
127+
necessarily appears on both `with_tax` and `get_tax_rate`, since nothing
128+
in the text tells them apart; this is stated plainly rather than
129+
pretended away. Regression tests confirm a clearly code-shaped miss
130+
(`TotallyMadeUpClass`) gets no caveat, since there the evidence really is
131+
as strong as the confidence claims.
122132
- **The resurfacing fix is a real improvement, not a complete solution.**
123133
BM25's IDF is inherently unstable with 1-2 documents; a small residual
124134
false-positive risk remains for very small decision corpora. Stated

src/verityai/consistency/check.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424

2525
from pathlib import Path
2626

27-
from verityai.consistency.claims import extract_claims, looks_like_path
27+
from verityai.consistency.claims import extract_claims, looks_like_bare_name, looks_like_path
2828
from verityai.context.rank import bm25_rank
2929
from verityai.core.models import (
3030
CheckStatus,
@@ -67,11 +67,21 @@ def check_symbol_exists(claim: Claim, query: GraphQuery) -> ClaimCheck:
6767
for m in matches[:5]
6868
],
6969
)
70+
explanation = f"no definition of {claim.subject!r} found anywhere in the graph"
71+
if looks_like_bare_name(claim.subject, claim.raw_text):
72+
# Same status and confidence as any other miss -- the verdict does
73+
# not change. Only the explanation gets more honest: this token's
74+
# shape cannot be distinguished from an ordinary local variable
75+
# name backtick-quoted for emphasis (ADR-0018).
76+
explanation += (
77+
" (note: this could also be a local variable name, which the graph "
78+
"does not track, rather than a hallucinated symbol)"
79+
)
7080
return ClaimCheck(
7181
claim=claim,
7282
status=CheckStatus.CONTRADICTED,
7383
confidence=1.0,
74-
explanation=f"no definition of {claim.subject!r} found anywhere in the graph",
84+
explanation=explanation,
7585
)
7686

7787

src/verityai/consistency/claims.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,23 @@ def looks_like_path(token: str) -> bool:
9090
_looks_like_path = looks_like_path
9191

9292

93+
def looks_like_bare_name(subject: str, raw_text: str) -> bool:
94+
"""True if this token carries no marker distinguishing it from an
95+
ordinary local variable name -- no call parens, no dotted/qualified
96+
path, no CamelCase.
97+
98+
This is deliberately the *same* shape a hallucinated bare function name
99+
has (ADR-0018's pilot caught 14/14 invented names, all in exactly this
100+
shape: plain snake_case, no punctuation, no parens). There is no
101+
lexical signal that separates "someone backtick-quoted a local variable
102+
for emphasis" from "someone invented a function name" -- so this is a
103+
caveat signal for the checker's explanation text, never a reason to
104+
change a verdict or confidence. Doing that would have silently traded
105+
away the very recall ADR-0018 measured.
106+
"""
107+
return "." not in subject and "()" not in raw_text and not any(c.isupper() for c in subject)
108+
109+
93110
def _looks_like_symbol(token: str) -> bool:
94111
return bool(_SYMBOL.match(token)) and (
95112
"." in token or "_" in token or token.endswith("()") or any(c.isupper() for c in token[1:])

tests/unit/test_claims.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
as the subject instead of the actual symbol.
99
"""
1010

11-
from verityai.consistency.claims import extract_claims
11+
from verityai.consistency.claims import extract_claims, looks_like_bare_name
1212
from verityai.core.models import ClaimKind
1313

1414

@@ -133,6 +133,24 @@ def test_a_relation_match_is_not_also_extracted_as_two_bare_symbols(self):
133133
assert len(claims) == 1
134134

135135

136+
class TestLooksLikeBareName:
137+
"""A caveat signal only -- see check_symbol_exists. Never used to change
138+
a verdict, since the same shape (plain snake_case) is exactly what
139+
ADR-0018's real hallucinated function names looked like too."""
140+
141+
def test_plain_snake_case_is_a_bare_name(self):
142+
assert looks_like_bare_name("with_tax", "`with_tax`") is True
143+
144+
def test_a_dotted_qualname_is_not_bare(self):
145+
assert looks_like_bare_name("Service.run", "`Service.run`") is False
146+
147+
def test_a_call_marker_means_not_bare(self):
148+
assert looks_like_bare_name("get_tax_rate", "`get_tax_rate()`") is False
149+
150+
def test_camel_case_is_not_bare(self):
151+
assert looks_like_bare_name("SomeClass", "`SomeClass`") is False
152+
153+
136154
class TestNoExtraction:
137155
def test_empty_text_extracts_nothing(self):
138156
assert extract_claims("") == []

tests/unit/test_consistency_check.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,28 @@ def test_a_qualified_method_name_resolves(self, query):
9191

9292
assert result.status is CheckStatus.SUPPORTED
9393

94+
def test_a_bare_snake_case_miss_gets_a_caveat_not_a_softer_verdict(self, query):
95+
"""Regression (ADR-0018): `with_tax` backtick-quoted for emphasis in
96+
ordinary prose is lexically identical to an invented function name.
97+
The verdict must stay CONTRADICTED at full confidence -- softening
98+
it would have silently cost the 14/14 recall the real pilot
99+
measured -- but the explanation should say the evidence is weaker
100+
here than for a clearly code-shaped miss."""
101+
result = check_symbol_exists(symbol_claim("with_tax"), query)
102+
103+
assert result.status is CheckStatus.CONTRADICTED
104+
assert result.confidence == 1.0
105+
assert "local variable" in result.explanation
106+
107+
def test_a_clearly_code_shaped_miss_gets_no_caveat(self, query):
108+
"""`TotallyMadeUpClass` (CamelCase) isn't the shape a bare local
109+
variable name would take, so the caveat would be misleading noise
110+
here -- the evidence really is as strong as the confidence claims."""
111+
result = check_symbol_exists(symbol_claim("TotallyMadeUpClass"), query)
112+
113+
assert result.status is CheckStatus.CONTRADICTED
114+
assert "local variable" not in result.explanation
115+
94116

95117
class TestSymbolRelation:
96118
def test_a_real_relation_is_supported(self, query):

0 commit comments

Comments
 (0)