#!/usr/bin/env python3 # file: scripts/audit_distribution_layout.py # version: 10 """Audit the static distribution layout expected by supported POCs.""" from __future__ import annotations import argparse import pathlib import sys FORBIDDEN_PATHS = ( "builds", ) REQUIRED_PATHS = ( "crates/apps/game-reflex-poc-desktop/Cargo.toml", "crates/apps/game-snake-poc-desktop/Cargo.toml", "crates/apps/game-reflex-poc-tauri/Cargo.toml", "crates/apps/game-reflex-poc-tauri/package.json", "crates/apps/game-reflex-poc-tauri/tsconfig.json", "crates/apps/game-reflex-poc-tauri/vite.config.ts", "crates/apps/game-reflex-poc-tauri/tauri.conf.json", "crates/apps/game-reflex-poc-tauri/capabilities/default.json", "crates/apps/game-reflex-poc-tauri/icons/icon.png", "crates/apps/game-reflex-poc-tauri/frontend/index.html", "crates/apps/game-reflex-poc-tauri/frontend/ts/main.ts", "crates/apps/game-reflex-poc-wasm/Cargo.toml", "crates/apps/game-snake-poc-wasm/Cargo.toml", "crates/apps/game-snake-poc-tauri/Cargo.toml", "crates/apps/game-snake-poc-tauri/README.md", "crates/apps/game-snake-poc-tauri/USAGE.md", "crates/apps/game-snake-poc-tauri/package.json", "crates/apps/game-snake-poc-tauri/tsconfig.json", "crates/apps/game-snake-poc-tauri/vite.config.ts", "crates/apps/game-snake-poc-tauri/tauri.conf.json", "crates/apps/game-snake-poc-tauri/capabilities/default.json", "crates/apps/game-snake-poc-tauri/icons/icon.png", "crates/apps/game-snake-poc-tauri/frontend/main.html", "crates/apps/game-snake-poc-tauri/frontend/ts/main.ts", "Web/game-snake-poc/package.json", "Web/game-snake-poc/tsconfig.json", "Web/game-snake-poc/vite.config.ts", "Web/game-snake-poc/frontend/main.html", "Web/game-snake-poc/frontend/sass/_simplebar.scss", "Web/game-snake-poc/frontend/sass/main.scss", "Web/game-snake-poc/frontend/ts/main.ts", "Web/game-snake-poc/frontend/ts/assets.ts", "Web/game-snake-poc/frontend/ts/lifecycle.ts", "Web/game-snake-poc/frontend/ts/logging.ts", "Web/game-snake-poc/frontend/ts/provenance.ts", "Android/game-reflex-poc/build.gradle", "Android/game-snake-poc/build.gradle", "scripts/build_android_rust.py", "scripts/build_reflex_tauri_wasm.py", "assets/common", "assets/game-reflex-poc", "assets/game-snake-poc", ) def audit_snake_web_shell(root: pathlib.Path) -> list[str]: """Validate the KSP-derived bounded SimpleBar shell contract.""" violations: list[str] = [] html_path = root / "Web/game-snake-poc/frontend/main.html" app_scss_path = root / "Web/game-snake-poc/frontend/sass/_app.scss" main_ts_path = root / "Web/game-snake-poc/frontend/ts/main.ts" if not html_path.exists() or not app_scss_path.exists() or not main_ts_path.exists(): return violations html = html_path.read_text(encoding="utf-8") app_scss = app_scss_path.read_text(encoding="utf-8") main_ts = main_ts_path.read_text(encoding="utf-8") required_html = ( '
', '
', '
', ) for fragment in required_html: if fragment not in html: violations.append(f"DIST-LAYOUT-003: Snake Web shell missing required HTML contract: {fragment}") if '
' in html: violations.append("DIST-LAYOUT-004: data-simplebar must be attached to the bounded app-content zone, not app-main") required_scss = ( ".app-scrollable {", "height: 100%;", "max-height: 100%;", ) for fragment in required_scss: if fragment not in app_scss: violations.append(f"DIST-LAYOUT-005: Snake Web shell missing required Sass contract: {fragment}") required_ts = ( 'import ResizeObserver from "resize-observer-polyfill";', 'import "simplebar";', ').ResizeObserver = ResizeObserver;', ) for fragment in required_ts: if fragment not in main_ts: violations.append(f"DIST-LAYOUT-006: Snake Web shell missing SimpleBar bootstrap contract: {fragment}") return violations def audit_snake_web_integration(root: pathlib.Path) -> list[str]: """Validate assets, lifecycle, logging and provenance wiring for the Snake Web POC.""" violations: list[str] = [] package_path = root / "Web/game-snake-poc/package.json" vite_path = root / "Web/game-snake-poc/vite.config.ts" main_path = root / "Web/game-snake-poc/frontend/ts/main.ts" lifecycle_path = root / "Web/game-snake-poc/frontend/ts/lifecycle.ts" provenance_path = root / "Web/game-snake-poc/frontend/ts/provenance.ts" wasm_manifest_path = root / "crates/apps/game-snake-poc-wasm/Cargo.toml" wasm_runtime_path = root / "crates/apps/game-snake-poc-wasm/src/runtime.rs" if not all(path.exists() for path in (package_path, vite_path, main_path, lifecycle_path, provenance_path, wasm_manifest_path, wasm_runtime_path)): return violations package = package_path.read_text(encoding="utf-8") vite = vite_path.read_text(encoding="utf-8") main = main_path.read_text(encoding="utf-8") lifecycle = lifecycle_path.read_text(encoding="utf-8") provenance = provenance_path.read_text(encoding="utf-8") wasm_manifest = wasm_manifest_path.read_text(encoding="utf-8") wasm_runtime = wasm_runtime_path.read_text(encoding="utf-8") required_fragments = ( (package, '"vite-plugin-static-copy"', "DIST-LAYOUT-007: Snake Web package must declare static asset packaging"), (vite, 'dest: "common/data", rename: { stripBase: true }', "DIST-LAYOUT-008: common Web assets must retain the common/data namespace without absolute source prefixes"), (vite, 'dest: "game/data", rename: { stripBase: true }', "DIST-LAYOUT-009: game Web assets must retain the game/data namespace without absolute source prefixes"), (main, 'loadSnakeWebAssets()', "DIST-LAYOUT-010: Snake Web startup must load runtime asset probes"), (main, 'bindBrowserLifecycle(session, stage)', "DIST-LAYOUT-011: Snake Web startup must bind browser lifecycle"), (lifecycle, '"visibilitychange"', "DIST-LAYOUT-012: Snake Web lifecycle must observe visibility changes"), (lifecycle, '"pagehide"', "DIST-LAYOUT-013: Snake Web lifecycle must observe pagehide"), (lifecycle, 'ResizeObserver', "DIST-LAYOUT-014: Snake Web lifecycle must observe resize"), (provenance, '"keyboard-mouse"', "DIST-LAYOUT-015: Snake Web provenance must distinguish keyboard/mouse input"), (provenance, '"touch"', "DIST-LAYOUT-016: Snake Web provenance must distinguish touch input"), (wasm_manifest, 'engine-v1-platform-api = { path = "../../engines/engine-v1-platform-api" }', "DIST-LAYOUT-017: Snake WASM adapter must consume the platform provenance API"), (wasm_runtime, 'PlatformFamily::Web', "DIST-LAYOUT-018: Snake WASM provenance must declare Web platform family"), (wasm_runtime, 'ExecutionModel::Wasm', "DIST-LAYOUT-019: Snake WASM provenance must declare WASM execution"), (wasm_runtime, 'RuntimeHost::Browser', "DIST-LAYOUT-020: Snake WASM provenance must declare browser host"), ) for content, fragment, message in required_fragments: if fragment not in content: violations.append(message) return violations def audit_snake_tauri_android_integration(root: pathlib.Path) -> list[str]: """Validate the minimal Snake Tauri Android host boundary.""" violations: list[str] = [] cargo_path = root / "crates/apps/game-snake-poc-tauri/Cargo.toml" package_path = root / "crates/apps/game-snake-poc-tauri/package.json" tauri_path = root / "crates/apps/game-snake-poc-tauri/tauri.conf.json" tauri_rust_path = root / "crates/apps/game-snake-poc-tauri/src/tauri.rs" build_script_path = root / "crates/apps/game-snake-poc-tauri/build.rs" frontend_path = root / "crates/apps/game-snake-poc-tauri/frontend/ts/main.ts" logging_path = root / "crates/apps/game-snake-poc-tauri/frontend/ts/logging.ts" wasm_runtime_path = root / "crates/apps/game-snake-poc-wasm/src/runtime.rs" paths = (cargo_path, package_path, tauri_path, tauri_rust_path, build_script_path, frontend_path, logging_path, wasm_runtime_path) if not all(path.exists() for path in paths): return violations cargo = cargo_path.read_text(encoding="utf-8") package = package_path.read_text(encoding="utf-8") tauri = tauri_path.read_text(encoding="utf-8") tauri_rust = tauri_rust_path.read_text(encoding="utf-8") build_script = build_script_path.read_text(encoding="utf-8") frontend = frontend_path.read_text(encoding="utf-8") logging = logging_path.read_text(encoding="utf-8") wasm_runtime = wasm_runtime_path.read_text(encoding="utf-8") required_fragments = ( (cargo, 'game-logging-lib = { path = "../../common/game-logging-lib" }', "DIST-LAYOUT-021: Snake Tauri host must use shared Rust logging"), (cargo, 'tauri-plugin-tracing.workspace = true', "DIST-LAYOUT-022: Snake Tauri host must enable the tracing plugin"), (package, '"wasm:dev": "cargo build -p game-snake-poc-wasm', "DIST-LAYOUT-023: Snake Tauri dev hook must build the shared Snake WASM adapter"), (package, 'wasm-bindgen ../../../../builds/sasedev-games/', "DIST-LAYOUT-024: Snake Tauri bindings must be generated outside the repository"), (package, '"dev": "npm run wasm:dev && vite"', "DIST-LAYOUT-036: Snake Tauri dev hook must leave Android generation to Tauri"), (package, '"build": "npm run wasm:build && tsc && vite build"', "DIST-LAYOUT-037: Snake Tauri build hook must leave Android generation to Tauri"), (build_script, "tauri_build::build();", "DIST-LAYOUT-038: Snake Tauri build script must delegate Android integration to tauri-build"), (tauri, '"script": "npm run dev"', "DIST-LAYOUT-025: Tauri must own the frontend development hook"), (tauri, '"script": "npm run build"', "DIST-LAYOUT-026: Tauri must own the frontend production hook"), (tauri_rust, '#[tauri::mobile_entry_point]', "DIST-LAYOUT-027: Snake Tauri run entry point must support Tauri mobile"), (tauri_rust, 'tauri_plugin_tracing::Builder::new()', "DIST-LAYOUT-028: Snake Tauri runtime must install the tracing plugin"), (frontend, 'configure_runtime_provenance(', "DIST-LAYOUT-029: Snake Tauri frontend must configure provenance through the shared adapter"), (logging, '@fltsci/tauri-plugin-tracing', "DIST-LAYOUT-030: Snake Tauri frontend logs must use the tracing bridge"), (wasm_runtime, '"tauri-webview" => Some(engine_v1_platform_api::RuntimeHost::TauriWebView)', "DIST-LAYOUT-031: Snake WASM adapter must accept the Tauri WebView host"), (wasm_runtime, '"android" => Some(engine_v1_platform_api::PlatformFamily::Android)', "DIST-LAYOUT-032: Snake WASM adapter must accept Android provenance"), ) for content, fragment, message in required_fragments: if fragment not in content: violations.append(message) forbidden_fragments = ( (package, "python", "DIST-LAYOUT-033: Snake Tauri build hooks must not use Python"), (package, "android:prepare", "DIST-LAYOUT-039: Snake Tauri build hooks must not patch generated Android Gradle files"), (build_script, "gen/android", "DIST-LAYOUT-040: Snake Tauri build script must not mutate generated Android Gradle files"), (tauri, "python", "DIST-LAYOUT-034: Snake Tauri configuration must not use Python build orchestration"), (frontend, "console.", "DIST-LAYOUT-035: Snake Tauri application frontend must not log directly through console.*"), ) for content, fragment, message in forbidden_fragments: if fragment in content.lower(): violations.append(message) return violations def main() -> int: """Validate that every static distribution boundary exists.""" parser = argparse.ArgumentParser() parser.add_argument("--root", default=".", help="workspace root") arguments = parser.parse_args() root = pathlib.Path(arguments.root).resolve() missing: list[str] = [] forbidden_present: list[str] = [] contract_violations: list[str] = [] for relative in REQUIRED_PATHS: if not (root / relative).exists(): missing.append(relative) for relative in FORBIDDEN_PATHS: if (root / relative).exists(): forbidden_present.append(relative) contract_violations.extend(audit_snake_web_shell(root)) contract_violations.extend(audit_snake_web_integration(root)) contract_violations.extend(audit_snake_tauri_android_integration(root)) if missing: for relative in missing: print(f"DIST-LAYOUT-001: missing required path: {relative}", file=sys.stderr) print(f"Distribution layout audit: {len(missing)} violation(s)", file=sys.stderr) return 1 if forbidden_present: for relative in forbidden_present: print(f"DIST-LAYOUT-002: generated path must stay outside repository: {relative}", file=sys.stderr) print(f"Distribution layout audit: {len(forbidden_present)} violation(s)", file=sys.stderr) return 1 if contract_violations: for violation in contract_violations: print(violation, file=sys.stderr) print(f"Distribution layout audit: {len(contract_violations)} violation(s)", file=sys.stderr) return 1 print(f"Distribution layout audit: clean ({len(REQUIRED_PATHS)} required path(s), {len(FORBIDDEN_PATHS)} forbidden path(s) absent)") return 0 if __name__ == "__main__": raise SystemExit(main())