"""Canonical download-job status values — the ONE source of truth for the lifecycle literals. A job's status is written from several places: the worker (claim → running, terminal done/error, requeue), the routes (pause/cancel/redownload/delete), the service (enqueue), and the GC. Keeping the literals here is a data-safety measure, not tidiness. A British/American drift once silently broke cancellation: the supervisor compared the live status against ``"cancelled"`` while the cancel route wrote ``"canceled"`` (models.py), so the supervisor's "a route stopped it → kill the child, free the asset" branch was DEAD. A canceled job's child then ran to completion and wrote ``"done"`` unconditionally over the route's ``"canceled"``; because ``done`` counts as a holding state but ``canceled`` does not (see ``service.release_asset``), a later delete released the asset a SECOND time, dropping ``ref_count`` below the live holder count and deleting a file still in use. Import these names instead of typing the strings. """ from enum import StrEnum class JobStatus(StrEnum): QUEUED = "queued" RUNNING = "running" PAUSED = "paused" DONE = "done" ERROR = "error" CANCELED = "canceled" # A job "holds" its media asset (the +1 on ``ref_count`` from enqueue) while in any of these states; # ``release_asset`` on a status OUTSIDE this set is a no-op. ``CANCELED`` is deliberately excluded: the # cancel route releases once while the job is still holding, then sets ``CANCELED``, so a later delete # does not double-release. This invariant only holds while a finishing child cannot flip ``CANCELED`` # back into the set — hence the worker's terminal writes are conditional on the row still being RUNNING. HOLDING = frozenset( {JobStatus.QUEUED, JobStatus.RUNNING, JobStatus.PAUSED, JobStatus.DONE, JobStatus.ERROR} ) # States a route sets to STOP a running job. The worker's supervisor must step in (kill the child, free # the asset, leave the route's status intact) when it observes one of these on a job it is running. STOPPED_BY_ROUTE = frozenset({JobStatus.PAUSED, JobStatus.CANCELED})