#!/usr/bin/env python3 """Local smoke checks for the Meta3 scaffold. No network calls.""" from __future__ import annotations import json import py_compile import re from pathlib import Path from typing import Any ROOT = Path(__file__).resolve().parents[1] REQUIRED_IMPROVEMENT_IDS = { "stratified_evidence", "claim_ledger", "counterevidence_pass", "validity_before_synthesis", "question_graph", "owner_ready_output", } REQUIRED_PROCESS_PHASE_IDS = { "p00_requirements_lock", "p01_baseline_audit", "p02_calibration_slice", "p03_stratified_backfill", "p04_claim_ledger_generation", "p05_counterevidence_pass", "p06_validity_review", "p07_owner_synthesis", "p08_morning_closeout", } REQUIRED_EXECUTION_SCRIPTS = { "meta3_prepare.py", "meta3_plan_batches.py", "meta3_build_workpackets.py", "meta3_collect_responses.py", "meta3_create_mock_response.py", "meta3_freeze_sources.py", "meta3_morning_report.py", "meta3_orchestrate_local.py", "meta3_preflight.py", "meta3_run_packets.py", "meta3_runner_status.py", "meta3_validate_run.py", "meta3_smoke.py", } def load_json(path: Path) -> dict[str, Any]: with path.open("r", encoding="utf-8") as handle: data = json.load(handle) if not isinstance(data, dict): raise ValueError(f"Expected object in {path}") return data def check_secret_patterns(paths: list[Path]) -> list[str]: findings: list[str] = [] patterns = [ re.compile(r"sk-[A-Za-z0-9_-]{20,}"), re.compile(r"api[_-]?key\"?\s*[:=]\s*\"[A-Za-z0-9_\-]{16,}\"", re.IGNORECASE), re.compile(r"token\"?\s*[:=]\s*\"[A-Za-z0-9_\-]{24,}\"", re.IGNORECASE), ] for path in paths: text = path.read_text(encoding="utf-8") for pattern in patterns: if pattern.search(text): findings.append(str(path.relative_to(ROOT))) break return findings def main() -> int: errors: list[str] = [] warnings: list[str] = [] required_dirs = ["config", "requirements", "scripts", "runs", "data", "reports", "logs", "docs"] for rel in required_dirs: if not (ROOT / rel).is_dir(): errors.append(f"missing dir: {rel}") try: profile = load_json(ROOT / "config/run_profile.example.json") except Exception as exc: # noqa: BLE001 - smoke should report all context errors.append(f"profile json invalid: {exc}") profile = {} try: questions_doc = load_json(ROOT / "config/questions.json") questions = questions_doc.get("questions", []) if not isinstance(questions, list) or not questions: errors.append("questions list missing or empty") else: ids = [q.get("id") for q in questions if isinstance(q, dict)] if len(ids) != len(set(ids)): errors.append("duplicate question ids") for q in questions: if not isinstance(q, dict) or not q.get("prompt"): errors.append("question without prompt") except Exception as exc: # noqa: BLE001 errors.append(f"questions json invalid: {exc}") try: experiment = load_json(ROOT / "config/experiment_design.json") if experiment.get("schema") != "meta3.experiment_design.v1": errors.append("experiment design schema invalid") requirements = experiment.get("improvement_requirements", []) if not isinstance(requirements, list): errors.append("experiment improvement_requirements missing or invalid") else: ids = {item.get("id") for item in requirements if isinstance(item, dict)} missing = sorted(REQUIRED_IMPROVEMENT_IDS - ids) if missing: errors.append("experiment improvement ids missing: " + ", ".join(missing)) except Exception as exc: # noqa: BLE001 errors.append(f"experiment design json invalid: {exc}") try: process = load_json(ROOT / "config/process_model.json") if process.get("schema") != "meta3.process_model.v1": errors.append("process model schema invalid") phases = process.get("phases", []) if not isinstance(phases, list): errors.append("process phases missing or invalid") else: phase_ids = {phase.get("id") for phase in phases if isinstance(phase, dict)} missing = sorted(REQUIRED_PROCESS_PHASE_IDS - phase_ids) if missing: errors.append("process phase ids missing: " + ", ".join(missing)) except Exception as exc: # noqa: BLE001 errors.append(f"process model json invalid: {exc}") if not (ROOT / "docs/evals/EVAL_meta3_improvement_over_meta2.md").is_file(): errors.append("missing eval spec: docs/evals/EVAL_meta3_improvement_over_meta2.md") if not (ROOT / "docs/META3_PROCESS.md").is_file(): errors.append("missing process doc: docs/META3_PROCESS.md") architecture_text = (ROOT / "ARCHITECTURE.md").read_text(encoding="utf-8") if "Meta3 Improvement Contract" not in architecture_text: errors.append("ARCHITECTURE.md missing Meta3 Improvement Contract") for key, value in profile.get("source_artifacts", {}).items(): path = Path(value) if not path.exists(): warnings.append(f"source missing: {key} -> {path}") for script in sorted((ROOT / "scripts").glob("*.py")): try: py_compile.compile(str(script), doraise=True) except py_compile.PyCompileError as exc: errors.append(f"script compile failed: {script.name}: {exc.msg}") script_names = {path.name for path in (ROOT / "scripts").glob("*.py")} missing_scripts = sorted(REQUIRED_EXECUTION_SCRIPTS - script_names) if missing_scripts: errors.append("missing execution scripts: " + ", ".join(missing_scripts)) secret_findings = check_secret_patterns( [ ROOT / "config/run_profile.example.json", ROOT / "config/questions.json", ROOT / "config/experiment_design.json", ROOT / "config/process_model.json", ] ) if secret_findings: errors.append("possible secret in: " + ", ".join(secret_findings)) if (ROOT / ".git").exists() and not (ROOT / ".git/HEAD").exists(): warnings.append(".git directory exists but is not a valid git repo") for warning in warnings: print(f"WARN {warning}") for error in errors: print(f"ERROR {error}") if errors: print("SMOKE FAIL") return 1 print("SMOKE PASS") return 0 if __name__ == "__main__": raise SystemExit(main())