"""worker_lane_sample — the last thing the sizing sweep measured about a lane. Milestone 422, 2026-09-23. A MEASUREMENT table, deliberately separate from `worker_lane`, which holds the one number an operator sets. ## Why this exists Operator, 2026-09-23, looking at the System tab: *"there is a repull every time this page loads — is there a reason this info isn't being tracked in the background and stored in some way?"* There was not a good one. `/api/system/workers` ran a full celery inspect — four broadcast round trips on an eleven-second budget — on every call, and the page polls it every fifteen seconds. Meanwhile `size_worker_lanes` was already inspecting on a timer to decide pool sizes, computing exactly these numbers, using them, and throwing them away. The browser then asked the broker for them again. So the sweep writes what it saw here, and the endpoint reads this table. The request path makes no broker call at all any more. ## Why NOT columns on `worker_lane` Because that is the mistake this milestone already made once and undid. That table used to carry `slots` — how many workers were running — beside `slots_cap`, and a measurement sitting next to a preference reads as a second preference: the operator had to keep two numbers in agreement, and the autoscaler had to be granted permission to move one of them. The distinction is the whole design, so it is a table boundary. Nothing an operator sets lives here; nothing here is ever an input to a decision about what they wanted. ## Freshness is a value, not an assumption `measured_at` is returned to the UI, which says how old the reading is rather than implying it is live. A sample is a fact about a moment, and a page that presents a one-minute-old number as current is how an operator ends up mistrusting the whole surface. """ from datetime import datetime from sqlalchemy import Boolean, DateTime, Integer, String, func from sqlalchemy.orm import Mapped, mapped_column from .base import Base class WorkerLaneSample(Base): __tablename__ = "worker_lane_sample" # The lane name from services/worker_lanes.LANES. One row per lane, # overwritten in place: this is the LATEST reading, not a history. A time # series would be a different table with a different retention problem, # and nothing has asked for one. lane: Mapped[str] = mapped_column(String(32), primary_key=True) # Whether anything answered for this lane. NOT the same as "zero workers" # — an unswept absence is not a verdict (snippet #3969). False here means # the inspect came back without this lane, so every count below is # meaningless and the UI must say "not answering" rather than "0". present: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False) replicas: Mapped[int] = mapped_column(Integer, nullable=False, default=0) # Pool size of ONE process, nullable because a worker that answered # without reporting its pool is unknown rather than empty. pool: Mapped[int | None] = mapped_column(Integer, nullable=True) active: Mapped[int] = mapped_column(Integer, nullable=False, default=0) reserved: Mapped[int] = mapped_column(Integer, nullable=False, default=0) # Redis LLEN across the lane's queues. Nullable for the same reason as # `pool`: a queue the broker did not answer for is unknown, and summing it # as zero would report a buried lane as idle. queue_depth: Mapped[int | None] = mapped_column(Integer, nullable=True) measured_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), nullable=False, server_default=func.now(), )