condor-for-games 0.4.0

Rust pathfinding library for grids, polygonal scenes, navmeshes, and replanning.
Documentation
#!/usr/bin/env python3
"""Audit Condor's local Cargo dependency graph against its layer contract."""

from __future__ import annotations

import argparse
import glob
import sys
import tomllib
from dataclasses import dataclass
from pathlib import Path
from typing import Any, Iterable


ROOT = Path(__file__).resolve().parent.parent
ROOT_PACKAGE = "condor-for-games"

CORE = "condor-pathfinding-core"
GEOMETRY = "condor-pathfinding-geometry"
GRID = "condor-pathfinding-grid"
NAVMESH = "condor-pathfinding-navmesh"
HARNESS = "condor-harness"
BENCH = "condor-bench"
LAB = "condor-lab"
PATH_LAB = "condor-path-lab"
TUI = "condor-tui"

# Values are the local packages that the key may depend on. External crates do
# not participate in this repository-layer audit.
ALLOWED_LOCAL_DEPENDENCIES = {
    ROOT_PACKAGE: frozenset({CORE, GEOMETRY, GRID, NAVMESH}),
    CORE: frozenset(),
    GEOMETRY: frozenset({CORE}),
    GRID: frozenset({CORE}),
    NAVMESH: frozenset({CORE, GEOMETRY}),
    HARNESS: frozenset({CORE, GEOMETRY, GRID, NAVMESH}),
    BENCH: frozenset({HARNESS, CORE, GEOMETRY, GRID, NAVMESH}),
    # Shared Ratatui chrome leaf: no local Condor package deps.
    TUI: frozenset(),
    LAB: frozenset({BENCH, TUI}),
    # Mutable Path Lab may call public owner crates, bench labels, and chrome.
    PATH_LAB: frozenset({BENCH, GEOMETRY, GRID, NAVMESH, TUI}),
}


@dataclass(frozen=True, order=True)
class Edge:
    source: str
    kind: str
    target: str

    def render(self) -> str:
        return f"{self.source} --{self.kind}--> {self.target}"


# This is the complete, kind-sensitive exception set. No normal or build
# facade edge from the harness or bench is permitted.
EXPECTED_CURRENT_VIOLATIONS = frozenset(
    {
        Edge(HARNESS, "dev", ROOT_PACKAGE),
        Edge(BENCH, "dev", ROOT_PACKAGE),
    }
)

# Lab consumes its complete read-only catalog through bench's normal developer
# API; it never takes an upward dependency on the public facade.
REQUIRED_LOCAL_EDGES = frozenset(
    {
        Edge(LAB, "normal", BENCH),
    }
)

POLYGON_PACK_PAIRS = (
    (
        ROOT / "src/polygonal/packs/starter.toml",
        ROOT / "dev/condor-harness/fixtures/polygonal/starter.toml",
    ),
    (
        ROOT / "src/polygonal/packs/stress.toml",
        ROOT / "dev/condor-harness/fixtures/polygonal/stress.toml",
    ),
)


def load_toml(path: Path) -> dict[str, Any]:
    with path.open("rb") as handle:
        return tomllib.load(handle)


def workspace_manifests() -> list[Path]:
    root_manifest = ROOT / "Cargo.toml"
    root_data = load_toml(root_manifest)
    workspace = root_data.get("workspace", {})
    manifests = {root_manifest.resolve()}

    for member in workspace.get("members", []):
        pattern = str(ROOT / member / "Cargo.toml")
        matches = [Path(path).resolve() for path in glob.glob(pattern)]
        if not matches:
            raise ValueError(f"workspace member does not resolve: {member}")
        manifests.update(matches)

    return sorted(manifests)


def dependency_tables(data: dict[str, Any]) -> Iterable[tuple[str, dict[str, Any]]]:
    sections = (
        ("normal", "dependencies"),
        ("dev", "dev-dependencies"),
        ("build", "build-dependencies"),
    )
    for kind, section in sections:
        yield kind, data.get(section, {})

    for target in data.get("target", {}).values():
        if not isinstance(target, dict):
            continue
        for kind, section in sections:
            yield kind, target.get(section, {})


def dependency_path(
    alias: str,
    declaration: Any,
    manifest_dir: Path,
    workspace_dependencies: dict[str, Any],
) -> Path | None:
    if not isinstance(declaration, dict):
        return None

    inherited = declaration.get("workspace") is True
    effective = workspace_dependencies.get(alias, {}) if inherited else declaration
    if not isinstance(effective, dict) or "path" not in effective:
        return None

    base = ROOT if inherited else manifest_dir
    return (base / effective["path"] / "Cargo.toml").resolve()


