from __future__ import annotations
|
|
from typing import Any, Mapping
|
|
from .models import ContractError, ErrorCode, HASH_RE, ItemStatus, PackageState, QuotaState
|
|
|
ITEM_KEYS = (
|
"item_id", "slot_id", "candidate_id", "report_identity", "state", "status",
|
"stop_code", "trigger_attempted", "triggered", "quota_state",
|
"quota_reservation_id", "quota_terminal_event_id", "quota_artifact_event_id",
|
"title", "institution", "report_date", "analysts", "page_count", "bytes",
|
"sha256", "source_cache_path", "source_file_name", "final_path", "manifest_row_id",
|
"reused_without_new_trigger", "error_or_note",
|
)
|
|
|
def terminal_item(values: Mapping[str, Any]) -> dict[str, Any]:
|
"""Build and validate the fixed public 26-key item wire."""
|
row = {key: values.get(key) for key in ITEM_KEYS}
|
if tuple(row) != ITEM_KEYS:
|
raise ContractError(ErrorCode.TASK_SPEC_INVALID, "item.keys", "26-key order mismatch")
|
if row["status"] not in {item.value for item in ItemStatus}:
|
raise ContractError(ErrorCode.TASK_SPEC_INVALID, "item.status", "enum")
|
if row["state"] not in {item.value for item in PackageState}:
|
raise ContractError(ErrorCode.TASK_SPEC_INVALID, "item.state", "enum")
|
if row["quota_state"] not in {item.value for item in QuotaState}:
|
raise ContractError(ErrorCode.TASK_SPEC_INVALID, "item.quota_state", "enum")
|
for key in ("trigger_attempted", "triggered", "reused_without_new_trigger"):
|
if type(row[key]) is not bool:
|
raise ContractError(ErrorCode.TASK_SPEC_INVALID, f"item.{key}", "BOOL required")
|
if row["stop_code"] is not None and row["stop_code"] not in {item.value for item in ErrorCode}:
|
raise ContractError(ErrorCode.TASK_SPEC_INVALID, "item.stop_code", "enum")
|
if row["sha256"] is not None and not HASH_RE.fullmatch(row["sha256"]):
|
raise ContractError(ErrorCode.TASK_SPEC_INVALID, "item.sha256", "hash")
|
if row["analysts"] is not None and not (
|
isinstance(row["analysts"], (list, tuple)) and all(isinstance(x, str) for x in row["analysts"])
|
):
|
raise ContractError(ErrorCode.TASK_SPEC_INVALID, "item.analysts", "string array or null")
|
if row["status"] == ItemStatus.SUCCESS.value:
|
required = (
|
"candidate_id", "report_identity", "title", "institution", "report_date",
|
"analysts", "page_count", "bytes", "sha256", "source_cache_path",
|
"source_file_name", "final_path", "manifest_row_id",
|
)
|
if any(row[key] is None for key in required) or row["stop_code"] is not None:
|
raise ContractError(ErrorCode.TASK_SPEC_INVALID, "item.success", "evidence incomplete")
|
if row["status"] == ItemStatus.DUPLICATE.value:
|
if row["stop_code"] != ErrorCode.DUPLICATE_EXISTING_ARTIFACT.value:
|
raise ContractError(ErrorCode.TASK_SPEC_INVALID, "item.duplicate", "stop code")
|
if row["status"] in {ItemStatus.FAILED.value, ItemStatus.STOPPED.value}:
|
if row["stop_code"] in {None, ErrorCode.NONE.value} or not row["error_or_note"]:
|
raise ContractError(ErrorCode.TASK_SPEC_INVALID, "item.failure", "code/note required")
|
return row
|