packet-tracer-skill 0.2.2 → 0.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +32 -1
- package/README.md +221 -51
- package/docs/automation-controller-proof.md +35 -0
- package/docs/curated-donor-registry.md +11 -0
- package/docs/generate-ready-pilot-design.md +30 -0
- package/docs/industrial-programming-proof.md +48 -0
- package/docs/ipv4-routing-management-proof.md +37 -0
- package/docs/l2-resiliency-bgp-proof.md +60 -0
- package/docs/l2-security-qos-proof.md +59 -0
- package/docs/packet-tracer-feature-gap-atlas.md +172 -15
- package/docs/release-checklist.md +13 -8
- package/docs/release-notes-0.2.2.md +1 -1
- package/docs/release-notes-0.2.3.md +59 -0
- package/docs/security-edge-deepening-proof.md +65 -0
- package/docs/voice-collaboration-proof.md +38 -0
- package/docs/wan-security-donor-proof.md +20 -3
- package/package.json +10 -1
- package/references/packettracer-feature-atlas.json +67 -17
- package/scripts/coverage_matrix.py +505 -12
- package/scripts/feature_atlas.py +65 -1
- package/scripts/generate_pkt.py +161 -3
- package/scripts/intent_parser.py +521 -2
- package/scripts/pkt_editor.py +477 -0
- package/scripts/remote_search.py +197 -21
- package/scripts/sample_catalog.py +57 -2
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import re
|
|
3
4
|
from dataclasses import asdict, dataclass, field
|
|
4
5
|
from typing import Any
|
|
5
6
|
|
|
@@ -26,6 +27,9 @@ DEVICE_FAMILY_MAP = {
|
|
|
26
27
|
"Tablet": "end devices",
|
|
27
28
|
"Smartphone": "end devices",
|
|
28
29
|
"Printer": "end devices",
|
|
30
|
+
"IpPhone": "end devices",
|
|
31
|
+
"HomeVoip": "end devices",
|
|
32
|
+
"AnalogPhone": "end devices",
|
|
29
33
|
"LightWeightAccessPoint": "access points",
|
|
30
34
|
"WirelessRouter": "home/wireless routers",
|
|
31
35
|
"WirelessRouterNewGeneration": "home/wireless routers",
|
|
@@ -77,6 +81,26 @@ REPORT_ONLY_CAPABILITIES = {
|
|
|
77
81
|
"span",
|
|
78
82
|
"qos",
|
|
79
83
|
"port_security",
|
|
84
|
+
"bgp",
|
|
85
|
+
"stp",
|
|
86
|
+
"rstp",
|
|
87
|
+
"etherchannel",
|
|
88
|
+
"lacp",
|
|
89
|
+
"pagp",
|
|
90
|
+
"vtp",
|
|
91
|
+
"dtp",
|
|
92
|
+
"ospfv2",
|
|
93
|
+
"eigrp_ipv4",
|
|
94
|
+
"ripv2",
|
|
95
|
+
"static_route",
|
|
96
|
+
"default_route",
|
|
97
|
+
"dhcp_relay",
|
|
98
|
+
"nat_static",
|
|
99
|
+
"nat_dynamic",
|
|
100
|
+
"pat",
|
|
101
|
+
"ssh_ios",
|
|
102
|
+
"ntp_ios",
|
|
103
|
+
"syslog_ios",
|
|
80
104
|
"asa_acl_nat",
|
|
81
105
|
"asa_service_policy",
|
|
82
106
|
"clientless_vpn",
|
|
@@ -127,21 +151,108 @@ IPV6_DONOR_BACKED_CAPABILITIES = {
|
|
|
127
151
|
"ripng",
|
|
128
152
|
"hsrp",
|
|
129
153
|
}
|
|
154
|
+
IPV6_DONOR_BACKED_READY_EDIT_CAPABILITIES = {
|
|
155
|
+
"ospfv3",
|
|
156
|
+
"eigrp_ipv6",
|
|
157
|
+
"ripng",
|
|
158
|
+
"hsrp",
|
|
159
|
+
}
|
|
130
160
|
L2_SECURITY_MONITORING_EDIT_PROVEN_CAPABILITIES = {
|
|
131
161
|
"dhcp_snooping",
|
|
132
162
|
"dai",
|
|
163
|
+
"dot1x",
|
|
133
164
|
"lldp",
|
|
134
165
|
"rep",
|
|
135
166
|
"snmp",
|
|
136
167
|
"netflow",
|
|
137
168
|
"span",
|
|
169
|
+
"qos",
|
|
138
170
|
"port_security",
|
|
139
171
|
}
|
|
140
172
|
WIRELESS_ADVANCED_EDIT_PROVEN_CAPABILITIES = {
|
|
141
173
|
"wep",
|
|
142
174
|
"wpa_enterprise",
|
|
143
175
|
}
|
|
144
|
-
|
|
176
|
+
PROGRAMMING_EDIT_PROVEN_CAPABILITIES = {
|
|
177
|
+
"real_http",
|
|
178
|
+
"real_websocket",
|
|
179
|
+
"python_programming",
|
|
180
|
+
"javascript_programming",
|
|
181
|
+
"tcp_udp_app",
|
|
182
|
+
}
|
|
183
|
+
AUTOMATION_CONTROLLER_EDIT_PROVEN_CAPABILITIES = {
|
|
184
|
+
"python_programming",
|
|
185
|
+
"javascript_programming",
|
|
186
|
+
"tcp_udp_app",
|
|
187
|
+
}
|
|
188
|
+
PROGRAMMING_DONOR_BACKED_READY_CAPABILITIES = {
|
|
189
|
+
"real_http",
|
|
190
|
+
"real_websocket",
|
|
191
|
+
"python_programming",
|
|
192
|
+
"javascript_programming",
|
|
193
|
+
"tcp_udp_app",
|
|
194
|
+
}
|
|
195
|
+
L2_SECURITY_MONITORING_DONOR_BACKED_READY_CAPABILITIES = {
|
|
196
|
+
"dot1x",
|
|
197
|
+
}
|
|
198
|
+
WAN_SECURITY_DONOR_BACKED_READY_EDIT_CAPABILITIES = {
|
|
199
|
+
"zfw",
|
|
200
|
+
}
|
|
201
|
+
VOICE_COLLABORATION_DONOR_BACKED_READY_CAPABILITIES = {
|
|
202
|
+
"voip",
|
|
203
|
+
"ip_phone",
|
|
204
|
+
"call_manager",
|
|
205
|
+
}
|
|
206
|
+
VOICE_COLLABORATION_EDIT_PROVEN_CAPABILITIES = {
|
|
207
|
+
"voip",
|
|
208
|
+
"ip_phone",
|
|
209
|
+
"call_manager",
|
|
210
|
+
}
|
|
211
|
+
WAN_SECURITY_EDIT_PROVEN_CAPABILITIES = {
|
|
212
|
+
"gre",
|
|
213
|
+
"ppp",
|
|
214
|
+
"ipsec",
|
|
215
|
+
"vpn",
|
|
216
|
+
"cbac",
|
|
217
|
+
"zfw",
|
|
218
|
+
}
|
|
219
|
+
L2_RESILIENCY_ROUTING_EDIT_PROVEN_CAPABILITIES = {
|
|
220
|
+
"bgp",
|
|
221
|
+
"stp",
|
|
222
|
+
"rstp",
|
|
223
|
+
"etherchannel",
|
|
224
|
+
"lacp",
|
|
225
|
+
"pagp",
|
|
226
|
+
"vtp",
|
|
227
|
+
"dtp",
|
|
228
|
+
}
|
|
229
|
+
IPV4_ROUTING_MANAGEMENT_EDIT_PROVEN_CAPABILITIES = {
|
|
230
|
+
"ospfv2",
|
|
231
|
+
"eigrp_ipv4",
|
|
232
|
+
"ripv2",
|
|
233
|
+
"static_route",
|
|
234
|
+
"default_route",
|
|
235
|
+
"dhcp_relay",
|
|
236
|
+
"nat_static",
|
|
237
|
+
"nat_dynamic",
|
|
238
|
+
"pat",
|
|
239
|
+
"ssh_ios",
|
|
240
|
+
"ntp_ios",
|
|
241
|
+
"syslog_ios",
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
WAN_SECURITY_DONOR_LIMITED_CAPABILITIES = {
|
|
245
|
+
"gre",
|
|
246
|
+
"ppp",
|
|
247
|
+
"ipsec",
|
|
248
|
+
"vpn",
|
|
249
|
+
"security_edge",
|
|
250
|
+
"multilayer_switching",
|
|
251
|
+
}
|
|
252
|
+
ALWAYS_EDIT_PROVEN_REPORT_CAPABILITIES = IPV6_DONOR_BACKED_CAPABILITIES | (
|
|
253
|
+
L2_SECURITY_MONITORING_EDIT_PROVEN_CAPABILITIES - {"dot1x", "qos"}
|
|
254
|
+
)
|
|
255
|
+
EDIT_PROVEN_REPORT_CAPABILITIES = ALWAYS_EDIT_PROVEN_REPORT_CAPABILITIES
|
|
145
256
|
|
|
146
257
|
CAPABILITY_PROVIDER_FAMILIES = {
|
|
147
258
|
"access_port": {"switches", "multilayer switches"},
|
|
@@ -161,6 +272,25 @@ CAPABILITY_PROVIDER_FAMILIES = {
|
|
|
161
272
|
"ipsec": {"routers", "security devices", "wan/cloud/dsl/cable devices"},
|
|
162
273
|
"gre": {"routers", "wan/cloud/dsl/cable devices"},
|
|
163
274
|
"ppp": {"routers", "wan/cloud/dsl/cable devices"},
|
|
275
|
+
"bgp": {"routers", "multilayer switches"},
|
|
276
|
+
"stp": {"switches", "multilayer switches"},
|
|
277
|
+
"rstp": {"switches", "multilayer switches"},
|
|
278
|
+
"etherchannel": {"switches", "multilayer switches"},
|
|
279
|
+
"lacp": {"switches", "multilayer switches"},
|
|
280
|
+
"pagp": {"switches", "multilayer switches"},
|
|
281
|
+
"vtp": {"switches", "multilayer switches"},
|
|
282
|
+
"dtp": {"switches", "multilayer switches"},
|
|
283
|
+
"ospfv2": {"routers", "multilayer switches"},
|
|
284
|
+
"eigrp_ipv4": {"routers", "multilayer switches"},
|
|
285
|
+
"ripv2": {"routers", "multilayer switches"},
|
|
286
|
+
"static_route": {"routers", "multilayer switches", "security devices"},
|
|
287
|
+
"default_route": {"routers", "multilayer switches", "security devices"},
|
|
288
|
+
"dhcp_relay": {"routers", "multilayer switches"},
|
|
289
|
+
"nat_static": {"routers", "security devices"},
|
|
290
|
+
"nat_dynamic": {"routers", "security devices"},
|
|
291
|
+
"ssh_ios": {"routers", "switches", "multilayer switches"},
|
|
292
|
+
"ntp_ios": {"routers", "switches", "multilayer switches"},
|
|
293
|
+
"syslog_ios": {"routers", "switches", "multilayer switches"},
|
|
164
294
|
"multilayer_switching": {"multilayer switches"},
|
|
165
295
|
"wan": {"wan/cloud/dsl/cable devices", "routers"},
|
|
166
296
|
"security_edge": {"security devices", "routers"},
|
|
@@ -278,6 +408,33 @@ class CoverageGapReport:
|
|
|
278
408
|
scenario_generate_readiness: dict[str, object] = field(default_factory=dict)
|
|
279
409
|
recommended_next_actions: list[str] = field(default_factory=list)
|
|
280
410
|
|
|
411
|
+
@property
|
|
412
|
+
def critical_capability_parity(self) -> list[dict[str, object]]:
|
|
413
|
+
critical = set(self.scenario_generate_readiness.get("critical_capabilities") or [])
|
|
414
|
+
if not critical:
|
|
415
|
+
return []
|
|
416
|
+
return [entry for entry in self.capability_parity if entry.get("capability") in critical]
|
|
417
|
+
|
|
418
|
+
@property
|
|
419
|
+
def critical_parity_supported_count(self) -> int:
|
|
420
|
+
return sum(1 for entry in self.critical_capability_parity if entry.get("inventory_supported") or entry.get("edit_supported"))
|
|
421
|
+
|
|
422
|
+
@property
|
|
423
|
+
def critical_parity_generate_ready_count(self) -> int:
|
|
424
|
+
return sum(1 for entry in self.critical_capability_parity if entry.get("generate_supported"))
|
|
425
|
+
|
|
426
|
+
@property
|
|
427
|
+
def critical_parity_acceptance_verified_count(self) -> int:
|
|
428
|
+
return sum(1 for entry in self.critical_capability_parity if entry.get("acceptance_verified"))
|
|
429
|
+
|
|
430
|
+
@property
|
|
431
|
+
def critical_parity_donor_backed_ready_count(self) -> int:
|
|
432
|
+
return sum(1 for entry in self.critical_capability_parity if entry.get("donor_backed_ready"))
|
|
433
|
+
|
|
434
|
+
@property
|
|
435
|
+
def critical_parity_mismatch_count(self) -> int:
|
|
436
|
+
return sum(1 for entry in self.critical_capability_parity if entry.get("generate_mismatch_reason"))
|
|
437
|
+
|
|
281
438
|
|
|
282
439
|
@dataclass
|
|
283
440
|
class BlueprintPlan:
|
|
@@ -457,10 +614,12 @@ def _scenario_family_for_plan(plan: IntentPlan, requested_families: list[str]) -
|
|
|
457
614
|
)
|
|
458
615
|
wan_signal = (
|
|
459
616
|
plan.network_style == "wan_security"
|
|
460
|
-
or capabilities & {"vpn", "ipsec", "gre", "ppp", "multilayer_switching", "security_edge"}
|
|
617
|
+
or capabilities & {"vpn", "ipsec", "gre", "ppp", "multilayer_switching", "security_edge", "cbac", "zfw"}
|
|
461
618
|
or {"wan/cloud/dsl/cable devices", "security devices"} & requested_family_set
|
|
462
619
|
)
|
|
463
620
|
ipv6_routing_signal = plan.network_style == "ipv6_routing" or capabilities & {"ipv6_slaac", "dhcpv6_stateful", "dhcpv6_stateless", "ipv6_prefix_delegation", "ipv6_dns_aaaa", "ipv6_tunneling", "isatap", "ospfv3", "eigrp_ipv6", "ripng", "hsrp"}
|
|
621
|
+
ipv4_routing_management_signal = plan.network_style == "ipv4_routing_management" or capabilities & {"ospfv2", "eigrp_ipv4", "ripv2", "static_route", "default_route", "dhcp_relay", "nat_static", "nat_dynamic", "pat", "ssh_ios", "ntp_ios", "syslog_ios"}
|
|
622
|
+
l2_resiliency_routing_signal = plan.network_style == "l2_resiliency_routing" or capabilities & {"bgp", "stp", "rstp", "etherchannel", "lacp", "pagp", "vtp", "dtp"}
|
|
464
623
|
l2_security_monitoring_signal = plan.network_style == "l2_security_monitoring" or capabilities & {"dhcp_snooping", "dai", "dot1x", "lldp", "rep", "snmp", "netflow", "span", "qos", "port_security"}
|
|
465
624
|
wireless_advanced_signal = plan.network_style == "wireless_advanced" or capabilities & {"wlc", "wpa_enterprise", "wep", "guest_wifi", "beamforming", "meraki", "cellular_5g", "bluetooth"}
|
|
466
625
|
automation_controller_signal = plan.network_style == "automation_controller" or capabilities & {"network_controller", "python_programming", "javascript_programming", "blockly_programming", "tcp_udp_app", "vm_iox"}
|
|
@@ -479,18 +638,22 @@ def _scenario_family_for_plan(plan: IntentPlan, requested_families: list[str]) -
|
|
|
479
638
|
return "automation_controller"
|
|
480
639
|
if voice_collaboration_signal:
|
|
481
640
|
return "voice_collaboration"
|
|
641
|
+
if physical_media_device_signal:
|
|
642
|
+
return "physical_media_device"
|
|
482
643
|
if wireless_advanced_signal:
|
|
483
644
|
return "wireless_advanced"
|
|
645
|
+
if wan_signal:
|
|
646
|
+
return "wan_security_edge"
|
|
647
|
+
if l2_resiliency_routing_signal:
|
|
648
|
+
return "l2_resiliency_routing"
|
|
649
|
+
if ipv4_routing_management_signal:
|
|
650
|
+
return "ipv4_routing_management"
|
|
484
651
|
if l2_security_monitoring_signal:
|
|
485
652
|
return "l2_security_monitoring"
|
|
486
653
|
if ipv6_routing_signal:
|
|
487
654
|
return "ipv6_routing"
|
|
488
|
-
if physical_media_device_signal:
|
|
489
|
-
return "physical_media_device"
|
|
490
655
|
if home_iot_signal:
|
|
491
656
|
return "home_iot"
|
|
492
|
-
if wan_signal:
|
|
493
|
-
return "wan_security_edge"
|
|
494
657
|
if campus_signal:
|
|
495
658
|
return "campus"
|
|
496
659
|
if service_signal:
|
|
@@ -502,8 +665,10 @@ SCENARIO_CAPABILITY_SETS = {
|
|
|
502
665
|
"campus": {"router_on_a_stick", "trunk", "access_port", "management_vlan", "telnet", "verification", "acl", "nat"},
|
|
503
666
|
"service_heavy": {"server_dns", "server_dhcp", "server_http", "server_https", "server_ftp", "server_tftp", "server_email", "server_syslog", "server_aaa", "ntp"},
|
|
504
667
|
"home_iot": {"iot", "iot_registration", "iot_control", "wireless_ap", "wireless_mutation"},
|
|
505
|
-
"wan_security_edge": {"vpn", "ipsec", "gre", "ppp", "acl", "nat"},
|
|
668
|
+
"wan_security_edge": {"vpn", "ipsec", "gre", "ppp", "acl", "nat", "cbac", "zfw"},
|
|
506
669
|
"ipv6_routing": {"ipv6_slaac", "dhcpv6_stateful", "dhcpv6_stateless", "ipv6_prefix_delegation", "ipv6_dns_aaaa", "ipv6_tunneling", "isatap", "ospfv3", "eigrp_ipv6", "ripng", "hsrp"},
|
|
670
|
+
"l2_resiliency_routing": {"bgp", "stp", "rstp", "etherchannel", "lacp", "pagp", "vtp", "dtp"},
|
|
671
|
+
"ipv4_routing_management": {"ospfv2", "eigrp_ipv4", "ripv2", "static_route", "default_route", "dhcp_relay", "nat_static", "nat_dynamic", "pat", "ssh_ios", "ntp_ios", "syslog_ios"},
|
|
507
672
|
"l2_security_monitoring": {"dhcp_snooping", "dai", "dot1x", "lldp", "rep", "snmp", "netflow", "span", "qos", "port_security"},
|
|
508
673
|
"wireless_advanced": {"wlc", "wpa_enterprise", "wep", "guest_wifi", "beamforming", "meraki", "cellular_5g", "bluetooth"},
|
|
509
674
|
"automation_controller": {"network_controller", "python_programming", "javascript_programming", "blockly_programming", "tcp_udp_app", "vm_iox"},
|
|
@@ -535,6 +700,8 @@ CAPABILITY_REQUIRED_ARCHETYPES = {
|
|
|
535
700
|
"ppp": ["WAN/security edge"],
|
|
536
701
|
"multilayer_switching": ["WAN/security edge", "campus/core"],
|
|
537
702
|
"security_edge": ["WAN/security edge"],
|
|
703
|
+
"cbac": ["WAN/security edge"],
|
|
704
|
+
"zfw": ["WAN/security edge"],
|
|
538
705
|
"ipv6_slaac": ["IPv6/routing"],
|
|
539
706
|
"dhcpv6_stateful": ["IPv6/routing"],
|
|
540
707
|
"dhcpv6_stateless": ["IPv6/routing"],
|
|
@@ -556,6 +723,26 @@ CAPABILITY_REQUIRED_ARCHETYPES = {
|
|
|
556
723
|
"span": ["L2 security/monitoring"],
|
|
557
724
|
"qos": ["L2 security/monitoring"],
|
|
558
725
|
"port_security": ["L2 security/monitoring"],
|
|
726
|
+
"bgp": ["L2 resiliency/routing"],
|
|
727
|
+
"stp": ["L2 resiliency/routing"],
|
|
728
|
+
"rstp": ["L2 resiliency/routing"],
|
|
729
|
+
"etherchannel": ["L2 resiliency/routing"],
|
|
730
|
+
"lacp": ["L2 resiliency/routing"],
|
|
731
|
+
"pagp": ["L2 resiliency/routing"],
|
|
732
|
+
"vtp": ["L2 resiliency/routing"],
|
|
733
|
+
"dtp": ["L2 resiliency/routing"],
|
|
734
|
+
"ospfv2": ["IPv4 routing/management"],
|
|
735
|
+
"eigrp_ipv4": ["IPv4 routing/management"],
|
|
736
|
+
"ripv2": ["IPv4 routing/management"],
|
|
737
|
+
"static_route": ["IPv4 routing/management"],
|
|
738
|
+
"default_route": ["IPv4 routing/management"],
|
|
739
|
+
"dhcp_relay": ["IPv4 routing/management"],
|
|
740
|
+
"nat_static": ["IPv4 routing/management"],
|
|
741
|
+
"nat_dynamic": ["IPv4 routing/management"],
|
|
742
|
+
"pat": ["IPv4 routing/management"],
|
|
743
|
+
"ssh_ios": ["IPv4 routing/management"],
|
|
744
|
+
"ntp_ios": ["IPv4 routing/management"],
|
|
745
|
+
"syslog_ios": ["IPv4 routing/management"],
|
|
559
746
|
"wlc": ["advanced wireless"],
|
|
560
747
|
"wpa_enterprise": ["advanced wireless"],
|
|
561
748
|
"wep": ["advanced wireless"],
|
|
@@ -604,6 +791,8 @@ CAPABILITY_REQUIRED_RUNTIME_FEATURES = {
|
|
|
604
791
|
"gre": ["workspace_validated", "tunnel_runtime"],
|
|
605
792
|
"ppp": ["workspace_validated", "wan_runtime"],
|
|
606
793
|
"security_edge": ["workspace_validated", "security_runtime"],
|
|
794
|
+
"cbac": ["workspace_validated", "security_runtime"],
|
|
795
|
+
"zfw": ["workspace_validated", "security_runtime"],
|
|
607
796
|
"multilayer_switching": ["workspace_validated", "multilayer_runtime"],
|
|
608
797
|
"ipv6_slaac": ["workspace_validated", "ipv6_runtime"],
|
|
609
798
|
"dhcpv6_stateful": ["workspace_validated", "ipv6_runtime"],
|
|
@@ -613,6 +802,18 @@ CAPABILITY_REQUIRED_RUNTIME_FEATURES = {
|
|
|
613
802
|
"hsrp": ["workspace_validated", "ipv6_runtime"],
|
|
614
803
|
"wep": ["workspace_validated", "wireless_runtime"],
|
|
615
804
|
"wpa_enterprise": ["workspace_validated", "wireless_runtime"],
|
|
805
|
+
"ospfv2": ["workspace_validated", "ipv4_routing_management_runtime"],
|
|
806
|
+
"eigrp_ipv4": ["workspace_validated", "ipv4_routing_management_runtime"],
|
|
807
|
+
"ripv2": ["workspace_validated", "ipv4_routing_management_runtime"],
|
|
808
|
+
"static_route": ["workspace_validated", "ipv4_routing_management_runtime"],
|
|
809
|
+
"default_route": ["workspace_validated", "ipv4_routing_management_runtime"],
|
|
810
|
+
"dhcp_relay": ["workspace_validated", "ipv4_routing_management_runtime"],
|
|
811
|
+
"nat_static": ["workspace_validated", "ipv4_routing_management_runtime"],
|
|
812
|
+
"nat_dynamic": ["workspace_validated", "ipv4_routing_management_runtime"],
|
|
813
|
+
"pat": ["workspace_validated", "ipv4_routing_management_runtime"],
|
|
814
|
+
"ssh_ios": ["workspace_validated", "ipv4_routing_management_runtime"],
|
|
815
|
+
"ntp_ios": ["workspace_validated", "ipv4_routing_management_runtime"],
|
|
816
|
+
"syslog_ios": ["workspace_validated", "ipv4_routing_management_runtime"],
|
|
616
817
|
}
|
|
617
818
|
|
|
618
819
|
|
|
@@ -621,6 +822,8 @@ def _generate_mismatch_reason(status: dict[str, object]) -> str | None:
|
|
|
621
822
|
return "report_only"
|
|
622
823
|
if not bool(status.get("inventory_supported")):
|
|
623
824
|
return "unsupported"
|
|
825
|
+
if bool(status.get("donor_limited_generate")):
|
|
826
|
+
return "supported_but_donor_limited"
|
|
624
827
|
if bool(status.get("edit_supported")) and not bool(status.get("safe_open_generate_supported")):
|
|
625
828
|
return "supported_in_edit_only"
|
|
626
829
|
if bool(status.get("safe_open_generate_supported")) and bool(status.get("requires_curated_donor")):
|
|
@@ -632,6 +835,10 @@ def _generate_mismatch_reason(status: dict[str, object]) -> str | None:
|
|
|
632
835
|
return "unsupported"
|
|
633
836
|
|
|
634
837
|
|
|
838
|
+
def _donor_backed_ready(status: dict[str, object]) -> bool:
|
|
839
|
+
return bool(status.get("donor_backed_ready"))
|
|
840
|
+
|
|
841
|
+
|
|
635
842
|
def _selected_sample_for_donor(
|
|
636
843
|
samples: list[SampleDescriptor],
|
|
637
844
|
selected_donor: str | None,
|
|
@@ -691,6 +898,159 @@ def _has_explicit_advanced_wireless_edit(plan: IntentPlan, capability: str) -> b
|
|
|
691
898
|
return False
|
|
692
899
|
|
|
693
900
|
|
|
901
|
+
def _has_explicit_programming_edit(plan: IntentPlan, capability: str) -> bool:
|
|
902
|
+
for op in plan.programming_ops:
|
|
903
|
+
if op.get("op") != "set_script_file_content":
|
|
904
|
+
continue
|
|
905
|
+
if not (
|
|
906
|
+
str(op.get("device") or "").strip()
|
|
907
|
+
and str(op.get("app_name") or "").strip()
|
|
908
|
+
and str(op.get("file_name") or "").strip()
|
|
909
|
+
):
|
|
910
|
+
continue
|
|
911
|
+
file_name = str(op.get("file_name") or "").lower()
|
|
912
|
+
app_name = str(op.get("app_name") or "").lower()
|
|
913
|
+
content = str(op.get("content") or "").lower()
|
|
914
|
+
if capability == "python_programming" and (file_name.endswith(".py") or "python" in app_name):
|
|
915
|
+
return True
|
|
916
|
+
if capability == "javascript_programming" and (file_name.endswith(".js") or "javascript" in app_name):
|
|
917
|
+
return True
|
|
918
|
+
script_text = " ".join([file_name, app_name, content])
|
|
919
|
+
if capability == "tcp_udp_app" and ("tcp" in script_text or "udp" in script_text):
|
|
920
|
+
return True
|
|
921
|
+
if capability == "real_http" and ("realhttp" in content or "real http" in app_name):
|
|
922
|
+
return True
|
|
923
|
+
if capability == "real_websocket" and ("realws" in content or "websocket" in content or "websocket" in app_name):
|
|
924
|
+
return True
|
|
925
|
+
return False
|
|
926
|
+
|
|
927
|
+
|
|
928
|
+
def _has_explicit_l2_security_edit(plan: IntentPlan, capability: str) -> bool:
|
|
929
|
+
expected_ops = {
|
|
930
|
+
"dot1x": {"set_dot1x"},
|
|
931
|
+
"qos": {"set_qos_policy"},
|
|
932
|
+
}.get(capability, set())
|
|
933
|
+
if not expected_ops:
|
|
934
|
+
return False
|
|
935
|
+
for op in plan.switch_ops:
|
|
936
|
+
if str(op.get("op")) not in expected_ops:
|
|
937
|
+
continue
|
|
938
|
+
if not str(op.get("device") or "").strip():
|
|
939
|
+
continue
|
|
940
|
+
if capability == "dot1x":
|
|
941
|
+
return bool(str(op.get("interface") or "").strip() and str(op.get("mode") or "").strip())
|
|
942
|
+
if capability == "qos":
|
|
943
|
+
return bool(
|
|
944
|
+
str(op.get("interface") or "").strip()
|
|
945
|
+
and str(op.get("class_map") or "").strip()
|
|
946
|
+
and str(op.get("policy_map") or "").strip()
|
|
947
|
+
and str(op.get("direction") or "").strip()
|
|
948
|
+
)
|
|
949
|
+
return False
|
|
950
|
+
|
|
951
|
+
|
|
952
|
+
def _has_explicit_l2_resiliency_edit(plan: IntentPlan, capability: str) -> bool:
|
|
953
|
+
if capability == "bgp":
|
|
954
|
+
return any(
|
|
955
|
+
op.get("op") == "set_bgp_neighbor"
|
|
956
|
+
and str(op.get("device") or "").strip()
|
|
957
|
+
and str(op.get("neighbor") or "").strip()
|
|
958
|
+
and str(op.get("remote_as") or "").strip()
|
|
959
|
+
for op in plan.router_ops
|
|
960
|
+
)
|
|
961
|
+
for op in plan.switch_ops:
|
|
962
|
+
if not str(op.get("device") or "").strip():
|
|
963
|
+
continue
|
|
964
|
+
op_name = str(op.get("op"))
|
|
965
|
+
if capability == "stp" and op_name == "set_stp":
|
|
966
|
+
return bool(str(op.get("mode") or "").strip())
|
|
967
|
+
if capability == "rstp" and op_name == "set_stp":
|
|
968
|
+
return bool(re.search(r"(?:rapid|rstp)", str(op.get("mode") or ""), flags=re.IGNORECASE))
|
|
969
|
+
if capability == "etherchannel" and op_name == "set_etherchannel":
|
|
970
|
+
return bool(op.get("interfaces"))
|
|
971
|
+
if capability == "lacp" and op_name == "set_etherchannel":
|
|
972
|
+
if str(op.get("mode") or "").lower() in {"active", "passive"} and bool(op.get("interfaces")):
|
|
973
|
+
return True
|
|
974
|
+
continue
|
|
975
|
+
if capability == "pagp" and op_name == "set_etherchannel":
|
|
976
|
+
if str(op.get("mode") or "").lower() in {"desirable", "auto"} and bool(op.get("interfaces")):
|
|
977
|
+
return True
|
|
978
|
+
continue
|
|
979
|
+
if capability == "vtp" and op_name == "set_vtp":
|
|
980
|
+
return bool(str(op.get("domain") or "").strip() and str(op.get("mode") or "").strip())
|
|
981
|
+
if capability == "dtp" and op_name == "set_dtp":
|
|
982
|
+
return bool(str(op.get("interface") or "").strip() and str(op.get("mode") or "").strip())
|
|
983
|
+
return False
|
|
984
|
+
|
|
985
|
+
|
|
986
|
+
def _has_explicit_ipv4_routing_management_edit(plan: IntentPlan, capability: str) -> bool:
|
|
987
|
+
operation_map = {
|
|
988
|
+
"ospfv2": {"set_ospfv2_network"},
|
|
989
|
+
"eigrp_ipv4": {"set_eigrp_ipv4_network"},
|
|
990
|
+
"ripv2": {"set_ripv2_network"},
|
|
991
|
+
"static_route": {"set_static_route"},
|
|
992
|
+
"default_route": {"set_static_route"},
|
|
993
|
+
"dhcp_relay": {"set_dhcp_relay"},
|
|
994
|
+
"nat_static": {"set_nat_static"},
|
|
995
|
+
"nat_dynamic": {"set_nat_interface"},
|
|
996
|
+
"pat": {"set_pat_overload"},
|
|
997
|
+
"ssh_ios": {"set_ssh_ios"},
|
|
998
|
+
"ntp_ios": {"set_ntp_server"},
|
|
999
|
+
"syslog_ios": {"set_syslog_server"},
|
|
1000
|
+
}
|
|
1001
|
+
expected_ops = operation_map.get(capability, set())
|
|
1002
|
+
if not expected_ops:
|
|
1003
|
+
return False
|
|
1004
|
+
for op in plan.router_ops:
|
|
1005
|
+
if str(op.get("op")) not in expected_ops:
|
|
1006
|
+
continue
|
|
1007
|
+
if not str(op.get("device") or "").strip():
|
|
1008
|
+
continue
|
|
1009
|
+
if capability == "default_route":
|
|
1010
|
+
return op.get("network") == "0.0.0.0" and op.get("prefix") == 0
|
|
1011
|
+
if capability == "nat_dynamic":
|
|
1012
|
+
return str(op.get("role") or "").lower() in {"inside", "outside"} and str(op.get("interface") or "").strip()
|
|
1013
|
+
return True
|
|
1014
|
+
return False
|
|
1015
|
+
|
|
1016
|
+
|
|
1017
|
+
def _has_explicit_wan_security_edit(plan: IntentPlan, capability: str) -> bool:
|
|
1018
|
+
expected_ops = {
|
|
1019
|
+
"gre": {"set_gre_tunnel"},
|
|
1020
|
+
"ppp": {"set_ppp_interface"},
|
|
1021
|
+
"ipsec": {"set_ipsec_transform_set", "set_crypto_map"},
|
|
1022
|
+
"vpn": {"set_crypto_map"},
|
|
1023
|
+
"cbac": {"set_cbac_inspect"},
|
|
1024
|
+
"zfw": {"set_zfw_zone_interface", "set_zfw_zone_pair", "set_zfw_policy"},
|
|
1025
|
+
}.get(capability, set())
|
|
1026
|
+
if not expected_ops:
|
|
1027
|
+
return False
|
|
1028
|
+
for op in plan.router_ops:
|
|
1029
|
+
if str(op.get("op")) not in expected_ops:
|
|
1030
|
+
continue
|
|
1031
|
+
if not str(op.get("device") or "").strip():
|
|
1032
|
+
continue
|
|
1033
|
+
if capability == "gre":
|
|
1034
|
+
return bool(str(op.get("interface") or "").strip() and str(op.get("source") or "").strip() and str(op.get("destination") or "").strip())
|
|
1035
|
+
if capability == "ppp":
|
|
1036
|
+
return bool(str(op.get("interface") or "").strip())
|
|
1037
|
+
if capability == "ipsec":
|
|
1038
|
+
if op.get("op") == "set_ipsec_transform_set":
|
|
1039
|
+
return bool(str(op.get("name") or "").strip() and str(op.get("encryption") or "").strip() and str(op.get("integrity") or "").strip())
|
|
1040
|
+
return bool(str(op.get("map_name") or "").strip() and str(op.get("transform_set") or "").strip())
|
|
1041
|
+
if capability == "vpn":
|
|
1042
|
+
return bool(str(op.get("map_name") or "").strip() and str(op.get("peer") or "").strip())
|
|
1043
|
+
if capability == "cbac":
|
|
1044
|
+
return bool(str(op.get("name") or "").strip() and str(op.get("protocol") or "").strip() and str(op.get("interface") or "").strip())
|
|
1045
|
+
if capability == "zfw":
|
|
1046
|
+
if op.get("op") == "set_zfw_zone_interface":
|
|
1047
|
+
return bool(str(op.get("zone") or "").strip() and str(op.get("interface") or "").strip())
|
|
1048
|
+
if op.get("op") == "set_zfw_zone_pair":
|
|
1049
|
+
return bool(str(op.get("pair_name") or "").strip() and str(op.get("source") or "").strip() and str(op.get("destination") or "").strip())
|
|
1050
|
+
return bool(str(op.get("class_map") or "").strip() and str(op.get("policy_map") or "").strip())
|
|
1051
|
+
return False
|
|
1052
|
+
|
|
1053
|
+
|
|
694
1054
|
def _has_explicit_wan_security_intent(plan: IntentPlan) -> bool:
|
|
695
1055
|
capabilities = set(plan.capabilities)
|
|
696
1056
|
prompt_lower = str(plan.prompt or "").lower()
|
|
@@ -701,6 +1061,17 @@ def _has_explicit_wan_security_intent(plan: IntentPlan) -> bool:
|
|
|
701
1061
|
)
|
|
702
1062
|
|
|
703
1063
|
|
|
1064
|
+
def _has_explicit_voice_edit(plan: IntentPlan, capability: str) -> bool:
|
|
1065
|
+
op_names = {str(op.get("op")) for op in plan.router_ops}
|
|
1066
|
+
if capability == "voip":
|
|
1067
|
+
return bool(op_names & {"set_telephony_service", "set_ephone_dn", "set_ephone", "set_dial_peer_voice"})
|
|
1068
|
+
if capability == "call_manager":
|
|
1069
|
+
return bool(op_names & {"set_telephony_service", "set_ephone_dn", "set_ephone"})
|
|
1070
|
+
if capability == "ip_phone":
|
|
1071
|
+
return bool(op_names & {"set_ephone_dn", "set_ephone"})
|
|
1072
|
+
return False
|
|
1073
|
+
|
|
1074
|
+
|
|
704
1075
|
def _has_explicit_ipv6_operation(plan: IntentPlan, capability: str) -> bool:
|
|
705
1076
|
operation_map = {
|
|
706
1077
|
"ipv6_slaac": {"enable_ipv6_unicast_routing", "set_ipv6_address", "set_ipv6_slaac"},
|
|
@@ -764,10 +1135,15 @@ def _selected_donor_capability_override(
|
|
|
764
1135
|
"multilayer_switching": {"multilayer_runtime"},
|
|
765
1136
|
}
|
|
766
1137
|
if capability in wan_runtime_by_capability:
|
|
1138
|
+
has_acceptance_backed_wan_generate = (
|
|
1139
|
+
bool(selected_sample.acceptance_fixtures)
|
|
1140
|
+
and selected_sample.apply_safety_level in {"acceptance-verified", "safe-open-generate-supported"}
|
|
1141
|
+
)
|
|
767
1142
|
return (
|
|
768
1143
|
"WAN/security edge" in archetypes
|
|
769
1144
|
and capability in sample_capabilities
|
|
770
1145
|
and bool(runtime_features & wan_runtime_by_capability[capability])
|
|
1146
|
+
and has_acceptance_backed_wan_generate
|
|
771
1147
|
and _has_explicit_wan_security_intent(plan)
|
|
772
1148
|
)
|
|
773
1149
|
if capability in IPV6_DONOR_BACKED_CAPABILITIES:
|
|
@@ -800,7 +1176,9 @@ def _best_maturity_level(status: dict[str, object]) -> str:
|
|
|
800
1176
|
def _recommended_next_action_for_capability(capability: str, mismatch_reason: str | None) -> str:
|
|
801
1177
|
if mismatch_reason == "report_only":
|
|
802
1178
|
return f"Keep {capability} in atlas/report mode until donor-backed edit or generate evidence exists."
|
|
803
|
-
if capability in {"vpn", "ipsec", "gre", "ppp", "security_edge", "multilayer_switching"}:
|
|
1179
|
+
if capability in {"vpn", "ipsec", "gre", "ppp", "security_edge", "multilayer_switching", "cbac", "zfw"}:
|
|
1180
|
+
if mismatch_reason == "supported_in_edit_only":
|
|
1181
|
+
return f"Use explicit WAN/security edit commands for {capability}; strict prompt generate still needs an acceptance-backed WAN/security donor."
|
|
804
1182
|
if mismatch_reason == "supported_but_donor_limited":
|
|
805
1183
|
return f"Provide a WAN/security edge donor with reusable ASA/cloud/serial or tunnel skeleton for {capability}."
|
|
806
1184
|
if mismatch_reason == "supported_but_acceptance_gated":
|
|
@@ -826,6 +1204,18 @@ def _recommended_next_action_for_capability(capability: str, mismatch_reason: st
|
|
|
826
1204
|
if mismatch_reason == "supported_but_donor_limited":
|
|
827
1205
|
return f"Provide an advanced wireless donor with reusable AP/router/WLC skeleton for {capability}."
|
|
828
1206
|
return f"Keep {capability} constrained to explicit wireless edit proof until an advanced wireless donor is acceptance-backed."
|
|
1207
|
+
if capability in VOICE_COLLABORATION_EDIT_PROVEN_CAPABILITIES:
|
|
1208
|
+
if mismatch_reason == "supported_in_edit_only":
|
|
1209
|
+
return f"Use explicit voice IOS edit commands for {capability}; strict prompt generate still needs an acceptance-backed voice/collaboration donor."
|
|
1210
|
+
if mismatch_reason == "supported_but_donor_limited":
|
|
1211
|
+
return f"Provide a voice/collaboration donor with reusable telephony-service, ephone, or dial-peer skeleton for {capability}."
|
|
1212
|
+
return f"Keep {capability} constrained to voice inventory/edit proof until a voice/collaboration donor is acceptance-backed."
|
|
1213
|
+
if capability in AUTOMATION_CONTROLLER_EDIT_PROVEN_CAPABILITIES:
|
|
1214
|
+
if mismatch_reason == "supported_in_edit_only":
|
|
1215
|
+
return f"Use explicit script-file edit commands for {capability}; strict prompt generate still needs an acceptance-backed automation/controller donor."
|
|
1216
|
+
if mismatch_reason == "supported_but_donor_limited":
|
|
1217
|
+
return f"Provide an automation/controller donor with reusable existing app/file script surfaces for {capability}."
|
|
1218
|
+
return f"Keep {capability} constrained to automation inventory/edit proof until an automation/controller donor is acceptance-backed."
|
|
829
1219
|
if capability == "iot_registration":
|
|
830
1220
|
if mismatch_reason == "supported_but_acceptance_gated":
|
|
831
1221
|
return "Use an IoT/home gateway donor and explicitly name the thing plus gateway/server target before strict generate."
|
|
@@ -1008,7 +1398,72 @@ def build_coverage_gap_report(
|
|
|
1008
1398
|
and _has_explicit_advanced_wireless_edit(plan, capability)
|
|
1009
1399
|
and not selected_donor_backed
|
|
1010
1400
|
)
|
|
1011
|
-
|
|
1401
|
+
programming_edit_proven_only = (
|
|
1402
|
+
capability in PROGRAMMING_EDIT_PROVEN_CAPABILITIES
|
|
1403
|
+
and _has_explicit_programming_edit(plan, capability)
|
|
1404
|
+
and not selected_donor_backed
|
|
1405
|
+
)
|
|
1406
|
+
programming_donor_backed_ready = (
|
|
1407
|
+
capability in PROGRAMMING_DONOR_BACKED_READY_CAPABILITIES
|
|
1408
|
+
and programming_edit_proven_only
|
|
1409
|
+
)
|
|
1410
|
+
voice_edit_proven_only = (
|
|
1411
|
+
capability in VOICE_COLLABORATION_EDIT_PROVEN_CAPABILITIES
|
|
1412
|
+
and _has_explicit_voice_edit(plan, capability)
|
|
1413
|
+
and not selected_donor_backed
|
|
1414
|
+
)
|
|
1415
|
+
voice_donor_backed_ready = (
|
|
1416
|
+
capability in VOICE_COLLABORATION_DONOR_BACKED_READY_CAPABILITIES
|
|
1417
|
+
and voice_edit_proven_only
|
|
1418
|
+
)
|
|
1419
|
+
l2_security_edit_proven_only = (
|
|
1420
|
+
capability in {"dot1x", "qos"}
|
|
1421
|
+
and _has_explicit_l2_security_edit(plan, capability)
|
|
1422
|
+
and not selected_donor_backed
|
|
1423
|
+
)
|
|
1424
|
+
l2_security_donor_backed_ready = (
|
|
1425
|
+
capability in L2_SECURITY_MONITORING_DONOR_BACKED_READY_CAPABILITIES
|
|
1426
|
+
and l2_security_edit_proven_only
|
|
1427
|
+
)
|
|
1428
|
+
wan_security_edit_proven_only = (
|
|
1429
|
+
capability in WAN_SECURITY_EDIT_PROVEN_CAPABILITIES
|
|
1430
|
+
and _has_explicit_wan_security_edit(plan, capability)
|
|
1431
|
+
and not selected_donor_backed
|
|
1432
|
+
)
|
|
1433
|
+
l2_resiliency_edit_proven_only = (
|
|
1434
|
+
capability in L2_RESILIENCY_ROUTING_EDIT_PROVEN_CAPABILITIES
|
|
1435
|
+
and _has_explicit_l2_resiliency_edit(plan, capability)
|
|
1436
|
+
and not selected_donor_backed
|
|
1437
|
+
)
|
|
1438
|
+
ipv4_routing_management_edit_proven_only = (
|
|
1439
|
+
capability in IPV4_ROUTING_MANAGEMENT_EDIT_PROVEN_CAPABILITIES
|
|
1440
|
+
and _has_explicit_ipv4_routing_management_edit(plan, capability)
|
|
1441
|
+
and not selected_donor_backed
|
|
1442
|
+
)
|
|
1443
|
+
wan_security_donor_backed_ready = (
|
|
1444
|
+
capability in WAN_SECURITY_DONOR_BACKED_READY_EDIT_CAPABILITIES
|
|
1445
|
+
and wan_security_edit_proven_only
|
|
1446
|
+
)
|
|
1447
|
+
wan_security_donor_limited = (
|
|
1448
|
+
capability in WAN_SECURITY_DONOR_LIMITED_CAPABILITIES
|
|
1449
|
+
and _has_explicit_wan_security_intent(plan)
|
|
1450
|
+
and not selected_donor_backed
|
|
1451
|
+
and not wan_security_edit_proven_only
|
|
1452
|
+
)
|
|
1453
|
+
edit_proven_only = (
|
|
1454
|
+
capability in EDIT_PROVEN_REPORT_CAPABILITIES
|
|
1455
|
+
or wireless_edit_proven_only
|
|
1456
|
+
or programming_edit_proven_only
|
|
1457
|
+
or voice_edit_proven_only
|
|
1458
|
+
or l2_security_edit_proven_only
|
|
1459
|
+
or wan_security_edit_proven_only
|
|
1460
|
+
or l2_resiliency_edit_proven_only
|
|
1461
|
+
or ipv4_routing_management_edit_proven_only
|
|
1462
|
+
) and not selected_donor_backed
|
|
1463
|
+
ipv6_donor_backed_ready = (
|
|
1464
|
+
capability in IPV6_DONOR_BACKED_READY_EDIT_CAPABILITIES
|
|
1465
|
+
and edit_proven_only
|
|
1466
|
+
)
|
|
1012
1467
|
report_only = capability in REPORT_ONLY_CAPABILITIES and not selected_donor_backed and not edit_proven_only
|
|
1013
1468
|
matching_entries = [
|
|
1014
1469
|
entry
|
|
@@ -1022,9 +1477,13 @@ def build_coverage_gap_report(
|
|
|
1022
1477
|
(any(entry.maturity_level == "acceptance-verified" for entry in matching_entries) and capability not in iot_acceptance_gated)
|
|
1023
1478
|
or selected_donor_backed
|
|
1024
1479
|
)
|
|
1025
|
-
if report_only or edit_proven_only:
|
|
1480
|
+
if report_only or edit_proven_only or wan_security_donor_limited:
|
|
1026
1481
|
acceptance_verified = False
|
|
1027
|
-
requires_curated =
|
|
1482
|
+
requires_curated = (
|
|
1483
|
+
best_entry.maturity_level == "safe-open-generate-supported"
|
|
1484
|
+
and not selected_donor_backed
|
|
1485
|
+
and not edit_proven_only
|
|
1486
|
+
) or wan_security_donor_limited
|
|
1028
1487
|
if requires_curated:
|
|
1029
1488
|
requires_curated_donor.append(capability)
|
|
1030
1489
|
if not acceptance_verified:
|
|
@@ -1038,17 +1497,27 @@ def build_coverage_gap_report(
|
|
|
1038
1497
|
"inventory_supported": any(MATURITY_RANK[entry.maturity_level] >= MATURITY_RANK["inventory-supported"] for entry in matching_entries),
|
|
1039
1498
|
"edit_supported": True if edit_proven_only else False if report_only else any(MATURITY_RANK[entry.maturity_level] >= MATURITY_RANK["config-mutation-supported"] for entry in matching_entries),
|
|
1040
1499
|
"config_mutation_supported": True if edit_proven_only else False if report_only else any(MATURITY_RANK[entry.maturity_level] >= MATURITY_RANK["config-mutation-supported"] for entry in matching_entries),
|
|
1041
|
-
"safe_open_generate_supported": False if report_only or edit_proven_only else any(MATURITY_RANK[entry.maturity_level] >= MATURITY_RANK["safe-open-generate-supported"] for entry in matching_entries),
|
|
1500
|
+
"safe_open_generate_supported": False if report_only or edit_proven_only or wan_security_donor_limited else any(MATURITY_RANK[entry.maturity_level] >= MATURITY_RANK["safe-open-generate-supported"] for entry in matching_entries),
|
|
1042
1501
|
"acceptance_verified": acceptance_verified,
|
|
1502
|
+
"donor_backed_ready": bool(
|
|
1503
|
+
selected_donor_backed
|
|
1504
|
+
or ipv6_donor_backed_ready
|
|
1505
|
+
or programming_donor_backed_ready
|
|
1506
|
+
or voice_donor_backed_ready
|
|
1507
|
+
or l2_security_donor_backed_ready
|
|
1508
|
+
or wan_security_donor_backed_ready
|
|
1509
|
+
),
|
|
1043
1510
|
"requires_curated_donor": requires_curated,
|
|
1044
1511
|
"requires_manual_acceptance": not acceptance_verified,
|
|
1045
1512
|
"report_only": report_only,
|
|
1513
|
+
"donor_limited_generate": wan_security_donor_limited,
|
|
1046
1514
|
"recommended_donors": list(dict.fromkeys(donor for entry in matching_entries for donor in entry.accepted_donors))[:10],
|
|
1047
1515
|
"known_limitations": list(dict.fromkeys(item for entry in matching_entries for item in entry.known_limitations)),
|
|
1048
1516
|
}
|
|
1049
1517
|
)
|
|
1050
1518
|
elif edit_proven_only:
|
|
1051
1519
|
supported_capabilities.append(capability)
|
|
1520
|
+
requires_manual_acceptance.append(capability)
|
|
1052
1521
|
capability_statuses.append(
|
|
1053
1522
|
{
|
|
1054
1523
|
"capability": capability,
|
|
@@ -1060,15 +1529,24 @@ def build_coverage_gap_report(
|
|
|
1060
1529
|
"config_mutation_supported": True,
|
|
1061
1530
|
"safe_open_generate_supported": False,
|
|
1062
1531
|
"acceptance_verified": False,
|
|
1532
|
+
"donor_backed_ready": bool(
|
|
1533
|
+
programming_donor_backed_ready
|
|
1534
|
+
or ipv6_donor_backed_ready
|
|
1535
|
+
or voice_donor_backed_ready
|
|
1536
|
+
or l2_security_donor_backed_ready
|
|
1537
|
+
or wan_security_donor_backed_ready
|
|
1538
|
+
),
|
|
1063
1539
|
"requires_curated_donor": False,
|
|
1064
1540
|
"requires_manual_acceptance": True,
|
|
1065
1541
|
"report_only": False,
|
|
1542
|
+
"donor_limited_generate": False,
|
|
1066
1543
|
"recommended_donors": [],
|
|
1067
1544
|
"known_limitations": ["editor-proven without sample-backed donor selection"],
|
|
1068
1545
|
}
|
|
1069
1546
|
)
|
|
1070
1547
|
elif report_only:
|
|
1071
1548
|
supported_capabilities.append(capability)
|
|
1549
|
+
requires_manual_acceptance.append(capability)
|
|
1072
1550
|
capability_statuses.append(
|
|
1073
1551
|
{
|
|
1074
1552
|
"capability": capability,
|
|
@@ -1080,9 +1558,11 @@ def build_coverage_gap_report(
|
|
|
1080
1558
|
"config_mutation_supported": False,
|
|
1081
1559
|
"safe_open_generate_supported": False,
|
|
1082
1560
|
"acceptance_verified": False,
|
|
1561
|
+
"donor_backed_ready": False,
|
|
1083
1562
|
"requires_curated_donor": False,
|
|
1084
1563
|
"requires_manual_acceptance": True,
|
|
1085
1564
|
"report_only": True,
|
|
1565
|
+
"donor_limited_generate": False,
|
|
1086
1566
|
"recommended_donors": [],
|
|
1087
1567
|
"known_limitations": ["report-supported without decode-backed donor selection"],
|
|
1088
1568
|
}
|
|
@@ -1100,8 +1580,10 @@ def build_coverage_gap_report(
|
|
|
1100
1580
|
"config_mutation_supported": True,
|
|
1101
1581
|
"safe_open_generate_supported": True,
|
|
1102
1582
|
"acceptance_verified": True,
|
|
1583
|
+
"donor_backed_ready": True,
|
|
1103
1584
|
"requires_curated_donor": False,
|
|
1104
1585
|
"requires_manual_acceptance": False,
|
|
1586
|
+
"donor_limited_generate": False,
|
|
1105
1587
|
"recommended_donors": [selected_sample.relative_path],
|
|
1106
1588
|
"known_limitations": [],
|
|
1107
1589
|
}
|
|
@@ -1119,8 +1601,10 @@ def build_coverage_gap_report(
|
|
|
1119
1601
|
"config_mutation_supported": False,
|
|
1120
1602
|
"safe_open_generate_supported": False,
|
|
1121
1603
|
"acceptance_verified": False,
|
|
1604
|
+
"donor_backed_ready": False,
|
|
1122
1605
|
"requires_curated_donor": False,
|
|
1123
1606
|
"requires_manual_acceptance": True,
|
|
1607
|
+
"donor_limited_generate": False,
|
|
1124
1608
|
"recommended_donors": [],
|
|
1125
1609
|
"known_limitations": ["no matching donor coverage found"],
|
|
1126
1610
|
}
|
|
@@ -1135,6 +1619,7 @@ def build_coverage_gap_report(
|
|
|
1135
1619
|
"capability": str(status.get("capability")),
|
|
1136
1620
|
"inventory_supported": bool(status.get("inventory_supported")),
|
|
1137
1621
|
"edit_supported": bool(status.get("edit_supported")),
|
|
1622
|
+
"donor_backed_ready": _donor_backed_ready(status),
|
|
1138
1623
|
"generate_supported": bool(status.get("safe_open_generate_supported")),
|
|
1139
1624
|
"acceptance_verified": bool(status.get("acceptance_verified")),
|
|
1140
1625
|
"best_maturity_level": _best_maturity_level(status),
|
|
@@ -1230,6 +1715,14 @@ def build_inventory_capability_report(payload: dict[str, Any]) -> dict[str, obje
|
|
|
1230
1715
|
capabilities.update(str(capability) for capability in routing_details.get("capabilities", []) if str(capability).strip())
|
|
1231
1716
|
for l2_details in payload.get("l2_security_monitoring", {}).values():
|
|
1232
1717
|
capabilities.update(str(capability) for capability in l2_details.get("capabilities", []) if str(capability).strip())
|
|
1718
|
+
for l2_resiliency_details in payload.get("l2_resiliency_routing", {}).values():
|
|
1719
|
+
capabilities.update(str(capability) for capability in l2_resiliency_details.get("capabilities", []) if str(capability).strip())
|
|
1720
|
+
for ipv4_details in payload.get("ipv4_routing_management", {}).values():
|
|
1721
|
+
capabilities.update(str(capability) for capability in ipv4_details.get("capabilities", []) if str(capability).strip())
|
|
1722
|
+
for voice_details in payload.get("voice", {}).values():
|
|
1723
|
+
capabilities.update(str(capability) for capability in voice_details.get("capabilities", []) if str(capability).strip())
|
|
1724
|
+
for programming_details in payload.get("programming", {}).values():
|
|
1725
|
+
capabilities.update(str(capability) for capability in programming_details.get("feature_tags", []) if str(capability).strip())
|
|
1233
1726
|
return {
|
|
1234
1727
|
"device_families": families,
|
|
1235
1728
|
"capabilities": sorted(capabilities),
|