Files

165 lines
5.3 KiB
Python

"""Validate the local interpreter and foundation-gate assumptions."""
from __future__ import annotations
import argparse
import json
import os
import sys
from pathlib import Path
from typing import Literal, TypedDict
import tomli
# IMPORTANT: keep this adjacent import; direct script entrypoints put scripts/ on sys.path.
from environment_diagnostics import ( # type: ignore[import-not-found]
EnvironmentProbeConfig,
collect_environment_observation,
evaluate_environment,
)
class PreflightReport(TypedDict):
status: str
python: str
executable: str
environment_prefix: str
expected_environment: str
project_local_venv: bool
node: str
diagnostics: dict[str, object]
errors: list[str]
REPOSITORY_ROOT = Path(__file__).resolve().parents[1]
def _expected_environment(override: Path | None = None) -> Path:
if override is not None:
return override.resolve()
name = ".venv" if os.name == "nt" else ".venv-wsl"
return (REPOSITORY_ROOT / name).resolve()
def _is_within(path: Path, parent: Path) -> bool:
try:
path.relative_to(parent)
except ValueError:
return False
return True
def build_report(
expected_environment_override: Path | None = None,
*,
optional_lane: Literal["none", "plot", "reference"] = "none",
) -> PreflightReport:
expected_environment = _expected_environment(expected_environment_override)
executable = Path(sys.executable).absolute()
environment_prefix = Path(sys.prefix).resolve()
local_environment = environment_prefix == expected_environment or _is_within(
environment_prefix, expected_environment
)
with (REPOSITORY_ROOT / "pyproject.toml").open("rb") as stream:
metadata = tomli.load(stream)
errors: list[str] = []
if sys.version_info < (3, 10): # noqa: UP036 - preflight must fail before package import
errors.append("Python 3.10 or newer is required.")
platform = "windows" if os.name == "nt" else "linux"
suffix = "win" if os.name == "nt" else "linux"
cache_root = Path(
os.environ.get(
"PRE_COMMIT_HOME",
REPOSITORY_ROOT / ".tmp" / f"pre-commit-{suffix}",
)
)
temp_root = Path(
os.environ.get(
"SIGMAX_TEMP_ROOT",
REPOSITORY_ROOT / ".tmp" / f"runtime-{suffix}",
)
)
diagnostics = evaluate_environment(
collect_environment_observation(
EnvironmentProbeConfig(
repository_root=REPOSITORY_ROOT,
environment_prefix=environment_prefix,
expected_environment=expected_environment,
cache_root=cache_root,
temp_root=temp_root,
platform=platform,
optional_lane=optional_lane,
path_value=os.environ.get("PATH", ""),
temp_capture_mitigated=(os.environ.get("SIGMAX_PYTEST_CAPTURE_MODE") == "sys"),
)
)
)
errors.extend(
f"[{issue.code}] {issue.summary} Remediation: {issue.remediation}"
for issue in diagnostics.issues
)
if metadata["project"]["requires-python"] != ">=3.10":
errors.append("pyproject.toml Python floor differs from the preflight policy.")
if metadata["project"]["dependencies"]:
errors.append("Mandatory runtime dependencies must remain empty at the foundation gate.")
node_status = (
"NOT_IMPLEMENTED" if (REPOSITORY_ROOT / "package.json").exists() else "NOT_APPLICABLE"
)
return {
"status": "FAIL" if errors else "PASS",
"python": ".".join(str(part) for part in sys.version_info[:3]),
"executable": str(executable),
"environment_prefix": str(environment_prefix),
"expected_environment": str(expected_environment),
"project_local_venv": local_environment,
"node": node_status,
"diagnostics": diagnostics.projection(),
"errors": errors,
}
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--json", action="store_true", help="Emit machine-readable output.")
parser.add_argument(
"--expected-environment",
type=Path,
help="Override the expected environment path for contract testing.",
)
parser.add_argument(
"--optional-lane",
choices=("none", "plot", "reference"),
default="none",
help="Require one fixed optional dependency lane.",
)
arguments = parser.parse_args()
report = build_report(
arguments.expected_environment,
optional_lane=arguments.optional_lane,
)
if arguments.json:
print(json.dumps(report, sort_keys=True))
else:
print(f"Preflight: {report['status']}")
print(f"Python: {report['python']}")
print(f"Interpreter: {report['executable']}")
print(f"Environment: {report['environment_prefix']}")
print(f"Node lane: {report['node']}")
mitigations = report["diagnostics"].get("mitigations")
if isinstance(mitigations, list):
for mitigation in mitigations:
if isinstance(mitigation, str):
print(f"Environment mitigation: {mitigation}")
for error in report["errors"]:
print(f"ERROR: {error}", file=sys.stderr)
return 0 if report["status"] == "PASS" else 2
if __name__ == "__main__":
raise SystemExit(main())