feat(perception): enforce six-layer freshness through receipt

This commit is contained in:
DCCONSTRUCTIONS
2026-09-02 10:14:28 +03:00
parent 4e633a662c
commit 097e450a87
9 changed files with 1067 additions and 43 deletions
+17 -2
View File
@@ -181,6 +181,9 @@ class LayerEvidence:
source_time_ns: int | None
state: str
payload_sha256: str | None
# Old callers omit this field. New layered results require it explicitly:
# recomputing a derived layer cannot refresh the age of its required inputs.
oldest_required_input_time_ns: int | None = None
def __post_init__(self) -> None:
if self.layer not in REQUIRED_LAYERS:
@@ -191,13 +194,22 @@ class LayerEvidence:
if self.state == "unavailable":
if any(
x is not None
for x in (self.source_sequence, self.source_time_ns, self.payload_sha256)
for x in (
self.source_sequence,
self.source_time_ns,
self.payload_sha256,
self.oldest_required_input_time_ns,
)
):
raise RealtimeContractError("unavailable evidence cannot claim a payload")
else:
_integer(self.source_sequence, "source_sequence")
_integer(self.source_time_ns, "source_time_ns")
_digest(self.payload_sha256, "payload_sha256")
if self.oldest_required_input_time_ns is not None:
_integer(self.oldest_required_input_time_ns, "oldest_required_input_time_ns")
if self.oldest_required_input_time_ns > cast(int, self.source_time_ns):
raise RealtimeContractError("required input is newer than its layer")
def validate_scene_layers(
@@ -223,7 +235,10 @@ def validate_scene_layers(
raise RealtimeContractError("layer belongs to another stream epoch")
if layer.source_time_ns is None:
continue
age_ns = source_time_ns - layer.source_time_ns
if layer.source_time_ns > source_time_ns:
raise RealtimeContractError("future observations are not causal input")
stamp = layer.oldest_required_input_time_ns
age_ns = source_time_ns - (layer.source_time_ns if stamp is None else stamp)
if age_ns < 0:
raise RealtimeContractError("future observations are not causal input")
if age_ns > maximum_layer_age_ms * 1_000_000 and layer.state in ("current", "held"):
+264
View File
@@ -0,0 +1,264 @@
"""Bounded six-layer freshness ABI; no I/O, models or control authority.
This wraps existing domain payloads. It is not the transport/session admission
protocol: a transport must also validate StreamStart identities and lease fences.
All times here are in ONE mapped source timeline, never raw remote worker clocks.
"""
from __future__ import annotations
from dataclasses import asdict, dataclass, replace
from typing import Final
from .realtime_contract import (
REQUIRED_LAYERS,
LayerEvidence,
RealtimeContractError,
_identifier,
_integer,
_number,
)
SCENE_FRESHNESS_SCHEMA: Final = "missioncore.perception-scene-freshness/v1"
_INT64_FIELDS: Final = ("source_sequence", "source_time_ns", "oldest_required_input_time_ns")
def _wire_integer(value: int) -> str:
_integer(value, "wire integer")
if value > 2**63 - 1:
raise RealtimeContractError("wire integer exceeds int64")
return str(value)
def _read_wire_integer(value: object) -> int:
if (
not isinstance(value, str)
or not value.isascii()
or not value.isdecimal()
or len(value) > 19
or (len(value) > 1 and value.startswith("0"))
):
raise RealtimeContractError("int64 must be a canonical decimal string")
parsed = int(value)
_wire_integer(parsed)
return parsed
def _layer_document(item: LayerEvidence) -> dict[str, object]:
value = asdict(item)
for name in _INT64_FIELDS:
number = value[name]
value[name] = None if number is None else _wire_integer(number)
return value
# Geometry includes detector association; history may add prohibitions, not
# independently authorize terrain. This is the full profile's dependency graph.
DEPENDENCIES: Final = {
"segmentation": (),
"objects": (),
"geometry": ("objects",),
"motion": ("geometry",),
"costmap": ("segmentation", "geometry", "motion"),
"policy": ("costmap", "objects", "motion"),
}
def derive_layer(
layer: str,
*,
epoch_id: str,
source_sequence: int,
source_time_ns: int,
payload_sha256: str,
inputs: tuple[LayerEvidence, ...],
oldest_required_input_time_ns: int,
) -> LayerEvidence:
"""Propagate missing/stale inputs and their original ages, not execution time."""
if layer not in DEPENDENCIES or tuple(x.layer for x in inputs) != DEPENDENCIES[layer]:
raise RealtimeContractError("derived layer dependencies changed")
if any(x.epoch_id != epoch_id for x in inputs):
raise RealtimeContractError("derived input belongs to another epoch")
if any(x.state == "unavailable" for x in inputs):
return LayerEvidence(layer, epoch_id, None, None, "unavailable", None)
stamps = [oldest_required_input_time_ns]
for item in inputs:
if item.oldest_required_input_time_ns is None:
raise RealtimeContractError("required input age is missing")
stamps.append(item.oldest_required_input_time_ns)
return LayerEvidence(
layer,
epoch_id,
source_sequence,
source_time_ns,
"stale" if any(x.state == "stale" for x in inputs) else "current",
payload_sha256,
min(stamps),
)
@dataclass(frozen=True, slots=True)
class FreshnessAssessment:
checked_at_source_time_ns: int
clock_uncertainty_ms: float
layers: tuple[LayerEvidence, ...]
age_upper_bound_ms: tuple[float | None, ...]
failures: tuple[str, ...]
@property
def fresh_complete(self) -> bool:
return not self.failures
def to_dict(self) -> dict[str, object]:
return {
"checked_at_source_time_ns": _wire_integer(self.checked_at_source_time_ns),
"clock_uncertainty_ms": self.clock_uncertainty_ms,
"layers": {
item.layer: {**_layer_document(item), "age_upper_bound_ms": age}
for item, age in zip(self.layers, self.age_upper_bound_ms, strict=True)
},
"fresh_complete": self.fresh_complete,
"failures": list(self.failures),
"commands_enabled": False,
"actuation_allowed": False,
}
@dataclass(frozen=True, slots=True)
class SceneFreshness:
epoch_id: str
clock_domain_id: str
source_sequence: int
source_time_ns: int
layers: tuple[LayerEvidence, ...]
def __post_init__(self) -> None:
_identifier(self.epoch_id, "epoch_id")
_identifier(self.clock_domain_id, "clock_domain_id")
_integer(self.source_sequence, "source_sequence")
_integer(self.source_time_ns, "source_time_ns")
if (
not isinstance(self.layers, tuple)
or tuple(x.layer for x in self.layers) != REQUIRED_LAYERS
):
raise RealtimeContractError("six layers required in canonical order")
indexed = {item.layer: item for item in self.layers}
for item in self.layers:
if item.epoch_id != self.epoch_id:
raise RealtimeContractError("layer belongs to another epoch")
if item.state == "unavailable":
continue
if (
item.source_sequence is None
or item.source_time_ns is None
or item.oldest_required_input_time_ns is None
):
raise RealtimeContractError("layer input lineage missing")
if (
item.source_sequence > self.source_sequence
or item.source_time_ns > self.source_time_ns
):
raise RealtimeContractError("future layer evidence")
if item.state == "current" and (
item.source_sequence != self.source_sequence
or item.source_time_ns != self.source_time_ns
):
raise RealtimeContractError("retained evidence must be held, not current")
if item.state == "held" and item.source_sequence == self.source_sequence:
raise RealtimeContractError("held layer must retain an earlier observation")
for name in DEPENDENCIES[item.layer]:
parent = indexed[name]
if parent.oldest_required_input_time_ns is None:
raise RealtimeContractError("available layer depends on unavailable input")
if item.oldest_required_input_time_ns > parent.oldest_required_input_time_ns:
raise RealtimeContractError("derived layer refreshed an older input")
if parent.state == "stale" and item.state != "stale":
raise RealtimeContractError("derived layer hid stale input")
def to_dict(self) -> dict[str, object]:
return {
"schema_version": SCENE_FRESHNESS_SCHEMA,
"epoch_id": self.epoch_id,
"clock_domain_id": self.clock_domain_id,
"source_sequence": _wire_integer(self.source_sequence),
"source_time_ns": _wire_integer(self.source_time_ns),
"layers": [_layer_document(item) for item in self.layers],
}
@classmethod
def from_dict(cls, value: object) -> SceneFreshness:
if not isinstance(value, dict) or set(value) != set(cls.__dataclass_fields__) | {
"schema_version"
}:
raise RealtimeContractError("scene freshness fields changed")
if value["schema_version"] != SCENE_FRESHNESS_SCHEMA:
raise RealtimeContractError("scene freshness schema changed")
raw = value["layers"]
if not isinstance(raw, list) or len(raw) != len(REQUIRED_LAYERS):
raise RealtimeContractError("six layers required")
if any(
not isinstance(x, dict) or set(x) != set(LayerEvidence.__dataclass_fields__)
for x in raw
):
raise RealtimeContractError("layer fields changed")
layers = []
for item in raw:
parsed = dict(item)
for name in _INT64_FIELDS:
parsed[name] = None if item[name] is None else _read_wire_integer(item[name])
layers.append(LayerEvidence(**parsed))
return cls(
value["epoch_id"],
value["clock_domain_id"],
_read_wire_integer(value["source_sequence"]),
_read_wire_integer(value["source_time_ns"]),
tuple(layers),
)
def assess(
self,
*,
epoch_id: str,
clock_domain_id: str,
observed_source_time_ns: int,
clock_uncertainty_ms: float,
maximum_clock_uncertainty_ms: float,
maximum_layer_age_ms: float,
) -> FreshnessAssessment:
"""Re-evaluate at publication, receipt AND use. Uncertainty adds to age.
Unknown/unmapped clocks must not call this with a guessed zero offset.
The source adapter owns the mapping; excessive known uncertainty yields
degraded output, never a complete fresh-scene claim.
"""
if (epoch_id, clock_domain_id) != (self.epoch_id, self.clock_domain_id):
raise RealtimeContractError("epoch or mapped clock domain mismatch")
_integer(observed_source_time_ns, "observed_source_time_ns")
_number(clock_uncertainty_ms, "clock_uncertainty_ms")
_number(maximum_clock_uncertainty_ms, "maximum_clock_uncertainty_ms", minimum=0.001)
_number(maximum_layer_age_ms, "maximum_layer_age_ms", minimum=0.001)
if observed_source_time_ns < self.source_time_ns:
raise RealtimeContractError("receipt precedes source observation")
clock_failed = clock_uncertainty_ms > maximum_clock_uncertainty_ms
failures = ["clock-uncertainty"] if clock_failed else []
evaluated, ages = [], []
for item in self.layers:
stamp = item.oldest_required_input_time_ns
age = (
None
if stamp is None
else (observed_source_time_ns - stamp) / 1e6 + clock_uncertainty_ms
)
if age is not None and (age > maximum_layer_age_ms or clock_failed):
item = replace(item, state="stale")
if item.state in ("stale", "unavailable"):
failures.append(f"{item.layer}-{item.state}")
evaluated.append(item)
ages.append(age)
return FreshnessAssessment(
observed_source_time_ns,
clock_uncertainty_ms,
tuple(evaluated),
tuple(ages),
tuple(failures),
)
@@ -0,0 +1,153 @@
"""Read-only post-warmup readiness checks, not GPU management or qualification.
An envelope is preregistered for a measured hardware/software context. A failed
check does not prohibit an explicitly labelled overload experiment. Passing is
only a prerequisite: it cannot certify clocks throughout a run, latency, model
quality, standalone packaging or network delivery. No host setters live here.
"""
from __future__ import annotations
from dataclasses import dataclass
from .realtime_contract import (
RealtimeContractError,
StreamStart,
_digest,
_identifier,
_integer,
)
@dataclass(frozen=True, slots=True)
class WorkerOperatingEnvelope:
envelope_id: str
gpu_name: str
driver_version: str
# Runtime versions and all model/CPU-thread settings belong to the pinned
# image/effective config in StreamStart; resource limits are explicit here.
cpu_limit_millicores: int
memory_limit_mib: int
minimum_sm_clock_mhz: int
minimum_memory_clock_mhz: int
maximum_snapshot_age_ms: int = 1000
def __post_init__(self) -> None:
_identifier(self.envelope_id, "envelope_id")
for field in ("gpu_name", "driver_version"):
value = getattr(self, field)
if not isinstance(value, str) or not value.strip() or len(value) > 160:
raise RealtimeContractError(f"{field} is missing or unbounded")
for field in (
"cpu_limit_millicores",
"memory_limit_mib",
"minimum_sm_clock_mhz",
"minimum_memory_clock_mhz",
"maximum_snapshot_age_ms",
):
_integer(getattr(self, field), field, minimum=1)
@dataclass(frozen=True, slots=True)
class WorkerSnapshot:
worker_id: str
clock_domain_id: str
observed_monotonic_ns: int
gpu_name: str | None
driver_version: str | None
image_sha256: str | None
effective_config_sha256: str | None
cpu_limit_millicores: int | None
memory_limit_mib: int | None
sm_clock_mhz: int | None
memory_clock_mhz: int | None
# Authoritative controller lease, NOT inferred from low GPU utilization.
gpu_owner_run_id: str | None
lease_generation: int | None
competing_gpu_clients: tuple[str, ...] | None
warmup_complete: bool | None
def __post_init__(self) -> None:
_identifier(self.worker_id, "worker_id")
_identifier(self.clock_domain_id, "clock_domain_id")
_integer(self.observed_monotonic_ns, "observed_monotonic_ns")
for field in ("image_sha256", "effective_config_sha256"):
value = getattr(self, field)
if value is not None:
_digest(value, field)
for field in ("gpu_name", "driver_version"):
value = getattr(self, field)
if value is not None and (
not isinstance(value, str) or not value.strip() or len(value) > 160
):
raise RealtimeContractError(f"{field} is invalid")
for field in (
"cpu_limit_millicores",
"memory_limit_mib",
"sm_clock_mhz",
"memory_clock_mhz",
):
value = getattr(self, field)
if value is not None:
# Docker 0 means unlimited; never confuse it with unknown=None.
_integer(value, field)
if self.gpu_owner_run_id is not None:
_identifier(self.gpu_owner_run_id, "gpu_owner_run_id")
if self.lease_generation is not None:
_integer(self.lease_generation, "lease_generation", minimum=1)
if self.competing_gpu_clients is not None:
if (
not isinstance(self.competing_gpu_clients, tuple)
or len(self.competing_gpu_clients) > 64
):
raise RealtimeContractError("GPU client inventory is unbounded")
for client in self.competing_gpu_clients:
_identifier(client, "competing_gpu_client")
if self.warmup_complete is not None and type(self.warmup_complete) is not bool:
raise RealtimeContractError("warmup_complete must be boolean or unknown")
def operating_envelope_failures(
expected: WorkerOperatingEnvelope,
start: StreamStart,
observed: WorkerSnapshot,
*,
now_monotonic_ns: int,
clock_domain_id: str,
) -> tuple[str, ...]:
"""Check facts collected in the Worker's clock domain by a trusted controller.
No resource expansion, frequency lock, container stop, lease acquisition or
host mutation. Snapshot refresh and ongoing telemetry remain caller-owned.
Changing hardware/resource conditions requires a new measured envelope, not
pretending that an old qualification applies to all compatible machines.
"""
_integer(now_monotonic_ns, "now_monotonic_ns")
if observed.clock_domain_id != clock_domain_id:
raise RealtimeContractError("worker snapshot clock domain mismatch")
age = now_monotonic_ns - observed.observed_monotonic_ns
if age < 0:
raise RealtimeContractError("worker snapshot is from the future")
failures = []
if age > expected.maximum_snapshot_age_ms * 1_000_000:
failures.append("worker-snapshot-expired")
for field in ("worker_id", "image_sha256", "effective_config_sha256"):
if getattr(observed, field) != getattr(start, field):
failures.append(f"{field}-mismatch-or-unknown")
for field in ("gpu_name", "driver_version", "cpu_limit_millicores", "memory_limit_mib"):
if getattr(observed, field) != getattr(expected, field):
failures.append(f"{field}-outside-envelope-or-unknown")
for field in ("sm_clock_mhz", "memory_clock_mhz"):
value = getattr(observed, field)
if value is None or value < getattr(expected, "minimum_" + field):
failures.append(f"{field}-below-envelope-or-unknown")
if (observed.gpu_owner_run_id, observed.lease_generation) != (
start.run_id,
start.lease_generation,
):
failures.append("exclusive-worker-lease-unproved")
if observed.competing_gpu_clients != ():
failures.append("competing-gpu-clients-or-inventory-unknown")
if observed.warmup_complete is not True:
failures.append("warmup-not-complete")
return tuple(failures)