def read_graph() -> tuple[set[str], set[Edge]]:
    root_data = load_toml(ROOT / "Cargo.toml")
    workspace_dependencies = root_data.get("workspace", {}).get("dependencies", {})
    manifests = workspace_manifests()
    package_by_manifest: dict[Path, str] = {}
    data_by_manifest: dict[Path, dict[str, Any]] = {}

    for manifest in manifests:
        data = load_toml(manifest)
        data_by_manifest[manifest] = data
        package_by_manifest[manifest] = data["package"]["name"]

    edges: set[Edge] = set()
    for manifest, data in data_by_manifest.items():
        source = package_by_manifest[manifest]
        for kind, dependencies in dependency_tables(data):
            for alias, declaration in dependencies.items():
                target_manifest = dependency_path(
                    alias, declaration, manifest.parent, workspace_dependencies
                )
                if target_manifest is None or not target_manifest.is_relative_to(ROOT):
                    continue

                if target_manifest not in package_by_manifest:
                    if not target_manifest.is_file():
                        raise ValueError(
                            f"local dependency manifest does not exist: {target_manifest}"
                        )
                    package_by_manifest[target_manifest] = load_toml(target_manifest)[
                        "package"
                    ]["name"]
                edges.add(Edge(source, kind, package_by_manifest[target_manifest]))

    return set(data_by_manifest[path]["package"]["name"] for path in manifests), edges


def graph_violations(packages: set[str], edges: set[Edge]) -> tuple[list[str], set[Edge]]:
    unknown_packages = sorted(packages - ALLOWED_LOCAL_DEPENDENCIES.keys())
    violations = {
        edge
        for edge in edges
        if edge.target not in ALLOWED_LOCAL_DEPENDENCIES.get(edge.source, frozenset())
    }
    return unknown_packages, violations


def print_edges(label: str, edges: Iterable[Edge]) -> None:
    rendered = list(sorted(edges))
    if not rendered:
        return
    print(label, file=sys.stderr)
    for edge in rendered:
        print(f"  - {edge.render()}", file=sys.stderr)


def check_package(package: str, packages: set[str], violations: set[Edge]) -> int:
    if package not in packages:
        print(f"architecture-check-package: unknown workspace package: {package}", file=sys.stderr)
        return 2
    if package not in ALLOWED_LOCAL_DEPENDENCIES:
        print(
            f"architecture-check-package: package has no declared architecture role: {package}",
            file=sys.stderr,
        )
        return 1

    package_violations = {edge for edge in violations if edge.source == package}
    if package_violations:
        print_edges(
            f"architecture-check-package: prohibited local dependencies for {package}:",
            package_violations,
        )
        return 1

    print(f"architecture-check-package: {package} conforms")
    return 0


def check_expected_violations(
    label: str, unknown: list[str], violations: set[Edge]
) -> int:
    if unknown:
        print(
            f"{label}: packages without declared roles: " + ", ".join(unknown),
            file=sys.stderr,
        )
        return 1

    unexpected = violations - EXPECTED_CURRENT_VIOLATIONS
    missing = EXPECTED_CURRENT_VIOLATIONS - violations
    if unexpected or missing:
        print_edges(
            f"{label}: unexpected prohibited dependencies:",
            unexpected,
        )
        print_edges(
            f"{label}: expected deferred dependencies not found:",
            missing,
        )
        return 1

    print(
        f"{label}: exact deferred facade-edge set confirmed "
        f"({len(violations)} edges)"
    )
    return 0


def check_required_edges(label: str, edges: set[Edge]) -> int:
    missing = REQUIRED_LOCAL_EDGES - edges
    if missing:
        print_edges(f"{label}: required local dependencies not found:", missing)
        return 1
    return 0


def check_polygon_pack_parity(label: str) -> int:
    mismatches = [
        (root_pack, harness_pack)
        for root_pack, harness_pack in POLYGON_PACK_PAIRS
        if not root_pack.is_file()
        or not harness_pack.is_file()
        or root_pack.read_bytes() != harness_pack.read_bytes()
    ]
    if mismatches:
        print(
            f"{label}: harness polygon-pack copies differ from root public packs:",
            file=sys.stderr,
        )
        for root_pack, harness_pack in mismatches:
            print(f"  - {root_pack.relative_to(ROOT)} != {harness_pack.relative_to(ROOT)}", file=sys.stderr)
        return 1

    return 0


def parse_args() -> argparse.Namespace:
    parser = argparse.ArgumentParser()
    subparsers = parser.add_subparsers(dest="mode", required=True)
    package = subparsers.add_parser("package")
    package.add_argument("name")
    subparsers.add_parser("transitional")
    subparsers.add_parser("final")
    return parser.parse_args()


def main() -> int:
    args = parse_args()
    try:
        packages, edges = read_graph()
        unknown, violations = graph_violations(packages, edges)
    except (OSError, KeyError, TypeError, ValueError, tomllib.TOMLDecodeError) as error:
        print(f"architecture-check: unable to read workspace graph: {error}", file=sys.stderr)
        return 2

    if args.mode == "package":
        return check_package(args.name, packages, violations)
    label = (
        "architecture-check-transitional"
        if args.mode == "transitional"
        else "architecture-check"
    )
    if check_expected_violations(label, unknown, violations):
        return 1
    if check_required_edges(label, edges):
        return 1
    if check_polygon_pack_parity(label):
        return 1
    return 0


if __name__ == "__main__":
    raise SystemExit(main())