"""worker_lane — how many slots the operator wants each worker lane to have. Milestone 422 step 1. One row per lane in `services/worker_lanes.LANES`. ## What is NOT in here The lane's queues and its display name. Those are decided by `celery_app.py`'s `task_routes` — an operator cannot move a backup off `maintenance_long` — so storing them would be a row that can contradict the routing table, with nothing to notice until a queue had no consumer. They live in `services/worker_lanes.py`; this table holds only what an operator may change. The DERIVED CEILING is also absent, and that is deliberate rather than an omission. It is computed from the container's cgroup limits on every read, so a row written on a 32GB host and later run in a 4GB container is bounded by the 4GB — a stored ceiling would quietly authorise what the box can no longer hold. ## The three numbers slots <= slots_cap <= derived_ceiling (live) (this row) (computed) Operator's distinction, 2026-09-22: the derived value is *a cap on the cap*. `slots_cap` is theirs and is always lowerable; it simply may not exceed what the container can hold. The CHECK constraint below enforces the left half, which is a fact about the row; the right half is enforced at write, because it depends on a value no database column holds. ## enabled Whether the lane consumes its queues at all. This is how ML ships off (milestone 422 step 6): `enabled=false` with `slots=0`, so a fresh install never loads a model or reaches HuggingFace, and turning tagging on in Settings is what triggers the fetch. Not a substitute for `slots=0`. A lane can be enabled with zero slots while it is being resized, and the two answer different questions: `enabled` is intent, `slots` is capacity. """ from datetime import datetime from sqlalchemy import ( Boolean, CheckConstraint, DateTime, Integer, String, func, ) from sqlalchemy.orm import Mapped, mapped_column from .base import Base class WorkerLane(Base): __tablename__ = "worker_lane" __table_args__ = ( # Bare names — Base.metadata's naming convention prepends # ck_worker_lane_. Pre-prefixing here doubles it, which is what # alembic 0088 had to rename four constraints for (#3275). CheckConstraint("slots >= 0", name="slots_non_negative"), CheckConstraint("slots_cap >= 0", name="cap_non_negative"), # The invariant that makes the cap mean anything. Enforced in the # database rather than only in the service, because a row that # violates it is not a rejected request — it is a lane that will be # reconciled UP to a value the operator capped. CheckConstraint("slots <= slots_cap", name="slots_within_cap"), ) # The lane name from services/worker_lanes.LANES — never a container # hostname. See models/service_seen.py for why: celery's worker names here # are `celery@`, minted fresh on every deploy. name: Mapped[str] = mapped_column(String(32), primary_key=True) slots: Mapped[int] = mapped_column(Integer, nullable=False) slots_cap: Mapped[int] = mapped_column(Integer, nullable=False) enabled: Mapped[bool] = mapped_column(Boolean, nullable=False) updated_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), nullable=False, server_default=func.now(), onupdate=func.now(), )