gesh75's picture
Upload folder using huggingface_hub
1bd64c1 verified
Raw History Blame Contribute Delete
9.66 kB
#!/usr/bin/env python3
"""
deep_gap_dig.py — exhaustive coverage-gap audit for the configurator corpus + Automate stack.
Reports (and optionally fails CI on) remaining holes across:
- thin OS floors (nxos / iosxe / sros / sonic / asa)
- overlay categories (VXLAN / EVPN)
- L2 promotions (Spanning-Tree / EtherChannel / BFD)
- Automate AUTO_* vendor keys (no silent Cisco fallback for network vendors)
- OS_DEV_TYPE / NETMIKO_DEV_TYPE / ANSIBLE_MOD coverage
- data-quality gates (prose cmd, over-long titles, escaped placeholders)
- essential-cat presence for network vendors (keyword OR dedicated cat)
Usage:
python3 scripts/deep_gap_dig.py # report + exit 1 on critical misses
python3 scripts/deep_gap_dig.py --json out.json
python3 scripts/deep_gap_dig.py --warn-only # never fail
"""
from __future__ import annotations
import argparse
import json
import re
import sys
from collections import Counter, defaultdict
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
CORPUS = ROOT / "commands.json"
HTML = ROOT / "index.html"
# Floors after wave-2 fills — keep conservative so CI stays stable.
OS_FLOORS = {
"nxos": 200,
"iosxe": 120,
"sros": 70,
"sonic": 450,
"asa": 400,
}
CAT_FLOORS = {
"VXLAN": 250,
"EVPN": 490,
"Spanning-Tree": 400,
"EtherChannel": 250,
"BFD": 100,
}
NETWORK_VENDORS = [
"Cisco",
"Juniper",
"Arista",
"FRR",
"VyOS",
"Nokia",
"Aruba",
"Huawei",
"NVIDIA",
"SONiC",
"Extreme",
"Mikrotik",
]
# FortiOS / PAN-OS intentionally excluded from L2/L3 Automate completeness —
# firewall platforms use different primitives.
AUTO_REQUIRED = NETWORK_VENDORS
AUTO_MAPS = [
"AUTO_IFACE_IPV4",
"AUTO_STATIC",
"AUTO_OSPF",
"AUTO_BGP",
"AUTO_VLAN",
"AUTO_HOSTNAME",
"AUTO_NTP",
"AUTO_SYSLOG",
]
ESSENTIAL_FOR_SWITCH_FABRIC = {
# vendor → required cats (dedicated category OR keyword hit acceptable via soft check)
"Extreme": ["Spanning-Tree", "BGP", "OSPF", "ACL", "EtherChannel"],
"Cisco": ["Spanning-Tree", "VXLAN", "EVPN", "BFD", "EtherChannel"],
"Arista": ["Spanning-Tree", "VXLAN", "EVPN", "EtherChannel"],
"SONiC": ["BGP", "VXLAN", "ACL", "VLAN"],
"Nokia": ["BGP", "OSPF", "VXLAN", "EVPN"],
}
def extract_auto_vendors(html: str, name: str) -> set[str]:
keys: set[str] = set()
# keys inside the const object (first ~80 lines after const)
m = re.search(rf"const\s+{name}\s*=\s*\{{", html)
if not m:
return keys
chunk = html[m.start() : m.start() + 12000]
end = chunk.find("\n};")
if end > 0:
chunk = chunk[:end]
for v in NETWORK_VENDORS + ["FortiOS", "PAN-OS", "Microsoft", "Linux", "Wireshark"]:
if re.search(rf"(?:^|\n)\s*{re.escape(v)}\s*:", chunk):
keys.add(v)
if re.search(rf"{re.escape(name)}\.{re.escape(v)}\s*=", html):
keys.add(v)
return keys
def extract_object_keys(html: str, const_name: str) -> set[str]:
m = re.search(rf"const\s+{const_name}\s*=\s*\{{([\s\S]*?)^\}};", html, re.M)
if not m:
return set()
body = m.group(1)
keys = set()
for raw in re.findall(r'["\']?([A-Za-z0-9_.-]+)["\']?\s*:', body):
if raw in ("show", "cfg", "collection", "vtysh"):
continue
keys.add(raw.strip("\"'"))
return keys
def cmd_is_prose(cmd: str) -> bool:
return bool(
re.search(
r"\b(this is|it means|for example|note that|in order to|you can|which is|"
r"such as|we'll see|by contrast|imagine that|halfway through)\b",
cmd,
re.I,
)
or re.search(r"\.\s+[A-Z]", cmd)
)
def main() -> int:
ap = argparse.ArgumentParser()
ap.add_argument("--json", metavar="FILE")
ap.add_argument("--warn-only", action="store_true")
args = ap.parse_args()
data = json.loads(CORPUS.read_text())
html = HTML.read_text()
report: dict = {"total": len(data), "critical": [], "warnings": [], "stats": {}}
os_c = Counter(r.get("os") for r in data)
cat_c = Counter(r.get("cat") for r in data)
vendor_c = Counter(r.get("vendor") for r in data)
report["stats"]["os"] = dict(os_c)
report["stats"]["cats_focus"] = {k: cat_c.get(k, 0) for k in CAT_FLOORS}
report["stats"]["vendors"] = dict(vendor_c)
print(f"Corpus: {len(data):,} commands · {len(vendor_c)} vendors · {len(os_c)} OS labels\n")
print("=== OS floors ===")
for o, floor in OS_FLOORS.items():
n = os_c.get(o, 0)
ok = n >= floor
print(f" {'PASS' if ok else 'FAIL'} {o}: {n} (floor {floor})")
if not ok:
report["critical"].append(f"os floor {o}: {n} < {floor}")
print("\n=== Category floors ===")
for c, floor in CAT_FLOORS.items():
n = cat_c.get(c, 0)
ok = n >= floor
print(f" {'PASS' if ok else 'FAIL'} {c}: {n} (floor {floor})")
if not ok:
report["critical"].append(f"cat floor {c}: {n} < {floor}")
print("\n=== Automate AUTO_* vendor keys ===")
for name in AUTO_MAPS:
present = extract_auto_vendors(html, name)
missing = [v for v in AUTO_REQUIRED if v not in present]
ok = not missing
print(f" {'PASS' if ok else 'FAIL'} {name}: missing {missing or '∅'}")
if missing:
report["critical"].append(f"{name} missing vendors: {missing}")
silent_fb = len(re.findall(r"\|\|AUTO_\w+\.Cisco\)\(v\)", html))
hardcoded = bool(
re.search(
r'\[\s*"Cisco"\s*,\s*"Juniper"\s*,\s*"Arista"\s*\]\.filter\(\s*v\s*=>\s*found\.mapping\.render',
html,
)
)
print("\n=== Automate safety ===")
print(f" {'PASS' if silent_fb == 0 else 'FAIL'} silent Cisco fallbacks: {silent_fb}")
print(f" {'PASS' if not hardcoded else 'FAIL'} openAutomation not C/J/A-only: {not hardcoded}")
if silent_fb:
report["critical"].append(f"silent AUTO_*.Cisco fallbacks: {silent_fb}")
if hardcoded:
report["critical"].append("openAutomation hardcodes Cisco/Juniper/Arista only")
if "AUTO_VENDORS" not in html:
report["critical"].append("openAutomation missing AUTO_VENDORS")
print(" FAIL AUTO_VENDORS missing")
else:
print(" PASS AUTO_VENDORS present")
if 'id="btn-clibuilder"' in html and "Builder" in html[html.find('id="btn-clibuilder"') : html.find('id="btn-clibuilder"') + 200]:
print(" PASS Builder navbar label")
else:
report["critical"].append("Builder navbar label missing")
print(" FAIL Builder navbar label")
print("\n=== Netmiko / Ansible maps ===")
netmiko = extract_object_keys(html, "NETMIKO_DEV_TYPE")
os_dev = extract_object_keys(html, "OS_DEV_TYPE")
ansible = extract_object_keys(html, "ANSIBLE_MOD")
for label, have, need in [
("NETMIKO_DEV_TYPE vendors", netmiko, set(vendor_c)),
("OS_DEV_TYPE os", os_dev, set(os_c)),
("ANSIBLE_MOD vendors", ansible, set(vendor_c)),
]:
missing = sorted(need - have)
# OS_DEV_TYPE may omit nothing critical; report all
ok = not missing
print(f" {'PASS' if ok else 'FAIL'} {label}: missing {missing or '∅'}")
if missing:
# OS map misses are critical; vendor map misses too
report["critical"].append(f"{label} missing: {missing}")
print("\n=== Essential fabric cats (dedicated category) ===")
by_v_cats: dict[str, set[str]] = defaultdict(set)
for r in data:
by_v_cats[r.get("vendor", "?")].add(r.get("cat") or "?")
for v, need in ESSENTIAL_FOR_SWITCH_FABRIC.items():
missing = [c for c in need if c not in by_v_cats.get(v, set())]
ok = not missing
print(f" {'PASS' if ok else 'FAIL'} {v}: missing {missing or '∅'}")
if missing:
report["critical"].append(f"{v} missing cats: {missing}")
print("\n=== Data quality ===")
prose = sum(1 for r in data if cmd_is_prose(r.get("cmd") or ""))
long_t = sum(1 for r in data if len(r.get("title") or "") > 90)
esc = sum(
1
for r in data
if any("\\>" in (r.get(f) or "") for f in ("cmd", "title", "desc"))
)
empty_desc = sum(1 for r in data if not (r.get("desc") or "").strip())
for label, n, critical in [
("cmd prose", prose, True),
("over-long titles", long_t, True),
("escaped placeholders", esc, True),
("empty desc", empty_desc, False),
]:
ok = n == 0 if critical else True
print(f" {'PASS' if (n == 0 if critical else True) else 'FAIL'} {label}: {n}")
if critical and n:
report["critical"].append(f"{label}: {n}")
if not critical and n:
report["warnings"].append(f"{label}: {n}")
print("\n=== Idempotency smoke (dry logic) ===")
# Re-running promotions on current corpus should be near-zero for retags
# (reported as warning only — deep dig itself does not mutate).
report["warnings"].append("run scripts/fix_coverage_gaps.py --dry-run separately")
print("\n=== Summary ===")
print(f" critical: {len(report['critical'])}")
print(f" warnings: {len(report['warnings'])}")
for c in report["critical"]:
print(f" ✗ {c}")
for w in report["warnings"][:12]:
print(f" ! {w}")
if args.json:
Path(args.json).write_text(json.dumps(report, indent=2))
print(f"\nWrote {args.json}")
if args.warn_only:
return 0
return 1 if report["critical"] else 0
if __name__ == "__main__":
raise SystemExit(main())