"""Job envelope stored in ``ix_jobs`` and returned by REST. Mirrors spec §3 ("Job envelope") and §4 ("Job store"). The lifecycle enum is a ``Literal`` so Pydantic rejects unknown values at parse time. ``callback_status`` is nullable until the worker attempts delivery (or skips delivery when there's no ``callback_url``). """ from __future__ import annotations from datetime import datetime from typing import Literal from uuid import UUID from pydantic import BaseModel, ConfigDict from ix.contracts.request import RequestIX from ix.contracts.response import ResponseIX JobStatus = Literal["pending", "running", "done", "error"] CallbackStatus = Literal["pending", "delivered", "failed"] class Job(BaseModel): """Row of ``ix_jobs`` + its request/response bodies. The invariant ``status='done' iff response.error is None`` is enforced by the worker, not here — callers occasionally hydrate a stale or in-flight row and we don't want the Pydantic validator to reject it. """ model_config = ConfigDict(extra="forbid") job_id: UUID ix_id: str client_id: str request_id: str status: JobStatus request: RequestIX response: ResponseIX | None = None callback_url: str | None = None callback_status: CallbackStatus | None = None attempts: int = 0 created_at: datetime started_at: datetime | None = None finished_at: datetime | None = None