packet-tracer-skill 0.2.0 → 0.2.2
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 +42 -4
- package/README.md +237 -88
- package/docs/campus-donor-proof.md +103 -0
- package/docs/curated-donor-registry.md +26 -0
- package/docs/github-launch-ops-0.2.1.md +45 -0
- package/docs/github-launch-ops-0.2.2.md +42 -0
- package/docs/github-metadata.md +23 -6
- package/docs/hero-demo-plan.md +18 -10
- package/docs/home-iot-donor-proof.md +64 -0
- package/docs/launch-announcement-0.2.1.md +15 -0
- package/docs/launch-announcement-0.2.2.md +15 -0
- package/docs/packet-tracer-feature-gap-atlas.md +87 -0
- package/docs/post-launch-follow-up.md +39 -0
- package/docs/publish-preview-roadmap.md +20 -17
- package/docs/release-checklist.md +22 -12
- package/docs/{release-notes-0.2.0.md → release-notes-0.2.1.md} +7 -6
- package/docs/release-notes-0.2.2.md +26 -0
- package/docs/runtime-truth.md +31 -0
- package/docs/wan-security-donor-proof.md +41 -0
- package/docs/wireless-advanced-proof.md +60 -0
- package/examples/README.md +13 -10
- package/examples/gallery.md +10 -4
- package/examples/index.json +3 -3
- package/package.json +44 -16
- package/references/curated-donor-registry.json +5 -2
- package/references/packettracer-feature-atlas.json +133 -0
- package/references/scenario-fixture-corpus.json +1 -0
- package/scripts/build_examples_index.py +27 -16
- package/scripts/coverage_matrix.py +559 -23
- package/scripts/feature_atlas.py +229 -0
- package/scripts/generate_pkt.py +330 -31
- package/scripts/intent_parser.py +278 -7
- package/scripts/pkt_editor.py +174 -0
- package/scripts/runtime_doctor.py +83 -10
- package/scripts/sample_catalog.py +210 -5
- package/docs/screenshots/.gitkeep +0 -1
- package/docs/screenshots/packet-tracer-topology-cropped.png +0 -0
- package/scripts/__pycache__/build_examples_index.cpython-314.pyc +0 -0
- package/scripts/__pycache__/build_sample_catalog.cpython-314.pyc +0 -0
- package/scripts/__pycache__/coverage_matrix.cpython-314.pyc +0 -0
- package/scripts/__pycache__/donor_diagnostics.cpython-314.pyc +0 -0
- package/scripts/__pycache__/generate_pkt.cpython-314.pyc +0 -0
- package/scripts/__pycache__/install_skill.cpython-314.pyc +0 -0
- package/scripts/__pycache__/intent_parser.cpython-314.pyc +0 -0
- package/scripts/__pycache__/packet_tracer_env.cpython-314.pyc +0 -0
- package/scripts/__pycache__/pkt_builder.cpython-314.pyc +0 -0
- package/scripts/__pycache__/pkt_codec.cpython-314.pyc +0 -0
- package/scripts/__pycache__/pkt_editor.cpython-314.pyc +0 -0
- package/scripts/__pycache__/pkt_transformer.cpython-314.pyc +0 -0
- package/scripts/__pycache__/remote_search.cpython-314.pyc +0 -0
- package/scripts/__pycache__/runtime_doctor.cpython-314.pyc +0 -0
- package/scripts/__pycache__/sample_catalog.cpython-314.pyc +0 -0
- package/scripts/__pycache__/sample_selector.cpython-314.pyc +0 -0
- package/scripts/__pycache__/twofish_diagnostics.cpython-314.pyc +0 -0
- package/scripts/__pycache__/twofish_runtime.cpython-314.pyc +0 -0
- package/scripts/__pycache__/workspace_repair.cpython-314.pyc +0 -0
- package/scripts/vendor/__pycache__/twofish.cpython-314.pyc +0 -0
|
@@ -37,6 +37,9 @@ DEVICE_FAMILY_MAP = {
|
|
|
37
37
|
"Dsl Modem": "wan/cloud/dsl/cable devices",
|
|
38
38
|
"Security Appliance": "security devices",
|
|
39
39
|
"ASA": "security devices",
|
|
40
|
+
"NetworkController": "network controllers",
|
|
41
|
+
"CentralOfficeServer": "wan/cloud/dsl/cable devices",
|
|
42
|
+
"CellTower": "wan/cloud/dsl/cable devices",
|
|
40
43
|
"IoT": "iot devices",
|
|
41
44
|
"Board": "iot devices",
|
|
42
45
|
"Sensor": "iot devices",
|
|
@@ -52,6 +55,94 @@ DEFAULT_FAMILY_LIMITATIONS = {
|
|
|
52
55
|
"security devices": ["advanced security devices remain donor-limited"],
|
|
53
56
|
}
|
|
54
57
|
|
|
58
|
+
REPORT_ONLY_CAPABILITIES = {
|
|
59
|
+
"ipv6_slaac",
|
|
60
|
+
"dhcpv6_stateful",
|
|
61
|
+
"dhcpv6_stateless",
|
|
62
|
+
"ipv6_prefix_delegation",
|
|
63
|
+
"ipv6_dns_aaaa",
|
|
64
|
+
"ipv6_tunneling",
|
|
65
|
+
"isatap",
|
|
66
|
+
"ospfv3",
|
|
67
|
+
"eigrp_ipv6",
|
|
68
|
+
"ripng",
|
|
69
|
+
"hsrp",
|
|
70
|
+
"dhcp_snooping",
|
|
71
|
+
"dai",
|
|
72
|
+
"dot1x",
|
|
73
|
+
"lldp",
|
|
74
|
+
"rep",
|
|
75
|
+
"snmp",
|
|
76
|
+
"netflow",
|
|
77
|
+
"span",
|
|
78
|
+
"qos",
|
|
79
|
+
"port_security",
|
|
80
|
+
"asa_acl_nat",
|
|
81
|
+
"asa_service_policy",
|
|
82
|
+
"clientless_vpn",
|
|
83
|
+
"cbac",
|
|
84
|
+
"zfw",
|
|
85
|
+
"sniffer",
|
|
86
|
+
"wlc",
|
|
87
|
+
"wpa_enterprise",
|
|
88
|
+
"wep",
|
|
89
|
+
"guest_wifi",
|
|
90
|
+
"beamforming",
|
|
91
|
+
"meraki",
|
|
92
|
+
"cellular_5g",
|
|
93
|
+
"bluetooth",
|
|
94
|
+
"network_controller",
|
|
95
|
+
"python_programming",
|
|
96
|
+
"javascript_programming",
|
|
97
|
+
"blockly_programming",
|
|
98
|
+
"tcp_udp_app",
|
|
99
|
+
"vm_iox",
|
|
100
|
+
"voip",
|
|
101
|
+
"ip_phone",
|
|
102
|
+
"call_manager",
|
|
103
|
+
"linksys_voice",
|
|
104
|
+
"mqtt",
|
|
105
|
+
"real_http",
|
|
106
|
+
"real_websocket",
|
|
107
|
+
"visual_scripting",
|
|
108
|
+
"ptp",
|
|
109
|
+
"profinet",
|
|
110
|
+
"l2nat",
|
|
111
|
+
"cyberobserver",
|
|
112
|
+
"industrial_firewall",
|
|
113
|
+
"coaxial",
|
|
114
|
+
"cable_dsl",
|
|
115
|
+
"central_office",
|
|
116
|
+
"cell_tower",
|
|
117
|
+
"power_distribution",
|
|
118
|
+
"hot_swappable",
|
|
119
|
+
"ios_license",
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
IPV6_DONOR_BACKED_CAPABILITIES = {
|
|
123
|
+
"ipv6_slaac",
|
|
124
|
+
"dhcpv6_stateful",
|
|
125
|
+
"ospfv3",
|
|
126
|
+
"eigrp_ipv6",
|
|
127
|
+
"ripng",
|
|
128
|
+
"hsrp",
|
|
129
|
+
}
|
|
130
|
+
L2_SECURITY_MONITORING_EDIT_PROVEN_CAPABILITIES = {
|
|
131
|
+
"dhcp_snooping",
|
|
132
|
+
"dai",
|
|
133
|
+
"lldp",
|
|
134
|
+
"rep",
|
|
135
|
+
"snmp",
|
|
136
|
+
"netflow",
|
|
137
|
+
"span",
|
|
138
|
+
"port_security",
|
|
139
|
+
}
|
|
140
|
+
WIRELESS_ADVANCED_EDIT_PROVEN_CAPABILITIES = {
|
|
141
|
+
"wep",
|
|
142
|
+
"wpa_enterprise",
|
|
143
|
+
}
|
|
144
|
+
EDIT_PROVEN_REPORT_CAPABILITIES = IPV6_DONOR_BACKED_CAPABILITIES | L2_SECURITY_MONITORING_EDIT_PROVEN_CAPABILITIES
|
|
145
|
+
|
|
55
146
|
CAPABILITY_PROVIDER_FAMILIES = {
|
|
56
147
|
"access_port": {"switches", "multilayer switches"},
|
|
57
148
|
"trunk": {"switches", "multilayer switches"},
|
|
@@ -99,6 +190,67 @@ CAPABILITY_PROVIDER_FAMILIES = {
|
|
|
99
190
|
"tablet": {"end devices"},
|
|
100
191
|
"printer": {"end devices"},
|
|
101
192
|
"verification": {"routers", "switches", "multilayer switches", "servers", "end devices", "access points"},
|
|
193
|
+
"ipv6_slaac": {"routers", "switches", "end devices", "pt-specific edge/utility devices"},
|
|
194
|
+
"dhcpv6_stateful": {"routers", "servers", "end devices"},
|
|
195
|
+
"dhcpv6_stateless": {"routers", "servers", "end devices"},
|
|
196
|
+
"ipv6_prefix_delegation": {"routers", "wan/cloud/dsl/cable devices"},
|
|
197
|
+
"ipv6_dns_aaaa": {"servers", "routers"},
|
|
198
|
+
"ipv6_tunneling": {"routers"},
|
|
199
|
+
"isatap": {"routers", "end devices"},
|
|
200
|
+
"ospfv3": {"routers"},
|
|
201
|
+
"eigrp_ipv6": {"routers"},
|
|
202
|
+
"ripng": {"routers"},
|
|
203
|
+
"hsrp": {"routers", "switches", "multilayer switches"},
|
|
204
|
+
"dhcp_snooping": {"switches", "multilayer switches"},
|
|
205
|
+
"dai": {"switches", "multilayer switches"},
|
|
206
|
+
"dot1x": {"switches", "servers", "end devices"},
|
|
207
|
+
"lldp": {"routers", "switches", "multilayer switches"},
|
|
208
|
+
"rep": {"switches", "multilayer switches"},
|
|
209
|
+
"snmp": {"routers", "switches", "multilayer switches", "access points", "servers"},
|
|
210
|
+
"netflow": {"routers"},
|
|
211
|
+
"span": {"switches", "multilayer switches"},
|
|
212
|
+
"qos": {"routers", "switches", "multilayer switches"},
|
|
213
|
+
"port_security": {"switches", "multilayer switches"},
|
|
214
|
+
"asa_acl_nat": {"security devices"},
|
|
215
|
+
"asa_service_policy": {"security devices"},
|
|
216
|
+
"clientless_vpn": {"security devices"},
|
|
217
|
+
"cbac": {"routers", "security devices"},
|
|
218
|
+
"zfw": {"routers", "security devices"},
|
|
219
|
+
"sniffer": {"end devices", "pt-specific edge/utility devices"},
|
|
220
|
+
"wlc": {"access points"},
|
|
221
|
+
"wpa_enterprise": {"access points", "home/wireless routers", "servers"},
|
|
222
|
+
"wep": {"access points", "home/wireless routers"},
|
|
223
|
+
"guest_wifi": {"home/wireless routers", "access points"},
|
|
224
|
+
"beamforming": {"home/wireless routers"},
|
|
225
|
+
"meraki": {"security devices", "home/wireless routers", "access points"},
|
|
226
|
+
"cellular_5g": {"wan/cloud/dsl/cable devices", "end devices"},
|
|
227
|
+
"bluetooth": {"iot devices", "end devices"},
|
|
228
|
+
"network_controller": {"pt-specific edge/utility devices", "routers", "switches"},
|
|
229
|
+
"python_programming": {"end devices", "pt-specific edge/utility devices"},
|
|
230
|
+
"javascript_programming": {"end devices", "pt-specific edge/utility devices"},
|
|
231
|
+
"blockly_programming": {"end devices", "pt-specific edge/utility devices"},
|
|
232
|
+
"tcp_udp_app": {"end devices", "servers"},
|
|
233
|
+
"vm_iox": {"routers", "pt-specific edge/utility devices"},
|
|
234
|
+
"voip": {"end devices", "switches", "routers"},
|
|
235
|
+
"ip_phone": {"end devices"},
|
|
236
|
+
"call_manager": {"routers", "servers", "end devices", "switches"},
|
|
237
|
+
"linksys_voice": {"home/wireless routers", "end devices"},
|
|
238
|
+
"mqtt": {"iot devices", "servers", "home/wireless routers", "pt-specific edge/utility devices"},
|
|
239
|
+
"real_http": {"iot devices", "servers", "pt-specific edge/utility devices"},
|
|
240
|
+
"real_websocket": {"iot devices", "servers", "pt-specific edge/utility devices"},
|
|
241
|
+
"visual_scripting": {"iot devices"},
|
|
242
|
+
"ptp": {"switches", "multilayer switches", "routers"},
|
|
243
|
+
"profinet": {"iot devices", "routers", "switches"},
|
|
244
|
+
"l2nat": {"switches", "multilayer switches"},
|
|
245
|
+
"cyberobserver": {"servers", "switches", "routers"},
|
|
246
|
+
"industrial_firewall": {"security devices", "routers"},
|
|
247
|
+
"coaxial": {"pt-specific edge/utility devices"},
|
|
248
|
+
"cable_dsl": {"wan/cloud/dsl/cable devices"},
|
|
249
|
+
"central_office": {"pt-specific edge/utility devices"},
|
|
250
|
+
"cell_tower": {"pt-specific edge/utility devices", "wan/cloud/dsl/cable devices"},
|
|
251
|
+
"power_distribution": {"pt-specific edge/utility devices"},
|
|
252
|
+
"hot_swappable": {"multilayer switches", "pt-specific edge/utility devices"},
|
|
253
|
+
"ios_license": {"routers", "switches", "multilayer switches"},
|
|
102
254
|
}
|
|
103
255
|
|
|
104
256
|
|
|
@@ -277,26 +429,72 @@ def _requested_device_families(plan: IntentPlan) -> list[str]:
|
|
|
277
429
|
def _scenario_family_for_plan(plan: IntentPlan, requested_families: list[str]) -> str | None:
|
|
278
430
|
capabilities = set(plan.capabilities)
|
|
279
431
|
requested_services = {str(service) for service in plan.service_requirements.get("services", []) if service}
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
432
|
+
requested_family_set = set(requested_families)
|
|
433
|
+
prompt_lower = str(plan.prompt or "").lower()
|
|
434
|
+
campus_capabilities = {"vlan", "router_on_a_stick", "management_vlan", "telnet", "acl", "nat", "pat", "trunk", "access_port"}
|
|
435
|
+
service_capabilities = {"server_dns", "server_dhcp", "server_http", "server_https", "server_ftp", "server_tftp", "server_email", "server_syslog", "server_aaa"}
|
|
436
|
+
explicit_campus_style = plan.network_style in {"campus", "department_lan"}
|
|
437
|
+
explicit_service_style = plan.network_style == "service_heavy"
|
|
438
|
+
explicit_home_style = plan.network_style in {"home_iot", "smart_home"}
|
|
439
|
+
explicit_home_gateway_prompt = any(
|
|
440
|
+
token in prompt_lower
|
|
441
|
+
for token in ["home gateway", "homegateway", "smart home", "iot "]
|
|
442
|
+
)
|
|
443
|
+
campus_signal = (
|
|
444
|
+
explicit_campus_style
|
|
445
|
+
or bool(plan.department_groups)
|
|
446
|
+
or "campus" in prompt_lower
|
|
447
|
+
or "kampus" in prompt_lower
|
|
448
|
+
or (requested_family_set & {"switches", "multilayer switches"} and capabilities & campus_capabilities)
|
|
449
|
+
or len(capabilities & campus_capabilities) >= 3
|
|
450
|
+
)
|
|
451
|
+
home_iot_signal = (
|
|
452
|
+
explicit_home_style
|
|
453
|
+
or explicit_home_gateway_prompt
|
|
454
|
+
or plan.wireless_mode == "home_router_edge"
|
|
455
|
+
or capabilities & {"iot", "iot_registration", "iot_control"}
|
|
456
|
+
or {"iot devices", "home/wireless routers"} & requested_family_set
|
|
457
|
+
)
|
|
458
|
+
wan_signal = (
|
|
283
459
|
plan.network_style == "wan_security"
|
|
284
460
|
or capabilities & {"vpn", "ipsec", "gre", "ppp", "multilayer_switching", "security_edge"}
|
|
285
|
-
or {"wan/cloud/dsl/cable devices", "security devices"} &
|
|
286
|
-
)
|
|
461
|
+
or {"wan/cloud/dsl/cable devices", "security devices"} & requested_family_set
|
|
462
|
+
)
|
|
463
|
+
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"}
|
|
464
|
+
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
|
+
wireless_advanced_signal = plan.network_style == "wireless_advanced" or capabilities & {"wlc", "wpa_enterprise", "wep", "guest_wifi", "beamforming", "meraki", "cellular_5g", "bluetooth"}
|
|
466
|
+
automation_controller_signal = plan.network_style == "automation_controller" or capabilities & {"network_controller", "python_programming", "javascript_programming", "blockly_programming", "tcp_udp_app", "vm_iox"}
|
|
467
|
+
voice_collaboration_signal = plan.network_style == "voice_collaboration" or capabilities & {"voip", "ip_phone", "call_manager", "linksys_voice"}
|
|
468
|
+
industrial_iot_signal = plan.network_style == "industrial_iot" or capabilities & {"mqtt", "real_http", "real_websocket", "visual_scripting", "ptp", "profinet", "l2nat", "cyberobserver", "industrial_firewall"}
|
|
469
|
+
physical_media_device_signal = plan.network_style == "physical_media_device" or capabilities & {"coaxial", "cable_dsl", "central_office", "cell_tower", "power_distribution", "hot_swappable", "ios_license"}
|
|
470
|
+
service_signal = (
|
|
471
|
+
explicit_service_style
|
|
472
|
+
or requested_services & {"dns", "dhcp", "http", "https", "ftp", "tftp", "email", "syslog", "aaa", "ntp"}
|
|
473
|
+
or capabilities & service_capabilities
|
|
474
|
+
or "servers" in requested_family_set
|
|
475
|
+
)
|
|
476
|
+
if industrial_iot_signal:
|
|
477
|
+
return "industrial_iot"
|
|
478
|
+
if automation_controller_signal:
|
|
479
|
+
return "automation_controller"
|
|
480
|
+
if voice_collaboration_signal:
|
|
481
|
+
return "voice_collaboration"
|
|
482
|
+
if wireless_advanced_signal:
|
|
483
|
+
return "wireless_advanced"
|
|
484
|
+
if l2_security_monitoring_signal:
|
|
485
|
+
return "l2_security_monitoring"
|
|
486
|
+
if ipv6_routing_signal:
|
|
487
|
+
return "ipv6_routing"
|
|
488
|
+
if physical_media_device_signal:
|
|
489
|
+
return "physical_media_device"
|
|
490
|
+
if home_iot_signal:
|
|
491
|
+
return "home_iot"
|
|
492
|
+
if wan_signal:
|
|
287
493
|
return "wan_security_edge"
|
|
288
|
-
if
|
|
289
|
-
requested_services & {"dns", "dhcp", "http", "https", "ftp", "tftp", "email", "syslog", "aaa", "ntp"}
|
|
290
|
-
or capabilities & {"server_dns", "server_dhcp", "server_http", "server_https", "server_ftp", "server_tftp", "server_email", "server_syslog", "server_aaa"}
|
|
291
|
-
or "servers" in requested_families
|
|
292
|
-
) and not (plan.department_groups or {"switches", "multilayer switches"} & set(requested_families) and capabilities & {"vlan", "router_on_a_stick", "management_vlan", "telnet"}):
|
|
293
|
-
return "service_heavy"
|
|
294
|
-
if (
|
|
295
|
-
plan.department_groups
|
|
296
|
-
or {"switches", "multilayer switches"} & set(requested_families)
|
|
297
|
-
or capabilities & {"vlan", "router_on_a_stick", "management_vlan", "telnet", "acl", "nat", "pat"}
|
|
298
|
-
):
|
|
494
|
+
if campus_signal:
|
|
299
495
|
return "campus"
|
|
496
|
+
if service_signal:
|
|
497
|
+
return "service_heavy"
|
|
300
498
|
return None
|
|
301
499
|
|
|
302
500
|
|
|
@@ -305,6 +503,13 @@ SCENARIO_CAPABILITY_SETS = {
|
|
|
305
503
|
"service_heavy": {"server_dns", "server_dhcp", "server_http", "server_https", "server_ftp", "server_tftp", "server_email", "server_syslog", "server_aaa", "ntp"},
|
|
306
504
|
"home_iot": {"iot", "iot_registration", "iot_control", "wireless_ap", "wireless_mutation"},
|
|
307
505
|
"wan_security_edge": {"vpn", "ipsec", "gre", "ppp", "acl", "nat"},
|
|
506
|
+
"ipv6_routing": {"ipv6_slaac", "dhcpv6_stateful", "dhcpv6_stateless", "ipv6_prefix_delegation", "ipv6_dns_aaaa", "ipv6_tunneling", "isatap", "ospfv3", "eigrp_ipv6", "ripng", "hsrp"},
|
|
507
|
+
"l2_security_monitoring": {"dhcp_snooping", "dai", "dot1x", "lldp", "rep", "snmp", "netflow", "span", "qos", "port_security"},
|
|
508
|
+
"wireless_advanced": {"wlc", "wpa_enterprise", "wep", "guest_wifi", "beamforming", "meraki", "cellular_5g", "bluetooth"},
|
|
509
|
+
"automation_controller": {"network_controller", "python_programming", "javascript_programming", "blockly_programming", "tcp_udp_app", "vm_iox"},
|
|
510
|
+
"voice_collaboration": {"voip", "ip_phone", "call_manager", "linksys_voice"},
|
|
511
|
+
"industrial_iot": {"mqtt", "real_http", "real_websocket", "visual_scripting", "ptp", "profinet", "l2nat", "cyberobserver", "industrial_firewall"},
|
|
512
|
+
"physical_media_device": {"coaxial", "cable_dsl", "central_office", "cell_tower", "power_distribution", "hot_swappable", "ios_license"},
|
|
308
513
|
}
|
|
309
514
|
|
|
310
515
|
CAPABILITY_REQUIRED_ARCHETYPES = {
|
|
@@ -330,6 +535,54 @@ CAPABILITY_REQUIRED_ARCHETYPES = {
|
|
|
330
535
|
"ppp": ["WAN/security edge"],
|
|
331
536
|
"multilayer_switching": ["WAN/security edge", "campus/core"],
|
|
332
537
|
"security_edge": ["WAN/security edge"],
|
|
538
|
+
"ipv6_slaac": ["IPv6/routing"],
|
|
539
|
+
"dhcpv6_stateful": ["IPv6/routing"],
|
|
540
|
+
"dhcpv6_stateless": ["IPv6/routing"],
|
|
541
|
+
"ipv6_prefix_delegation": ["IPv6/routing"],
|
|
542
|
+
"ipv6_dns_aaaa": ["IPv6/routing"],
|
|
543
|
+
"ipv6_tunneling": ["IPv6/routing"],
|
|
544
|
+
"isatap": ["IPv6/routing"],
|
|
545
|
+
"ospfv3": ["IPv6/routing"],
|
|
546
|
+
"eigrp_ipv6": ["IPv6/routing"],
|
|
547
|
+
"ripng": ["IPv6/routing"],
|
|
548
|
+
"hsrp": ["IPv6/routing"],
|
|
549
|
+
"dhcp_snooping": ["L2 security/monitoring"],
|
|
550
|
+
"dai": ["L2 security/monitoring"],
|
|
551
|
+
"dot1x": ["L2 security/monitoring"],
|
|
552
|
+
"lldp": ["L2 security/monitoring"],
|
|
553
|
+
"rep": ["L2 security/monitoring"],
|
|
554
|
+
"snmp": ["L2 security/monitoring"],
|
|
555
|
+
"netflow": ["L2 security/monitoring"],
|
|
556
|
+
"span": ["L2 security/monitoring"],
|
|
557
|
+
"qos": ["L2 security/monitoring"],
|
|
558
|
+
"port_security": ["L2 security/monitoring"],
|
|
559
|
+
"wlc": ["advanced wireless"],
|
|
560
|
+
"wpa_enterprise": ["advanced wireless"],
|
|
561
|
+
"wep": ["advanced wireless"],
|
|
562
|
+
"guest_wifi": ["advanced wireless"],
|
|
563
|
+
"beamforming": ["advanced wireless"],
|
|
564
|
+
"meraki": ["advanced wireless"],
|
|
565
|
+
"cellular_5g": ["advanced wireless"],
|
|
566
|
+
"bluetooth": ["advanced wireless"],
|
|
567
|
+
"network_controller": ["automation/controller"],
|
|
568
|
+
"python_programming": ["automation/controller"],
|
|
569
|
+
"javascript_programming": ["automation/controller"],
|
|
570
|
+
"blockly_programming": ["automation/controller"],
|
|
571
|
+
"tcp_udp_app": ["automation/controller"],
|
|
572
|
+
"vm_iox": ["automation/controller"],
|
|
573
|
+
"voip": ["voice/collaboration"],
|
|
574
|
+
"ip_phone": ["voice/collaboration"],
|
|
575
|
+
"call_manager": ["voice/collaboration"],
|
|
576
|
+
"linksys_voice": ["voice/collaboration"],
|
|
577
|
+
"mqtt": ["industrial IoT"],
|
|
578
|
+
"real_http": ["industrial IoT"],
|
|
579
|
+
"real_websocket": ["industrial IoT"],
|
|
580
|
+
"visual_scripting": ["industrial IoT"],
|
|
581
|
+
"ptp": ["industrial IoT"],
|
|
582
|
+
"profinet": ["industrial IoT"],
|
|
583
|
+
"l2nat": ["industrial IoT"],
|
|
584
|
+
"cyberobserver": ["industrial IoT"],
|
|
585
|
+
"industrial_firewall": ["industrial IoT"],
|
|
333
586
|
}
|
|
334
587
|
|
|
335
588
|
CAPABILITY_REQUIRED_RUNTIME_FEATURES = {
|
|
@@ -346,10 +599,26 @@ CAPABILITY_REQUIRED_RUNTIME_FEATURES = {
|
|
|
346
599
|
"wireless_client_association": ["workspace_validated", "wireless_runtime"],
|
|
347
600
|
"iot_registration": ["workspace_validated", "iot_runtime"],
|
|
348
601
|
"iot_control": ["workspace_validated", "iot_runtime"],
|
|
602
|
+
"vpn": ["workspace_validated", "tunnel_runtime"],
|
|
603
|
+
"ipsec": ["workspace_validated", "tunnel_runtime"],
|
|
604
|
+
"gre": ["workspace_validated", "tunnel_runtime"],
|
|
605
|
+
"ppp": ["workspace_validated", "wan_runtime"],
|
|
606
|
+
"security_edge": ["workspace_validated", "security_runtime"],
|
|
607
|
+
"multilayer_switching": ["workspace_validated", "multilayer_runtime"],
|
|
608
|
+
"ipv6_slaac": ["workspace_validated", "ipv6_runtime"],
|
|
609
|
+
"dhcpv6_stateful": ["workspace_validated", "ipv6_runtime"],
|
|
610
|
+
"ospfv3": ["workspace_validated", "ipv6_runtime"],
|
|
611
|
+
"eigrp_ipv6": ["workspace_validated", "ipv6_runtime"],
|
|
612
|
+
"ripng": ["workspace_validated", "ipv6_runtime"],
|
|
613
|
+
"hsrp": ["workspace_validated", "ipv6_runtime"],
|
|
614
|
+
"wep": ["workspace_validated", "wireless_runtime"],
|
|
615
|
+
"wpa_enterprise": ["workspace_validated", "wireless_runtime"],
|
|
349
616
|
}
|
|
350
617
|
|
|
351
618
|
|
|
352
619
|
def _generate_mismatch_reason(status: dict[str, object]) -> str | None:
|
|
620
|
+
if bool(status.get("report_only")):
|
|
621
|
+
return "report_only"
|
|
353
622
|
if not bool(status.get("inventory_supported")):
|
|
354
623
|
return "unsupported"
|
|
355
624
|
if bool(status.get("edit_supported")) and not bool(status.get("safe_open_generate_supported")):
|
|
@@ -363,6 +632,154 @@ def _generate_mismatch_reason(status: dict[str, object]) -> str | None:
|
|
|
363
632
|
return "unsupported"
|
|
364
633
|
|
|
365
634
|
|
|
635
|
+
def _selected_sample_for_donor(
|
|
636
|
+
samples: list[SampleDescriptor],
|
|
637
|
+
selected_donor: str | None,
|
|
638
|
+
) -> SampleDescriptor | None:
|
|
639
|
+
if not selected_donor:
|
|
640
|
+
return None
|
|
641
|
+
needle = str(selected_donor).replace("\\", "/").lower()
|
|
642
|
+
for sample in samples:
|
|
643
|
+
candidates = {
|
|
644
|
+
str(sample.path).replace("\\", "/").lower(),
|
|
645
|
+
str(sample.relative_path).replace("\\", "/").lower(),
|
|
646
|
+
str(sample.path).replace("\\", "/").split("/")[-1].lower(),
|
|
647
|
+
str(sample.relative_path).replace("\\", "/").split("/")[-1].lower(),
|
|
648
|
+
}
|
|
649
|
+
if needle in candidates:
|
|
650
|
+
return sample
|
|
651
|
+
return None
|
|
652
|
+
|
|
653
|
+
|
|
654
|
+
def _has_explicit_iot_registration(plan: IntentPlan) -> bool:
|
|
655
|
+
return any(
|
|
656
|
+
op.get("op") == "set_iot_registration"
|
|
657
|
+
and str(op.get("device", "")).strip()
|
|
658
|
+
and str(op.get("target", "")).strip()
|
|
659
|
+
for op in plan.iot_ops
|
|
660
|
+
)
|
|
661
|
+
|
|
662
|
+
|
|
663
|
+
def _has_explicit_iot_control(plan: IntentPlan) -> bool:
|
|
664
|
+
return any(
|
|
665
|
+
op.get("op") == "set_iot_rule_state"
|
|
666
|
+
and str(op.get("device", "")).strip()
|
|
667
|
+
and str(op.get("rule_name", "")).strip()
|
|
668
|
+
for op in plan.iot_ops
|
|
669
|
+
)
|
|
670
|
+
|
|
671
|
+
|
|
672
|
+
def _has_explicit_wireless_association(plan: IntentPlan) -> bool:
|
|
673
|
+
return any(
|
|
674
|
+
op.get("op") == "associate_wireless_client"
|
|
675
|
+
and str(op.get("device", "")).strip()
|
|
676
|
+
and str(op.get("ap", "")).strip()
|
|
677
|
+
and str(op.get("ssid", "")).strip()
|
|
678
|
+
for op in plan.wireless_ops
|
|
679
|
+
)
|
|
680
|
+
|
|
681
|
+
|
|
682
|
+
def _has_explicit_advanced_wireless_edit(plan: IntentPlan, capability: str) -> bool:
|
|
683
|
+
for op in plan.wireless_ops:
|
|
684
|
+
if op.get("op") != "set_wireless_ssid":
|
|
685
|
+
continue
|
|
686
|
+
security = str(op.get("security") or "").strip().lower()
|
|
687
|
+
if capability == "wep" and security == "wep" and str(op.get("device") or "").strip() and str(op.get("ssid") or "").strip():
|
|
688
|
+
return True
|
|
689
|
+
if capability == "wpa_enterprise" and security in {"wpa-enterprise", "wpa2-enterprise", "802.1x"} and str(op.get("device") or "").strip() and str(op.get("ssid") or "").strip():
|
|
690
|
+
return True
|
|
691
|
+
return False
|
|
692
|
+
|
|
693
|
+
|
|
694
|
+
def _has_explicit_wan_security_intent(plan: IntentPlan) -> bool:
|
|
695
|
+
capabilities = set(plan.capabilities)
|
|
696
|
+
prompt_lower = str(plan.prompt or "").lower()
|
|
697
|
+
return (
|
|
698
|
+
plan.network_style == "wan_security"
|
|
699
|
+
or bool(capabilities & {"vpn", "ipsec", "gre", "ppp", "security_edge", "multilayer_switching"})
|
|
700
|
+
or any(token in prompt_lower for token in ["site-to-site", "firewall", "asa", "cloud", "multilayer"])
|
|
701
|
+
)
|
|
702
|
+
|
|
703
|
+
|
|
704
|
+
def _has_explicit_ipv6_operation(plan: IntentPlan, capability: str) -> bool:
|
|
705
|
+
operation_map = {
|
|
706
|
+
"ipv6_slaac": {"enable_ipv6_unicast_routing", "set_ipv6_address", "set_ipv6_slaac"},
|
|
707
|
+
"dhcpv6_stateful": {"set_dhcpv6_pool"},
|
|
708
|
+
"ospfv3": {"set_ospfv3_interface"},
|
|
709
|
+
"eigrp_ipv6": {"set_eigrp_ipv6_interface"},
|
|
710
|
+
"ripng": {"set_ripng_interface"},
|
|
711
|
+
"hsrp": {"set_hsrp_ipv6"},
|
|
712
|
+
}
|
|
713
|
+
expected_ops = operation_map.get(capability, set())
|
|
714
|
+
return any(str(op.get("op")) in expected_ops for op in plan.router_ops)
|
|
715
|
+
|
|
716
|
+
|
|
717
|
+
def _selected_donor_capability_override(
|
|
718
|
+
plan: IntentPlan,
|
|
719
|
+
capability: str,
|
|
720
|
+
selected_sample: SampleDescriptor | None,
|
|
721
|
+
) -> bool:
|
|
722
|
+
if selected_sample is None:
|
|
723
|
+
return False
|
|
724
|
+
device_families = sorted({_device_family_for_type(device.get("type", "")) for device in selected_sample.devices if device.get("type")})
|
|
725
|
+
sample_capabilities = set(_expanded_sample_capabilities(selected_sample, device_families))
|
|
726
|
+
sample_capabilities.update(str(item) for item in selected_sample.validated_edit_capabilities if str(item).strip())
|
|
727
|
+
archetypes = {str(item) for item in selected_sample.archetype_tags if str(item).strip()}
|
|
728
|
+
runtime_features = {str(item) for item in selected_sample.runtime_features if str(item).strip()}
|
|
729
|
+
|
|
730
|
+
if capability == "iot":
|
|
731
|
+
return (
|
|
732
|
+
bool(archetypes & {"IoT/home gateway"})
|
|
733
|
+
and bool(sample_capabilities & {"iot", "iot_registration", "iot_control"})
|
|
734
|
+
and bool(runtime_features & {"iot_runtime"})
|
|
735
|
+
and (_has_explicit_iot_registration(plan) or _has_explicit_iot_control(plan))
|
|
736
|
+
)
|
|
737
|
+
if capability == "iot_registration":
|
|
738
|
+
return (
|
|
739
|
+
"IoT/home gateway" in archetypes
|
|
740
|
+
and "iot_registration" in sample_capabilities
|
|
741
|
+
and "iot_runtime" in runtime_features
|
|
742
|
+
and _has_explicit_iot_registration(plan)
|
|
743
|
+
)
|
|
744
|
+
if capability == "iot_control":
|
|
745
|
+
return (
|
|
746
|
+
"IoT/home gateway" in archetypes
|
|
747
|
+
and "iot_control" in sample_capabilities
|
|
748
|
+
and "iot_runtime" in runtime_features
|
|
749
|
+
and _has_explicit_iot_control(plan)
|
|
750
|
+
)
|
|
751
|
+
if capability == "wireless_client_association":
|
|
752
|
+
return (
|
|
753
|
+
bool(archetypes & {"IoT/home gateway", "wireless-heavy"})
|
|
754
|
+
and "wireless_client_association" in sample_capabilities
|
|
755
|
+
and "wireless_runtime" in runtime_features
|
|
756
|
+
and _has_explicit_wireless_association(plan)
|
|
757
|
+
)
|
|
758
|
+
wan_runtime_by_capability = {
|
|
759
|
+
"vpn": {"tunnel_runtime", "security_runtime", "wan_runtime"},
|
|
760
|
+
"ipsec": {"tunnel_runtime", "security_runtime"},
|
|
761
|
+
"gre": {"tunnel_runtime", "wan_runtime"},
|
|
762
|
+
"ppp": {"wan_runtime"},
|
|
763
|
+
"security_edge": {"security_runtime"},
|
|
764
|
+
"multilayer_switching": {"multilayer_runtime"},
|
|
765
|
+
}
|
|
766
|
+
if capability in wan_runtime_by_capability:
|
|
767
|
+
return (
|
|
768
|
+
"WAN/security edge" in archetypes
|
|
769
|
+
and capability in sample_capabilities
|
|
770
|
+
and bool(runtime_features & wan_runtime_by_capability[capability])
|
|
771
|
+
and _has_explicit_wan_security_intent(plan)
|
|
772
|
+
)
|
|
773
|
+
if capability in IPV6_DONOR_BACKED_CAPABILITIES:
|
|
774
|
+
return (
|
|
775
|
+
"IPv6/routing" in archetypes
|
|
776
|
+
and capability in sample_capabilities
|
|
777
|
+
and "ipv6_runtime" in runtime_features
|
|
778
|
+
and _has_explicit_ipv6_operation(plan, capability)
|
|
779
|
+
)
|
|
780
|
+
return False
|
|
781
|
+
|
|
782
|
+
|
|
366
783
|
def _best_maturity_level(status: dict[str, object]) -> str:
|
|
367
784
|
level = str(status.get("best_maturity_level") or "").strip()
|
|
368
785
|
if level in MATURITY_RANK:
|
|
@@ -381,6 +798,46 @@ def _best_maturity_level(status: dict[str, object]) -> str:
|
|
|
381
798
|
|
|
382
799
|
|
|
383
800
|
def _recommended_next_action_for_capability(capability: str, mismatch_reason: str | None) -> str:
|
|
801
|
+
if mismatch_reason == "report_only":
|
|
802
|
+
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"}:
|
|
804
|
+
if mismatch_reason == "supported_but_donor_limited":
|
|
805
|
+
return f"Provide a WAN/security edge donor with reusable ASA/cloud/serial or tunnel skeleton for {capability}."
|
|
806
|
+
if mismatch_reason == "supported_but_acceptance_gated":
|
|
807
|
+
return f"Keep {capability} in report/selection flow until a WAN/security edge donor is acceptance-backed."
|
|
808
|
+
return f"Prefer a WAN/security edge donor with explicit {capability} evidence before strict generate."
|
|
809
|
+
if capability in IPV6_DONOR_BACKED_CAPABILITIES:
|
|
810
|
+
if mismatch_reason == "supported_in_edit_only":
|
|
811
|
+
return f"Use explicit edit commands for {capability}; strict prompt generate still needs a selected IPv6/routing donor."
|
|
812
|
+
if mismatch_reason == "supported_but_donor_limited":
|
|
813
|
+
return f"Provide an IPv6/routing donor with reusable IPv6 interface and routing skeleton for {capability}."
|
|
814
|
+
if mismatch_reason == "supported_but_acceptance_gated":
|
|
815
|
+
return f"Keep {capability} in report/edit flow until an IPv6/routing donor is acceptance-backed."
|
|
816
|
+
return f"Prefer an IPv6/routing donor and explicit router/interface targets before strict generate for {capability}."
|
|
817
|
+
if capability in L2_SECURITY_MONITORING_EDIT_PROVEN_CAPABILITIES:
|
|
818
|
+
if mismatch_reason == "supported_in_edit_only":
|
|
819
|
+
return f"Use explicit edit commands for {capability}; strict prompt generate still needs an acceptance-backed L2 security/monitoring donor."
|
|
820
|
+
if mismatch_reason == "supported_but_donor_limited":
|
|
821
|
+
return f"Provide an L2 security/monitoring donor with reusable switch/router monitoring skeleton for {capability}."
|
|
822
|
+
return f"Keep {capability} constrained to edit/inventory proof until an L2 security/monitoring donor is acceptance-backed."
|
|
823
|
+
if capability in WIRELESS_ADVANCED_EDIT_PROVEN_CAPABILITIES:
|
|
824
|
+
if mismatch_reason == "supported_in_edit_only":
|
|
825
|
+
return f"Use explicit wireless edit commands for {capability}; strict prompt generate still needs an acceptance-backed advanced wireless donor."
|
|
826
|
+
if mismatch_reason == "supported_but_donor_limited":
|
|
827
|
+
return f"Provide an advanced wireless donor with reusable AP/router/WLC skeleton for {capability}."
|
|
828
|
+
return f"Keep {capability} constrained to explicit wireless edit proof until an advanced wireless donor is acceptance-backed."
|
|
829
|
+
if capability == "iot_registration":
|
|
830
|
+
if mismatch_reason == "supported_but_acceptance_gated":
|
|
831
|
+
return "Use an IoT/home gateway donor and explicitly name the thing plus gateway/server target before strict generate."
|
|
832
|
+
return "Prefer an IoT/home gateway donor and explicitly name the thing plus gateway/server target."
|
|
833
|
+
if capability == "iot_control":
|
|
834
|
+
if mismatch_reason == "supported_but_acceptance_gated":
|
|
835
|
+
return "Use a donor that already contains IoT rules and explicitly name the rule-bearing device before strict generate."
|
|
836
|
+
return "Prefer an IoT/home gateway donor with existing rule/control structure before strict generate."
|
|
837
|
+
if capability == "wireless_client_association":
|
|
838
|
+
if mismatch_reason == "supported_but_acceptance_gated":
|
|
839
|
+
return "Use a donor-backed wireless prompt that explicitly names the client, AP/router, and SSID before strict generate."
|
|
840
|
+
return "Prefer a wireless-heavy or IoT/home gateway donor and explicitly name the client, AP/router, and SSID."
|
|
384
841
|
if mismatch_reason == "supported_in_edit_only":
|
|
385
842
|
return f"Use donor-backed edit flow for {capability} until safe-open generate coverage is raised."
|
|
386
843
|
if mismatch_reason == "supported_but_donor_limited":
|
|
@@ -528,6 +985,7 @@ def build_coverage_gap_report(
|
|
|
528
985
|
selected_donor: str | None = None,
|
|
529
986
|
) -> CoverageGapReport:
|
|
530
987
|
iot_acceptance_gated = {"iot", "iot_registration", "iot_control"}
|
|
988
|
+
selected_sample = _selected_sample_for_donor(samples, selected_donor)
|
|
531
989
|
matrix = build_capability_matrix(samples)
|
|
532
990
|
by_capability: dict[str, list[CapabilityMatrixEntry]] = {}
|
|
533
991
|
by_family: dict[str, list[CapabilityMatrixEntry]] = {}
|
|
@@ -544,6 +1002,14 @@ def build_coverage_gap_report(
|
|
|
544
1002
|
capability_statuses: list[dict[str, object]] = []
|
|
545
1003
|
for capability in requested_capabilities:
|
|
546
1004
|
provider_families = _provider_families_for_capability(capability, requested_families)
|
|
1005
|
+
selected_donor_backed = _selected_donor_capability_override(plan, capability, selected_sample)
|
|
1006
|
+
wireless_edit_proven_only = (
|
|
1007
|
+
capability in WIRELESS_ADVANCED_EDIT_PROVEN_CAPABILITIES
|
|
1008
|
+
and _has_explicit_advanced_wireless_edit(plan, capability)
|
|
1009
|
+
and not selected_donor_backed
|
|
1010
|
+
)
|
|
1011
|
+
edit_proven_only = (capability in EDIT_PROVEN_REPORT_CAPABILITIES or wireless_edit_proven_only) and not selected_donor_backed
|
|
1012
|
+
report_only = capability in REPORT_ONLY_CAPABILITIES and not selected_donor_backed and not edit_proven_only
|
|
547
1013
|
matching_entries = [
|
|
548
1014
|
entry
|
|
549
1015
|
for entry in by_capability.get(capability, [])
|
|
@@ -552,8 +1018,14 @@ def build_coverage_gap_report(
|
|
|
552
1018
|
if matching_entries:
|
|
553
1019
|
supported_capabilities.append(capability)
|
|
554
1020
|
best_entry = max(matching_entries, key=lambda entry: MATURITY_RANK[entry.maturity_level])
|
|
555
|
-
acceptance_verified =
|
|
556
|
-
|
|
1021
|
+
acceptance_verified = (
|
|
1022
|
+
(any(entry.maturity_level == "acceptance-verified" for entry in matching_entries) and capability not in iot_acceptance_gated)
|
|
1023
|
+
or selected_donor_backed
|
|
1024
|
+
)
|
|
1025
|
+
if report_only or edit_proven_only:
|
|
1026
|
+
acceptance_verified = False
|
|
1027
|
+
requires_curated = best_entry.maturity_level == "safe-open-generate-supported" and not selected_donor_backed and not edit_proven_only
|
|
1028
|
+
if requires_curated:
|
|
557
1029
|
requires_curated_donor.append(capability)
|
|
558
1030
|
if not acceptance_verified:
|
|
559
1031
|
requires_manual_acceptance.append(capability)
|
|
@@ -562,18 +1034,78 @@ def build_coverage_gap_report(
|
|
|
562
1034
|
"capability": capability,
|
|
563
1035
|
"provider_families": sorted(provider_families),
|
|
564
1036
|
"matching_device_families": sorted({entry.device_family for entry in matching_entries}),
|
|
565
|
-
"best_maturity_level": best_entry.maturity_level,
|
|
1037
|
+
"best_maturity_level": "inventory-supported" if report_only else "config-mutation-supported" if edit_proven_only else best_entry.maturity_level,
|
|
566
1038
|
"inventory_supported": any(MATURITY_RANK[entry.maturity_level] >= MATURITY_RANK["inventory-supported"] for entry in matching_entries),
|
|
567
|
-
"edit_supported": any(MATURITY_RANK[entry.maturity_level] >= MATURITY_RANK["config-mutation-supported"] for entry in matching_entries),
|
|
568
|
-
"config_mutation_supported": any(MATURITY_RANK[entry.maturity_level] >= MATURITY_RANK["config-mutation-supported"] for entry in matching_entries),
|
|
569
|
-
"safe_open_generate_supported": any(MATURITY_RANK[entry.maturity_level] >= MATURITY_RANK["safe-open-generate-supported"] for entry in matching_entries),
|
|
1039
|
+
"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
|
+
"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),
|
|
570
1042
|
"acceptance_verified": acceptance_verified,
|
|
571
|
-
"requires_curated_donor":
|
|
1043
|
+
"requires_curated_donor": requires_curated,
|
|
572
1044
|
"requires_manual_acceptance": not acceptance_verified,
|
|
1045
|
+
"report_only": report_only,
|
|
573
1046
|
"recommended_donors": list(dict.fromkeys(donor for entry in matching_entries for donor in entry.accepted_donors))[:10],
|
|
574
1047
|
"known_limitations": list(dict.fromkeys(item for entry in matching_entries for item in entry.known_limitations)),
|
|
575
1048
|
}
|
|
576
1049
|
)
|
|
1050
|
+
elif edit_proven_only:
|
|
1051
|
+
supported_capabilities.append(capability)
|
|
1052
|
+
capability_statuses.append(
|
|
1053
|
+
{
|
|
1054
|
+
"capability": capability,
|
|
1055
|
+
"provider_families": sorted(provider_families),
|
|
1056
|
+
"matching_device_families": sorted(provider_families),
|
|
1057
|
+
"best_maturity_level": "config-mutation-supported",
|
|
1058
|
+
"inventory_supported": True,
|
|
1059
|
+
"edit_supported": True,
|
|
1060
|
+
"config_mutation_supported": True,
|
|
1061
|
+
"safe_open_generate_supported": False,
|
|
1062
|
+
"acceptance_verified": False,
|
|
1063
|
+
"requires_curated_donor": False,
|
|
1064
|
+
"requires_manual_acceptance": True,
|
|
1065
|
+
"report_only": False,
|
|
1066
|
+
"recommended_donors": [],
|
|
1067
|
+
"known_limitations": ["editor-proven without sample-backed donor selection"],
|
|
1068
|
+
}
|
|
1069
|
+
)
|
|
1070
|
+
elif report_only:
|
|
1071
|
+
supported_capabilities.append(capability)
|
|
1072
|
+
capability_statuses.append(
|
|
1073
|
+
{
|
|
1074
|
+
"capability": capability,
|
|
1075
|
+
"provider_families": sorted(provider_families),
|
|
1076
|
+
"matching_device_families": sorted(provider_families),
|
|
1077
|
+
"best_maturity_level": "inventory-supported",
|
|
1078
|
+
"inventory_supported": True,
|
|
1079
|
+
"edit_supported": False,
|
|
1080
|
+
"config_mutation_supported": False,
|
|
1081
|
+
"safe_open_generate_supported": False,
|
|
1082
|
+
"acceptance_verified": False,
|
|
1083
|
+
"requires_curated_donor": False,
|
|
1084
|
+
"requires_manual_acceptance": True,
|
|
1085
|
+
"report_only": True,
|
|
1086
|
+
"recommended_donors": [],
|
|
1087
|
+
"known_limitations": ["report-supported without decode-backed donor selection"],
|
|
1088
|
+
}
|
|
1089
|
+
)
|
|
1090
|
+
elif selected_donor_backed and selected_sample is not None:
|
|
1091
|
+
supported_capabilities.append(capability)
|
|
1092
|
+
capability_statuses.append(
|
|
1093
|
+
{
|
|
1094
|
+
"capability": capability,
|
|
1095
|
+
"provider_families": sorted(provider_families),
|
|
1096
|
+
"matching_device_families": sorted({family for family in (selected_sample.device_families or []) if not provider_families or family in provider_families}),
|
|
1097
|
+
"best_maturity_level": selected_sample.apply_safety_level or "safe-open-generate-supported",
|
|
1098
|
+
"inventory_supported": True,
|
|
1099
|
+
"edit_supported": True,
|
|
1100
|
+
"config_mutation_supported": True,
|
|
1101
|
+
"safe_open_generate_supported": True,
|
|
1102
|
+
"acceptance_verified": True,
|
|
1103
|
+
"requires_curated_donor": False,
|
|
1104
|
+
"requires_manual_acceptance": False,
|
|
1105
|
+
"recommended_donors": [selected_sample.relative_path],
|
|
1106
|
+
"known_limitations": [],
|
|
1107
|
+
}
|
|
1108
|
+
)
|
|
577
1109
|
else:
|
|
578
1110
|
unsupported_capabilities.append(capability)
|
|
579
1111
|
capability_statuses.append(
|
|
@@ -694,6 +1226,10 @@ def build_inventory_capability_report(payload: dict[str, Any]) -> dict[str, obje
|
|
|
694
1226
|
capabilities.add("acl")
|
|
695
1227
|
if payload.get("topology_summary", {}).get("network_style") == "wan_security":
|
|
696
1228
|
capabilities.update({"vpn", "ipsec", "gre", "ppp"})
|
|
1229
|
+
for routing_details in payload.get("routing", {}).values():
|
|
1230
|
+
capabilities.update(str(capability) for capability in routing_details.get("capabilities", []) if str(capability).strip())
|
|
1231
|
+
for l2_details in payload.get("l2_security_monitoring", {}).values():
|
|
1232
|
+
capabilities.update(str(capability) for capability in l2_details.get("capabilities", []) if str(capability).strip())
|
|
697
1233
|
return {
|
|
698
1234
|
"device_families": families,
|
|
699
1235
|
"capabilities": sorted(capabilities),
|