Prove adapter launcher modules are runnable
This commit is contained in:
parent
3bf3836ac3
commit
ecbf94d14a
3 changed files with 293 additions and 10 deletions
|
|
@ -2,11 +2,14 @@
|
|||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import re
|
||||
import stat
|
||||
import subprocess
|
||||
import sys
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import Literal
|
||||
from typing import Literal, cast
|
||||
|
||||
from .changeset_contract import document_hash
|
||||
from .config_validation import ID_PATTERN
|
||||
|
|
@ -15,8 +18,22 @@ from .models import IncrementalStateProject, ProjectService, RuntimeValidatedPro
|
|||
|
||||
ADAPTER_LAUNCHER_SCHEMA_VERSION = 1
|
||||
_PYTHON_MODULE = re.compile(r"[A-Za-z_][A-Za-z0-9_]*(?:\.[A-Za-z_][A-Za-z0-9_]*){0,31}")
|
||||
_TOP_LEVEL_PYTHON_MODULE = re.compile(r"[A-Za-z_][A-Za-z0-9_]*")
|
||||
_ADAPTER_IDENTITY = re.compile(r"[A-Za-z0-9][A-Za-z0-9_.-]{0,127}@[A-Za-z0-9][A-Za-z0-9_.+-]{0,63}")
|
||||
_SHA256 = re.compile(r"[0-9a-f]{64}")
|
||||
_TRUSTED_DOTTED_MODULE = "docforge.reference_mcp"
|
||||
_MODULE_PROBE_TIMEOUT_SECONDS = 5
|
||||
_MAX_MODULE_PROBE_BYTES = 8_192
|
||||
_MODULE_PROBE = (
|
||||
"import importlib.util,json,sys;"
|
||||
"spec=importlib.util.find_spec(sys.argv[1]);"
|
||||
"result=None if spec is None else {"
|
||||
"'has_loader':spec.loader is not None,"
|
||||
"'is_package':spec.submodule_search_locations is not None,"
|
||||
"'origin':spec.origin};"
|
||||
"print(json.dumps(result,sort_keys=True,separators=(',',':')))"
|
||||
)
|
||||
_run_module_probe = subprocess.run
|
||||
__all__ = [
|
||||
"ADAPTER_LAUNCHER_SCHEMA_VERSION",
|
||||
"AdapterLauncherV1",
|
||||
|
|
@ -226,10 +243,125 @@ def _validate_launcher_fields(launcher: AdapterLauncherV1) -> None:
|
|||
or len(module) > 255
|
||||
or _PYTHON_MODULE.fullmatch(module) is None
|
||||
or module == "docforge.mcp_server"
|
||||
or (module != _TRUSTED_DOTTED_MODULE and _TOP_LEVEL_PYTHON_MODULE.fullmatch(module) is None)
|
||||
):
|
||||
raise DocForgeError(
|
||||
"invalid_adapter_launcher",
|
||||
"Adapter launcher module must be one constrained project-owned Python module",
|
||||
(
|
||||
"Adapter launcher module must be one installed project-owned top-level "
|
||||
"Python module or the fixed DocForge reference binding"
|
||||
),
|
||||
)
|
||||
_validate_isolated_module(project_root, module)
|
||||
|
||||
|
||||
def _validate_isolated_module(project_root: Path, module: str) -> None:
|
||||
"""Prove ``python -I -m`` can resolve one module without importing project code."""
|
||||
|
||||
executable = Path(sys.executable)
|
||||
try:
|
||||
executable_status = executable.stat()
|
||||
except OSError as error:
|
||||
raise DocForgeError(
|
||||
"adapter_launcher_unavailable",
|
||||
"The isolated Python executable is unavailable",
|
||||
) from error
|
||||
if not stat.S_ISREG(executable_status.st_mode):
|
||||
raise DocForgeError(
|
||||
"adapter_launcher_unavailable",
|
||||
"The isolated Python executable is not a regular file",
|
||||
)
|
||||
try:
|
||||
completed = _run_module_probe(
|
||||
[str(executable), "-I", "-c", _MODULE_PROBE, module],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=_MODULE_PROBE_TIMEOUT_SECONDS,
|
||||
check=False,
|
||||
)
|
||||
except (OSError, subprocess.TimeoutExpired) as error:
|
||||
raise DocForgeError(
|
||||
"adapter_launcher_unavailable",
|
||||
"The isolated adapter launcher probe could not complete",
|
||||
module=module,
|
||||
) from error
|
||||
if (
|
||||
completed.returncode != 0
|
||||
or len(completed.stdout.encode("utf-8")) > _MAX_MODULE_PROBE_BYTES
|
||||
or len(completed.stderr.encode("utf-8")) > _MAX_MODULE_PROBE_BYTES
|
||||
):
|
||||
raise DocForgeError(
|
||||
"adapter_launcher_unavailable",
|
||||
"The isolated adapter launcher module could not be resolved",
|
||||
module=module,
|
||||
)
|
||||
try:
|
||||
result: object = json.loads(completed.stdout)
|
||||
except (UnicodeError, json.JSONDecodeError) as error:
|
||||
raise DocForgeError(
|
||||
"adapter_launcher_unavailable",
|
||||
"The isolated adapter launcher probe returned invalid evidence",
|
||||
module=module,
|
||||
) from error
|
||||
if result is None:
|
||||
raise DocForgeError(
|
||||
"adapter_launcher_unavailable",
|
||||
"The isolated adapter launcher module is not installed",
|
||||
module=module,
|
||||
)
|
||||
if not isinstance(result, dict):
|
||||
raise DocForgeError(
|
||||
"adapter_launcher_unavailable",
|
||||
"The isolated adapter launcher probe returned invalid evidence",
|
||||
module=module,
|
||||
)
|
||||
evidence = cast(dict[object, object], result)
|
||||
if (
|
||||
set(evidence) != {"has_loader", "is_package", "origin"}
|
||||
or evidence["has_loader"] is not True
|
||||
or evidence["is_package"] is not False
|
||||
or not isinstance(evidence["origin"], str)
|
||||
):
|
||||
raise DocForgeError(
|
||||
"invalid_adapter_launcher",
|
||||
"The isolated adapter launcher must resolve to one executable module file",
|
||||
module=module,
|
||||
)
|
||||
origin = Path(evidence["origin"])
|
||||
try:
|
||||
origin_status = origin.lstat()
|
||||
resolved_origin = origin.resolve(strict=True)
|
||||
except OSError as error:
|
||||
raise DocForgeError(
|
||||
"adapter_launcher_unavailable",
|
||||
"The isolated adapter launcher module origin is unavailable",
|
||||
module=module,
|
||||
) from error
|
||||
if (
|
||||
not origin.is_absolute()
|
||||
or stat.S_ISLNK(origin_status.st_mode)
|
||||
or not stat.S_ISREG(origin_status.st_mode)
|
||||
or resolved_origin != origin
|
||||
or origin.suffix != ".py"
|
||||
):
|
||||
raise DocForgeError(
|
||||
"invalid_adapter_launcher",
|
||||
"The isolated adapter launcher must be one canonical Python module file",
|
||||
module=module,
|
||||
)
|
||||
if module == _TRUSTED_DOTTED_MODULE:
|
||||
expected = Path(__file__).with_name("reference_mcp.py").resolve(strict=True)
|
||||
if origin != expected:
|
||||
raise DocForgeError(
|
||||
"adapter_launcher_mismatch",
|
||||
"The fixed reference launcher resolved outside this DocForge installation",
|
||||
module=module,
|
||||
)
|
||||
elif not origin.is_relative_to(project_root):
|
||||
raise DocForgeError(
|
||||
"invalid_adapter_launcher",
|
||||
"The installed adapter launcher module is not owned by the selected project",
|
||||
module=module,
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue