#!/usr/bin/env python3 from __future__ import annotations import csv import json import py_compile import re import shutil import sys import xml.etree.ElementTree as ET from pathlib import Path ROOT = Path(__file__).resolve().parents[1] ERRORS: list[str] = [] WARNINGS: list[str] = [] CHECKS: list[str] = [] def ok(message: str) -> None: CHECKS.append(message) def fail(message: str) -> None: ERRORS.append(message) def warn(message: str) -> None: WARNINGS.append(message) def active_files(pattern: str): for path in ROOT.rglob(pattern): rel = path.relative_to(ROOT) if rel.parts[:1] == ("attachments",): continue if rel.parts[:2] == ("research", "original"): continue if rel.parts[:1] == ("tests",): continue if any(part in {"bin", "obj", "node_modules", "__pycache__"} for part in rel.parts): continue yield path required = [ "README.md", "AGENTS.md", "global.json", "KArtSell.sln", "docs/v12/00_EXECUTIVE_INTEGRATED_PROPOSAL.md", "docs/v12/07_WBS_MASTER.csv", "docs/v12/08_TECH_DEBT_REGISTER.csv", "docs/v12/09_TRACEABILITY_MATRIX.csv", "db/migrations/0012_signal_engine_integrated_hardening.sql", "research/hardening/TEST_RESULTS.txt", "attachments/original/K-ArtSell_12_2_complete_package(1).zip", "attachments/original/K-ArtSell_퀀트투자자문_SI_본프로그램착수_누적고도화_통합실행기준서_v10.0(1).docx", "attachments/original/KArtSell_v11_implementation_acceleration(1).zip", "attachments/original/KArtSell_Aegis_v11_1_hardening(1).zip", ] for name in required: if not (ROOT / name).exists(): fail(f"missing required file: {name}") if not ERRORS: ok(f"required files present: {len(required)}") for path in active_files("*.json"): try: json.loads(path.read_text(encoding="utf-8")) except Exception as exc: fail(f"invalid JSON {path.relative_to(ROOT)}: {exc}") ok("JSON parse complete") for path in [*active_files("*.csproj"), *active_files("*.props")]: try: ET.parse(path) except Exception as exc: fail(f"invalid XML {path.relative_to(ROOT)}: {exc}") ok("MSBuild XML parse complete") for path in active_files("*.csv"): try: with path.open(encoding="utf-8-sig", newline="") as handle: rows = list(csv.reader(handle)) if not rows: fail(f"empty CSV {path.relative_to(ROOT)}") else: width = len(rows[0]) bad = [i + 1 for i, row in enumerate(rows) if len(row) != width] if bad: fail(f"ragged CSV {path.relative_to(ROOT)} rows {bad[:10]}") except Exception as exc: fail(f"invalid CSV {path.relative_to(ROOT)}: {exc}") ok("CSV structure complete") for path in active_files("*.py"): try: py_compile.compile(str(path), doraise=True) except Exception as exc: fail(f"Python compile failed {path.relative_to(ROOT)}: {exc}") ok("Python compile complete") for path in active_files("*.yaml"): text = path.read_text(encoding="utf-8") if "\t" in text: fail(f"YAML contains tab indentation: {path.relative_to(ROOT)}") if not text.strip(): fail(f"empty YAML: {path.relative_to(ROOT)}") ok("YAML basic checks complete") source_files = [*active_files("*.cs"), *active_files("*.sql")] patterns = { "AllowAnonymous()": "anonymous module endpoint", "IGenericRepository": "generic repository", "DateTime.Now": "direct wall-clock use", "DateTime.UtcNow": "direct wall-clock use", } for token, label in patterns.items(): hits = [str(p.relative_to(ROOT)) for p in source_files if token in p.read_text(encoding="utf-8")] if hits: fail(f"{label} pattern {token}: {hits}") select_star = [] for path in source_files: text = path.read_text(encoding="utf-8") if re.search(r"\bselect\s+\*", text, flags=re.IGNORECASE): select_star.append(str(path.relative_to(ROOT))) if select_star: fail(f"SELECT * prohibited: {select_star}") ok("prohibited source pattern scan complete") for path in active_files("Endpoint.cs"): text = path.read_text(encoding="utf-8") if "Roles(" not in text and "Policies(" not in text: fail(f"endpoint lacks Roles/Policies: {path.relative_to(ROOT)}") ok("endpoint authorization declarations complete") for path in active_files("*.cs"): if f"{Path('Domain')}" in str(path.relative_to(ROOT)): text = path.read_text(encoding="utf-8") for token in ["using Dapper", "using Npgsql", "using FastEndpoints", "using Hangfire", "HttpContext", "DbConnection"]: if token in text: fail(f"domain framework dependency {token}: {path.relative_to(ROOT)}") ok("domain dependency scan complete") migration_names = sorted(p.name for p in (ROOT / "db" / "migrations").glob("*.sql")) if not all(re.match(r"^\d{4}_[a-z0-9_]+\.sql$", name) for name in migration_names): fail("migration filename format must be NNNN_snake_case.sql") if len(migration_names) != len(set(migration_names)): fail("duplicate migration filename") ok(f"migration ordering complete: {len(migration_names)} scripts") for path in active_files("*"): if path.is_file() and path.name.lower() in {"testfile", "temp", "tmp"}: fail(f"placeholder file: {path.relative_to(ROOT)}") ok("placeholder scan complete") if not (ROOT / "frontend" / "pnpm-lock.yaml").exists(): warn("frontend/pnpm-lock.yaml is missing by design; G0 must generate and review it") if shutil.which("dotnet") is None: warn("dotnet SDK unavailable in current validation environment") if shutil.which("pnpm") is None: warn("pnpm unavailable in current validation environment") print("K-ArtSell Aegis v12.0 static validation") print(f"root={ROOT}") for message in CHECKS: print(f"PASS: {message}") for message in WARNINGS: print(f"WARN: {message}") for message in ERRORS: print(f"FAIL: {message}") print(f"summary: pass={len(CHECKS)} warn={len(WARNINGS)} fail={len(ERRORS)}") sys.exit(1 if ERRORS else 0)