feat: sync latest ARR implementation

This commit is contained in:
Wyndham ARR
2026-07-31 15:11:42 +08:00
parent d6f8a747fa
commit bf7939dd1a
185 changed files with 17527 additions and 2260 deletions

View File

@@ -1,29 +1,13 @@
"""Mutation seams for uploads and deterministic monthly report generation."""
"""Small active Web mutation ports for ARR2.0."""
from __future__ import annotations
import os
import tempfile
import threading
import uuid
from dataclasses import dataclass
from datetime import date
from pathlib import Path
from typing import Any, Dict, Protocol
from arr_ingestion.contracts import IngestionError
from arr_ingestion.direct_contracts import SubmissionGrant
from arr_ingestion.repository import JobRegistration
from arr_processing.callbacks import AgentResultWriteback
from arr_processing.contracts import ProcessingRequest
from arr_processing.errors import ProcessingError
from arr_processing.runner import processing_dispatch_idempotency_key
from arr_storage.store import ManagedObjectStore
from arr_web.contracts import (
PortalError,
validate_upload_filename,
validate_xml_payload,
)
from arr_web.contracts import PortalError
from monthly_reports.service import MonthlyReportService, RunRequest
@@ -37,33 +21,7 @@ class MonthlyCoordinator(Protocol):
...
class AgentResultCoordinator(Protocol):
def accept(self, raw_signed_result: bytes) -> Dict[str, Any]:
...
class ProcessingStarter(Protocol):
def start(self, request: ProcessingRequest) -> Any:
...
class DirectJobRepository(Protocol):
def register_job(self, registration: JobRegistration) -> None:
...
def issue_grant(
self,
job_id: str,
attempt_no: int,
*,
ttl_seconds: int,
) -> SubmissionGrant:
...
class UnavailableUploadCoordinator:
"""Production-safe default until private OSS and the remote file API are wired."""
def submit(self, original_filename: str, payload: bytes) -> Dict[str, Any]:
raise PortalError(
"PROCESSING_GATEWAY_UNAVAILABLE",
@@ -81,171 +39,9 @@ class UnavailableMonthlyCoordinator:
)
class UnavailableAgentResultCoordinator:
def accept(self, raw_signed_result: bytes) -> Dict[str, Any]:
raise PortalError(
"AGENT_WRITEBACK_UNAVAILABLE",
"Agent 结果回写服务尚未启用",
503,
)
@dataclass
class ProcessingAgentResultCoordinator:
"""Web-safe adapter around the authenticated ARR result pipeline."""
writeback: AgentResultWriteback
def accept(self, raw_signed_result: bytes) -> Dict[str, Any]:
try:
return self.writeback.accept(raw_signed_result).to_dict()
except (IngestionError, ProcessingError) as error:
code = error.code
if code in {
"PROCESSING_SIGNATURE_INVALID",
"PROCESSING_SIGNATURE_EXPIRED",
}:
status = 401
message = "Agent 结果签名无效或已过期"
elif code.endswith("_NOT_FOUND") or code in {
"JOB_NOT_FOUND",
"PROCESSING_JOB_NOT_FOUND",
}:
status = 404
message = "Agent 结果对应的业务任务不存在"
elif "CONFLICT" in code or code in {
"JOB_TERMINAL",
"PROCESSING_RESULT_MISMATCH",
}:
status = 409
message = "Agent 结果与现有业务任务冲突"
elif getattr(error, "retryable", False) or code.startswith("DATABASE_"):
status = 503
message = "Agent 结果暂时无法写入,请使用相同回调重试"
else:
status = 422
message = "Agent 结果未通过业务验收"
raise PortalError(code, message, status) from None
@dataclass
class ObjectStoreUploadCoordinator:
"""ARR upload boundary: private object -> DB registration -> remote dispatch."""
object_store: ManagedObjectStore
ingestion_repository: DirectJobRepository
processing_starter: ProcessingStarter
processor_version: str
rule_set_sha256: str
submission_grant_ttl_seconds: int = 900
def __post_init__(self) -> None:
if (
not isinstance(self.submission_grant_ttl_seconds, int)
or isinstance(self.submission_grant_ttl_seconds, bool)
or not 30 <= self.submission_grant_ttl_seconds <= 1800
):
raise ValueError("submission grant lifetime is invalid")
def submit(self, original_filename: str, payload: bytes) -> Dict[str, Any]:
validate_upload_filename(original_filename)
validate_xml_payload(payload)
job_id = "arrjob-" + uuid.uuid4().hex
attempt_no = 1
try:
with tempfile.TemporaryDirectory(prefix="arr-web-upload-") as temporary:
source_path = Path(temporary) / "source.xml"
descriptor = os.open(
source_path,
os.O_WRONLY | os.O_CREAT | os.O_EXCL,
0o600,
)
try:
with os.fdopen(descriptor, "wb") as target:
descriptor = -1
target.write(payload)
target.flush()
os.fsync(target.fileno())
finally:
if descriptor >= 0:
os.close(descriptor)
stored = self.object_store.upload_committed(
job_id=job_id,
attempt_no=attempt_no,
role="source_xml",
source=source_path,
original_filename="source.xml",
)
source_file_id = "arr_file_" + uuid.uuid4().hex
request_without_grant = ProcessingRequest(
job_id=job_id,
attempt_no=attempt_no,
source_file_id=source_file_id,
processor_version=self.processor_version,
rule_set_sha256=self.rule_set_sha256,
)
self.ingestion_repository.register_job(
JobRegistration(
job_id=job_id,
source=stored.to_artifact_ref(),
processor_version=self.processor_version,
rule_set_sha256=self.rule_set_sha256,
attempt_no=attempt_no,
idempotency_key=processing_dispatch_idempotency_key(
request_without_grant
),
)
)
grant = self.ingestion_repository.issue_grant(
job_id,
attempt_no,
ttl_seconds=self.submission_grant_ttl_seconds,
)
if grant.job_id != job_id or grant.attempt_no != attempt_no:
raise IngestionError(
"SUBMISSION_GRANT_UNAVAILABLE",
"direct result submission grant identity is invalid",
retryable=True,
)
request = ProcessingRequest(
job_id=job_id,
attempt_no=attempt_no,
source_file_id=source_file_id,
processor_version=self.processor_version,
rule_set_sha256=self.rule_set_sha256,
submission_grant=grant.submission_grant,
)
record = self.processing_starter.start(request)
except (IngestionError, ProcessingError) as error:
status = (
503
if getattr(error, "retryable", False)
or error.code.startswith("DATABASE_")
or error.code in {
"OBJECT_STORE_UNAVAILABLE",
}
else 422
)
raise PortalError(error.code, error.safe_message, status) from None
except Exception:
raise PortalError(
"PROCESSING_SUBMISSION_FAILED",
"文件已安全接收,但处理任务未能启动",
503,
) from None
remote_status = str(getattr(record, "remote_status", "queued"))
return {
"job_id": job_id,
"status": "queued" if remote_status in {"reserved", "pending"} else remote_status,
"attempt_no": attempt_no,
"source_sha256": stored.sha256,
"source_byte_size": stored.byte_size,
}
@dataclass
class ProgramMonthlyCoordinator:
"""Runs the ordinary database program; it never invokes an Agent."""
"""Run the deterministic monthly database program."""
service: MonthlyReportService