From ee1e058559e8ef989d0d5ae5d188e089e3f6691d Mon Sep 17 00:00:00 2001 From: Ryan Malloy Date: Tue, 5 May 2026 19:24:23 -0600 Subject: [PATCH] prompts: add dead_dn_finder (cross-server, story C from threads) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit First cross-server prompt landing. Story C from axl/agent-threads/cross-server-prompts/. Closes a concrete cucx-docs open finding (orphaned paging DNs 1302 / 1304 at /systems/paging/ carried as "confirm with operator"). A DN is "definitively dead" when: 1. It exists in numplan but has no devicenumplanmap entry — no device claims it as a line (mcaxl, required) 2. It is not referenced as a UCCX trigger entry point (mcuccx, strongly recommended) 3. It has no recent CDR activity (mcsiphon, optional) Each layer narrows the candidate set; the intersection is "safe to retire — verified across CUCM dial plan + UCCX contact center + CDR activity." The prompt embodies the architectural decisions confirmed in cross-server-prompts/002: - Per-primary-lens placement (Q1): lives in mcaxl because the dial plan is its primary lens - Graceful degradation with explicit gaps (Q2): the verdict declares MCP availability up-front and adjusts confidence per sibling; distinguishes connected-but-broken (include error) from not-connected (note "unavailable") - Normal naming, no cross_* prefix (Q3): just "dead_dn_finder" Tier output: "definitively dead" / "likely dead, partial coverage" / "structurally orphan, unconfirmed" / "active". The cucx-docs paging DNs (1302, 1304) get explicit name-callouts in the verdict if they appear, closing the loop back to /systems/paging/. Tests: registration sentinel updated to 13 prompts. 238/238 passing. Live-cluster smoke test pending — cucx-docs will run against the Bingham cluster once they consume this thread direction. --- src/mcaxl/prompts/__init__.py | 2 + src/mcaxl/prompts/dead_dn_finder.py | 230 ++++++++++++++++++++++++++++ src/mcaxl/server.py | 18 +++ tests/test_prompts_package.py | 1 + 4 files changed, 251 insertions(+) create mode 100644 src/mcaxl/prompts/dead_dn_finder.py diff --git a/src/mcaxl/prompts/__init__.py b/src/mcaxl/prompts/__init__.py index 4856cc3..837eec1 100644 --- a/src/mcaxl/prompts/__init__.py +++ b/src/mcaxl/prompts/__init__.py @@ -20,6 +20,7 @@ shim is where the parameter contract lives. from . import ( audit_routing, cucm_sql_help, + dead_dn_finder, did_block_overlap, hunt_pilot_audit, inbound_did_audit, @@ -35,6 +36,7 @@ from . import ( __all__ = [ "audit_routing", "cucm_sql_help", + "dead_dn_finder", "did_block_overlap", "hunt_pilot_audit", "inbound_did_audit", diff --git a/src/mcaxl/prompts/dead_dn_finder.py b/src/mcaxl/prompts/dead_dn_finder.py new file mode 100644 index 0000000..110a4bc --- /dev/null +++ b/src/mcaxl/prompts/dead_dn_finder.py @@ -0,0 +1,230 @@ +"""Cross-server prompt: find DNs that are definitively dead. + +A DN is "definitively dead" when: + + 1. It exists in `numplan` but has no `devicenumplanmap` entry — no + device claims it as a line (mcaxl) + 2. It is not referenced as a UCCX trigger entry point (mcuccx) + 3. It has no recent CDR activity (mcsiphon) + +Each layer narrows the candidate set; the final list is "safe to +retire — verified across CUCM dial plan + UCCX contact center + CDR +activity." Closes findings of the shape "DN exists in numplan but +nobody knows whose it is" (e.g. orphaned paging DNs that were created +for a use-case that's since been retired). + +This is a **cross-server** prompt — see +`axl/agent-threads/cross-server-prompts/` for the architectural +discussion that motivated the per-primary-lens placement (this prompt +lives in mcaxl because the dial plan is its primary lens) and the +graceful-degradation behavior when sibling MCPs are unavailable. +""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from ._common import render_schema_block + +if TYPE_CHECKING: + from ..docs_loader import DocsIndex + + +_KEYWORDS = [ + "directory number", "numplan", "device numplan map", + "orphan DN", "paging", "CTI route point", +] + + +def render(docs: "DocsIndex | None", days_inactive: int = 30) -> str: + """Find DNs with no device, no UCCX trigger, and no recent CDR + activity — the intersection is "definitively dead." + + Args: + days_inactive: window for "no recent CDR activity" check, in days. + Default 30. Operator may widen for low-volume DNs (paging, + emergency lines) where 30 days is genuinely quiet. + + Required MCP servers: + - mcaxl (always — this prompt lives in mcaxl, primary data source) + + Strongly recommended: + - mcuccx — cross-checks UCCX trigger references. Without it, the + prompt cannot rule out "this DN is a UCCX trigger entry point + even though no CUCM device claims it." + + Optional: + - mcsiphon — confirms zero CDR activity in the time window. + Without it, the prompt produces a "structurally orphan" verdict + but cannot confirm operational quietness. + """ + schema_block = render_schema_block( + docs, _KEYWORDS, max_chunks=3, max_chars_per_chunk=800 + ) + + return f"""# Dead-DN Finder (cross-server audit) + +Find directory numbers that are **definitively dead** — exist in the +dial plan but have no claiming device, no UCCX trigger reference, and +no recent CDR activity. The intersection of those three conditions is +"safe to retire" with high confidence. + +## MCP availability — declare up-front in the verdict + +Before producing the final list, **state which sibling MCP servers are +connected** in your output. This determines the verdict-confidence +level: + +- **All three (mcaxl + mcuccx + mcsiphon) connected** → "definitively + dead" verdict possible +- **mcaxl + mcuccx, no mcsiphon** → "structurally orphan, CDR activity + unconfirmed" — operator may want to widen the time window or check + CDRs manually before retiring +- **mcaxl + mcsiphon, no mcuccx** → "no device + no recent activity, + UCCX trigger status unconfirmed" — risk: a paging DN driven by a + UCCX self-service script would falsely qualify +- **mcaxl only** → "structurally orphan from CUCM perspective only" — + weakest verdict; treat as candidates-for-investigation, not + candidates-for-retirement + +If a sibling MCP is **connected but errors mid-call** (different from +not-connected), include the error message in the verdict's confidence +notes — a connected-but-broken sibling is its own state. + +## Step 1 — Structurally-orphan DNs (mcaxl, required) + +Find every DN that exists in `numplan` but has no `devicenumplanmap` +entry claiming it: + +```sql +SELECT + np.dnorpattern AS dn, + rp.name AS partition, + np.description, + tpu.name AS pattern_type, + np.alertingname, + np.fkroutepartition AS partition_pkid +FROM numplan np +LEFT OUTER JOIN routepartition rp ON np.fkroutepartition = rp.pkid +LEFT OUTER JOIN typepatternusage tpu ON np.tkpatternusage = tpu.enum +LEFT OUTER JOIN devicenumplanmap m ON m.fknumplan = np.pkid +WHERE m.fknumplan IS NULL + AND tpu.name = 'Device' -- only true DNs, not route patterns +ORDER BY np.dnorpattern; +``` + +The `LEFT OUTER JOIN ... WHERE m.fknumplan IS NULL` is the +"orphan-detection" idiom — DNs that are NOT in the join table. + +Filter to `tpu.name = 'Device'` because numplan also holds route +patterns, translation patterns, etc. — those are not DNs and aren't +relevant here. Verify the enum name matches by running +`axl_describe_table("typepatternusage")` first if you're unsure. + +## Step 2 — UCCX trigger cross-check (mcuccx, strongly recommended) + +A DN can be a UCCX trigger entry point even with no CUCM device +claiming it — UCCX scripts can answer calls directed at a CTI route +point that fronts the trigger. + +If `mcuccx` is connected, call `list_triggers()` and cross-reference +the result against the orphan candidates from Step 1: + +- Each UCCX trigger has a `cti_route_point_dn` (or similar field — + consult mcuccx's tool output schema) — that's a DN the trigger + answers on +- Remove from the candidate list any DN that appears as a trigger entry + point + +If `mcuccx` is **not connected**, note this explicitly in your verdict: +*"UCCX trigger cross-check unavailable (mcuccx not connected); orphan +DNs in the result MAY include trigger entry points — verify before +retiring any DN that resembles a contact-center entry point."* + +## Step 3 — CDR activity confirmation (mcsiphon, optional) + +If `mcsiphon` is connected, for each remaining candidate DN, check +recent CDR activity via `cdr_query_calls(start, end, called_number=DN)`: + +- Time window: `{days_inactive}` days back from today +- Field to check: count of returned records; if > 0, the DN had + activity (calls placed to it) in the window +- Note: mcsiphon's CDR records use `dateTimeOrigination_iso_local` — + the values are local-cluster-time, NOT UTC (see + `cucm-schema-cheatsheet.md` for the localization convention). Don't + do timezone conversion on the values. + +DNs with zero CDR activity in the window pass this check. + +If `mcsiphon` is **not connected**, note this explicitly: +*"CDR activity confirmation unavailable (mcsiphon not connected); +orphan DNs in the result are structurally orphan but operational +quietness is unverified."* + +## Step 4 — Verdict per candidate DN + +For each remaining DN, produce a structured entry: + +``` +DN: 1302 +Partition: Internal-PT +Description: SNRC Paging +Verdict: definitively dead +Evidence: + - mcaxl: no device claims this DN (no devicenumplanmap entry) + - mcuccx: not referenced as a UCCX trigger + - mcsiphon: zero CDR activity in the last {days_inactive} days +Confidence: high (all three sibling MCPs reported) +Recommended action: safe to retire +``` + +For partial-coverage cases (some MCPs unavailable), the `Evidence` +block lists what was checked and what was skipped, and `Confidence` +adjusts down accordingly. + +## Findings to surface + +Group the output by verdict tier: + +- **Definitively dead** (passed all three available checks) — primary + retirement candidates +- **Likely dead, partial coverage** (passed mcaxl + 1 sibling) — + retirement candidates with a verification step required +- **Structurally orphan, CDR/UCCX unconfirmed** (mcaxl-only) — + candidates for investigation, not retirement +- **Active** (failed any check) — not orphan; do not retire. List + briefly with reason ("appears as UCCX trigger 'PatientIntakeQueue'", + "had 47 calls in the last 30 days", etc.) + +## Common-cause cluster of findings + +Patterns to watch for in the result: + +- **Paging DNs** with descriptions like "All Campus Paging", " + Paging", "Code Page" — frequently created during initial + deployment and never connected to a real device after the paging + product changed (Algo, InformaCast, native CUCM). Often genuinely + dead; sometimes a forgotten endpoint +- **Conference / IVR DNs** — may LOOK orphan from CUCM's perspective + but be UCCX entry points. The mcuccx check is critical here +- **Test / staging DNs** with descriptions naming a tester or a date + — almost always safe to retire if the date is > 1 year old +- **Single-DN range patterns** that look like wildcard escapes — verify + the pattern is actually a DN (`tpu.name = 'Device'`) and not a route + pattern that happened to match the orphan-detection query + +## Reference: DN-vs-pattern semantics, devicenumplanmap join + +""" + schema_block + """ + +Produce a structured findings report grouped by verdict tier. Lead +with the MCP-availability declaration so the operator immediately +sees the confidence level. For each definitively-dead DN, include the +recommended action. For active DNs, list briefly to confirm they were +checked but ruled out — don't enumerate every active line. + +If `1302` (SNRC Paging) or `1304` (All Campus Paging) appear in the +candidate set, those are explicitly tracked findings on the cucx-docs +side — flag them by name in the verdict so the loop closes back to the +`/systems/paging/` page. +""" diff --git a/src/mcaxl/server.py b/src/mcaxl/server.py index b8ade82..bad94cd 100644 --- a/src/mcaxl/server.py +++ b/src/mcaxl/server.py @@ -560,6 +560,24 @@ def did_block_overlap(block_pattern: str) -> str: return _prompts.did_block_overlap.render(_docs, block_pattern) +@mcp.prompt +def dead_dn_finder(days_inactive: int = 30) -> str: + """Find DNs that are definitively dead — exist in numplan but have + no claiming device, no UCCX trigger reference, and no recent CDR + activity. Cross-server prompt: composes mcaxl (required) + mcuccx + (strongly recommended) + mcsiphon (optional). Closes findings of + the shape "DN exists but nobody knows whose it is" — the canonical + Bingham example is paging DNs `1302` / `1304` carried as "confirm + with operator" on `/systems/paging/`. + + Args: + days_inactive: window for "no recent CDR activity" check, in + days (default 30). Widen for low-volume DNs (paging, + emergency lines) where 30 days is genuinely quiet. + """ + return _prompts.dead_dn_finder.render(_docs, days_inactive) + + @mcp.prompt def partition_summary(partition_name: str | None = None) -> str: """Compose a "what is this partition for?" report from existing diff --git a/tests/test_prompts_package.py b/tests/test_prompts_package.py index a2c166a..c3551e2 100644 --- a/tests/test_prompts_package.py +++ b/tests/test_prompts_package.py @@ -171,6 +171,7 @@ def test_all_prompts_registered_in_server(): "whoami", "did_block_overlap", "partition_summary", + "dead_dn_finder", }, f"unexpected prompt set: {names}"