2026-08-11 00:54:05 +08:00
|
|
|
"""Isolated stdin/stdout worker for the CP-SAT assignment protocol."""
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
import contextlib
|
|
|
|
|
import hashlib
|
|
|
|
|
import importlib.metadata
|
|
|
|
|
import importlib.util
|
|
|
|
|
import io
|
|
|
|
|
import json
|
|
|
|
|
import os
|
|
|
|
|
import platform
|
|
|
|
|
import site
|
|
|
|
|
import sys
|
|
|
|
|
import traceback
|
|
|
|
|
from collections.abc import Mapping
|
2026-08-26 00:25:46 +08:00
|
|
|
from datetime import date
|
2026-08-11 00:54:05 +08:00
|
|
|
from pathlib import Path
|
|
|
|
|
from typing import Any
|
|
|
|
|
|
|
|
|
|
# ``python -I /abs/path/solver_worker.py`` does not trust cwd/PYTHONPATH.
|
|
|
|
|
_REPO_ROOT = Path(__file__).resolve().parents[2]
|
|
|
|
|
if str(_REPO_ROOT) not in sys.path:
|
|
|
|
|
sys.path.insert(0, str(_REPO_ROOT))
|
|
|
|
|
|
|
|
|
|
from server.engines.solver_process import (
|
|
|
|
|
ERROR_MARKER,
|
|
|
|
|
PROTOCOL_VERSION,
|
|
|
|
|
SUCCESS_MARKER,
|
|
|
|
|
_canonical_json,
|
|
|
|
|
_json_value,
|
|
|
|
|
_request_digest,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
_MAX_REQUEST_BYTES = 64 * 1024 * 1024
|
2026-08-26 00:25:46 +08:00
|
|
|
_DIAGNOSTIC_RELAXABLE_CONSTRAINTS = frozenset({
|
|
|
|
|
"C1_precedence", "C2_no_overlap", "C3_calendar", "C7_capacity", "C10_changeover",
|
|
|
|
|
"C11_freeze", "C12_team", "C12_tooling",
|
|
|
|
|
})
|
|
|
|
|
_DIAGNOSTIC_RHS_PARAMETERS = frozenset({
|
|
|
|
|
"C7_line_day_capacity_minutes", "C8_due_date_allowance",
|
|
|
|
|
"C12_team_capacity", "C12_tooling_capacity",
|
|
|
|
|
})
|
2026-08-11 00:54:05 +08:00
|
|
|
_DIAGNOSTIC_LIMIT = 8_000
|
|
|
|
|
_REQUIRED_NATIVE_PACKAGES = ("ortools", "numpy", "pandas")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _resolved(path: str | os.PathLike[str]) -> Path:
|
|
|
|
|
return Path(path).expanduser().resolve()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _under(path: Path, root: Path) -> bool:
|
|
|
|
|
try:
|
|
|
|
|
path.relative_to(root)
|
|
|
|
|
return True
|
|
|
|
|
except ValueError:
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _package_identity(name: str, allowed_roots: list[Path]) -> tuple[dict[str, Any], list[str]]:
|
|
|
|
|
reasons: list[str] = []
|
|
|
|
|
spec = importlib.util.find_spec(name)
|
|
|
|
|
origin = None if spec is None else spec.origin
|
|
|
|
|
try:
|
|
|
|
|
version = importlib.metadata.version(name)
|
|
|
|
|
except importlib.metadata.PackageNotFoundError:
|
|
|
|
|
version = None
|
|
|
|
|
identity: dict[str, Any] = {"version": version, "origin": origin}
|
|
|
|
|
if spec is None or not origin:
|
|
|
|
|
reasons.append(f"{name}:missing")
|
|
|
|
|
return identity, reasons
|
|
|
|
|
origin_path = _resolved(origin)
|
|
|
|
|
if not any(_under(origin_path, root) for root in allowed_roots):
|
|
|
|
|
reasons.append(f"{name}:outside-runtime-prefix")
|
|
|
|
|
user_site = site.getusersitepackages()
|
|
|
|
|
if user_site and _under(origin_path, _resolved(user_site)):
|
|
|
|
|
reasons.append(f"{name}:user-site")
|
|
|
|
|
return identity, reasons
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def runtime_identity() -> dict[str, Any]:
|
|
|
|
|
frozen = bool(getattr(sys, "frozen", False))
|
|
|
|
|
roots = [_resolved(sys.prefix), _resolved(sys.exec_prefix)]
|
|
|
|
|
meipass = getattr(sys, "_MEIPASS", None)
|
|
|
|
|
if meipass:
|
|
|
|
|
roots.append(_resolved(meipass))
|
|
|
|
|
roots = list(dict.fromkeys(roots))
|
|
|
|
|
|
|
|
|
|
reasons: list[str] = []
|
|
|
|
|
if platform.python_implementation() != "CPython":
|
|
|
|
|
reasons.append("implementation:not-cpython")
|
|
|
|
|
if not frozen and not bool(sys.flags.isolated):
|
|
|
|
|
reasons.append("python:not-isolated")
|
|
|
|
|
if bool(site.ENABLE_USER_SITE):
|
|
|
|
|
reasons.append("python:user-site-enabled")
|
|
|
|
|
|
|
|
|
|
packages: dict[str, Any] = {}
|
|
|
|
|
for package in _REQUIRED_NATIVE_PACKAGES:
|
|
|
|
|
packages[package], package_reasons = _package_identity(package, roots)
|
|
|
|
|
reasons.extend(package_reasons)
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
"implementation": platform.python_implementation(),
|
|
|
|
|
"pythonVersion": platform.python_version(),
|
|
|
|
|
"executable": str(_resolved(sys.executable)),
|
|
|
|
|
"baseExecutable": str(_resolved(getattr(sys, "_base_executable", sys.executable))),
|
|
|
|
|
"prefix": str(_resolved(sys.prefix)),
|
|
|
|
|
"basePrefix": str(_resolved(sys.base_prefix)),
|
|
|
|
|
"isolated": bool(sys.flags.isolated),
|
|
|
|
|
"noUserSite": bool(sys.flags.no_user_site),
|
|
|
|
|
"enableUserSite": bool(site.ENABLE_USER_SITE),
|
|
|
|
|
"frozen": frozen,
|
|
|
|
|
"allowedRoots": [str(root) for root in roots],
|
|
|
|
|
"packages": packages,
|
|
|
|
|
"safe": not reasons,
|
|
|
|
|
"reasons": sorted(set(reasons)),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _trim(value: str) -> str:
|
|
|
|
|
return value if len(value) <= _DIAGNOSTIC_LIMIT else value[:_DIAGNOSTIC_LIMIT] + "\n...[truncated]"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _emit(payload: Mapping[str, Any]) -> None:
|
2026-08-26 00:25:46 +08:00
|
|
|
encoded = _canonical_json(_json_value(payload)).encode("utf-8")
|
|
|
|
|
binary = getattr(sys.stdout, "buffer", None)
|
|
|
|
|
if binary is not None:
|
|
|
|
|
binary.write(encoded)
|
|
|
|
|
binary.flush()
|
|
|
|
|
else: # pragma: no cover - embedded hosts may expose only a text stream
|
|
|
|
|
sys.stdout.write(encoded.decode("utf-8"))
|
|
|
|
|
sys.stdout.flush()
|
2026-08-11 00:54:05 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def _emit_error(
|
|
|
|
|
code: str,
|
|
|
|
|
message: str,
|
|
|
|
|
*,
|
|
|
|
|
request_id: str = "",
|
|
|
|
|
details: Mapping[str, Any] | None = None,
|
|
|
|
|
) -> None:
|
|
|
|
|
_emit(
|
|
|
|
|
{
|
|
|
|
|
"protocolVersion": PROTOCOL_VERSION,
|
|
|
|
|
"marker": ERROR_MARKER,
|
|
|
|
|
"ok": False,
|
|
|
|
|
"requestId": request_id,
|
|
|
|
|
"error": {"code": code, "message": message, "details": dict(details or {})},
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _read_request() -> dict[str, Any]:
|
|
|
|
|
raw = sys.stdin.buffer.read(_MAX_REQUEST_BYTES + 1)
|
|
|
|
|
if len(raw) > _MAX_REQUEST_BYTES:
|
|
|
|
|
raise ValueError("request exceeds size limit")
|
|
|
|
|
try:
|
|
|
|
|
payload = json.loads(raw.decode("utf-8"))
|
|
|
|
|
except (UnicodeDecodeError, json.JSONDecodeError) as exc:
|
|
|
|
|
raise ValueError(f"invalid request JSON: {exc}") from exc
|
|
|
|
|
if not isinstance(payload, dict):
|
|
|
|
|
raise TypeError("request must be an object")
|
|
|
|
|
return payload
|
|
|
|
|
|
|
|
|
|
|
2026-08-26 00:25:46 +08:00
|
|
|
def _validate_request(
|
|
|
|
|
request: Mapping[str, Any],
|
|
|
|
|
) -> tuple[
|
|
|
|
|
str, dict[str, Any], list[dict], dict, list[dict] | None, str,
|
|
|
|
|
str, list[str], dict[str, Any] | None, str | None, bool, bool,
|
|
|
|
|
]:
|
2026-08-11 00:54:05 +08:00
|
|
|
request_id = request.get("requestId")
|
|
|
|
|
if not isinstance(request_id, str) or not request_id:
|
|
|
|
|
raise ValueError("requestId is required")
|
|
|
|
|
if request.get("protocolVersion") != PROTOCOL_VERSION:
|
|
|
|
|
raise RuntimeError("protocol mismatch")
|
2026-08-26 00:25:46 +08:00
|
|
|
operation = request.get("operation")
|
|
|
|
|
common_keys = {
|
|
|
|
|
"requestId", "protocolVersion", "operation", "world", "entries",
|
|
|
|
|
"params", "warmStart", "pipelineLabel",
|
|
|
|
|
}
|
|
|
|
|
constraint_diagnostic = operation in {
|
|
|
|
|
"diagnose_constraint_baseline", "diagnose_constraint_removal",
|
|
|
|
|
}
|
|
|
|
|
rhs_diagnostic = operation in {"diagnose_rhs_baseline", "diagnose_rhs_perturbation"}
|
|
|
|
|
diagnostic = constraint_diagnostic or rhs_diagnostic
|
|
|
|
|
diagnostic_keys = (
|
|
|
|
|
{"relaxedConstraintIds", "invocationId"}
|
|
|
|
|
if constraint_diagnostic
|
|
|
|
|
else ({"rhsPerturbation", "invocationId"} if rhs_diagnostic else set())
|
|
|
|
|
)
|
|
|
|
|
expected_keys = common_keys | diagnostic_keys
|
|
|
|
|
if set(request) != expected_keys:
|
|
|
|
|
raise ValueError(
|
|
|
|
|
f"request keys mismatch: missing={sorted(expected_keys - set(request))} "
|
|
|
|
|
f"unknown={sorted(set(request) - expected_keys)}"
|
|
|
|
|
)
|
2026-08-11 00:54:05 +08:00
|
|
|
body = {key: value for key, value in request.items() if key != "requestId"}
|
|
|
|
|
if _request_digest(body) != request_id:
|
|
|
|
|
raise ValueError("requestId digest mismatch")
|
2026-08-26 00:25:46 +08:00
|
|
|
if operation not in {
|
|
|
|
|
"optimize_line_assignment", "diagnose_constraint_baseline",
|
|
|
|
|
"diagnose_constraint_removal", "diagnose_rhs_baseline",
|
|
|
|
|
"diagnose_rhs_perturbation",
|
|
|
|
|
}:
|
2026-08-11 00:54:05 +08:00
|
|
|
raise ValueError("unsupported operation")
|
|
|
|
|
world = request.get("world")
|
|
|
|
|
entries = request.get("entries")
|
|
|
|
|
params = request.get("params")
|
|
|
|
|
warm_start = request.get("warmStart")
|
|
|
|
|
pipeline_label = request.get("pipelineLabel")
|
2026-08-26 00:25:46 +08:00
|
|
|
relaxed_constraint_ids = request.get("relaxedConstraintIds", [])
|
|
|
|
|
rhs_perturbation = request.get("rhsPerturbation")
|
|
|
|
|
invocation_id = request.get("invocationId")
|
2026-08-11 00:54:05 +08:00
|
|
|
if not isinstance(world, dict):
|
|
|
|
|
raise TypeError("world must be an object")
|
|
|
|
|
if not isinstance(entries, list) or not all(isinstance(item, dict) for item in entries):
|
|
|
|
|
raise TypeError("entries must be an object array")
|
|
|
|
|
if not isinstance(params, dict):
|
|
|
|
|
raise TypeError("params must be an object")
|
|
|
|
|
if warm_start is not None and (
|
|
|
|
|
not isinstance(warm_start, list) or not all(isinstance(item, dict) for item in warm_start)
|
|
|
|
|
):
|
|
|
|
|
raise TypeError("warmStart must be an object array")
|
2026-08-26 00:25:46 +08:00
|
|
|
if diagnostic and warm_start is not None:
|
|
|
|
|
raise ValueError("diagnostic operations do not accept warmStart")
|
2026-08-11 00:54:05 +08:00
|
|
|
if not isinstance(pipeline_label, str) or not pipeline_label.strip():
|
|
|
|
|
raise ValueError("pipelineLabel is required")
|
2026-08-26 00:25:46 +08:00
|
|
|
if not isinstance(relaxed_constraint_ids, list) or not all(
|
|
|
|
|
isinstance(value, str) and value for value in relaxed_constraint_ids
|
|
|
|
|
):
|
|
|
|
|
raise TypeError("relaxedConstraintIds must be a string array")
|
|
|
|
|
if len(relaxed_constraint_ids) != len(set(relaxed_constraint_ids)):
|
|
|
|
|
raise ValueError("relaxedConstraintIds must not contain duplicates")
|
|
|
|
|
unknown_relaxed = set(relaxed_constraint_ids) - _DIAGNOSTIC_RELAXABLE_CONSTRAINTS
|
|
|
|
|
if unknown_relaxed:
|
|
|
|
|
raise ValueError(f"unsupported relaxedConstraintIds: {sorted(unknown_relaxed)}")
|
|
|
|
|
if operation == "optimize_line_assignment" and relaxed_constraint_ids:
|
|
|
|
|
raise ValueError("production operation cannot relax constraints")
|
|
|
|
|
if operation == "diagnose_constraint_baseline" and relaxed_constraint_ids:
|
|
|
|
|
raise ValueError("diagnostic baseline cannot relax constraints")
|
|
|
|
|
if operation == "diagnose_constraint_removal" and len(relaxed_constraint_ids) != 1:
|
|
|
|
|
raise ValueError("diagnostic removal requires exactly one constraint")
|
|
|
|
|
normalized_rhs: dict[str, Any] | None = None
|
|
|
|
|
if rhs_perturbation is not None:
|
|
|
|
|
if not isinstance(rhs_perturbation, dict):
|
|
|
|
|
raise TypeError("rhsPerturbation must be an object or null")
|
|
|
|
|
parameter_id = rhs_perturbation.get("parameterId")
|
|
|
|
|
increment = rhs_perturbation.get("increment")
|
|
|
|
|
if parameter_id not in _DIAGNOSTIC_RHS_PARAMETERS:
|
|
|
|
|
raise ValueError("rhsPerturbation parameterId is unsupported")
|
|
|
|
|
if isinstance(increment, bool) or not isinstance(increment, int) or increment <= 0:
|
|
|
|
|
raise ValueError("rhsPerturbation increment must be a positive integer")
|
|
|
|
|
if parameter_id == "C7_line_day_capacity_minutes":
|
|
|
|
|
line_id = rhs_perturbation.get("lineId")
|
|
|
|
|
bucket_date = rhs_perturbation.get("bucketDate")
|
|
|
|
|
if (
|
|
|
|
|
set(rhs_perturbation) != {"parameterId", "lineId", "bucketDate", "increment"}
|
|
|
|
|
or isinstance(line_id, bool)
|
|
|
|
|
or not isinstance(line_id, int)
|
|
|
|
|
or line_id <= 0
|
|
|
|
|
or not isinstance(bucket_date, str)
|
|
|
|
|
or increment > 1_440
|
|
|
|
|
):
|
|
|
|
|
raise ValueError("C7 rhsPerturbation is invalid")
|
|
|
|
|
try:
|
|
|
|
|
if date.fromisoformat(bucket_date).isoformat() != bucket_date:
|
|
|
|
|
raise ValueError
|
|
|
|
|
except ValueError as exc:
|
|
|
|
|
raise ValueError("C7 rhsPerturbation bucketDate is invalid") from exc
|
|
|
|
|
normalized_rhs = {
|
|
|
|
|
"parameterId": parameter_id,
|
|
|
|
|
"lineId": line_id,
|
|
|
|
|
"bucketDate": bucket_date,
|
|
|
|
|
"increment": increment,
|
|
|
|
|
}
|
|
|
|
|
elif parameter_id == "C8_due_date_allowance":
|
|
|
|
|
if set(rhs_perturbation) != {"parameterId", "increment"} or increment > 10_080:
|
|
|
|
|
raise ValueError("C8 rhsPerturbation is invalid")
|
|
|
|
|
normalized_rhs = {"parameterId": parameter_id, "increment": increment}
|
|
|
|
|
else:
|
|
|
|
|
resource_id = rhs_perturbation.get("resourceId")
|
|
|
|
|
if (
|
|
|
|
|
set(rhs_perturbation) != {"parameterId", "resourceId", "increment"}
|
|
|
|
|
or isinstance(resource_id, bool)
|
|
|
|
|
or not isinstance(resource_id, int)
|
|
|
|
|
or resource_id <= 0
|
|
|
|
|
or increment > 100
|
|
|
|
|
):
|
|
|
|
|
raise ValueError("C12 rhsPerturbation is invalid")
|
|
|
|
|
normalized_rhs = {
|
|
|
|
|
"parameterId": parameter_id,
|
|
|
|
|
"resourceId": resource_id,
|
|
|
|
|
"increment": increment,
|
|
|
|
|
}
|
|
|
|
|
if operation == "diagnose_rhs_baseline" and normalized_rhs is not None:
|
|
|
|
|
raise ValueError("RHS diagnostic baseline cannot perturb a parameter")
|
|
|
|
|
if operation == "diagnose_rhs_perturbation" and normalized_rhs is None:
|
|
|
|
|
raise ValueError("RHS diagnostic perturbation requires one parameter")
|
|
|
|
|
if not rhs_diagnostic and normalized_rhs is not None:
|
|
|
|
|
raise ValueError("non-RHS operation cannot perturb RHS parameters")
|
|
|
|
|
if diagnostic and (
|
|
|
|
|
not isinstance(invocation_id, str)
|
|
|
|
|
or len(invocation_id) != 32
|
|
|
|
|
or any(char not in "0123456789abcdef" for char in invocation_id.lower())
|
|
|
|
|
):
|
|
|
|
|
raise ValueError("diagnostic invocationId must be 32 hex characters")
|
|
|
|
|
return (
|
|
|
|
|
request_id, world, entries, params, warm_start, pipeline_label,
|
|
|
|
|
str(operation), sorted(relaxed_constraint_ids), normalized_rhs,
|
|
|
|
|
str(invocation_id) if diagnostic else None, diagnostic, rhs_diagnostic,
|
|
|
|
|
)
|
2026-08-11 00:54:05 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def main() -> int:
|
|
|
|
|
request_id = ""
|
|
|
|
|
try:
|
|
|
|
|
request = _read_request()
|
|
|
|
|
request_id = str(request.get("requestId") or "")
|
|
|
|
|
except ValueError as exc:
|
|
|
|
|
_emit_error("SOLVER_REQUEST_INVALID", "求解请求非法", details={"error": str(exc)})
|
|
|
|
|
return 0
|
|
|
|
|
|
|
|
|
|
if request.get("protocolVersion") != PROTOCOL_VERSION:
|
|
|
|
|
_emit_error(
|
|
|
|
|
"SOLVER_PROTOCOL_MISMATCH",
|
|
|
|
|
"求解请求协议版本不一致",
|
|
|
|
|
request_id=request_id,
|
|
|
|
|
details={"expected": PROTOCOL_VERSION, "actual": request.get("protocolVersion")},
|
|
|
|
|
)
|
|
|
|
|
return 0
|
|
|
|
|
|
|
|
|
|
try:
|
2026-08-26 00:25:46 +08:00
|
|
|
(
|
|
|
|
|
request_id, world, entries, params_payload, warm_start,
|
|
|
|
|
pipeline_label, operation, relaxed_constraint_ids,
|
|
|
|
|
rhs_perturbation, invocation_id, diagnostic_mode, rhs_diagnostic_mode,
|
|
|
|
|
) = _validate_request(request)
|
2026-08-11 00:54:05 +08:00
|
|
|
except RuntimeError:
|
|
|
|
|
_emit_error(
|
|
|
|
|
"SOLVER_PROTOCOL_MISMATCH",
|
|
|
|
|
"求解请求协议版本不一致",
|
|
|
|
|
request_id=request_id,
|
|
|
|
|
)
|
|
|
|
|
return 0
|
|
|
|
|
except (TypeError, ValueError) as exc:
|
|
|
|
|
_emit_error(
|
|
|
|
|
"SOLVER_REQUEST_INVALID",
|
|
|
|
|
"求解请求非法",
|
|
|
|
|
request_id=request_id,
|
|
|
|
|
details={"error": str(exc)},
|
|
|
|
|
)
|
|
|
|
|
return 0
|
|
|
|
|
|
|
|
|
|
identity = runtime_identity()
|
|
|
|
|
if identity.get("safe") is not True:
|
|
|
|
|
_emit_error(
|
|
|
|
|
"SOLVER_RUNTIME_UNSAFE",
|
|
|
|
|
"求解子进程运行时身份不安全",
|
|
|
|
|
request_id=request_id,
|
|
|
|
|
details={"runtimeIdentity": identity},
|
|
|
|
|
)
|
|
|
|
|
return 0
|
|
|
|
|
|
|
|
|
|
captured_stdout = io.StringIO()
|
|
|
|
|
captured_stderr = io.StringIO()
|
|
|
|
|
try:
|
|
|
|
|
with contextlib.redirect_stdout(captured_stdout), contextlib.redirect_stderr(captured_stderr):
|
|
|
|
|
from server.engines.base import EngineParams
|
|
|
|
|
from server.engines.cp_engine import optimize_line_assignment
|
|
|
|
|
|
|
|
|
|
params = EngineParams.model_validate(params_payload)
|
|
|
|
|
ordered, solver_meta = optimize_line_assignment(
|
|
|
|
|
world,
|
|
|
|
|
entries,
|
|
|
|
|
params,
|
|
|
|
|
warm_start=warm_start,
|
|
|
|
|
pipeline_label=pipeline_label,
|
2026-08-26 00:25:46 +08:00
|
|
|
relaxed_constraint_ids=relaxed_constraint_ids,
|
|
|
|
|
diagnostic_mode=diagnostic_mode,
|
|
|
|
|
rhs_diagnostic_mode=rhs_diagnostic_mode,
|
|
|
|
|
rhs_perturbation=rhs_perturbation,
|
2026-08-11 00:54:05 +08:00
|
|
|
)
|
|
|
|
|
if not isinstance(ordered, list) or not all(isinstance(item, dict) for item in ordered):
|
|
|
|
|
raise TypeError("optimizer entries result is not an object array")
|
|
|
|
|
if not isinstance(solver_meta, dict):
|
|
|
|
|
raise TypeError("optimizer solverMeta result is not an object")
|
|
|
|
|
except Exception as exc: # noqa: BLE001 - child must translate every Python-level failure
|
|
|
|
|
_emit_error(
|
|
|
|
|
"SOLVER_EXECUTION_FAILED",
|
|
|
|
|
"CP-SAT 子进程执行失败",
|
|
|
|
|
request_id=request_id,
|
|
|
|
|
details={
|
|
|
|
|
"exceptionType": type(exc).__name__,
|
|
|
|
|
"error": str(exc),
|
|
|
|
|
"stdout": _trim(captured_stdout.getvalue()),
|
|
|
|
|
"stderr": _trim(captured_stderr.getvalue()),
|
|
|
|
|
"traceback": _trim(traceback.format_exc()),
|
|
|
|
|
"runtimeIdentity": identity,
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
return 0
|
|
|
|
|
|
|
|
|
|
response_body = {
|
|
|
|
|
"protocolVersion": PROTOCOL_VERSION,
|
|
|
|
|
"marker": SUCCESS_MARKER,
|
|
|
|
|
"ok": True,
|
|
|
|
|
"requestId": request_id,
|
2026-08-26 00:25:46 +08:00
|
|
|
"operation": operation,
|
|
|
|
|
"invocationId": invocation_id,
|
2026-08-11 00:54:05 +08:00
|
|
|
"runtimeIdentity": identity,
|
|
|
|
|
"result": {"entries": ordered, "solverMeta": solver_meta},
|
|
|
|
|
}
|
|
|
|
|
response_body["responseDigest"] = hashlib.sha256(
|
|
|
|
|
_canonical_json(_json_value(response_body)).encode("utf-8")
|
|
|
|
|
).hexdigest()
|
|
|
|
|
_emit(response_body)
|
|
|
|
|
return 0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
raise SystemExit(main())
|