packet-tracer-skill 0.2.1 → 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 +53 -2
- package/README.md +443 -124
- package/docs/automation-controller-proof.md +35 -0
- package/docs/campus-donor-proof.md +103 -0
- package/docs/curated-donor-registry.md +37 -0
- package/docs/generate-ready-pilot-design.md +30 -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/home-iot-donor-proof.md +64 -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/launch-announcement-0.2.2.md +15 -0
- package/docs/packet-tracer-feature-gap-atlas.md +244 -0
- package/docs/post-launch-follow-up.md +39 -0
- package/docs/publish-preview-roadmap.md +20 -17
- package/docs/release-checklist.md +32 -17
- package/docs/release-notes-0.2.1.md +7 -6
- package/docs/release-notes-0.2.2.md +26 -0
- package/docs/release-notes-0.2.3.md +59 -0
- package/docs/runtime-truth.md +31 -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 +58 -0
- package/docs/wireless-advanced-proof.md +60 -0
- package/examples/README.md +11 -8
- package/examples/gallery.md +9 -3
- package/examples/index.json +3 -3
- package/package.json +20 -1
- package/references/curated-donor-registry.json +5 -2
- package/references/packettracer-feature-atlas.json +183 -0
- package/references/scenario-fixture-corpus.json +1 -0
- package/scripts/build_examples_index.py +26 -15
- package/scripts/coverage_matrix.py +1054 -25
- package/scripts/feature_atlas.py +293 -0
- package/scripts/generate_pkt.py +491 -34
- package/scripts/intent_parser.py +798 -8
- package/scripts/pkt_editor.py +651 -0
- package/scripts/remote_search.py +197 -21
- package/scripts/runtime_doctor.py +83 -10
- package/scripts/sample_catalog.py +266 -6
|
@@ -24,20 +24,102 @@ CAPABILITY_KEYWORDS = {
|
|
|
24
24
|
"dhcp_pool": ["dhcp", "reservation", "apipa"],
|
|
25
25
|
"router_dhcp": ["dhcp", "reservation", "apipa"],
|
|
26
26
|
"server_dhcp": ["dhcp", "reservation", "apipa"],
|
|
27
|
+
"dhcpv6_stateful": ["dhcpv6", "stateful_dhcpv6"],
|
|
28
|
+
"dhcpv6_stateless": ["stateless_dhcpv6"],
|
|
29
|
+
"ipv6_slaac": ["slaac"],
|
|
30
|
+
"ipv6_prefix_delegation": ["prefix_delegation"],
|
|
31
|
+
"ipv6_dns_aaaa": ["aaaa"],
|
|
32
|
+
"ipv6_tunneling": ["ipv6ip", "ipv6 tunneling"],
|
|
33
|
+
"isatap": ["isatap"],
|
|
27
34
|
"dhcp_snooping": ["dhcp snooping", "option_82", "trusted_untrusted"],
|
|
28
35
|
"ospf": ["ospf"],
|
|
36
|
+
"ospfv2": ["ospf", "ospfv2", "single-area ospf"],
|
|
37
|
+
"ospfv3": ["ospfv3", "ipv6_ospf"],
|
|
29
38
|
"eigrp": ["eigrp"],
|
|
39
|
+
"eigrp_ipv4": ["eigrp", "ipv4 eigrp"],
|
|
40
|
+
"eigrp_ipv6": ["ipv6_eigrp"],
|
|
30
41
|
"rip": ["rip"],
|
|
42
|
+
"ripv2": ["ripv2", "rip"],
|
|
43
|
+
"ripng": ["ripng", "ipv6 rip"],
|
|
44
|
+
"hsrp": ["hsrp"],
|
|
31
45
|
"nat": ["nat"],
|
|
46
|
+
"nat_static": ["static nat"],
|
|
47
|
+
"nat_dynamic": ["dynamic nat"],
|
|
48
|
+
"pat": ["pat", "overload"],
|
|
49
|
+
"static_route": ["static route", "ip route"],
|
|
50
|
+
"default_route": ["default route"],
|
|
51
|
+
"dhcp_relay": ["dhcp relay", "helper-address"],
|
|
52
|
+
"ssh_ios": ["ssh"],
|
|
53
|
+
"ntp_ios": ["ntp"],
|
|
54
|
+
"syslog_ios": ["syslog"],
|
|
32
55
|
"acl": ["acl", "access-list"],
|
|
56
|
+
"dai": ["dai", "dynamic arp inspection"],
|
|
57
|
+
"dot1x": ["dot1x", "802.1x", "port-based nac"],
|
|
58
|
+
"lldp": ["lldp"],
|
|
59
|
+
"rep": ["rep_"],
|
|
60
|
+
"snmp": ["snmp"],
|
|
61
|
+
"netflow": ["netflow"],
|
|
62
|
+
"span": ["rspan", "monitor session", "span session"],
|
|
63
|
+
"qos": ["qos"],
|
|
64
|
+
"port_security": ["port security", "port-security"],
|
|
65
|
+
"bgp": ["bgp"],
|
|
66
|
+
"stp": ["stp", "spanning tree", "spanning-tree"],
|
|
67
|
+
"rstp": ["rstp", "rapid-pvst"],
|
|
68
|
+
"etherchannel": ["etherchannel", "port-channel", "channel-group"],
|
|
69
|
+
"lacp": ["lacp"],
|
|
70
|
+
"pagp": ["pagp"],
|
|
71
|
+
"vtp": ["vtp"],
|
|
72
|
+
"dtp": ["dtp"],
|
|
33
73
|
"vpn": ["vpn", "ipsec", "gre"],
|
|
34
74
|
"ipsec": ["ipsec", "ike"],
|
|
35
75
|
"gre": ["gre"],
|
|
36
76
|
"ppp": ["ppp", "chap", "pap"],
|
|
77
|
+
"wan": ["wan", "cloud", "serial", "pppoe", "ppp"],
|
|
78
|
+
"security_edge": ["asa", "firewall", "security appliance"],
|
|
79
|
+
"asa_acl_nat": ["asa_acl_nat"],
|
|
80
|
+
"asa_service_policy": ["asa_service_policy", "service policy"],
|
|
81
|
+
"clientless_vpn": ["clientless_vpn"],
|
|
82
|
+
"cbac": ["cbac"],
|
|
83
|
+
"zfw": ["zfw", "zone based firewall"],
|
|
84
|
+
"sniffer": ["sniffer"],
|
|
37
85
|
"multilayer_switching": ["multilayer", "layer 3", "3560", "svi"],
|
|
38
86
|
"wireless": ["wireless", "wlan", "wlc", "wifi", "ssid", "wpa", "wep", "5g", "bluetooth", "cellular"],
|
|
39
87
|
"wireless_ap": ["wireless", "wlan", "wlc", "wifi", "ssid", "wpa", "wep"],
|
|
40
88
|
"wireless_client": ["wireless", "wifi", "tablet", "smartphone", "laptop"],
|
|
89
|
+
"wlc": ["wlc", "wireless lan controller"],
|
|
90
|
+
"wpa_enterprise": ["wpa_radius", "wpa2_enterprise"],
|
|
91
|
+
"wep": ["wep"],
|
|
92
|
+
"guest_wifi": ["guest"],
|
|
93
|
+
"beamforming": ["beamforming"],
|
|
94
|
+
"meraki": ["meraki"],
|
|
95
|
+
"cellular_5g": ["5g", "cellular", "cell tower"],
|
|
96
|
+
"bluetooth": ["bluetooth"],
|
|
97
|
+
"network_controller": ["network controller", "netcon"],
|
|
98
|
+
"python_programming": ["python"],
|
|
99
|
+
"javascript_programming": ["javascript"],
|
|
100
|
+
"blockly_programming": ["blockly"],
|
|
101
|
+
"tcp_udp_app": ["tcp_test_app", "udp_test_app"],
|
|
102
|
+
"vm_iox": ["iox", "uploading_and_running_vm"],
|
|
103
|
+
"voip": ["voip"],
|
|
104
|
+
"ip_phone": ["ip phone", "phone"],
|
|
105
|
+
"call_manager": ["call manager", "telephony", "voip"],
|
|
106
|
+
"linksys_voice": ["voip linksys"],
|
|
107
|
+
"mqtt": ["mqtt"],
|
|
108
|
+
"real_http": ["real-http", "real http"],
|
|
109
|
+
"real_websocket": ["real-websocket", "real websocket"],
|
|
110
|
+
"visual_scripting": ["visual-scripting", "visual scripting"],
|
|
111
|
+
"ptp": ["ptp"],
|
|
112
|
+
"profinet": ["profinet"],
|
|
113
|
+
"l2nat": ["l2nat"],
|
|
114
|
+
"cyberobserver": ["cyberobserver"],
|
|
115
|
+
"industrial_firewall": ["isa3000", "industrial cybersecurity"],
|
|
116
|
+
"coaxial": ["coaxial"],
|
|
117
|
+
"cable_dsl": ["cable modem", "dsl modem"],
|
|
118
|
+
"central_office": ["centraloffice", "central office"],
|
|
119
|
+
"cell_tower": ["cell tower"],
|
|
120
|
+
"power_distribution": ["power distribution"],
|
|
121
|
+
"hot_swappable": ["hotswappable", "hot swappable"],
|
|
122
|
+
"ios_license": ["ios_15", "license"],
|
|
41
123
|
"ntp": ["ntp"],
|
|
42
124
|
"dns": ["dns"],
|
|
43
125
|
"server_dns": ["dns"],
|
|
@@ -54,6 +136,90 @@ CAPABILITY_KEYWORDS = {
|
|
|
54
136
|
"printer": ["printer"],
|
|
55
137
|
}
|
|
56
138
|
|
|
139
|
+
REPORT_ONLY_CAPABILITIES = {
|
|
140
|
+
"ipv6_slaac",
|
|
141
|
+
"dhcpv6_stateful",
|
|
142
|
+
"dhcpv6_stateless",
|
|
143
|
+
"ipv6_prefix_delegation",
|
|
144
|
+
"ipv6_dns_aaaa",
|
|
145
|
+
"ipv6_tunneling",
|
|
146
|
+
"isatap",
|
|
147
|
+
"ospfv3",
|
|
148
|
+
"eigrp_ipv6",
|
|
149
|
+
"ripng",
|
|
150
|
+
"hsrp",
|
|
151
|
+
"dhcp_snooping",
|
|
152
|
+
"dai",
|
|
153
|
+
"dot1x",
|
|
154
|
+
"lldp",
|
|
155
|
+
"rep",
|
|
156
|
+
"snmp",
|
|
157
|
+
"netflow",
|
|
158
|
+
"span",
|
|
159
|
+
"qos",
|
|
160
|
+
"port_security",
|
|
161
|
+
"bgp",
|
|
162
|
+
"stp",
|
|
163
|
+
"rstp",
|
|
164
|
+
"etherchannel",
|
|
165
|
+
"lacp",
|
|
166
|
+
"pagp",
|
|
167
|
+
"vtp",
|
|
168
|
+
"dtp",
|
|
169
|
+
"ospfv2",
|
|
170
|
+
"eigrp_ipv4",
|
|
171
|
+
"ripv2",
|
|
172
|
+
"static_route",
|
|
173
|
+
"default_route",
|
|
174
|
+
"dhcp_relay",
|
|
175
|
+
"nat_static",
|
|
176
|
+
"nat_dynamic",
|
|
177
|
+
"pat",
|
|
178
|
+
"ssh_ios",
|
|
179
|
+
"ntp_ios",
|
|
180
|
+
"syslog_ios",
|
|
181
|
+
"asa_acl_nat",
|
|
182
|
+
"asa_service_policy",
|
|
183
|
+
"clientless_vpn",
|
|
184
|
+
"cbac",
|
|
185
|
+
"zfw",
|
|
186
|
+
"sniffer",
|
|
187
|
+
"wlc",
|
|
188
|
+
"wpa_enterprise",
|
|
189
|
+
"wep",
|
|
190
|
+
"guest_wifi",
|
|
191
|
+
"beamforming",
|
|
192
|
+
"meraki",
|
|
193
|
+
"cellular_5g",
|
|
194
|
+
"bluetooth",
|
|
195
|
+
"network_controller",
|
|
196
|
+
"python_programming",
|
|
197
|
+
"javascript_programming",
|
|
198
|
+
"blockly_programming",
|
|
199
|
+
"tcp_udp_app",
|
|
200
|
+
"vm_iox",
|
|
201
|
+
"voip",
|
|
202
|
+
"ip_phone",
|
|
203
|
+
"call_manager",
|
|
204
|
+
"linksys_voice",
|
|
205
|
+
"mqtt",
|
|
206
|
+
"real_http",
|
|
207
|
+
"real_websocket",
|
|
208
|
+
"visual_scripting",
|
|
209
|
+
"ptp",
|
|
210
|
+
"profinet",
|
|
211
|
+
"l2nat",
|
|
212
|
+
"cyberobserver",
|
|
213
|
+
"industrial_firewall",
|
|
214
|
+
"coaxial",
|
|
215
|
+
"cable_dsl",
|
|
216
|
+
"central_office",
|
|
217
|
+
"cell_tower",
|
|
218
|
+
"power_distribution",
|
|
219
|
+
"hot_swappable",
|
|
220
|
+
"ios_license",
|
|
221
|
+
}
|
|
222
|
+
|
|
57
223
|
TYPE_NORMALIZATION = {
|
|
58
224
|
"pc": "PC",
|
|
59
225
|
"pc-pt": "PC",
|
|
@@ -76,6 +242,9 @@ TYPE_NORMALIZATION = {
|
|
|
76
242
|
"printer": "Printer",
|
|
77
243
|
"smartphone": "Smartphone",
|
|
78
244
|
"mcucomponent": "IoT",
|
|
245
|
+
"networkcontroller": "NetworkController",
|
|
246
|
+
"centralofficeserver": "CentralOfficeServer",
|
|
247
|
+
"celltower": "CellTower",
|
|
79
248
|
}
|
|
80
249
|
|
|
81
250
|
LICENSE_FILENAMES = [
|
|
@@ -215,6 +384,7 @@ def infer_capability_tags(item: dict[str, Any]) -> list[str]:
|
|
|
215
384
|
rel = item.get("relative_path", "").lower().replace("\\", "/")
|
|
216
385
|
rel_flat = rel.replace("/", " ")
|
|
217
386
|
devices = item.get("devices", [])
|
|
387
|
+
raw_types = {str(device.get("type", "")).strip() for device in devices if str(device.get("type", "")).strip()}
|
|
218
388
|
normalized_types = [normalize_device_type(device.get("type", "")) for device in devices]
|
|
219
389
|
router_count = normalized_types.count("Router")
|
|
220
390
|
switch_count = normalized_types.count("Switch")
|
|
@@ -232,6 +402,15 @@ def infer_capability_tags(item: dict[str, Any]) -> list[str]:
|
|
|
232
402
|
if any(dtype in {"WirelessRouter", "LightWeightAccessPoint"} for dtype in normalized_types):
|
|
233
403
|
tags.add("wireless")
|
|
234
404
|
tags.add("wireless_ap")
|
|
405
|
+
if "HomeGateway" in raw_types or any(dtype == "IoT" for dtype in normalized_types):
|
|
406
|
+
tags.add("iot")
|
|
407
|
+
if any(dtype in {"ASA", "Security Appliance"} for dtype in normalized_types) or raw_types & {"ASA", "Security Appliance"}:
|
|
408
|
+
tags.add("security_edge")
|
|
409
|
+
tags.add("acl")
|
|
410
|
+
if any(dtype in {"Cloud", "Cable Modem", "Dsl Modem"} for dtype in normalized_types):
|
|
411
|
+
tags.add("wan")
|
|
412
|
+
if any(dtype == "MultiLayerSwitch" for dtype in normalized_types):
|
|
413
|
+
tags.add("multilayer_switching")
|
|
235
414
|
if any(dtype in {"Tablet", "Laptop", "Smartphone"} for dtype in normalized_types):
|
|
236
415
|
tags.add("wireless_client")
|
|
237
416
|
if any(dtype == "Tablet" for dtype in normalized_types):
|
|
@@ -283,8 +462,12 @@ def infer_preferred_roles(item: dict[str, Any]) -> list[str]:
|
|
|
283
462
|
roles.append("preferred_vlan")
|
|
284
463
|
if "dhcp_pool" in tags:
|
|
285
464
|
roles.append("preferred_dhcp")
|
|
286
|
-
if {"ospf", "eigrp", "rip"} & tags:
|
|
465
|
+
if {"ospf", "eigrp", "rip", "bgp", "ospfv2", "eigrp_ipv4", "ripv2", "static_route", "default_route"} & tags:
|
|
287
466
|
roles.append("preferred_routing")
|
|
467
|
+
if {"dhcp_relay", "nat_static", "nat_dynamic", "pat", "ssh_ios", "ntp_ios", "syslog_ios"} & tags:
|
|
468
|
+
roles.append("preferred_ipv4_management")
|
|
469
|
+
if {"stp", "rstp", "etherchannel", "lacp", "pagp", "vtp", "dtp"} & tags:
|
|
470
|
+
roles.append("preferred_l2_resiliency")
|
|
288
471
|
if {"nat", "acl", "vpn"} & tags:
|
|
289
472
|
roles.append("preferred_security")
|
|
290
473
|
if {"vpn", "ipsec", "gre", "ppp", "multilayer_switching"} & tags:
|
|
@@ -314,6 +497,23 @@ def infer_wireless_mode_tags(item: dict[str, Any]) -> list[str]:
|
|
|
314
497
|
return sorted(tags)
|
|
315
498
|
|
|
316
499
|
|
|
500
|
+
def infer_iot_roles(item: dict[str, Any]) -> list[str]:
|
|
501
|
+
roles = {str(role).strip() for role in item.get("iot_roles", []) if str(role).strip()}
|
|
502
|
+
raw_types = {str(device.get("type", "")).strip() for device in item.get("devices", []) if str(device.get("type", "")).strip()}
|
|
503
|
+
normalized_types = {normalize_device_type(device.get("type", "")) for device in item.get("devices", []) if device.get("type")}
|
|
504
|
+
families = set(item.get("device_families", []) or infer_device_families(item))
|
|
505
|
+
has_thing = bool(raw_types & {"MCUComponent", "Board", "Sensor", "Actuator"}) or "iot devices" in families
|
|
506
|
+
has_gateway = "HomeGateway" in raw_types or ("home/wireless routers" in families and has_thing)
|
|
507
|
+
has_server = "Server" in normalized_types or "servers" in families
|
|
508
|
+
if has_thing:
|
|
509
|
+
roles.add("thing")
|
|
510
|
+
if has_gateway:
|
|
511
|
+
roles.add("gateway")
|
|
512
|
+
if has_server:
|
|
513
|
+
roles.add("server")
|
|
514
|
+
return sorted(roles)
|
|
515
|
+
|
|
516
|
+
|
|
317
517
|
def infer_device_families(item: dict[str, Any]) -> list[str]:
|
|
318
518
|
family_map = {
|
|
319
519
|
"Router": "routers",
|
|
@@ -325,6 +525,9 @@ def infer_device_families(item: dict[str, Any]) -> list[str]:
|
|
|
325
525
|
"Tablet": "end devices",
|
|
326
526
|
"Smartphone": "end devices",
|
|
327
527
|
"Printer": "end devices",
|
|
528
|
+
"IpPhone": "end devices",
|
|
529
|
+
"HomeVoip": "end devices",
|
|
530
|
+
"AnalogPhone": "end devices",
|
|
328
531
|
"LightWeightAccessPoint": "access points",
|
|
329
532
|
"WirelessRouter": "home/wireless routers",
|
|
330
533
|
"WirelessRouterNewGeneration": "home/wireless routers",
|
|
@@ -336,6 +539,9 @@ def infer_device_families(item: dict[str, Any]) -> list[str]:
|
|
|
336
539
|
"Dsl Modem": "wan/cloud/dsl/cable devices",
|
|
337
540
|
"Security Appliance": "security devices",
|
|
338
541
|
"ASA": "security devices",
|
|
542
|
+
"NetworkController": "network controllers",
|
|
543
|
+
"CentralOfficeServer": "wan/cloud/dsl/cable devices",
|
|
544
|
+
"CellTower": "wan/cloud/dsl/cable devices",
|
|
339
545
|
"IoT": "iot devices",
|
|
340
546
|
"Sensor": "iot devices",
|
|
341
547
|
"Actuator": "iot devices",
|
|
@@ -397,12 +603,28 @@ def infer_service_support(item: dict[str, Any]) -> list[str]:
|
|
|
397
603
|
|
|
398
604
|
def infer_runtime_features(item: dict[str, Any]) -> list[str]:
|
|
399
605
|
features: set[str] = set()
|
|
606
|
+
tags = set(item.get("capability_tags", []))
|
|
607
|
+
families = set(item.get("device_families", []) or infer_device_families(item))
|
|
400
608
|
if item.get("wireless_mode_tags"):
|
|
401
609
|
features.add("wireless_runtime")
|
|
402
610
|
if item.get("service_support"):
|
|
403
611
|
features.add("service_runtime")
|
|
404
|
-
if item
|
|
612
|
+
if infer_iot_roles(item):
|
|
405
613
|
features.add("iot_runtime")
|
|
614
|
+
if families & {"wan/cloud/dsl/cable devices"} or tags & {"wan", "ppp"}:
|
|
615
|
+
features.add("wan_runtime")
|
|
616
|
+
if families & {"security devices"} or tags & {"security_edge", "acl", "nat"}:
|
|
617
|
+
features.add("security_runtime")
|
|
618
|
+
if tags & {"vpn", "ipsec", "gre"}:
|
|
619
|
+
features.add("tunnel_runtime")
|
|
620
|
+
if tags & {"ipv6_slaac", "dhcpv6_stateful", "dhcpv6_stateless", "ospfv3", "eigrp_ipv6", "ripng", "hsrp"}:
|
|
621
|
+
features.add("ipv6_runtime")
|
|
622
|
+
if tags & {"bgp", "stp", "rstp", "etherchannel", "lacp", "pagp", "vtp", "dtp"}:
|
|
623
|
+
features.add("l2_resiliency_runtime")
|
|
624
|
+
if tags & {"ospfv2", "eigrp_ipv4", "ripv2", "static_route", "default_route", "dhcp_relay", "nat_static", "nat_dynamic", "pat", "ssh_ios", "ntp_ios", "syslog_ios"}:
|
|
625
|
+
features.add("ipv4_routing_management_runtime")
|
|
626
|
+
if families & {"multilayer switches"} or tags & {"multilayer_switching"}:
|
|
627
|
+
features.add("multilayer_runtime")
|
|
406
628
|
if item.get("workspace_validation"):
|
|
407
629
|
features.add("workspace_validated")
|
|
408
630
|
if any(str(link.get("from", "")) and str(link.get("to", "")) for link in item.get("links", [])):
|
|
@@ -416,7 +638,7 @@ def infer_archetype_tags(item: dict[str, Any]) -> list[str]:
|
|
|
416
638
|
families = set(item.get("device_families", []))
|
|
417
639
|
services = set(item.get("service_support", []))
|
|
418
640
|
wireless_modes = set(item.get("wireless_mode_tags", []))
|
|
419
|
-
iot_roles = set(item
|
|
641
|
+
iot_roles = set(infer_iot_roles(item))
|
|
420
642
|
|
|
421
643
|
if topology & {"chain", "core_access", "department_lan", "router_on_a_stick"} or (
|
|
422
644
|
families & {"routers", "switches", "multilayer switches"}
|
|
@@ -431,10 +653,33 @@ def infer_archetype_tags(item: dict[str, Any]) -> list[str]:
|
|
|
431
653
|
tags.add("IoT/home gateway")
|
|
432
654
|
if families & {"wan/cloud/dsl/cable devices", "security devices"} or set(item.get("capability_tags", [])) & {
|
|
433
655
|
"vpn",
|
|
656
|
+
"ipsec",
|
|
657
|
+
"gre",
|
|
658
|
+
"ppp",
|
|
659
|
+
"wan",
|
|
660
|
+
"security_edge",
|
|
661
|
+
"multilayer_switching",
|
|
434
662
|
"nat",
|
|
435
663
|
"acl",
|
|
436
664
|
}:
|
|
437
665
|
tags.add("WAN/security edge")
|
|
666
|
+
capability_tags = set(item.get("capability_tags", []))
|
|
667
|
+
if capability_tags & {"ipv6_slaac", "dhcpv6_stateful", "dhcpv6_stateless", "ipv6_prefix_delegation", "ipv6_dns_aaaa", "ipv6_tunneling", "isatap", "ospfv3", "eigrp_ipv6", "ripng", "hsrp"}:
|
|
668
|
+
tags.add("IPv6/routing")
|
|
669
|
+
if capability_tags & {"dhcp_snooping", "dai", "dot1x", "lldp", "rep", "snmp", "netflow", "span", "qos", "port_security"}:
|
|
670
|
+
tags.add("L2 security/monitoring")
|
|
671
|
+
if capability_tags & {"bgp", "stp", "rstp", "etherchannel", "lacp", "pagp", "vtp", "dtp"}:
|
|
672
|
+
tags.add("L2 resiliency/routing")
|
|
673
|
+
if capability_tags & {"ospfv2", "eigrp_ipv4", "ripv2", "static_route", "default_route", "dhcp_relay", "nat_static", "nat_dynamic", "pat", "ssh_ios", "ntp_ios", "syslog_ios"}:
|
|
674
|
+
tags.add("IPv4 routing/management")
|
|
675
|
+
if capability_tags & {"wlc", "wpa_enterprise", "wep", "guest_wifi", "beamforming", "meraki", "cellular_5g", "bluetooth"}:
|
|
676
|
+
tags.add("advanced wireless")
|
|
677
|
+
if capability_tags & {"network_controller", "python_programming", "javascript_programming", "blockly_programming", "tcp_udp_app", "vm_iox"} or "network controllers" in families:
|
|
678
|
+
tags.add("automation/controller")
|
|
679
|
+
if capability_tags & {"voip", "ip_phone", "call_manager", "linksys_voice"}:
|
|
680
|
+
tags.add("voice/collaboration")
|
|
681
|
+
if capability_tags & {"mqtt", "real_http", "real_websocket", "visual_scripting", "ptp", "profinet", "l2nat", "cyberobserver", "industrial_firewall"}:
|
|
682
|
+
tags.add("industrial IoT")
|
|
438
683
|
return sorted(tags)
|
|
439
684
|
|
|
440
685
|
|
|
@@ -467,6 +712,8 @@ def infer_apply_safety_level(item: dict[str, Any]) -> str:
|
|
|
467
712
|
|
|
468
713
|
def infer_validated_edit_capabilities(item: dict[str, Any]) -> list[str]:
|
|
469
714
|
capabilities: set[str] = {str(tag) for tag in item.get("capability_tags", []) if str(tag).strip()}
|
|
715
|
+
inferred_iot_roles = set(infer_iot_roles(item))
|
|
716
|
+
families = set(item.get("device_families", []) or infer_device_families(item))
|
|
470
717
|
service_capability_map = {
|
|
471
718
|
"dhcp": "server_dhcp",
|
|
472
719
|
"dns": "server_dns",
|
|
@@ -487,12 +734,24 @@ def infer_validated_edit_capabilities(item: dict[str, Any]) -> list[str]:
|
|
|
487
734
|
capabilities.add("wireless_mutation")
|
|
488
735
|
if any(device_family == "end devices" for device_family in item.get("device_families", [])):
|
|
489
736
|
capabilities.add("end_device_mutation")
|
|
490
|
-
if
|
|
737
|
+
if inferred_iot_roles:
|
|
738
|
+
capabilities.add("iot")
|
|
491
739
|
capabilities.add("iot_registration")
|
|
492
|
-
if "server"
|
|
740
|
+
if {"server", "gateway"} & inferred_iot_roles:
|
|
493
741
|
capabilities.add("iot_control")
|
|
742
|
+
if capabilities & {"vpn", "ipsec", "gre"}:
|
|
743
|
+
capabilities.update({"vpn", "ipsec", "gre"})
|
|
744
|
+
if capabilities & {"ppp", "wan"} or families & {"wan/cloud/dsl/cable devices"}:
|
|
745
|
+
capabilities.add("wan")
|
|
746
|
+
if "ppp" in capabilities:
|
|
747
|
+
capabilities.add("ppp")
|
|
748
|
+
if capabilities & {"acl", "nat", "security_edge"} or families & {"security devices"}:
|
|
749
|
+
capabilities.add("security_edge")
|
|
750
|
+
if "multilayer switches" in families or "multilayer_switching" in capabilities:
|
|
751
|
+
capabilities.add("multilayer_switching")
|
|
494
752
|
if item.get("apply_safety_level") == "inventory-supported":
|
|
495
753
|
return []
|
|
754
|
+
capabilities -= REPORT_ONLY_CAPABILITIES
|
|
496
755
|
return sorted(capabilities)
|
|
497
756
|
|
|
498
757
|
|
|
@@ -731,7 +990,8 @@ def enrich_catalog_items(items: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
|
|
731
990
|
new_item.setdefault("model_families", infer_model_families(new_item))
|
|
732
991
|
new_item.setdefault("port_media_types", infer_port_media_types(new_item))
|
|
733
992
|
new_item.setdefault("service_support", infer_service_support(new_item))
|
|
734
|
-
new_item.setdefault("
|
|
993
|
+
new_item.setdefault("iot_roles", infer_iot_roles(new_item))
|
|
994
|
+
new_item["runtime_features"] = sorted(set(new_item.get("runtime_features", [])) | set(infer_runtime_features(new_item)))
|
|
735
995
|
new_item.setdefault("donor_graph_fingerprint", infer_donor_graph_fingerprint(new_item))
|
|
736
996
|
new_item.setdefault("apply_safety_level", infer_apply_safety_level(new_item))
|
|
737
997
|
new_item.setdefault("archetype_tags", infer_archetype_tags(new_item))
|