Files
wyndham-ARR/arr_processing/callbacks.py
2026-07-29 16:38:05 +08:00

112 lines
3.6 KiB
Python

"""ARR-owned writeback pipeline for authenticated Super Agent results."""
from __future__ import annotations
from dataclasses import dataclass
from typing import Any, Dict, Optional, Protocol
from arr_ingestion.contracts import ArtifactRef
from arr_ingestion.repository import IngestionOutcome
from arr_ingestion.service import IngestionService
from arr_processing.registration import ProcessingOutputRegistrar
from arr_processing.runner import ProcessingRunner
class SourceArtifactResolver(Protocol):
"""Resolve the immutable source registered for one ARR processing attempt."""
def source_for_attempt(self, job_id: str, attempt_no: int) -> ArtifactRef:
...
class DeliveryOutcomeResolver(Protocol):
"""Read a committed delivery without fetching its output files again."""
def outcome_for_delivery(self, delivery_id: str) -> Optional[IngestionOutcome]:
...
@dataclass(frozen=True)
class AgentWritebackReceipt:
status: str
job_id: str
business_date: Optional[str]
daily_version_id: Optional[int]
version_no: Optional[int]
callback_replayed: bool
@classmethod
def from_outcome(
cls,
outcome: IngestionOutcome,
*,
callback_replayed: bool,
) -> "AgentWritebackReceipt":
return cls(
status=outcome.status,
job_id=outcome.job_id,
business_date=(
outcome.business_date.isoformat()
if outcome.business_date is not None
else None
),
daily_version_id=outcome.daily_version_id,
version_no=outcome.version_no,
callback_replayed=callback_replayed,
)
def to_dict(self) -> Dict[str, Any]:
return {
"status": self.status,
"job_id": self.job_id,
"business_date": self.business_date,
"daily_version_id": self.daily_version_id,
"version_no": self.version_no,
"callback_replayed": self.callback_replayed,
}
class AgentResultWriteback:
"""Verify, correlate, fetch, independently validate, and atomically persist."""
def __init__(
self,
runner: ProcessingRunner,
source_resolver: SourceArtifactResolver,
output_registrar: ProcessingOutputRegistrar,
ingestion_service: IngestionService,
*,
outcome_resolver: Optional[DeliveryOutcomeResolver] = None,
) -> None:
self._runner = runner
self._source_resolver = source_resolver
self._output_registrar = output_registrar
self._ingestion_service = ingestion_service
self._outcome_resolver = outcome_resolver
def accept(self, raw_signed_result: bytes) -> AgentWritebackReceipt:
completion = self._runner.accept_callback(raw_signed_result)
result = completion.result
if completion.callback_replayed and self._outcome_resolver is not None:
existing = self._outcome_resolver.outcome_for_delivery(result.delivery_id)
if existing is not None:
return AgentWritebackReceipt.from_outcome(
existing,
callback_replayed=True,
)
source = self._source_resolver.source_for_attempt(
result.job_id,
result.attempt_no,
)
registered = self._output_registrar.register(result, source)
outcome = self._ingestion_service.ingest(registered.raw_envelope)
return AgentWritebackReceipt.from_outcome(
outcome,
callback_replayed=(
completion.callback_replayed
or outcome.status == "already_committed"
),
)