158 lines
5.5 KiB
Python
158 lines
5.5 KiB
Python
"""Production composition for authenticated Agent result callbacks."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import importlib.util
|
|
import os
|
|
import sys
|
|
from dataclasses import dataclass
|
|
from pathlib import Path
|
|
from typing import Any, Callable, Optional
|
|
|
|
from arr_ingestion.postgres import DatabaseConfig, PostgresIngestionRepository
|
|
from arr_ingestion.service import IngestionService
|
|
from arr_ingestion.validation import DeliveryValidator, ProcessorPolicy
|
|
from arr_processing.callbacks import AgentResultWriteback
|
|
from arr_processing.config import ResultVerificationConfig
|
|
from arr_processing.errors import ProcessingTransportError
|
|
from arr_processing.postgres import PostgresProcessingState, ProcessingDatabaseConfig
|
|
from arr_processing.registration import ProcessingOutputRegistrar, RemoteFilePort
|
|
from arr_processing.remote_files import PrefixRemoteFilePort
|
|
from arr_processing.runner import ProcessingRunner, RemoteRunSnapshot
|
|
from arr_storage.aliyun_oss_v2 import AliyunOssConfig, AliyunOssV2Client
|
|
from arr_storage.contracts import ObjectKeyPolicy
|
|
from arr_storage.exchange import OutputExchangeConfig
|
|
from arr_storage.remote import CloudObjectBackend
|
|
from arr_storage.store import ManagedObjectStore
|
|
from arr_web.services import ProcessingAgentResultCoordinator
|
|
|
|
|
|
class CallbackOnlyTransport:
|
|
"""Callbacks do not require remote Agent network authority in this process."""
|
|
|
|
@staticmethod
|
|
def _disabled() -> None:
|
|
raise ProcessingTransportError(
|
|
"PROCESSING_REMOTE_DISABLED",
|
|
retryable=False,
|
|
)
|
|
|
|
def submit(self, request: Any, idempotency_key: str) -> RemoteRunSnapshot:
|
|
del request, idempotency_key
|
|
self._disabled()
|
|
|
|
def get(self, request: Any, remote_run_id: str) -> RemoteRunSnapshot:
|
|
del request, remote_run_id
|
|
self._disabled()
|
|
|
|
def cancel(self, request: Any, remote_run_id: str) -> RemoteRunSnapshot:
|
|
del request, remote_run_id
|
|
self._disabled()
|
|
|
|
|
|
@dataclass
|
|
class AgentWritebackRuntime:
|
|
coordinator: ProcessingAgentResultCoordinator
|
|
close_callback: Optional[Callable[[], None]] = None
|
|
|
|
def close(self) -> None:
|
|
if self.close_callback is not None:
|
|
self.close_callback()
|
|
|
|
|
|
def load_processor_policy(project_root: Path) -> ProcessorPolicy:
|
|
skill_root = (project_root / "arr-opera-daily-ingest").resolve()
|
|
script = skill_root / "scripts" / "process_daily.py"
|
|
module_name = "_arr_runtime_process_daily"
|
|
specification = importlib.util.spec_from_file_location(module_name, script)
|
|
if specification is None or specification.loader is None:
|
|
raise ValueError("ARR daily processor identity is unavailable")
|
|
module = importlib.util.module_from_spec(specification)
|
|
prior = sys.modules.get(module_name)
|
|
sys.modules[module_name] = module
|
|
try:
|
|
specification.loader.exec_module(module)
|
|
processor_version = str(module.PROCESSOR_VERSION)
|
|
rule_set_sha256 = str(module.rule_set_sha256())
|
|
finally:
|
|
if prior is None:
|
|
sys.modules.pop(module_name, None)
|
|
else:
|
|
sys.modules[module_name] = prior
|
|
return ProcessorPolicy(
|
|
processor_version=processor_version,
|
|
rule_set_sha256=rule_set_sha256,
|
|
skill_root=skill_root,
|
|
)
|
|
|
|
|
|
def compose_agent_writeback(
|
|
*,
|
|
object_store: ManagedObjectStore,
|
|
remote_files: RemoteFilePort,
|
|
processor_policy: ProcessorPolicy,
|
|
verification: ResultVerificationConfig,
|
|
connect: Optional[Callable[[str], Any]] = None,
|
|
) -> AgentWritebackRuntime:
|
|
processing_config = (
|
|
ProcessingDatabaseConfig("controlled")
|
|
if connect is not None
|
|
else ProcessingDatabaseConfig.from_environment()
|
|
)
|
|
ingestion_config = (
|
|
DatabaseConfig("controlled")
|
|
if connect is not None
|
|
else DatabaseConfig.from_environment()
|
|
)
|
|
state = PostgresProcessingState(processing_config, connect=connect)
|
|
runner = ProcessingRunner(
|
|
CallbackOnlyTransport(),
|
|
state,
|
|
verification.verifier(),
|
|
)
|
|
repository = PostgresIngestionRepository(ingestion_config, connect=connect)
|
|
writeback = AgentResultWriteback(
|
|
runner,
|
|
state,
|
|
ProcessingOutputRegistrar(remote_files, object_store),
|
|
IngestionService(
|
|
DeliveryValidator(object_store, processor_policy),
|
|
repository,
|
|
),
|
|
outcome_resolver=state,
|
|
)
|
|
return AgentWritebackRuntime(ProcessingAgentResultCoordinator(writeback))
|
|
|
|
|
|
def compose_oss_agent_writeback(
|
|
*,
|
|
project_root: Path,
|
|
connect: Optional[Callable[[str], Any]] = None,
|
|
) -> AgentWritebackRuntime:
|
|
oss_client = AliyunOssV2Client(AliyunOssConfig.from_environment())
|
|
try:
|
|
oss_client.assert_immutable_writes_supported()
|
|
key_policy = ObjectKeyPolicy(os.environ.get("ARR_OBJECT_PREFIX", "arr"))
|
|
object_store = ManagedObjectStore(
|
|
CloudObjectBackend(oss_client),
|
|
key_policy,
|
|
)
|
|
runtime = compose_agent_writeback(
|
|
object_store=object_store,
|
|
remote_files=PrefixRemoteFilePort(
|
|
oss_client,
|
|
OutputExchangeConfig.from_environment(),
|
|
),
|
|
processor_policy=load_processor_policy(project_root),
|
|
verification=ResultVerificationConfig.from_environment(),
|
|
connect=connect,
|
|
)
|
|
runtime.close_callback = oss_client.close
|
|
return runtime
|
|
except Exception:
|
|
try:
|
|
oss_client.close()
|
|
except Exception:
|
|
pass
|
|
raise
|