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.
Files changed (57) hide show
  1. package/CHANGELOG.md +42 -4
  2. package/README.md +237 -88
  3. package/docs/campus-donor-proof.md +103 -0
  4. package/docs/curated-donor-registry.md +26 -0
  5. package/docs/github-launch-ops-0.2.1.md +45 -0
  6. package/docs/github-launch-ops-0.2.2.md +42 -0
  7. package/docs/github-metadata.md +23 -6
  8. package/docs/hero-demo-plan.md +18 -10
  9. package/docs/home-iot-donor-proof.md +64 -0
  10. package/docs/launch-announcement-0.2.1.md +15 -0
  11. package/docs/launch-announcement-0.2.2.md +15 -0
  12. package/docs/packet-tracer-feature-gap-atlas.md +87 -0
  13. package/docs/post-launch-follow-up.md +39 -0
  14. package/docs/publish-preview-roadmap.md +20 -17
  15. package/docs/release-checklist.md +22 -12
  16. package/docs/{release-notes-0.2.0.md → release-notes-0.2.1.md} +7 -6
  17. package/docs/release-notes-0.2.2.md +26 -0
  18. package/docs/runtime-truth.md +31 -0
  19. package/docs/wan-security-donor-proof.md +41 -0
  20. package/docs/wireless-advanced-proof.md +60 -0
  21. package/examples/README.md +13 -10
  22. package/examples/gallery.md +10 -4
  23. package/examples/index.json +3 -3
  24. package/package.json +44 -16
  25. package/references/curated-donor-registry.json +5 -2
  26. package/references/packettracer-feature-atlas.json +133 -0
  27. package/references/scenario-fixture-corpus.json +1 -0
  28. package/scripts/build_examples_index.py +27 -16
  29. package/scripts/coverage_matrix.py +559 -23
  30. package/scripts/feature_atlas.py +229 -0
  31. package/scripts/generate_pkt.py +330 -31
  32. package/scripts/intent_parser.py +278 -7
  33. package/scripts/pkt_editor.py +174 -0
  34. package/scripts/runtime_doctor.py +83 -10
  35. package/scripts/sample_catalog.py +210 -5
  36. package/docs/screenshots/.gitkeep +0 -1
  37. package/docs/screenshots/packet-tracer-topology-cropped.png +0 -0
  38. package/scripts/__pycache__/build_examples_index.cpython-314.pyc +0 -0
  39. package/scripts/__pycache__/build_sample_catalog.cpython-314.pyc +0 -0
  40. package/scripts/__pycache__/coverage_matrix.cpython-314.pyc +0 -0
  41. package/scripts/__pycache__/donor_diagnostics.cpython-314.pyc +0 -0
  42. package/scripts/__pycache__/generate_pkt.cpython-314.pyc +0 -0
  43. package/scripts/__pycache__/install_skill.cpython-314.pyc +0 -0
  44. package/scripts/__pycache__/intent_parser.cpython-314.pyc +0 -0
  45. package/scripts/__pycache__/packet_tracer_env.cpython-314.pyc +0 -0
  46. package/scripts/__pycache__/pkt_builder.cpython-314.pyc +0 -0
  47. package/scripts/__pycache__/pkt_codec.cpython-314.pyc +0 -0
  48. package/scripts/__pycache__/pkt_editor.cpython-314.pyc +0 -0
  49. package/scripts/__pycache__/pkt_transformer.cpython-314.pyc +0 -0
  50. package/scripts/__pycache__/remote_search.cpython-314.pyc +0 -0
  51. package/scripts/__pycache__/runtime_doctor.cpython-314.pyc +0 -0
  52. package/scripts/__pycache__/sample_catalog.cpython-314.pyc +0 -0
  53. package/scripts/__pycache__/sample_selector.cpython-314.pyc +0 -0
  54. package/scripts/__pycache__/twofish_diagnostics.cpython-314.pyc +0 -0
  55. package/scripts/__pycache__/twofish_runtime.cpython-314.pyc +0 -0
  56. package/scripts/__pycache__/workspace_repair.cpython-314.pyc +0 -0
  57. 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
- if capabilities & {"iot", "iot_registration", "iot_control"} or {"iot devices", "home/wireless routers"} & set(requested_families):
281
- return "home_iot"
282
- if (
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"} & set(requested_families)
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 = any(entry.maturity_level == "acceptance-verified" for entry in matching_entries) and capability not in iot_acceptance_gated
556
- if best_entry.maturity_level == "safe-open-generate-supported":
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": best_entry.maturity_level == "safe-open-generate-supported",
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),