Surface both canceljob failure shapes as verbatim rejections 🟩

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Daniel Roth 2026-07-07 15:34:35 +00:00
parent a384b87d8f
commit 157f9fc4b4

View file

@ -110,7 +110,7 @@ class AbriClient:
if isinstance(outcome, AbriRequestRejected):
return outcome
return self._parse_job_abandoned(outcome)
return self._parse_abandon_result(outcome)
def get_tenant_data(self, place_ref: PlaceRef) -> GetTenantDataResult:
outcome = self._exchange(
@ -210,20 +210,39 @@ class AbriClient:
)
@staticmethod
def _parse_job_abandoned(root: ET.Element) -> JobAbandoned:
def _parse_abandon_result(root: ET.Element) -> AbandonJobResult:
# canceljob diverges from the other flows: failure can arrive as a
# result document whose cancellation collection is empty and which
# carries an ErrorDetails element, rather than the shared failure
# envelope. A present JobCancelled is success; a present ErrorDetails
# is a business rejection. Neither present is genuinely ambiguous and
# must stay retriable — never silently a success or a rejection.
job_cancelled = root.find(".//JobCancelled")
if job_cancelled is not None:
job_no = job_cancelled.get("job_no")
if job_no is None:
raise AbriResponseParseError("JobCancelled element missing job_no")
return JobAbandoned(job_no=job_no)
if job_cancelled is None:
error_details = root.find(".//ErrorDetails")
if error_details is not None:
return AbriClient._rejection_from_error_details(error_details)
raise AbriResponseParseError(
"canceljob response has neither JobCancelled nor ErrorDetails"
)
@staticmethod
def _rejection_from_error_details(error_details: ET.Element) -> AbriRequestRejected:
code = error_details.get("code")
message = error_details.get("message")
if code is None or message is None:
raise AbriResponseParseError(
"JobCancelled element missing from relay response"
"canceljob ErrorDetails missing code or message"
)
job_no = job_cancelled.get("job_no")
if job_no is None:
raise AbriResponseParseError("JobCancelled element missing job_no")
return JobAbandoned(job_no=job_no)
return AbriRequestRejected(code=code, message=message)
@staticmethod
def _parse_tenancy_data(root: ET.Element) -> TenancyData: