#!/usr/bin/env bash # Validation coverage gate (validation-rules.md, "Every public type is validated or excluded"). # # Enumerates every public type in the PureYAML target from the compiler's symbol graph (the # authoritative public surface, not a grep heuristic), then checks each against the coverage # manifest. Every public type MUST be classified exactly once as `validated` or `excluded`; an # unclassified type, or a manifest entry that is no longer public, fails the build by name. # # Phase 1 enforces classification completeness. The framework phase additionally asserts that each # `validated` entry is backed by a `Validation` whose Subject is that type. set -euo pipefail cd "$(dirname "$0")/.." MANIFEST="scripts/validation-coverage.tsv" GRAPH_DIR=".build/symbolgraph" if [[ ! -f "$MANIFEST" ]]; then echo "check-validation-coverage: missing manifest $MANIFEST" >&2 exit 1 fi mkdir -p "$GRAPH_DIR" swift build --target PureYAML \ -Xswiftc -emit-symbol-graph \ -Xswiftc -emit-symbol-graph-dir -Xswiftc "$GRAPH_DIR" >/dev/null python3 - "$GRAPH_DIR/PureYAML.symbols.json" "$MANIFEST" <<'PY' import json import sys graph_path, manifest_path = sys.argv[1], sys.argv[2] type_kinds = {"swift.struct", "swift.enum", "swift.class", "swift.actor", "swift.protocol"} with open(graph_path) as handle: graph = json.load(handle) public_types = { ".".join(symbol["pathComponents"]) for symbol in graph["symbols"] if symbol["kind"]["identifier"] in type_kinds and symbol.get("accessLevel") == "public" } classified = {} duplicates = [] with open(manifest_path) as handle: for raw in handle: line = raw.split("#", 1)[0].strip() if not line: continue parts = line.split("\t") if len(parts) < 3 or parts[1] not in ("validated", "excluded"): print(f"check-validation-coverage: malformed manifest line: {raw.rstrip()}", file=sys.stderr) sys.exit(1) name, kind = parts[0], parts[1] if name in classified: duplicates.append(name) classified[name] = kind errors = [] for name in sorted(public_types - set(classified)): errors.append(f" unclassified public type (add to {manifest_path} as validated or excluded): {name}") for name in sorted(set(classified) - public_types): errors.append(f" stale manifest entry (no longer a public type): {name}") for name in sorted(set(duplicates)): errors.append(f" duplicate manifest entry: {name}") if errors: print("check-validation-coverage: FAILED", file=sys.stderr) print("\n".join(errors), file=sys.stderr) sys.exit(1) validated = sum(1 for k in classified.values() if k == "validated") excluded = sum(1 for k in classified.values() if k == "excluded") print(f"check-validation-coverage: OK, {len(public_types)} public types ({validated} validated, {excluded} excluded)") PY