prompts: add dead_dn_finder (cross-server, story C from threads)
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.
This commit is contained in:
parent
a07f8c7291
commit
ee1e058559
@ -20,6 +20,7 @@ shim is where the parameter contract lives.
|
|||||||
from . import (
|
from . import (
|
||||||
audit_routing,
|
audit_routing,
|
||||||
cucm_sql_help,
|
cucm_sql_help,
|
||||||
|
dead_dn_finder,
|
||||||
did_block_overlap,
|
did_block_overlap,
|
||||||
hunt_pilot_audit,
|
hunt_pilot_audit,
|
||||||
inbound_did_audit,
|
inbound_did_audit,
|
||||||
@ -35,6 +36,7 @@ from . import (
|
|||||||
__all__ = [
|
__all__ = [
|
||||||
"audit_routing",
|
"audit_routing",
|
||||||
"cucm_sql_help",
|
"cucm_sql_help",
|
||||||
|
"dead_dn_finder",
|
||||||
"did_block_overlap",
|
"did_block_overlap",
|
||||||
"hunt_pilot_audit",
|
"hunt_pilot_audit",
|
||||||
"inbound_did_audit",
|
"inbound_did_audit",
|
||||||
|
|||||||
230
src/mcaxl/prompts/dead_dn_finder.py
Normal file
230
src/mcaxl/prompts/dead_dn_finder.py
Normal file
@ -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", "<unit>
|
||||||
|
Paging", "Code <X> 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.
|
||||||
|
"""
|
||||||
@ -560,6 +560,24 @@ def did_block_overlap(block_pattern: str) -> str:
|
|||||||
return _prompts.did_block_overlap.render(_docs, block_pattern)
|
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
|
@mcp.prompt
|
||||||
def partition_summary(partition_name: str | None = None) -> str:
|
def partition_summary(partition_name: str | None = None) -> str:
|
||||||
"""Compose a "what is this partition for?" report from existing
|
"""Compose a "what is this partition for?" report from existing
|
||||||
|
|||||||
@ -171,6 +171,7 @@ def test_all_prompts_registered_in_server():
|
|||||||
"whoami",
|
"whoami",
|
||||||
"did_block_overlap",
|
"did_block_overlap",
|
||||||
"partition_summary",
|
"partition_summary",
|
||||||
|
"dead_dn_finder",
|
||||||
}, f"unexpected prompt set: {names}"
|
}, f"unexpected prompt set: {names}"
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user