feat: prepare ARR for controlled public deployment

This commit is contained in:
Wyndham ARR
2026-07-29 16:38:05 +08:00
commit a701de9f0e
271 changed files with 48472 additions and 0 deletions

View File

@@ -0,0 +1,488 @@
"""Trusted runtime adapter: publish Agent files, sign the result, call ARR."""
from __future__ import annotations
import argparse
import hashlib
import json
import os
import re
import stat
import time
from dataclasses import dataclass
from datetime import date
from pathlib import Path
from typing import Any, Dict, Mapping, Optional, Protocol, Sequence, Tuple
from urllib.parse import urlparse
from arr_ingestion.contracts import OPAQUE_ID_RE, PROCESSOR_VERSION_RE, SHA256_RE, XLSX_MIME
from arr_processing.config import ResultVerificationConfig
from arr_processing.contracts import ProcessingResult
from arr_processing.errors import ProcessingError
from arr_storage.aliyun_oss_v2 import AliyunOssConfig, AliyunOssV2Client
from arr_storage.exchange import OutputExchangeConfig, OutputExchangePublisher
FROZEN_FIELDS = {
"contract_version",
"job_id",
"source_file_id",
"status",
"business_date",
"processor_result",
"files",
}
PROCESSOR_RESULT_FIELDS = {
"version",
"status",
"business_date",
"message",
"metrics",
"outputs",
"errors",
}
FILE_FIELDS = {"filename", "local_path"}
FILE_ROLES = {"daily_report", "result_json", "structured_result", "exception_report"}
ROLE_MIME = {
"daily_report": XLSX_MIME,
"result_json": "application/json",
"structured_result_json": "application/json",
"exception_report": XLSX_MIME,
}
RESULT_ROLE = {
"daily_report": "daily_report",
"result_json": "result_json",
"structured_result": "structured_result_json",
"exception_report": "exception_report",
}
SAFE_FILENAME_RE = re.compile(r"^[^/\\\x00-\x1f\x7f]{1,255}$")
class RuntimeWritebackError(RuntimeError):
def __init__(self, code: str, safe_message: str, *, retryable: bool = False) -> None:
super().__init__(safe_message)
self.code = code
self.safe_message = safe_message
self.retryable = retryable
class OutputPublisher(Protocol):
def publish(
self,
*,
file_handle: str,
source: Path,
mime_type: str,
metadata: Mapping[str, str],
) -> Any:
...
class HttpClient(Protocol):
def post(self, url: str, **kwargs: Any) -> Any:
...
def _strict_json(raw: bytes) -> Mapping[str, Any]:
def pairs(values: Sequence[Tuple[str, Any]]) -> Dict[str, Any]:
output: Dict[str, Any] = {}
for key, value in values:
if key in output:
raise ValueError("duplicate JSON key")
output[key] = value
return output
try:
value = json.loads(raw.decode("utf-8"), object_pairs_hook=pairs)
except (UnicodeError, ValueError, json.JSONDecodeError):
raise RuntimeWritebackError(
"RUNTIME_RESULT_INVALID", "Agent result is not strict JSON"
) from None
if not isinstance(value, Mapping):
raise RuntimeWritebackError(
"RUNTIME_RESULT_INVALID", "Agent result must be a JSON object"
)
return value
def _opaque(value: Any, label: str) -> str:
if not isinstance(value, str) or not OPAQUE_ID_RE.fullmatch(value):
raise RuntimeWritebackError("RUNTIME_RESULT_INVALID", f"{label} is invalid")
return value
@dataclass(frozen=True)
class FrozenFile:
filename: str
path: Path
@dataclass(frozen=True)
class FrozenAgentResult:
job_id: str
source_file_id: str
status: str
business_date: Optional[date]
files: Mapping[str, Optional[FrozenFile]]
@classmethod
def parse(cls, raw: bytes, *, output_root: Path) -> "FrozenAgentResult":
values = _strict_json(raw)
if set(values) != FROZEN_FIELDS or values.get("contract_version") != "arr-opera-daily-agent-1":
raise RuntimeWritebackError(
"RUNTIME_RESULT_INVALID", "Agent result contract is invalid"
)
job_id = _opaque(values.get("job_id"), "job identifier")
source_file_id = _opaque(values.get("source_file_id"), "source file identifier")
status = values.get("status")
if status not in {"success", "failed"}:
raise RuntimeWritebackError("RUNTIME_RESULT_INVALID", "Agent status is invalid")
raw_date = values.get("business_date")
try:
if raw_date == "":
business_date = None
elif isinstance(raw_date, str):
business_date = date.fromisoformat(raw_date)
else:
raise ValueError
except ValueError:
raise RuntimeWritebackError(
"RUNTIME_RESULT_INVALID", "Agent business date is invalid"
) from None
processor_business_date = business_date.isoformat() if business_date else None
processor = values.get("processor_result")
if not isinstance(processor, Mapping) or set(processor) != PROCESSOR_RESULT_FIELDS:
raise RuntimeWritebackError(
"RUNTIME_RESULT_INVALID", "processor result contract is invalid"
)
if (
processor.get("version") != "3.0"
or processor.get("status") != status
or processor.get("business_date") != processor_business_date
or not isinstance(processor.get("message"), str)
or not isinstance(processor.get("metrics"), Mapping)
or not isinstance(processor.get("outputs"), Mapping)
or not isinstance(processor.get("errors"), list)
):
raise RuntimeWritebackError(
"RUNTIME_RESULT_INVALID", "processor result identity is invalid"
)
raw_files = values.get("files")
if not isinstance(raw_files, Mapping) or set(raw_files) != FILE_ROLES:
raise RuntimeWritebackError(
"RUNTIME_RESULT_INVALID", "Agent output file contract is invalid"
)
root = output_root.resolve(strict=True)
if not root.is_dir():
raise RuntimeWritebackError(
"RUNTIME_OUTPUT_ROOT_INVALID", "Agent output root is unavailable"
)
files: Dict[str, Optional[FrozenFile]] = {}
for role in sorted(FILE_ROLES):
item = raw_files.get(role)
if item is None:
files[role] = None
continue
if not isinstance(item, Mapping) or set(item) != FILE_FIELDS:
raise RuntimeWritebackError(
"RUNTIME_RESULT_INVALID", f"{role} file contract is invalid"
)
filename = item.get("filename")
local_path = item.get("local_path")
if (
not isinstance(filename, str)
or SAFE_FILENAME_RE.fullmatch(filename) is None
or not isinstance(local_path, str)
or not Path(local_path).is_absolute()
):
raise RuntimeWritebackError(
"RUNTIME_RESULT_INVALID", f"{role} file identity is invalid"
)
path = Path(local_path)
try:
details = path.lstat()
resolved = path.resolve(strict=True)
resolved.relative_to(root)
except (OSError, ValueError):
raise RuntimeWritebackError(
"RUNTIME_OUTPUT_INVALID", f"{role} output is outside the approved root"
) from None
if path.is_symlink() or not stat.S_ISREG(details.st_mode) or resolved.name != filename:
raise RuntimeWritebackError(
"RUNTIME_OUTPUT_INVALID", f"{role} output is not an approved file"
)
expected_extension = ".json" if role in {"result_json", "structured_result"} else ".xlsx"
if not filename.lower().endswith(expected_extension):
raise RuntimeWritebackError(
"RUNTIME_OUTPUT_INVALID", f"{role} output type is invalid"
)
files[role] = FrozenFile(filename, resolved)
if files["result_json"] is None or files["structured_result"] is None:
raise RuntimeWritebackError(
"RUNTIME_RESULT_INVALID", "required Agent result files are missing"
)
if status == "success":
valid_shape = (
business_date is not None
and files["daily_report"] is not None
and files["exception_report"] is None
)
else:
valid_shape = files["daily_report"] is None and files["exception_report"] is not None
if not valid_shape:
raise RuntimeWritebackError(
"RUNTIME_RESULT_INVALID", "Agent result file shape is invalid"
)
return cls(job_id, source_file_id, str(status), business_date, files)
@dataclass
class AgentRuntimeWritebackAdapter:
publisher: OutputPublisher
signer: Any
http_client: HttpClient
callback_url: str
processor_version: str
rule_set_sha256: str
max_http_attempts: int = 3
retry_delay_seconds: float = 0.5
def __post_init__(self) -> None:
parsed = urlparse(self.callback_url)
localhost = parsed.hostname in {"127.0.0.1", "localhost", "::1"}
if (
parsed.scheme not in ({"http", "https"} if localhost else {"https"})
or not parsed.netloc
or parsed.query
or parsed.fragment
):
raise ValueError("ARR Agent callback URL must be an HTTPS URL")
if not PROCESSOR_VERSION_RE.fullmatch(self.processor_version):
raise ValueError("processor version is invalid")
if not SHA256_RE.fullmatch(self.rule_set_sha256):
raise ValueError("rule set identity is invalid")
if self.max_http_attempts < 1 or self.retry_delay_seconds < 0:
raise ValueError("runtime writeback retry policy is invalid")
def deliver(
self,
raw_frozen_result: bytes,
*,
output_root: Path,
attempt_no: int,
remote_run_id: str,
) -> Mapping[str, Any]:
frozen = FrozenAgentResult.parse(raw_frozen_result, output_root=output_root)
remote_run_id = _opaque(remote_run_id, "remote run identifier")
if not isinstance(attempt_no, int) or isinstance(attempt_no, bool) or not 1 <= attempt_no <= 9999:
raise RuntimeWritebackError(
"RUNTIME_RESULT_INVALID", "attempt number is invalid"
)
artifacts: Dict[str, Any] = {
"daily_report": None,
"result_json": None,
"structured_result_json": None,
"exception_report": None,
}
for frozen_role in sorted(FILE_ROLES):
item = frozen.files[frozen_role]
if item is None:
continue
role = RESULT_ROLE[frozen_role]
digest, byte_size = self._identity(item.path)
file_handle = self._file_handle(
frozen.job_id,
attempt_no,
remote_run_id,
role,
digest,
)
mime_type = ROLE_MIME[role]
metadata = {
"arr-exchange-schema": "1.0",
"arr-file-handle": file_handle,
"arr-job-id": frozen.job_id,
"arr-attempt-no": str(attempt_no),
"arr-role": role,
"arr-sha256": digest,
"arr-byte-size": str(byte_size),
"arr-mime-type": mime_type,
}
try:
self.publisher.publish(
file_handle=file_handle,
source=item.path,
mime_type=mime_type,
metadata=metadata,
)
except Exception as error:
if isinstance(error, RuntimeWritebackError):
raise
raise RuntimeWritebackError(
"RUNTIME_OUTPUT_PUBLISH_FAILED",
"Agent output could not be published",
retryable=True,
) from None
artifacts[role] = {
"file_handle": file_handle,
"original_filename": item.filename,
"sha256": digest,
"byte_size": byte_size,
"mime_type": mime_type,
}
result = ProcessingResult.from_dict(
{
"contract_version": "arr-opera-daily-result-1",
"delivery_id": self._delivery_id(frozen.job_id, attempt_no, remote_run_id),
"job_id": frozen.job_id,
"attempt_no": attempt_no,
"remote_run_id": remote_run_id,
"status": frozen.status,
"business_date": frozen.business_date.isoformat() if frozen.business_date else None,
"processor_version": self.processor_version,
"rule_set_sha256": self.rule_set_sha256,
"result_schema_version": "3.0",
"artifacts": artifacts,
}
)
signed = self.signer.sign(result)
return self._post_same_bytes(signed)
def _post_same_bytes(self, signed: bytes) -> Mapping[str, Any]:
for attempt in range(1, self.max_http_attempts + 1):
try:
response = self.http_client.post(
self.callback_url,
content=signed,
headers={
"Content-Type": "application/json",
"Accept": "application/json",
"User-Agent": "arr-agent-runtime-writeback/1.0",
},
)
except Exception:
if attempt >= self.max_http_attempts:
raise RuntimeWritebackError(
"RUNTIME_CALLBACK_UNAVAILABLE",
"ARR callback did not respond",
retryable=True,
) from None
time.sleep(self.retry_delay_seconds * attempt)
continue
status = int(getattr(response, "status_code", 0) or 0)
if status == 200:
try:
payload = response.json()
except Exception:
raise RuntimeWritebackError(
"RUNTIME_CALLBACK_PROTOCOL_INVALID",
"ARR callback returned an invalid response",
) from None
if (
not isinstance(payload, Mapping)
or payload.get("ok") is not True
or not isinstance(payload.get("data"), Mapping)
):
raise RuntimeWritebackError(
"RUNTIME_CALLBACK_PROTOCOL_INVALID",
"ARR callback returned an invalid response",
)
return dict(payload["data"])
if status in {408, 429} or status >= 500:
if attempt < self.max_http_attempts:
time.sleep(self.retry_delay_seconds * attempt)
continue
raise RuntimeWritebackError(
"RUNTIME_CALLBACK_UNAVAILABLE",
"ARR callback is temporarily unavailable",
retryable=True,
)
raise RuntimeWritebackError(
"RUNTIME_CALLBACK_REJECTED",
"ARR rejected the Agent result",
)
raise AssertionError("unreachable retry loop")
@staticmethod
def _identity(path: Path) -> Tuple[str, int]:
digest = hashlib.sha256()
size = 0
with path.open("rb") as source:
for chunk in iter(lambda: source.read(1024 * 1024), b""):
size += len(chunk)
digest.update(chunk)
return digest.hexdigest(), size
@staticmethod
def _file_handle(
job_id: str,
attempt_no: int,
remote_run_id: str,
role: str,
sha256: str,
) -> str:
value = "\x1f".join((job_id, str(attempt_no), remote_run_id, role, sha256))
return "arrout_" + hashlib.sha256(value.encode("utf-8")).hexdigest()[:48]
@staticmethod
def _delivery_id(job_id: str, attempt_no: int, remote_run_id: str) -> str:
value = "\x1f".join((job_id, str(attempt_no), remote_run_id, "delivery"))
return "arrdel_" + hashlib.sha256(value.encode("utf-8")).hexdigest()[:48]
def _parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(prog="arr-agent-writeback")
parser.add_argument("--frozen-result", type=Path, required=True)
parser.add_argument("--output-root", type=Path, required=True)
parser.add_argument("--attempt-no", type=int, required=True)
parser.add_argument("--remote-run-id", required=True)
parser.add_argument("--processor-version", required=True)
parser.add_argument("--rule-set-sha256", required=True)
parser.add_argument(
"--callback-url",
default=os.environ.get("ARR_AGENT_CALLBACK_URL", ""),
)
return parser
def main(argv: Optional[Sequence[str]] = None) -> int:
args = _parser().parse_args(argv)
if not args.callback_url:
print(json.dumps({"ok": False, "error": {"code": "CALLBACK_CONFIG_MISSING"}}))
return 2
try:
import httpx
oss_client = AliyunOssV2Client(AliyunOssConfig.from_environment())
oss_client.assert_immutable_writes_supported()
publisher = OutputExchangePublisher(
oss_client,
OutputExchangeConfig.from_environment(),
)
signing = ResultVerificationConfig.from_environment().signer()
with httpx.Client(timeout=httpx.Timeout(30.0, connect=10.0)) as http_client:
receipt = AgentRuntimeWritebackAdapter(
publisher=publisher,
signer=signing,
http_client=http_client,
callback_url=args.callback_url,
processor_version=args.processor_version,
rule_set_sha256=args.rule_set_sha256,
).deliver(
args.frozen_result.read_bytes(),
output_root=args.output_root,
attempt_no=args.attempt_no,
remote_run_id=args.remote_run_id,
)
oss_client.close()
except (OSError, ValueError, ProcessingError, RuntimeWritebackError) as error:
code = getattr(error, "code", "RUNTIME_WRITEBACK_FAILED")
print(json.dumps({"ok": False, "error": {"code": code}}))
return 3
print(json.dumps({"ok": True, "data": receipt}, ensure_ascii=False))
return 0
if __name__ == "__main__":
raise SystemExit(main())