From 99986daa45e850e16174db8d7993b0c93eab7c2b Mon Sep 17 00:00:00 2001 From: Ryan Malloy Date: Sat, 9 May 2026 03:36:51 -0600 Subject: [PATCH] route_plan: cti_failsafe_reachability fix-suggestion handles dotted patterns MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Limitation surfaced by the live Bingham smoke-test (cti-audit-prompts/004): the canonical 912-CTI-RP finding got the broken-forward flag correct, but the suggested-fix message couldn't name CER911-PT (where pattern '10.911' lives) because the exact-literal lookup `WHERE np.dnorpattern = '10911'` doesn't match the dot-form `10.911`. The CUCM separator-dot in patterns is purely visual — represents access-code boundary, not a digit. A destination string `10911` should match a configured pattern `10.911` since both represent the same dialed digits. Two-stage match in _suggest_failsafe_fix: 1. Exact-literal: WHERE np.dnorpattern = '' (current behavior) 2. Dot-stripped: pull all patterns with `.` in them, filter Python-side by `pattern.replace('.', '') == dest` Stage 2 only runs when stage 1 returns no partitions, so the common case (exact-literal hit) takes the fast path. Falls back to the wildcard-investigation generic message only when neither stage finds a match. The fix message also distinguishes the two cases: - Exact-literal hit → "Pattern '10911' lives in partition X..." - Dot-stripped hit → "Pattern '10.911' (matches destination '10911') lives in partition X..." Naming both the pattern form and the destination keeps the operator oriented when the dialed digits and the configured pattern look different. Tests: +5 in TestDotStrippedFixSuggestion exercising: - dot-stripped match cites the dotted pattern form - exact-literal takes precedence over dotted match - multi-partition dotted match - no-exact-no-dotted falls back to generic - irrelevant dot-positions correctly excluded from match One existing assertion updated from "no exact-literal pattern" to "no exact-literal or dot-stripped pattern" (more accurate after the patch). Full mcaxl suite: 264 → 269 passing (+5 dot-stripped tests). The 1 unrelated test_wildcard.py timing flake is pre-existing (regex-backtracking timing assertion fails by 36ms under load). Cross-references: - Live smoke-test findings: agent-threads/cti-audit-prompts/004 - Original tool: agent-threads/cti-audit-prompts/002, commit d33cd7c --- src/mcaxl/route_plan.py | 78 +++++++++++-- tests/test_cti_failsafe_reachability.py | 142 +++++++++++++++++++++--- 2 files changed, 191 insertions(+), 29 deletions(-) diff --git a/src/mcaxl/route_plan.py b/src/mcaxl/route_plan.py index 94077d5..a1a3a9d 100644 --- a/src/mcaxl/route_plan.py +++ b/src/mcaxl/route_plan.py @@ -732,12 +732,32 @@ def _suggest_failsafe_fix(client: "AxlClient", dest: str, broken_css: str) -> st then suggests either adding that partition to the broken CSS or switching the CSS to one that includes it. - Falls back to a generic message if the destination matches no - pattern in any partition (rarer; usually means the destination is - a literal extension that was deleted). + Two-stage match: + + 1. **Exact-literal match** — `np.dnorpattern = ''`. Catches + the common case where the destination string equals the + configured pattern. + 2. **Dot-stripped match** — for each pattern in `numplan` that + contains `.`, strip the dot and compare to `dest`. The CUCM + separator-dot represents access-code boundary, not a digit; + destination `10911` should match a configured pattern + `10.911`. + + Falls back to a generic message if neither match yields a + partition (rarer — usually means the destination is a wildcard + match like `60XXX` or a literal extension that was deleted, both + of which need operator investigation). + + Source: cti-audit-prompts/004 — the live Bingham smoke-test of + `912-CTI-RP` surfaced this limitation. The tool correctly flagged + the broken forward, but the suggested-fix message couldn't + identify `CER911-PT` (where pattern `10.911` lives) because the + exact-literal lookup didn't strip the separator-dot. """ safe_dest = _esc(dest) - sql = f""" + + # Stage 1: exact-literal match + exact_sql = f""" SELECT DISTINCT rp.name AS partition FROM numplan np LEFT OUTER JOIN routepartition rp ON np.fkroutepartition = rp.pkid @@ -745,33 +765,67 @@ def _suggest_failsafe_fix(client: "AxlClient", dest: str, broken_css: str) -> st AND rp.name IS NOT NULL """ try: - result = client.execute_sql_query(sql) + result = client.execute_sql_query(exact_sql) except Exception: return ( f"Destination {dest!r} is unreachable from CSS {broken_css!r}. " "Manual investigation needed to identify the correct partition." ) - partitions = [r["partition"] for r in result["rows"] if r.get("partition")] + matched_pattern_form = dest # what we'll cite in the fix message + + if not partitions: + # Stage 2: dot-stripped match. The CUCM separator-dot is purely + # visual; `10.911` and `10911` represent the same dialed digits. + # Pull all dot-containing patterns and filter Python-side rather + # than building a complex Informix REGEXP query. + dotted_sql = f""" + SELECT DISTINCT + np.dnorpattern AS pattern, + rp.name AS partition + FROM numplan np + LEFT OUTER JOIN routepartition rp ON np.fkroutepartition = rp.pkid + WHERE np.dnorpattern LIKE '%.%' + AND rp.name IS NOT NULL + """ + try: + dotted = client.execute_sql_query(dotted_sql) + except Exception: + dotted = {"rows": []} + for row in dotted["rows"]: + pat = row.get("pattern") or "" + if pat.replace(".", "") == dest: + partitions.append(row["partition"]) + matched_pattern_form = pat + # Dedupe in case the same partition holds multiple dot-variants + partitions = list(dict.fromkeys(partitions)) + if not partitions: return ( - f"Destination {dest!r} matches no exact-literal pattern in any " - f"partition. Either the destination string is wrong or it " - f"matches a wildcard pattern (use route_translation_chain to " - f"investigate further)." + f"Destination {dest!r} matches no exact-literal or " + f"dot-stripped pattern in any partition. Either the " + f"destination string is wrong or it matches a wildcard " + f"pattern (use route_translation_chain to investigate " + f"further)." ) + cited = ( + f"Pattern {matched_pattern_form!r} (matches destination {dest!r})" + if matched_pattern_form != dest + else f"Pattern {dest!r}" + ) + if len(partitions) == 1: part = partitions[0] return ( - f"Pattern {dest!r} lives in partition {part!r}. Either add " + f"{cited} lives in partition {part!r}. Either add " f"{part!r} to CSS {broken_css!r}, OR change the forward CSS " f"to a CSS that already contains {part!r}." ) return ( - f"Pattern {dest!r} exists in multiple partitions ({', '.join(partitions)}). " + f"{cited} exists in multiple partitions ({', '.join(partitions)}). " f"Identify the intended target partition, then either add it to " f"CSS {broken_css!r} or change the forward CSS accordingly." ) diff --git a/tests/test_cti_failsafe_reachability.py b/tests/test_cti_failsafe_reachability.py index 5138d87..717132c 100644 --- a/tests/test_cti_failsafe_reachability.py +++ b/tests/test_cti_failsafe_reachability.py @@ -32,7 +32,11 @@ class FakeAxlClient: - reachable_destinations: set of (destination, css) pairs that have a matching pattern (translation_chain returns match_count > 0 for these) - destination_partitions: dict {destination: [partition_name, ...]} - used by the _suggest_failsafe_fix's partition-lookup query + for the exact-literal partition-lookup query in _suggest_failsafe_fix + - dotted_patterns: list of (pattern, partition) tuples for the + dot-stripped lookup. The pattern includes literal dots (e.g. + ``"10.911"``); _suggest_failsafe_fix strips dots and compares to + the destination """ def __init__( @@ -40,10 +44,12 @@ class FakeAxlClient: cti_rp_rows: list[dict], reachable_destinations: set[tuple[str, str]] | None = None, destination_partitions: dict[str, list[str]] | None = None, + dotted_patterns: list[tuple[str, str]] | None = None, ): self._cti_rows = cti_rp_rows self._reachable = reachable_destinations or set() self._dest_partitions = destination_partitions or {} + self._dotted_patterns = dotted_patterns or [] self.queries: list[str] = [] def execute_sql_query(self, sql: str) -> dict: @@ -56,19 +62,8 @@ class FakeAxlClient: # 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: - # Extract the destination + CSS from the SQL to figure out - # whether to return a "match" row or no rows. The destination - # appears in the called-side filter; the CSS appears in the - # callingsearchspace WHERE clause. - # - # Simplest dispatch: scan the query for the (dest, css) pairs - # we know are reachable. If any match, return a fake matching - # pattern row. for dest, css in self._reachable: if f"name = '{css}'" in sql: - # For each reachable destination, the test fake returns - # a single pattern that exactly equals the destination - # so translation_chain's wildcard matcher resolves it. return { "row_count": 1, "rows": [{ @@ -85,9 +80,19 @@ class FakeAxlClient: } return {"row_count": 0, "rows": []} - # Dispatch 3: _suggest_failsafe_fix's partition-lookup query + # Dispatch 3a: _suggest_failsafe_fix's dot-stripped lookup + # (Stage 2 of the fix-suggestion logic — pulls all dot-containing + # patterns and filters Python-side) + if "np.dnorpattern LIKE '%.%'" in sql: + rows = [ + {"pattern": pat, "partition": part} + for pat, part in self._dotted_patterns + ] + return {"row_count": len(rows), "rows": rows} + + # Dispatch 3b: _suggest_failsafe_fix's exact-literal lookup + # (Stage 1 — exact match on np.dnorpattern) if "rp.name IS NOT NULL" in sql and "np.dnorpattern" in sql: - # Extract the dnorpattern literal from the SQL for dest, parts in self._dest_partitions.items(): if f"np.dnorpattern = '{dest}'" in sql: rows = [{"partition": p} for p in parts] @@ -277,17 +282,19 @@ class TestCtiFailsafeReachability: def test_suggested_fix_when_no_partition_holds_destination(self): # Edge case: destination doesn't match any literal pattern - # (might match a wildcard, but not an exact-literal). Suggest_fix - # falls back to a generic message. + # OR any dot-stripped variant (might match a wildcard, but not + # something exact). Falls back to the wildcard-investigation + # generic message. client = FakeAxlClient( cti_rp_rows=[ _cti_row("Wild-RP", "Generic", cfna="orphan-dest", cfna_css="BadCSS"), ], destination_partitions={}, # no partition holds 'orphan-dest' + # dotted_patterns defaults to [] → no dot-stripped match either ) result = cti_failsafe_reachability(client) fix = result["findings"][0]["suggested_fix"] - assert "matches no exact-literal pattern" in fix + assert "no exact-literal or dot-stripped pattern" in fix assert "wildcard" in fix.lower() def test_suggested_fix_when_destination_in_multiple_partitions(self): @@ -313,3 +320,104 @@ class TestCtiFailsafeReachability: # documented, and the life-safety token list is named. assert "CFB" in result["_note"] assert "emergency" in result["_note"] + + +# ─── Dot-stripped fix-suggestion (cti-audit-prompts/004 limitation) ──── +# +# The CUCM separator-dot in patterns like `10.911` is purely visual — +# it represents access-code boundary, not a digit. A destination string +# `10911` (no dot) should match a configured pattern `10.911`. The +# original _suggest_failsafe_fix only did exact-literal lookups and +# missed this; the live Bingham smoke-test surfaced the limitation on +# `912-CTI-RP`. These tests pin the dot-stripped fallback behavior. + +class TestDotStrippedFixSuggestion: + + def test_dot_stripped_match_cites_dotted_pattern(self): + # Destination "10911" should match pattern "10.911" via dot-strip + client = FakeAxlClient( + cti_rp_rows=[ + _cti_row("Test-RP", "Generic", cfna="10911", cfna_css="BadCSS"), + ], + destination_partitions={}, # no exact-literal match + dotted_patterns=[("10.911", "CER911-PT")], + ) + result = cti_failsafe_reachability(client) + fix = result["findings"][0]["suggested_fix"] + # The fix message names BOTH the pattern form and the destination + # so the operator sees what the dot-strip matched + assert "'10.911'" in fix + assert "'10911'" in fix + assert "CER911-PT" in fix + assert "BadCSS" in fix + + def test_exact_literal_takes_precedence_over_dotted(self): + # If both an exact-literal match and a dotted match exist, the + # exact-literal wins — no need to mention the dotted form + client = FakeAxlClient( + cti_rp_rows=[ + _cti_row("Test-RP", "Generic", cfna="912", cfna_css="BadCSS"), + ], + destination_partitions={"912": ["911CER-PT"]}, + dotted_patterns=[("9.12", "Decoy-PT")], # would match if dotted ran + ) + result = cti_failsafe_reachability(client) + fix = result["findings"][0]["suggested_fix"] + assert "911CER-PT" in fix + # Decoy-PT shouldn't appear — exact-literal should short-circuit + assert "Decoy-PT" not in fix + + def test_dotted_match_with_multiple_partitions(self): + # If the same dotted pattern exists in multiple partitions, the + # multi-partition message format applies — same as exact-literal + client = FakeAxlClient( + cti_rp_rows=[ + _cti_row("Test-RP", "Generic", cfna="10911", cfna_css="BadCSS"), + ], + destination_partitions={}, + dotted_patterns=[ + ("10.911", "Site-A-PT"), + ("10.911", "Site-B-PT"), + ], + ) + result = cti_failsafe_reachability(client) + fix = result["findings"][0]["suggested_fix"] + assert "multiple partitions" in fix + assert "Site-A-PT" in fix + assert "Site-B-PT" in fix + + def test_no_exact_no_dotted_falls_back_to_generic(self): + # Neither exact-literal nor dot-stripped lookup finds a match + # → fall back to the wildcard-investigation generic message + client = FakeAxlClient( + cti_rp_rows=[ + _cti_row("Test-RP", "Generic", cfna="60003", cfna_css="BadCSS"), + ], + destination_partitions={}, + dotted_patterns=[], + ) + result = cti_failsafe_reachability(client) + fix = result["findings"][0]["suggested_fix"] + assert "no exact-literal or dot-stripped pattern" in fix + assert "wildcard pattern" in fix.lower() + + def test_dotted_pattern_with_irrelevant_dot_does_not_match(self): + # Pattern "1.0911" has a dot but its dot-stripped form is "10911" + # — should match. Pattern "1.0912" stripped is "10912" — should NOT. + # This exercises the substring-equality logic. + client = FakeAxlClient( + cti_rp_rows=[ + _cti_row("Test-RP", "Generic", cfna="10911", cfna_css="BadCSS"), + ], + destination_partitions={}, + dotted_patterns=[ + ("1.0911", "Match-PT"), # strips to "10911" → matches + ("1.0912", "Nonmatch-PT"), # strips to "10912" → no match + ("10.91", "AnotherMatch-PT"), # strips to "1091" → no match + ], + ) + result = cti_failsafe_reachability(client) + fix = result["findings"][0]["suggested_fix"] + assert "Match-PT" in fix + assert "Nonmatch-PT" not in fix + assert "AnotherMatch-PT" not in fix