Meta3.0 / scripts /meta3_preflight.py
smlflg's picture
Initial public upload from Projekte/Meta3.0
1219ee2 verified
Raw History Blame Contribute Delete
11 kB
#!/usr/bin/env python3
"""Create a Meta3 preflight report for a future network run. No network calls."""
from __future__ import annotations
import argparse
import json
import os
from datetime import datetime, timezone
from pathlib import Path
from typing import Any
ROOT = Path(__file__).resolve().parents[1]
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 JSON object: {path}")
return data
def write_json(path: Path, data: dict[str, Any]) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(json.dumps(data, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
def latest_run_dir() -> Path:
runs_root = ROOT / "runs"
candidates = sorted(p for p in runs_root.iterdir() if p.is_dir())
if not candidates:
raise FileNotFoundError("No run dirs found. Run scripts/meta3_prepare.py first.")
return candidates[-1]
def run_dir_for(run_id: str | None) -> Path:
return ROOT / "runs" / run_id if run_id else latest_run_dir()
def first_env_value(names: list[str]) -> tuple[str | None, str | None]:
for name in names:
value = os.environ.get(name)
if value:
return name, value
return None, None
def api_key_count(value: str | None) -> int:
if not value:
return 0
return len([part for part in value.replace("\n", ",").split(",") if part.strip()])
def gate(gate_id: str, status: str, meaning: str, **details: Any) -> dict[str, Any]:
return {"id": gate_id, "status": status, "meaning": meaning, **details}
def planned_calls_for_modes(run_dir: Path, modes: list[str], limit: int) -> tuple[int, dict[str, int]]:
total = 0
by_mode: dict[str, int] = {}
for mode in modes:
manifest_path = run_dir / f"outputs/workpackets/{mode}_packet_manifest.json"
if not manifest_path.exists():
by_mode[mode] = 0
continue
manifest = load_json(manifest_path)
planned = int(manifest.get("planned_call_count") or 0)
if limit:
planned = min(planned, limit)
by_mode[mode] = planned
total += planned
return total, by_mode
def runner_lock_path(run_dir: Path, mode: str) -> Path:
return run_dir / "state" / f"{mode}_runner.lock.json"
def stop_paths(run_dir: Path, mode: str) -> list[Path]:
return [run_dir / "state" / "STOP", run_dir / "state" / f"STOP_{mode}"]
def requirements_lock_status(path: Path) -> tuple[str, str]:
if not path.exists():
return "open", "requirements/LOCKED.md must exist before network execution."
text = path.read_text(encoding="utf-8")
required_markers = [
"Status: locked",
"## Ziel",
"## Fragen",
"## Quellen",
"## Budget",
"## Outputs",
"## Abbruchbedingungen",
"## Morgen-Erfolg",
]
missing = [marker for marker in required_markers if marker not in text]
if missing:
return "open", "requirements/LOCKED.md is missing required markers: " + ", ".join(missing)
if "TODO" in text or "TBD" in text:
return "open", "requirements/LOCKED.md still contains TODO/TBD placeholders."
return "pass", "Requirements are locked in requirements/LOCKED.md."
def markdown_report(report: dict[str, Any]) -> str:
lines = [
"# Meta3 Network Preflight",
"",
f"Run: `{report['run_id']}`",
f"Created: `{report['created_at']}`",
f"Status: `{report['status']}`",
"",
"## Planned Scope",
"",
f"- Modes: {', '.join(report['modes'])}",
f"- Planned calls: {report['planned_calls_total']}",
f"- Accepted call budget: {report['accepted_call_budget']}",
f"- Concurrency: {report['concurrency']}",
f"- API key env: {report['api_key_env'] or 'missing'}",
f"- API key count: {report['api_key_count']}",
f"- Base URL configured: {report['base_url_configured']}",
"",
"## Gates",
"",
]
for item in report["gates"]:
lines.append(f"- `{item['id']}`: `{item['status']}` - {item['meaning']}")
lines.extend(["", "## Network", "", "- network_calls_made: 0"])
return "\n".join(lines) + "\n"
def main() -> int:
parser = argparse.ArgumentParser(description="Write Meta3 network preflight report without API calls.")
parser.add_argument("--run-id", default=None, help="Run id under runs/. Defaults to latest run.")
parser.add_argument(
"--mode",
action="append",
choices=["calibration", "claim_ledger"],
help="Mode to include. Can be repeated. Defaults to both.",
)
parser.add_argument("--limit", type=int, default=0, help="Per-mode selected packet cap. 0 means all.")
parser.add_argument("--concurrency", type=int, default=0, help="Defaults to profile safe_concurrency.")
parser.add_argument("--accepted-call-budget", type=int, default=0)
parser.add_argument("--strict", action="store_true", help="Exit nonzero when gates are open or blocked.")
args = parser.parse_args()
run_dir = run_dir_for(args.run_id)
run_id = run_dir.name
modes = args.mode or ["calibration", "claim_ledger"]
manifest = load_json(run_dir / "manifests/run_manifest.json")
profile = manifest["profile"]
limits = profile.get("limits", {})
api_profile = profile.get("api_profile", {})
safe_concurrency = int(limits.get("safe_concurrency") or 1)
max_concurrency = int(limits.get("max_concurrency_without_new_approval") or safe_concurrency)
concurrency = args.concurrency or safe_concurrency
max_api_calls = int(limits.get("max_api_calls") or 0)
base_env, base_value = first_env_value(list(api_profile.get("base_url_envs", [])))
key_env, key_value = first_env_value(list(api_profile.get("api_key_envs", [])))
base_url = (base_value or api_profile.get("default_base_url") or "").rstrip("/")
key_count = api_key_count(key_value)
planned_total, planned_by_mode = planned_calls_for_modes(run_dir, modes, args.limit)
gates: list[dict[str, Any]] = []
requirements_lock = ROOT / "requirements/LOCKED.md"
lock_status, lock_meaning = requirements_lock_status(requirements_lock)
gates.append(
gate(
"requirements_locked",
lock_status,
lock_meaning,
path=str(requirements_lock),
)
)
readiness_path = run_dir / "reports/run_readiness_report.json"
readiness_status = None
if readiness_path.exists():
readiness_status = load_json(readiness_path).get("status")
gates.append(
gate(
"run_readiness_passed",
"pass" if readiness_status == "pass" else "open",
"Run artifact validator must pass.",
path=str(readiness_path),
readiness_status=readiness_status,
)
)
for mode in modes:
dry_run_path = run_dir / "logs" / f"{mode}_runner_dry_run.json"
dry_run_status = "pass" if dry_run_path.exists() else "open"
gates.append(
gate(
f"{mode}_runner_dry_run_exists",
dry_run_status,
"Runner dry-run must exist before network execution.",
path=str(dry_run_path),
)
)
lock_path = runner_lock_path(run_dir, mode)
gates.append(
gate(
f"{mode}_runner_lock_absent",
"pass" if not lock_path.exists() else "blocked",
"No runner lock may exist before a new network execution starts.",
path=str(lock_path),
)
)
active_stop_paths = [str(path) for path in stop_paths(run_dir, mode) if path.exists()]
gates.append(
gate(
f"{mode}_stop_file_absent",
"pass" if not active_stop_paths else "blocked",
"Stop files block network execution until intentionally removed.",
active_stop_paths=active_stop_paths,
)
)
gates.append(
gate(
"accepted_call_budget_covers_plan",
"pass" if args.accepted_call_budget >= planned_total and planned_total > 0 else "open",
"Accepted call budget must cover selected planned calls.",
accepted_call_budget=args.accepted_call_budget,
planned_calls_total=planned_total,
)
)
gates.append(
gate(
"planned_calls_within_profile_cap",
"pass" if not max_api_calls or planned_total <= max_api_calls else "blocked",
"Planned calls must be inside profile max_api_calls.",
max_api_calls=max_api_calls,
planned_calls_total=planned_total,
)
)
gates.append(
gate(
"concurrency_within_approved_limit",
"pass" if concurrency <= max_concurrency else "blocked",
"Concurrency must not exceed max_concurrency_without_new_approval.",
concurrency=concurrency,
max_concurrency_without_new_approval=max_concurrency,
)
)
gates.append(
gate(
"api_base_url_configured",
"pass" if bool(base_url) else "open",
"OpenAI-compatible base URL must be configured.",
base_url_env=base_env,
)
)
gates.append(
gate(
"api_key_env_present",
"pass" if key_count > 0 else "open",
"At least one API key must be present via configured env vars.",
api_key_env=key_env,
api_key_count=key_count,
)
)
status = "pass" if all(item["status"] == "pass" for item in gates) else "blocked"
report = {
"schema": "meta3.network_preflight.v1",
"run_id": run_id,
"created_at": datetime.now(timezone.utc).isoformat(),
"status": status,
"modes": modes,
"planned_calls_total": planned_total,
"planned_calls_by_mode": planned_by_mode,
"accepted_call_budget": args.accepted_call_budget,
"concurrency": concurrency,
"api_key_env": key_env,
"api_key_count": key_count,
"base_url_configured": bool(base_url),
"base_url_env": base_env,
"gates": gates,
"network_calls_made": 0,
}
report_path = run_dir / "reports/network_preflight.json"
report_md_path = run_dir / "reports/NETWORK_PREFLIGHT.md"
write_json(report_path, report)
report_md_path.write_text(markdown_report(report), encoding="utf-8")
print(f"network_preflight={report_path}")
print(f"network_preflight_md={report_md_path}")
print(f"status={status}")
print(f"planned_calls_total={planned_total}")
print(f"accepted_call_budget={args.accepted_call_budget}")
print("network_calls_made=0")
return 0 if status == "pass" or not args.strict else 1
if __name__ == "__main__":
raise SystemExit(main())