← Files VeraARCHIVED FILE

modules/passive-invoice-audit/scripts/check_dependencies.py

3.22 KB · Oct 2, 2026 · 00:29 UTC

↓ Download file

"""Check dependencies and native Luna prerequisites for this Vera workflow."""

from __future__ import annotations

import argparse
import importlib.util
import json
import logging
import re
import shutil
from pathlib import Path

__all__ = ["main"]

LOGGER = logging.getLogger(__name__)
PLUGIN_ROOT = Path(__file__).resolve().parents[1]
REQUIRED_IMPORTS = ("openpyxl", "xlsxwriter")
PACKAGE_IMPORTS = {"openpyxl": "openpyxl", "xlsxwriter": "xlsxwriter"}


def _requirement_name(line: str) -> str:
    """Return the normalized distribution name from one requirements line."""

    cleaned = line.split("#", 1)[0].strip()
    if not cleaned or cleaned.startswith(("-", "git+", "http://", "https://")):
        return ""
    match = re.match(r"([A-Za-z0-9_.-]+)", cleaned)
    return match.group(1).lower() if match else ""


def main(argv: list[str] | None = None) -> int:
    """Return zero only when deterministic and native-Codex prerequisites exist."""

    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument(
        "--requirements",
        action="append",
        type=Path,
        default=[],
        help="Explicit requirements file to validate; repeat for multiple files.",
    )
    args = parser.parse_args(argv)
    logging.basicConfig(level=logging.INFO, format="%(message)s")
    requirement_files = args.requirements or [PLUGIN_ROOT / "requirements.txt"]
    packages: set[str] = set()
    for requirements_path in requirement_files:
        if not requirements_path.is_file():
            LOGGER.error("Requirements file is unavailable: %s", requirements_path)
            return 1
        packages.update(
            name
            for line in requirements_path.read_text(encoding="utf-8").splitlines()
            if (name := _requirement_name(line))
        )
    imports = {
        PACKAGE_IMPORTS.get(package, package.replace("-", "_")) for package in packages
    }
    imports.update(REQUIRED_IMPORTS)
    missing = [
        name for name in sorted(imports) if importlib.util.find_spec(name) is None
    ]
    if missing:
        LOGGER.error("Missing Python dependencies: %s", ", ".join(missing))
        return 1
    from cowork_worker import configured_runtime

    if configured_runtime() == "cowork-haiku":
        LOGGER.info(
            "OK: Python dependencies available. Semantic review requires the packaged Cowork Haiku subagent; this check does not verify model availability."
        )
        return 0
    codex = shutil.which("codex")
    if not codex:
        LOGGER.error("Codex CLI is unavailable; native GPT-5.6 Luna cannot run")
        return 1
    codex_path = Path(codex).resolve()
    from luna_worker import inspect_execution_host

    try:
        evidence = inspect_execution_host(codex_path)
    except (OSError, ValueError) as exc:
        LOGGER.error("Native worker prerequisite inspection failed: %s", exc)
        return 1
    if evidence["status"] != "prerequisites_match":
        LOGGER.error(
            "Native worker prerequisites do not match: %s", json.dumps(evidence)
        )
        return 1
    LOGGER.info(
        "OK: prerequisites match; each launch still checks its boundary: %s",
        json.dumps(evidence),
    )
    return 0


if __name__ == "__main__":
    raise SystemExit(main())

SHA-256: aadbca9ce1aa5c7234a0d0d7c147ff3aa0f06d683aade38469abac9dca3d3922