diff --git a/src/mcaxl/route_plan.py b/src/mcaxl/route_plan.py index a1a3a9d..e11f6d3 100644 --- a/src/mcaxl/route_plan.py +++ b/src/mcaxl/route_plan.py @@ -1750,7 +1750,23 @@ def translation_chain(client: "AxlClient", number: str, css_name: str | None = N LEFT OUTER JOIN typepatternusage tpu ON np.tkpatternusage = tpu.enum LEFT OUTER JOIN digitdiscardinstruction ddi ON np.fkdigitdiscardinstruction = ddi.pkid LEFT OUTER JOIN routefilter rf ON np.fkroutefilter = rf.pkid - WHERE np.tkpatternusage IN (3, 5, 7) + # tkpatternusage candidates included in the match scan: + # 2 = Device (DN) — directly-dialable directory numbers + # 3 = Translation — translation patterns + # 5 = Route Pattern — outbound routing + # 7 = Hunt Pilot — hunt pilot pattern + # + # Device DNs are included because CUCM's runtime CFNA/CFUR matcher + # includes them — when a forward destination is a Device DN (e.g. + # the DN of another CTI Route Point in a CER failsafe chain), the + # runtime call succeeds via that DN. Excluding them caused + # `cti_failsafe_reachability` false-positive HIGH findings on + # CTI-RP-to-CTI-RP failsafe chains. cucx-docs verified empirically + # in agent-threads/cti-audit-prompts/007 that omitting Device DNs + # was the cause of the false-positive: candidates_evaluated stayed + # 23 vs 26 numplan rows in the reachable partition, with the 3-row + # gap being exactly the 3 Device DNs. + WHERE np.tkpatternusage IN (2, 3, 5, 7) AND np.dnorpattern IS NOT NULL {css_filter} """ @@ -1772,6 +1788,8 @@ def translation_chain(client: "AxlClient", number: str, css_name: str | None = N "match_count": len(matches), "matches": matches, "_note": ( + "Candidate set: tkpatternusage 2 (Device DN), 3 (Translation), " + "5 (Route Pattern), 7 (Hunt Pilot). " "Wildcards evaluated: X, !, [0-9], @, \\+. " "@-pattern matches any digit string (route filter constraints not applied). " "Longest-match-wins is approximated by pattern length; CUCM uses pattern " diff --git a/tests/test_cti_failsafe_reachability.py b/tests/test_cti_failsafe_reachability.py index 717132c..f51eced 100644 --- a/tests/test_cti_failsafe_reachability.py +++ b/tests/test_cti_failsafe_reachability.py @@ -60,8 +60,12 @@ class FakeAxlClient: return {"row_count": len(self._cti_rows), "rows": self._cti_rows} # Dispatch 2: translation_chain's reachability check - # Recognizable by `tkpatternusage IN (3, 5, 7)` from route_plan.py - if "tkpatternusage IN (3, 5, 7)" in sql: + # Recognizable by `tkpatternusage IN (2, 3, 5, 7)` from route_plan.py + # (tkpatternusage = 2 / Device DN was added 2026-05-09 after + # cucx-docs's empirical proof in cti-audit-prompts/007 that + # excluding Device DNs caused false-positive HIGH findings on + # CTI-RP-to-CTI-RP failsafe chains) + if "tkpatternusage IN (2, 3, 5, 7)" in sql: for dest, css in self._reachable: if f"name = '{css}'" in sql: return { @@ -421,3 +425,76 @@ class TestDotStrippedFixSuggestion: assert "Match-PT" in fix assert "Nonmatch-PT" not in fix assert "AnotherMatch-PT" not in fix + + +# ─── Device-DN inclusion in translation_chain (cti-audit-prompts/007) ── +# +# cucx-docs verified empirically in 007 that `route_translation_chain`'s +# candidate filter excluded Device DNs (tkpatternusage=2), which caused +# false-positive HIGH findings on CTI-RP-to-CTI-RP failsafe chains +# (typical CER pattern). The fix: include tkpatternusage=2 in the +# candidate set so Device DNs get checked alongside translation/route/ +# hunt patterns. +# +# These tests pin the regression by: +# 1. Verifying the SQL emitted by translation_chain includes +# `tkpatternusage IN (2, 3, 5, 7)` literally +# 2. Demonstrating the cti audit doesn't false-positive on a CTI-RP- +# to-CTI-RP failsafe shape + +class TestDeviceDnInTranslationChainCandidates: + + def test_translation_chain_sql_includes_device_dn_usage(self): + """Lock the candidate-filter SQL down so a future contributor + can't accidentally re-narrow it to (3, 5, 7) and re-introduce + the cti-audit-prompts/007 false-positive class. + """ + client = FakeAxlClient(cti_rp_rows=[ + _cti_row("Test-RP", "Generic", cfna="912", cfna_css="SomeCSS"), + ]) + cti_failsafe_reachability(client) + # Find the translation_chain query in the captured SQL + chain_query = next( + q for q in client.queries + if "tkpatternusage IN" in q and "callingsearchspace" in q + ) + assert "(2, 3, 5, 7)" in chain_query, ( + "translation_chain candidate set must include tkpatternusage=2 " + "(Device DN). Excluding Device DNs causes false-positive HIGH " + "findings on CTI-RP-to-CTI-RP failsafe chains. See " + "agent-threads/cti-audit-prompts/007 for cucx-docs's " + "empirical proof." + ) + + def test_cti_rp_to_cti_rp_failsafe_does_not_false_positive(self): + """The motivating Bingham case: 911-CTI-RP CFNA → 912 (Device DN + of 912-CTI-RP) under 911CER-CSS reaching 911CER-PT. + + Pre-fix: cti_failsafe_reachability flagged this as HIGH because + translation_chain excluded the Device DN from its candidate set. + + Post-fix: the Device DN '912' is in the candidate set under the + reachable partition, so the forward correctly resolves and no + finding is produced. + """ + # Simulate the fix's effect: the reachable_destinations set + # captures (912, 911CER-CSS) — meaning translation_chain returns + # match_count > 0 because the Device DN is now in the candidate + # set and gets matched against the dialed number. + client = FakeAxlClient( + cti_rp_rows=[ + _cti_row( + "911-CTI-RP", "CTI RP for Primary CER Server", + cfna="912", cfna_css="911CER-CSS", + cfur="912", cfur_css="911CER-CSS", + ), + ], + reachable_destinations={ + ("912", "911CER-CSS"), # Device DN now reachable + }, + ) + result = cti_failsafe_reachability(client) + # No findings — the Tier-1 forward correctly resolves + assert result["broken_cfna"] == 0 + assert result["broken_cfur"] == 0 + assert result["findings"] == []