From 1642a0adec8472d82fe2a368afec1b752fb61c82 Mon Sep 17 00:00:00 2001 From: oliver Date: Sun, 20 Sep 2026 09:48:48 +0800 Subject: [PATCH] Merge duplicate if-intf checks into Config Interface Intent. Hide standalone IF VRF from the catalog and point ARP enrich at config_interface so one CLI maps to one check item. Co-authored-by: Cursor --- netx_api/biz_state/parsers/__init__.py | 2 +- netx_api/biz_state/parsers/zte/arp.py | 2 +- netx_api/biz_state/profiles.py | 15 +++++++++------ skills/biz-state/netx-biz-state/SKILL.md | 6 +++--- tests/test_enrich_framework.py | 14 ++++++++------ tests/test_multi_command_arp.py | 9 ++++++--- tests/test_zte_config_intent.py | 3 +++ tests/test_zte_status_parsers.py | 7 ++++++- 8 files changed, 37 insertions(+), 21 deletions(-) diff --git a/netx_api/biz_state/parsers/__init__.py b/netx_api/biz_state/parsers/__init__.py index ad10d1a..7ee1e41 100644 --- a/netx_api/biz_state/parsers/__init__.py +++ b/netx_api/biz_state/parsers/__init__.py @@ -21,7 +21,7 @@ Cross-command (multi aux) 1. Implement each aux as a normal status profile (steps 1–4) 2. On the primary profile:: - aux_commands=[AuxCommand(key="if_intf", profile_id="zte.if_intf")] + aux_commands=[AuxCommand(key="if_intf", profile_id="zte.config_interface")] enrich_joins=[EnrichJoin(from_aux="if_intf", on="interface", take=("vrf",))] 3. CollectSession caches identical concrete CLI in one batch; enrich runs after diff --git a/netx_api/biz_state/parsers/zte/arp.py b/netx_api/biz_state/parsers/zte/arp.py index eb0e6e9..3a08dbe 100644 --- a/netx_api/biz_state/parsers/zte/arp.py +++ b/netx_api/biz_state/parsers/zte/arp.py @@ -1,7 +1,7 @@ """ZTE: show arp [| one-line]. Pipeline: TextFSM ``zte_zxros_show_arp`` → map → hand fallback. -Cross-command VRF comes from profile ``enrich_joins`` (if_intf), not here. +Cross-command VRF comes from profile ``enrich_joins`` (config_interface aux), not here. """ from __future__ import annotations diff --git a/netx_api/biz_state/profiles.py b/netx_api/biz_state/profiles.py index 5c47d2a..d16a335 100644 --- a/netx_api/biz_state/profiles.py +++ b/netx_api/biz_state/profiles.py @@ -605,14 +605,14 @@ def _zte_status_profiles() -> list[ParseProfile]: command_template="show arp | one-line", match=r"(?i)^\s*show\s+arp(?:\s*\|\s*one-line)?\s*$", textfsm_command="show arp", - description="ARP entries (IP/MAC/interface); VRF from if-intf aux.", + description="ARP entries (IP/MAC/interface); VRF from config_interface aux.", fields=list(_ARP_FIELDS), tags=["arp", "l3", "status"], sort_order=320, enabled=True, kind="collect", aux_commands=[ - AuxCommand(key="if_intf", profile_id="zte.if_intf"), + AuxCommand(key="if_intf", profile_id="zte.config_interface"), ], enrich_joins=[ EnrichJoin(from_aux="if_intf", on="interface", take=("vrf",)), @@ -628,13 +628,13 @@ def _zte_status_profiles() -> list[ParseProfile]: match=r"(?i)^\s*show\s+running-config\s+if-intf\s*$", textfsm_command="show running-config if-intf", description=( - "Interface VRF map for ARP enrich (aux). " - "Free-form CLI match prefers zte.config_interface (longer regex)." + "Compat/aux-only interface→VRF map (disabled in catalog). " + "Use Config Interface Intent (zte.config_interface) instead." ), fields=list(_IF_INTF_FIELDS), tags=["interface", "vrf", "config", "status", "aux"], sort_order=325, - enabled=True, + enabled=False, kind="collect", ), ParseProfile( @@ -1202,7 +1202,10 @@ def _zte_status_profiles() -> list[ParseProfile]: command_template="show running-config if-intf", match=r"(?i)^\s*show\s+running-config\s+if-intf(?:\s*\|\s*one-line)?\s*$", textfsm_command="show running-config if-intf", - description="Interface VRF/IP/admin intent (richer than if_intf aux map).", + description=( + "Interface VRF/IP/admin intent; also ARP VRF enrich source " + "(replaces standalone IF VRF / if_intf check)." + ), fields=list(_CONFIG_IFACE_FIELDS), tags=["config", "interface", "intent"], sort_order=510, diff --git a/skills/biz-state/netx-biz-state/SKILL.md b/skills/biz-state/netx-biz-state/SKILL.md index 01f153a..eab5d1b 100644 --- a/skills/biz-state/netx-biz-state/SKILL.md +++ b/skills/biz-state/netx-biz-state/SKILL.md @@ -36,7 +36,7 @@ ParseProfile (命令 + schema + aux + enrich) | `netx_api/biz_state/enrich.py` | 声明式等值 join | | `netx_api/biz_state/collect_runner.py` | 任务会话采集落库 | -样板:`zte/interface_brief`(单命令)、`zte/if_intf`(可复用辅表)、`zte.arp`(主+辅+enrich)。 +样板:`zte/interface_brief`(单命令)、`zte.config_interface`(可复用辅表)、`zte.arp`(主+辅+enrich)。 --- @@ -98,7 +98,7 @@ ParseProfile (命令 + schema + aux + enrich) ```python aux_commands=[ - AuxCommand(key="if_intf", profile_id="zte.if_intf"), + AuxCommand(key="if_intf", profile_id="zte.config_interface"), # 多辅:再 append;key 唯一 ], enrich_joins=[ @@ -159,5 +159,5 @@ Sheet(`metrics_json[]`)能力: - Enrich:`biz_state/enrich.py` - Session:`biz_state/collect_session.py` - Compare:`biz_state/compare_rules.py`、`compare_engine.py`、`compare_service.py` -- ARP+VRF:`profiles.py` → `zte.arp`;`parsers/zte/arp.py`;`parsers/zte/if_intf.py` +- ARP+VRF:`profiles.py` → `zte.arp`;`parsers/zte/arp.py`;辅表 `zte.config_interface`(`zte.if_intf` 已禁用仅兼容) - 模板:`cli_templates/zte/zte_zxros_show_arp.textfsm`、`..._if_intf.textfsm` diff --git a/tests/test_enrich_framework.py b/tests/test_enrich_framework.py index 03a52a5..aa21aae 100644 --- a/tests/test_enrich_framework.py +++ b/tests/test_enrich_framework.py @@ -59,18 +59,20 @@ class AuxResolveTests(unittest.TestCase): reload_profiles() def test_resolve_from_profile_id(self) -> None: - ra = resolve_aux_command(AuxCommand(key="if_intf", profile_id="zte.if_intf")) + ra = resolve_aux_command(AuxCommand(key="if_intf", profile_id="zte.config_interface")) self.assertEqual(ra.command, "show running-config if-intf") - self.assertEqual(ra.parser_id, "if_intf") - self.assertIn("zte_zxros_show_running_config_if_intf", ra.rule_keys) + self.assertEqual(ra.parser_id, "config_interface") def test_arp_profile_slim(self) -> None: p = get_profile("zte.arp") assert p is not None self.assertEqual(p.aux_commands[0].key, "if_intf") - self.assertEqual(p.aux_commands[0].profile_id, "zte.if_intf") + self.assertEqual(p.aux_commands[0].profile_id, "zte.config_interface") self.assertEqual(len(p.enrich_joins), 1) self.assertEqual(p.enrich_joins[0].take, ("vrf",)) + ii = get_profile("zte.if_intf") + assert ii is not None + self.assertFalse(ii.enabled) class CollectSessionCacheTests(unittest.TestCase): @@ -98,13 +100,13 @@ class ArpEnrichPipelineTests(unittest.TestCase): def test_run_primary_with_enrich(self) -> None: if_recs, if_fsm, _ = run_parser( - "if_intf", + "config_interface", raw_text=IF_INTF_SAMPLE, vendor="zte", device_type="zte_zxros", command="show running-config if-intf", ) - ra = resolve_aux_command(AuxCommand(key="if_intf", profile_id="zte.if_intf")) + ra = resolve_aux_command(AuxCommand(key="if_intf", profile_id="zte.config_interface")) bundle = build_parse_bundle( primary_raw=ARP_MATCHING, primary_parser_id="arp", diff --git a/tests/test_multi_command_arp.py b/tests/test_multi_command_arp.py index 0f2bc7d..9f89175 100644 --- a/tests/test_multi_command_arp.py +++ b/tests/test_multi_command_arp.py @@ -97,7 +97,10 @@ class ArpMultiCommandTests(unittest.TestCase): assert p is not None self.assertEqual(len(p.aux_commands), 1) self.assertEqual(p.aux_commands[0].key, "if_intf") - self.assertEqual(p.aux_commands[0].profile_id, "zte.if_intf") + self.assertEqual(p.aux_commands[0].profile_id, "zte.config_interface") + ii = get_profile("zte.if_intf") + assert ii is not None + self.assertFalse(ii.enabled) self.assertEqual(get_parser_meta("if_intf")["rule_keys"], ("zte_zxros_show_running_config_if_intf",)) def test_arp_enriches_vrf_via_aux_records(self) -> None: @@ -110,13 +113,13 @@ class ArpMultiCommandTests(unittest.TestCase): from netx_api.biz_state.profiles import AuxCommand if_recs, if_fsm, _ = run_parser( - "if_intf", + "config_interface", raw_text=IF_INTF_SAMPLE, vendor="zte", device_type="zte_zxros", command="show running-config if-intf", ) - ra = resolve_aux_command(AuxCommand(key="if_intf", profile_id="zte.if_intf")) + ra = resolve_aux_command(AuxCommand(key="if_intf", profile_id="zte.config_interface")) bundle = build_parse_bundle( primary_raw=ARP_MATCHING, primary_parser_id="arp", diff --git a/tests/test_zte_config_intent.py b/tests/test_zte_config_intent.py index f0ba5c6..7a3a220 100644 --- a/tests/test_zte_config_intent.py +++ b/tests/test_zte_config_intent.py @@ -233,6 +233,9 @@ class ZteConfigIntentTests(unittest.TestCase): self.assertIsNotNone(hit) assert hit is not None self.assertEqual(hit.profile.profile_id, "zte.config_interface") + ii = get_profile("zte.if_intf") + assert ii is not None + self.assertFalse(ii.enabled) def test_config_vrf(self) -> None: rows = normalize_config_vrf(raw_text=_CFG_VRF, command="show running-config vrf") diff --git a/tests/test_zte_status_parsers.py b/tests/test_zte_status_parsers.py index ce6dbd4..296f7fa 100644 --- a/tests/test_zte_status_parsers.py +++ b/tests/test_zte_status_parsers.py @@ -54,7 +54,12 @@ class ZteStatusParserTests(unittest.TestCase): for mid in ("isis_adjacency", "interface_brief", "arp", "nd6_cache", "bgp_peer", "if_intf"): self.assertIn(mid, metric_field_map()) self.assertIsNotNone(get_profile("zte.isis_adjacency")) - self.assertIsNotNone(get_profile("zte.if_intf")) + ii = get_profile("zte.if_intf") + self.assertIsNotNone(ii) + assert ii is not None + self.assertFalse(ii.enabled) + self.assertNotIn("zte.if_intf", zte) + self.assertIn("zte.config_interface", zte) def test_isis_adjacency(self) -> None: text = _section(