feat(perception): gate streaming lifecycle on controller readiness

This commit is contained in:
DCCONSTRUCTIONS
2026-09-02 14:49:36 +03:00
parent b8ba0cae1e
commit 92625bf48b
4 changed files with 773 additions and 0 deletions
@@ -23,6 +23,8 @@ from .realtime_contract import StreamStart
from .realtime_scene import _wire_integer
from .streaming_queue import IngressReservation, StreamBundle, StreamMailbox
from .worker_lease import WorkerLease, WorkerLeaseError
from .worker_operating_envelope import WorkerSnapshot
from .worker_readiness import WorkerReadinessError, WorkerReadinessMonitor
def _group_exists(pgid: int) -> bool:
@@ -45,8 +47,12 @@ class StreamingLifecycle:
*,
ttl_seconds: float = 2.0,
clock_ns: Callable[[], int] = time.monotonic_ns,
readiness: WorkerReadinessMonitor | None = None,
) -> None:
self.start, self.mailbox, self.stop_event = start, mailbox, stop
self._clock_ns, self._readiness = clock_ns, readiness
if readiness is not None:
readiness.check(start, now_monotonic_ns=clock_ns(), require_warmup=False)
self._lock = threading.RLock()
self._cleanup_lock = threading.Lock()
self._children: list[subprocess.Popen[bytes]] = []
@@ -75,6 +81,40 @@ class StreamingLifecycle:
if start == self.start:
self.request_stop("lease-lost")
raise
self._check_readiness(require_warmup=self.state == GraphState.RUNNING)
def _check_readiness(self, *, require_warmup: bool) -> None:
if self._readiness is not None:
try:
self._readiness.check(
self.start,
now_monotonic_ns=self._clock_ns(),
require_warmup=require_warmup,
)
except WorkerReadinessError:
self.request_stop("worker-not-ready")
raise
def observe_worker(self, start: StreamStart, observed: WorkerSnapshot) -> None:
"""Trusted control plane only; sensor payloads never call this method.
This is not a lease renewal. A fresh inventory and an active controller
heartbeat are independent requirements. No host operations under lock.
"""
with self._lock:
self._check(start, starting=True)
if self._readiness is None:
raise WorkerReadinessError("worker readiness monitoring is not configured")
try:
self._readiness.observe(
start,
observed,
now_monotonic_ns=self._clock_ns(),
require_warmup=self.state == GraphState.RUNNING,
)
except WorkerReadinessError:
self.request_stop("worker-not-ready")
raise
def renew(self, start: StreamStart) -> None:
with self._lock:
@@ -89,6 +129,7 @@ class StreamingLifecycle:
if any(p.poll() is not None for p in self._children):
self.request_stop("child-exited")
raise WorkerLeaseError("owned child exited during warmup")
self._check_readiness(require_warmup=True)
self.state = GraphState.RUNNING
def spawn(self, factory: Callable[[], subprocess.Popen[bytes]]) -> subprocess.Popen[bytes]:
@@ -157,6 +198,7 @@ class StreamingLifecycle:
"lease-lost",
"child-exited",
"invalid-process-group",
"worker-not-ready",
"failed",
):
raise ValueError("unknown runtime stop reason")
@@ -262,4 +304,9 @@ class StreamingLifecycle:
_wire_integer(self.retired_ns) if self.retired_ns is not None else None
),
"event_clock": "worker-process-monotonic",
"worker_readiness": (
self._readiness.snapshot()
if self._readiness is not None
else {"enabled": False, "realtime_qualified": False}
),
}
+140
View File
@@ -0,0 +1,140 @@
"""Bounded controller-owned readiness state, independent of the sensor stream.
The trusted Worker controller supplies observations in its local monotonic clock
domain. It must include mode/envelope in the effective configuration identity.
This does not collect Docker/NVML facts, authenticate a remote controller, set
clocks, or certify real-time operation. The lifecycle serializes all access.
"""
from __future__ import annotations
from dataclasses import asdict
from typing import Literal
from .realtime_contract import RealtimeContractError, StreamStart, _identifier, _integer
from .worker_lease import WorkerLeaseError
from .worker_operating_envelope import (
WorkerOperatingEnvelope,
WorkerSnapshot,
operating_envelope_failures,
)
ReadinessMode = Literal["strict-envelope", "labelled-experiment"]
# Only measured performance conditions are waivable. Missing ownership, input
# identity, warmup or inventory NEVER becomes an allowed overload experiment.
_PERFORMANCE_FAILURES = frozenset(
f"{field}-outside-envelope-or-unknown"
for field in ("gpu_name", "driver_version", "cpu_limit_millicores", "memory_limit_mib")
) | frozenset(
f"{field}-below-envelope-or-unknown" for field in ("sm_clock_mhz", "memory_clock_mhz")
)
class WorkerReadinessError(WorkerLeaseError):
"""A current controller lost the prerequisites for further execution."""
class WorkerReadinessMonitor:
def __init__(
self,
start: StreamStart,
envelope: WorkerOperatingEnvelope,
initial: WorkerSnapshot,
*,
mode: ReadinessMode,
clock_domain_id: str,
now_monotonic_ns: int,
) -> None:
if mode not in ("strict-envelope", "labelled-experiment"):
raise RealtimeContractError("unknown Worker readiness mode")
_identifier(clock_domain_id, "worker clock domain")
self.start, self.envelope = start, envelope
self.mode, self.clock_domain_id = mode, clock_domain_id
self._observed = initial
self._last_check_ns = now_monotonic_ns
self._failure: tuple[str, ...] = ()
self._current: tuple[str, ...] = ()
self._post_warmup_violations: set[str] = set()
self._warmup_checked = False
self.observations = 1
self.check(start, now_monotonic_ns=now_monotonic_ns, require_warmup=False)
def _bound(self, start: StreamStart) -> None:
# A stale client may not poison the current owner's state.
if start != self.start:
raise WorkerReadinessError("readiness stream identity mismatch")
def _fail(self, reasons: tuple[str, ...]) -> None:
self._failure = self._failure or reasons
raise WorkerReadinessError("worker readiness lost: " + ",".join(self._failure))
def check(self, start: StreamStart, *, now_monotonic_ns: int, require_warmup: bool) -> None:
self._bound(start)
if self._failure:
self._fail(self._failure)
try:
_integer(now_monotonic_ns, "worker monotonic time")
if now_monotonic_ns < self._last_check_ns:
raise RealtimeContractError("worker clock moved backwards")
failures = operating_envelope_failures(
self.envelope,
start,
self._observed,
now_monotonic_ns=now_monotonic_ns,
clock_domain_id=self.clock_domain_id,
)
except RealtimeContractError:
self._fail(("worker-snapshot-clock-invalid",))
return # Unreachable, keeps static narrowing explicit.
self._last_check_ns = now_monotonic_ns
self._current = failures
if require_warmup:
self._warmup_checked = True
self._post_warmup_violations.update(set(failures) & _PERFORMANCE_FAILURES)
fatal = tuple(
failure
for failure in failures
if failure not in _PERFORMANCE_FAILURES
and (require_warmup or failure != "warmup-not-complete")
)
if fatal:
self._fail(fatal)
if require_warmup and self.mode == "strict-envelope" and failures:
self._fail(failures)
def observe(
self,
start: StreamStart,
observed: WorkerSnapshot,
*,
now_monotonic_ns: int,
require_warmup: bool,
) -> None:
# Recheck the OLD observation before replacement: late telemetry cannot
# resurrect expired authority, even if the watchdog has not run yet.
self.check(start, now_monotonic_ns=now_monotonic_ns, require_warmup=require_warmup)
if observed.observed_monotonic_ns <= self._observed.observed_monotonic_ns:
self._fail(("worker-snapshot-not-increasing",))
self._observed = observed
self.observations += 1
self.check(start, now_monotonic_ns=now_monotonic_ns, require_warmup=require_warmup)
def snapshot(self) -> dict[str, object]:
# No history of individual telemetry samples, only fixed-size state.
observed = asdict(self._observed)
observed["observed_monotonic_ns"] = str(self._observed.observed_monotonic_ns)
return {
"enabled": True,
"mode": self.mode,
"envelope": asdict(self.envelope),
"worker_clock_domain_id": self.clock_domain_id,
"last_check_monotonic_ns": str(self._last_check_ns),
"observations": self.observations,
"latest_observation": observed,
"current_failures": list(self._current),
"terminal_failures": list(self._failure),
"post_warmup_envelope_violations": sorted(self._post_warmup_violations),
"warmup_readiness_checked": self._warmup_checked,
"realtime_qualified": False,
"actuation_allowed": False,
}