diff --git a/arborist/qa/verify.py b/arborist/qa/verify.py index b444af8..25e0411 100644 --- a/arborist/qa/verify.py +++ b/arborist/qa/verify.py @@ -1168,7 +1168,21 @@ def _claim_title_overlap(claim_text: str, source_title: str | None) -> bool: # collapse the same way ('movies' vs 'movie', 'simpsons' vs # 'simpson'). Defined in qa/query.py to avoid an import cycle: # inline a minimal copy here instead. + # + # 2026-05-30: also normalize possessive apostrophes that the + # tokenizer leaves in mid-word ("homer's" → "homers" before stem). + # Without this, claim "Homer's boss" cited to title "Dancin' Homer" + # tripped TITLE_MISMATCH because "homer's" stemmed to "homer'" + # which didn't match "homer" stemmed from the title. The bug + # cascades: when EVERY resolving claim trips TITLE_MISMATCH, the + # verifier demotes audit_mode → UNGROUNDED even though the + # citations are honest, producing the "UNGROUNDED 2/2" inversion. def _stem(t: str) -> str: + # Strip apostrophes anywhere — possessives, Unicode quotes + # ("Dancin'" or "Dancin’"). Done before length/suffix + # check so "homer's" (7 chars, mid apostrophe) becomes + # "homers" then stems to "homer". + t = t.replace("'", "").replace("’", "") if len(t) > 4 and t.endswith("s") and not t.endswith("ss"): return t[:-1] return t