#!/usr/bin/env -S uv run --script # /// script # requires-python = ">=3.11" # dependencies = ["jsonschema>=4.23,<5", "PyYAML>=6,<7"] # /// from __future__ import annotations import argparse import json import re import sys from dataclasses import asdict, dataclass from pathlib import Path from typing import Any import jsonschema import yaml PROFILE_ID = "netkingdom-emission-cadence-security-profile-v0.1" @dataclass(frozen=True, slots=True) class Finding: level: str code: str event_class: str | None message: str def load_document(path: Path) -> Any: with path.open(encoding="utf-8") as handle: return yaml.safe_load(handle) def _finding(level: str, code: str, event_class: str | None, message: str) -> Finding: return Finding(level=level, code=code, event_class=event_class, message=message) def _schema_findings(schema: Any, declaration: Any) -> list[Finding]: try: jsonschema.Draft202012Validator.check_schema(schema) except jsonschema.SchemaError as exc: return [_finding("MUST", "contract-schema-invalid", None, exc.message)] validator = jsonschema.Draft202012Validator( schema, format_checker=jsonschema.FormatChecker() ) findings: list[Finding] = [] for error in sorted( validator.iter_errors(declaration), key=lambda item: tuple(str(part) for part in item.path), ): location = "/" + "/".join(str(part) for part in error.path) findings.append( _finding( "MUST", "contract-validation-failed", None, f"{location}: {error.message}", ) ) return findings def _entries(declaration: Any) -> tuple[list[dict[str, Any]], list[Finding]]: if not isinstance(declaration, dict): return [], [ _finding( "MUST", "declaration-not-object", None, "declaration must be an object" ) ] raw_entries = declaration.get("sources") if not isinstance(raw_entries, list): return [], [ _finding( "MUST", "sources-unavailable", None, "the imported contract must expose cadence entries at /sources", ) ] entries: list[dict[str, Any]] = [] for index, item in enumerate(raw_entries): if not isinstance(item, dict): return [], [ _finding( "MUST", "source-entry-not-object", None, f"/sources/{index} must be an object", ) ] entries.append(item) return entries, [] def evaluate_profile( declaration: Any, *, load_bearing: set[str], rare_load_bearing: set[str], attributive: set[str], ) -> list[Finding]: findings: list[Finding] = [] overlap = (load_bearing | rare_load_bearing) & attributive for event_class in sorted(overlap): findings.append( _finding( "MUST", "inventory-class-conflict", event_class, "the source inventory classifies the event as both load-bearing and attributive", ) ) entries, structural = _entries(declaration) if structural: return findings + structural indexed: dict[str, dict[str, Any]] = {} source_ids: set[str] = set() for entry in entries: source_id = entry.get("source_id") if isinstance(source_id, str): if source_id in source_ids: findings.append( _finding( "MUST", "duplicate-source-id", None, "source_id values must be unique", ) ) source_ids.add(source_id) event_class = entry.get("event_class") if not isinstance(event_class, str) or not event_class: findings.append( _finding( "MUST", "event-class-unavailable", None, "each cadence entry needs event_class", ) ) continue if event_class in indexed: findings.append( _finding( "MUST", "duplicate-event-class", event_class, "only one cadence entry is permitted per event class", ) ) continue indexed[event_class] = entry required_load_bearing = load_bearing | rare_load_bearing for event_class in sorted(required_load_bearing): entry = indexed.get(event_class) if entry is None: findings.append( _finding( "MUST", "load-bearing-cadence-missing", event_class, "a source-declared load-bearing class must declare cadence", ) ) elif ( entry.get("extensions", {}).get("netkingdom", {}).get("evidence_class") != "load-bearing" ): findings.append( _finding( "MUST", "evidence-class-mismatch", event_class, "cadence evidence_class must match the source inventory: load-bearing", ) ) for event_class in sorted(attributive): entry = indexed.get(event_class) if entry is None: findings.append( _finding( "SHOULD", "attributive-cadence-missing", event_class, "an attributive class should declare cadence; completeness must not be claimed", ) ) elif ( entry.get("extensions", {}).get("netkingdom", {}).get("evidence_class") != "attributive" ): findings.append( _finding( "MUST", "evidence-class-mismatch", event_class, "cadence evidence_class must match the source inventory: attributive", ) ) for event_class in sorted(rare_load_bearing): entry = indexed.get(event_class) if entry is None: continue if entry.get("form") != "heartbeat-or-reconciliation": findings.append( _finding( "MUST", "rare-form-invalid", event_class, "rare load-bearing evidence requires heartbeat-or-reconciliation", ) ) if ( entry.get("extensions", {}).get("netkingdom", {}).get("rate_monitoring") != "forbidden" ): findings.append( _finding( "MUST", "rare-rate-monitoring-not-forbidden", event_class, "rate_monitoring must be forbidden for rare load-bearing evidence", ) ) heartbeat = entry.get("heartbeat") if not isinstance(heartbeat, dict): findings.append( _finding( "MUST", "rare-heartbeat-missing", event_class, "rare load-bearing evidence requires a positive heartbeat", ) ) else: if not heartbeat.get("assertion"): findings.append( _finding( "MUST", "heartbeat-assertion-missing", event_class, "heartbeat must carry a positive assertion", ) ) if heartbeat.get("missing") != "finding": findings.append( _finding( "MUST", "heartbeat-missing-not-finding", event_class, "a missing heartbeat must be a finding", ) ) reconciliation = entry.get("reconciliation", {}) local = reconciliation.get("compare_local") observed = reconciliation.get("compare_observed") divergence = reconciliation.get("divergence") if local is None or observed is None: findings.append( _finding( "MUST", "rare-reconciliation-missing", event_class, "reconciliation must compare source and evidence-engine counts", ) ) if divergence != "finding": findings.append( _finding( "MUST", "reconciliation-divergence-not-finding", event_class, "reconciliation divergence must be a finding", ) ) for event_class in sorted(required_load_bearing - rare_load_bearing): entry = indexed.get(event_class) if ( entry is None or entry.get("extensions", {}).get("netkingdom", {}).get("evidence_class") != "load-bearing" ): continue if entry.get("form") == "expected-rate": window = entry.get("window_seconds", entry.get("window")) if ( not window or ( isinstance(window, str) and not any(int(part) > 0 for part in re.findall(r"\d+", window)) ) or isinstance(window, bool) or (isinstance(window, (int, float)) and window <= 0) ): findings.append( _finding( "MUST", "rate-window-invalid", event_class, "expected-rate load-bearing evidence needs a positive window", ) ) if ( isinstance(entry.get("expected_min"), bool) or not isinstance(entry.get("expected_min"), int) or entry["expected_min"] <= 0 ): findings.append( _finding( "MUST", "expected-min-invalid", event_class, "expected-rate load-bearing evidence needs expected_min > 0", ) ) if entry.get("drop_below") != "finding": findings.append( _finding( "MUST", "rate-drop-not-finding", event_class, "a drop below the declared load-bearing rate must be a finding", ) ) return sorted( findings, key=lambda item: (item.level, item.code, item.event_class or "", item.message), ) def build_report( contract_schema: Any, declaration: Any, *, contract_schema_path: str, declaration_path: str, load_bearing: set[str], rare_load_bearing: set[str], attributive: set[str], fail_on_should: bool = False, ) -> dict[str, Any]: findings = _schema_findings(contract_schema, declaration) contract_valid = not findings if contract_valid: findings.extend( evaluate_profile( declaration, load_bearing=load_bearing, rare_load_bearing=rare_load_bearing, attributive=attributive, ) ) must_count = sum(item.level == "MUST" for item in findings) should_count = sum(item.level == "SHOULD" for item in findings) return { "profile": PROFILE_ID, "contract_schema": contract_schema_path, "declaration": declaration_path, "contract_valid": contract_valid, "conformant": must_count == 0 and (not fail_on_should or should_count == 0), "summary": {"must": must_count, "should": should_count}, "findings": [asdict(item) for item in findings], } def parse_args(argv: list[str] | None = None) -> argparse.Namespace: parser = argparse.ArgumentParser( description="Validate an imported emission-cadence declaration against the NetKingdom profile." ) parser.add_argument("declaration", type=Path) parser.add_argument("--contract-schema", required=True, type=Path) parser.add_argument( "--load-bearing", action="append", default=[], metavar="EVENT_CLASS" ) parser.add_argument( "--rare-load-bearing", action="append", default=[], metavar="EVENT_CLASS" ) parser.add_argument( "--attributive", action="append", default=[], metavar="EVENT_CLASS" ) parser.add_argument("--fail-on-should", action="store_true") return parser.parse_args(argv) def main(argv: list[str] | None = None) -> int: args = parse_args(argv) try: schema = load_document(args.contract_schema) declaration = load_document(args.declaration) except (OSError, yaml.YAMLError) as exc: print(json.dumps({"error": str(exc)}, sort_keys=True), file=sys.stderr) return 2 report = build_report( schema, declaration, contract_schema_path=str(args.contract_schema), declaration_path=str(args.declaration), load_bearing=set(args.load_bearing), rare_load_bearing=set(args.rare_load_bearing), attributive=set(args.attributive), fail_on_should=args.fail_on_should, ) print(json.dumps(report, indent=2, sort_keys=True)) return 0 if report["conformant"] else 1 if __name__ == "__main__": raise SystemExit(main())