#!/usr/bin/env python3 from __future__ import annotations import csv import hashlib import json import py_compile import re import sys import xml.etree.ElementTree as ET from pathlib import Path ROOT = Path(__file__).resolve().parents[1] ERRORS: list[str] = [] WARNINGS: list[str] = [] PASSES: list[str] = [] def ok(message: str) -> None: PASSES.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 any(part in {'bin', 'obj', 'node_modules', '__pycache__', '.git'} for part in rel.parts): continue yield path required = [ 'README.md', 'RELEASE_NOTES_V12_5.md', 'SOURCE_BASIS_V12_5.md', 'docs/v12_5/00_EXECUTIVE_CRITICAL_REVIEW.md', 'docs/v12_5/01_SOURCE_COVERAGE_MATRIX.csv', 'docs/v12_5/02_ROLE_BASED_BRUTAL_AUDIT.md', 'docs/v12_5/03_TARGET_ARCHITECTURE_SOLID_REFACTORING.md', 'docs/v12_5/04_ALGORITHM_MODEL_EVALUATION.md', 'docs/v12_5/05_DATA_DB_INTEGRITY.md', 'docs/v12_5/06_BE_FE_HANGFIRE_IMPLEMENTATION.md', 'docs/v12_5/07_QA_SECURITY_OPERATIONS.md', 'docs/v12_5/08_PROCESS_SIMPLIFICATION_VIBE_CODING.md', 'docs/v12_5/09_ROADMAP_20D_90D_36M.md', 'docs/v12_5/10_DETAILED_WBS_MASTER.csv', 'docs/v12_5/11_TECH_DEBT_REGISTER.csv', 'docs/v12_5/12_TRACEABILITY_MATRIX.csv', 'docs/v12_5/13_DECISION_LOG.csv', 'docs/v12_5/14_PACKAGE_CONTENTS.md', 'docs/v12_5/15_JOB_CATALOGUE.csv', 'docs/v12_5/16_RACI_MATRIX.csv', 'docs/v12_5/17_RISK_REGISTER.csv', 'docs/v12_5/18_VALIDATION_MATRIX.csv', 'docs/v12_5/K-ArtSell_Aegis_v12_5_실행보증_통합고도화_제안서.docx', 'db/migrations/0017_execution_assurance.sql', 'contracts/schedules/execution-assurance.v1.json', 'contracts/model-governance/evidence-classification.v1.json', 'src/KArtSell.Modules.ModelOperations/Domain/ModelOperationExecutionBoundary.cs', 'tests/KArtSell.ModelOperations.UnitTests/ModelOperationExecutionBoundaryTests.cs', ] for name in required: if not (ROOT / name).exists(): fail(f'missing required file: {name}') if not ERRORS: ok(f'required v12.5 files present: {len(required)}') # Current request direct attachment preservation. coverage_path = ROOT / 'docs/v12_5/01_SOURCE_COVERAGE_MATRIX.csv' coverage: dict[str, str] = {} if coverage_path.exists(): with coverage_path.open(encoding='utf-8-sig', newline='') as f: for row in csv.DictReader(f): coverage[row['Attachment']] = row['SHA256'] attachment_dir = ROOT / 'attachments/current_request_20260802' attachments = [p for p in sorted(attachment_dir.iterdir()) if p.is_file()] if len(attachments) != 3: fail(f'expected exactly three direct current-request attachments, found {len(attachments)}') for path in attachments: actual = hashlib.sha256(path.read_bytes()).hexdigest() if coverage.get(path.name) != actual: fail(f'attachment coverage/hash mismatch: {path.name}') if len(coverage) != len(attachments): fail('source coverage row count mismatch') if not ERRORS: ok('three direct attachments preserved byte-for-byte') # Parse structured files. 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 MSBuild 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 f: rows = list(csv.reader(f)) if not rows: fail(f'empty CSV {path.relative_to(ROOT)}'); continue 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') # WBS/debt/trace dimensions. def read_dicts(rel: str): with (ROOT / rel).open(encoding='utf-8-sig', newline='') as f: return list(csv.DictReader(f)) wbs = read_dicts('docs/v12_5/10_DETAILED_WBS_MASTER.csv') if (ROOT/'docs/v12_5/10_DETAILED_WBS_MASTER.csv').exists() else [] if len(wbs) != 336: fail(f'v12.5 WBS must contain 336 rows, found {len(wbs)}') ids = [r.get('WBS_ID','') for r in wbs] if len(ids) != len(set(ids)): fail('duplicate WBS_ID') if sum(1 for r in wbs if r.get('WBS_ID','').startswith('AEG-V12-5-')) != 48: fail('v12.5 WBS delta must contain 48 rows') if any(not r.get('Acceptance_Evidence','').strip() or not r.get('Primary_Owner','').strip() for r in wbs): fail('WBS blank acceptance/owner') ok('WBS 336 rows, unique IDs, 48-row delta') debt = read_dicts('docs/v12_5/11_TECH_DEBT_REGISTER.csv') if (ROOT/'docs/v12_5/11_TECH_DEBT_REGISTER.csv').exists() else [] if len(debt) != 68: fail(f'tech debt must contain 68 rows, found {len(debt)}') if len({r.get('ID') for r in debt}) != len(debt): fail('duplicate tech debt ID') ok('technical debt register 68 rows') trace = read_dicts('docs/v12_5/12_TRACEABILITY_MATRIX.csv') if (ROOT/'docs/v12_5/12_TRACEABILITY_MATRIX.csv').exists() else [] for i in range(1, 13): if not any(r.get('Requirement_ID') == f'REQ-EXA-{i:03d}' for r in trace): fail(f'missing trace requirement REQ-EXA-{i:03d}') ok('execution-assurance trace requirements complete') # Fail-closed configuration and code markers. settings = json.loads((ROOT/'src/KArtSell.Host/appsettings.json').read_text(encoding='utf-8')) if settings.get('ModelOperations', {}).get('DispatcherEnabled') is not False: fail('ModelOperations dispatcher must default OFF') program = (ROOT/'src/KArtSell.Host/Program.cs').read_text(encoding='utf-8') for marker in ['modelOperationsDispatcherEnabled', '!modelOperationsDispatcherEnabled || x.ShadowEvaluation', 'RegisterModelOperationsSchedules(modelOperationsDispatcherEnabled', '!x.AutomaticOrder', '!x.KisOrderAdapter']: if marker not in program: fail(f'missing fail-closed marker: {marker}') scheduler = (ROOT/'src/KArtSell.Modules.ModelOperations/Scheduling/ModelOperationsScheduler.cs').read_text(encoding='utf-8') for marker in ['if (!dispatcherEnabled)', 'RemoveIfExists', 'DispatcherJobId']: if marker not in scheduler: fail(f'missing scheduler safety marker: {marker}') contract = (ROOT/'contracts/schedules/execution-assurance.v1.json') if contract.exists(): c = json.loads(contract.read_text(encoding='utf-8')) if c.get('dispatcher', {}).get('enabledByDefault') is not False: fail('schedule contract must default dispatcher OFF') if len(c.get('operations', [])) != 6: fail('execution-assurance contract must contain J25-J30') ok('fail-closed model-operations boundary complete') registry = (ROOT/'src/KArtSell.Modules.ModelOperations/Domain/ModelOperationDefinition.cs').read_text(encoding='utf-8') codes = re.findall(r'new\("(J\d+)"', registry) if len(codes) != 16 or len(set(codes)) != 16: fail(f'model operation registry expected 16 unique operations, found {len(codes)}/{len(set(codes))}') for code in [f'J{i}' for i in range(25,31)]: if code not in codes: fail(f'missing registry operation {code}') ok('model operation registry J10-J30 approved set complete') migration = (ROOT/'db/migrations/0017_execution_assurance.sql').read_text(encoding='utf-8') for marker in ['V12_5_REAPPROVAL_REQUIRED', 'model_operation_artifact', 'release_evidence_bundle', 'source_contract_snapshot', 'execution_assurance_decision', "'J30'"]: if marker not in migration: fail(f'migration 0017 missing marker: {marker}') ok('migration 0017 execution-assurance structures complete') # Prohibited patterns in active source. source_files = [p for p in [*active_files('*.cs'), *active_files('*.sql')] if 'tests' not in p.relative_to(ROOT).parts] patterns = {'AllowAnonymous()':'anonymous endpoint','IGenericRepository':'generic repository','DateTime.Now':'direct wall clock','DateTime.UtcNow':'direct wall clock'} 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}: {token} in {hits}') select_star = [str(p.relative_to(ROOT)) for p in source_files if re.search(r'\bselect\s+\*', p.read_text(encoding='utf-8'), flags=re.I)] if select_star: fail(f'SELECT * prohibited: {select_star}') ok('prohibited-pattern scan complete') # Domain framework dependency scan and C# structural smoke. for path in active_files('*.cs'): rel = path.relative_to(ROOT) text = path.read_text(encoding='utf-8') if 'Domain' in rel.parts: for token in ['using Dapper', 'using Npgsql', 'using FastEndpoints', 'using Hangfire', 'HttpContext', 'DbConnection']: if token in text: fail(f'domain framework dependency {token}: {rel}') scrub = re.sub(r'""".*?"""', '', text, flags=re.S) scrub = re.sub(r'"(?:\\.|[^"\\])*"', '', scrub) if scrub.count('{') != scrub.count('}'): fail(f'C# brace imbalance: {rel}') ok('domain dependency and C# structural smoke complete') # Package status honesty. claims = [] for path in [ROOT/'README.md', ROOT/'RELEASE_NOTES_V12_5.md', ROOT/'docs/v12_5/00_EXECUTIVE_CRITICAL_REVIEW.md']: text = path.read_text(encoding='utf-8') for forbidden in ['PRODUCTION_READY', 'BUILD_VALIDATED / DB_REHEARSED', 'SHADOW_VALIDATED']: if forbidden in text: claims.append(f'{path.name}:{forbidden}') if claims: fail(f'unsupported readiness claim: {claims}') else: ok('readiness claims remain evidence-bounded') # Toolchain warnings are expected in this container/package. if not (ROOT/'frontend/pnpm-lock.yaml').exists(): warn('pnpm-lock.yaml is still absent; frozen frontend validation remains blocked') warn('.NET 10 restore/build/test is not executed by static validator') warn('PostgreSQL DbUp fresh/upgrade/re-run/failure rehearsal is not executed by static validator') warn('252-trading-day Shadow, PBO/DSR, security and DR evidence remain unmet') print(f'PASS={len(PASSES)} WARN={len(WARNINGS)} FAIL={len(ERRORS)}') for m in PASSES: print(f'PASS: {m}') for m in WARNINGS: print(f'WARN: {m}') for m in ERRORS: print(f'FAIL: {m}') sys.exit(1 if ERRORS else 0)