feat: add atomic state store and legacy migration
This commit is contained in:
@@ -0,0 +1,113 @@
|
||||
"""Controller domain records shared by persistence and orchestration."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
|
||||
|
||||
MEDIA_STATES = frozenset(
|
||||
{"empty", "queued", "downloading", "ready", "error", "deleting"}
|
||||
)
|
||||
LIVE_JOB_STATES = frozenset({"reserved", "running"})
|
||||
LIVE_GUARD_STATES = frozenset({"active", "review_required"})
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class SlotRecord:
|
||||
slot: int
|
||||
generation: int
|
||||
media_state: str
|
||||
url: str | None
|
||||
yt_id: str | None
|
||||
title: str
|
||||
item_count: int
|
||||
published_item_count: int
|
||||
created_at: float | None
|
||||
ready_at: float | None
|
||||
expires_at: float | None
|
||||
last_error: str | None
|
||||
updated_at: float
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class DownloadReservation:
|
||||
status: str
|
||||
slot: int | None = None
|
||||
generation: int | None = None
|
||||
job_id: str | None = None
|
||||
reused_slot: bool = False
|
||||
oldest_expires_at: float | None = None
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class DownloadJob:
|
||||
job_id: str
|
||||
slot: int
|
||||
generation: int
|
||||
url: str
|
||||
status: str
|
||||
submitter: str | None
|
||||
note: str | None
|
||||
requested_at: float
|
||||
started_at: float | None
|
||||
finished_at: float | None
|
||||
error: str | None
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class DeleteToken:
|
||||
slot: int
|
||||
generation: int
|
||||
reason: str
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class SlotGuard:
|
||||
guard_id: str
|
||||
slot: int
|
||||
kind: str
|
||||
state: str
|
||||
created_at: float
|
||||
review_after: float | None
|
||||
cleared_at: float | None
|
||||
cleared_by: str | None
|
||||
clear_reason: str | None
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class PlayerObservation:
|
||||
availability: str
|
||||
phase: str
|
||||
active_slot: int | None
|
||||
active_path: str | None
|
||||
paused: bool | None
|
||||
generation: int
|
||||
reconciliation_state: str
|
||||
reconciliation_due_at: float | None
|
||||
last_status_at: float | None
|
||||
last_telemetry_at: float | None
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class MigrationReport:
|
||||
migrated: bool
|
||||
source_path: str | None = None
|
||||
source_sha256: str | None = None
|
||||
backup_path: str | None = None
|
||||
warnings: tuple[str, ...] = ()
|
||||
|
||||
|
||||
class StoreError(RuntimeError):
|
||||
"""Base persistence error."""
|
||||
|
||||
|
||||
class MigrationError(StoreError):
|
||||
"""Legacy migration was rejected or could not complete safely."""
|
||||
|
||||
|
||||
class SlotProtectedError(StoreError):
|
||||
"""A slot cannot be modified because live activity protects it."""
|
||||
|
||||
|
||||
class StaleOperationError(StoreError):
|
||||
"""An operation token no longer identifies the slot occupant."""
|
||||
Reference in New Issue
Block a user