Spaces:
Sleeping
Sleeping
| """uofa validate β SHACL validation (and optional integrity check) on all example UofA files.""" | |
| from __future__ import annotations | |
| from pathlib import Path | |
| from uofa_cli.output import step_header, result_line, color | |
| from uofa_cli.shacl_friendly import run_shacl_multi, print_violations | |
| from uofa_cli.integrity import verify_file | |
| from uofa_cli import paths | |
| HELP = "validate all examples against SHACL profiles" | |
| _EXCLUDE_DIRS = {"templates"} | |
| def add_arguments(parser): | |
| parser.add_argument("--dir", type=Path, help="directory to scan (default: all pack examples)") | |
| parser.add_argument("--verify", action="store_true", | |
| help="also verify hash + signature integrity on each file") | |
| parser.add_argument("--pubkey", type=Path, help="ed25519 public key (default: keys/research.pub)") | |
| def run(args) -> int: | |
| # Scan a single dir if specified, otherwise all pack example directories | |
| if args.dir: | |
| scan_dirs = [args.dir] | |
| else: | |
| scan_dirs = paths.all_example_dirs() | |
| if not scan_dirs: | |
| raise FileNotFoundError("No example directories found in packs/") | |
| step_header("SHACL validation: all examples") | |
| files = sorted( | |
| f | |
| for d in scan_dirs if d.exists() | |
| for f in d.rglob("*.jsonld") | |
| if not any(part in _EXCLUDE_DIRS for part in f.parts) | |
| ) | |
| # Use first scan dir as reference for relative paths | |
| examples = scan_dirs[0].parent if len(scan_dirs) == 1 else paths.find_repo_root() | |
| if not files: | |
| result_line("No .jsonld files found", False) | |
| return 1 | |
| # When scanning all examples, use core-only shapes (no factor enum) | |
| # unless a specific pack was explicitly requested via --pack. | |
| # This avoids rejecting NASA examples when default pack is vv40. | |
| if args.pack is None: | |
| shacl_paths = [paths.shacl_schema()] | |
| else: | |
| shacl_paths = paths.all_shacl_schemas(active=paths.resolve_active_packs(args)) | |
| passed = 0 | |
| failed = 0 | |
| for f in files: | |
| conforms, violations = run_shacl_multi(f, shacl_paths) | |
| rel = f.relative_to(examples.parent) if examples.parent in f.parents else f | |
| result_line(str(rel), conforms) | |
| if conforms: | |
| passed += 1 | |
| else: | |
| failed += 1 | |
| print_violations(violations) | |
| print() | |
| total = passed + failed | |
| if failed == 0: | |
| result_line(f"All {total} example(s) conform", True) | |
| else: | |
| result_line(f"{failed}/{total} example(s) failed", False) | |
| # ββ Optional integrity verification βββββββββββββββββββββββ | |
| if args.verify: | |
| step_header("Integrity verification: all examples") | |
| pubkey = args.pubkey or paths.default_pubkey() | |
| if not pubkey.exists(): | |
| result_line("Public key not found", False, str(pubkey)) | |
| return 1 | |
| ctx = paths.context_file() | |
| v_passed = 0 | |
| v_failed = 0 | |
| import json | |
| for f in files: | |
| rel = f.relative_to(examples.parent) if examples.parent in f.parents else f | |
| try: | |
| with open(f) as fh: | |
| doc = json.load(fh) | |
| declared_hash = doc.get("hash", "") | |
| # Skip files with placeholder hashes (unsigned templates/starters) | |
| if declared_hash.endswith("0" * 64): | |
| result_line(str(rel), True, "unsigned β skipped") | |
| v_passed += 1 | |
| continue | |
| hash_ok, sig_ok = verify_file(f, pubkey, ctx) | |
| ok = hash_ok and sig_ok | |
| detail = "" | |
| if not hash_ok: | |
| detail = "hash mismatch" | |
| elif not sig_ok: | |
| detail = "signature invalid" | |
| result_line(str(rel), ok, detail) | |
| if ok: | |
| v_passed += 1 | |
| else: | |
| v_failed += 1 | |
| except Exception as exc: | |
| result_line(str(rel), False, str(exc)) | |
| v_failed += 1 | |
| print() | |
| v_total = v_passed + v_failed | |
| if v_failed == 0: | |
| result_line(f"All {v_total} example(s) verified", True) | |
| else: | |
| result_line(f"{v_failed}/{v_total} example(s) failed integrity check", False) | |
| failed += v_failed | |
| return 0 if failed == 0 else 1 | |