feat(perception): enforce six-layer freshness through receipt
This commit is contained in:
@@ -0,0 +1,298 @@
|
||||
"""Small synthetic scene/consumer tests; no models, network or source archives."""
|
||||
|
||||
import copy
|
||||
import importlib
|
||||
import json
|
||||
from dataclasses import replace
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from k1link.perception.realtime_contract import (
|
||||
REQUIRED_LAYERS,
|
||||
LayerEvidence,
|
||||
RealtimeContractError,
|
||||
)
|
||||
from k1link.perception.realtime_scene import DEPENDENCIES, SceneFreshness, derive_layer
|
||||
|
||||
NOW = 1_000_000_000
|
||||
|
||||
|
||||
def scene(*, held_ms=0, missing=False):
|
||||
layers = {
|
||||
"segmentation": LayerEvidence(
|
||||
"segmentation",
|
||||
"epoch",
|
||||
1 if held_ms else 2,
|
||||
NOW - held_ms * 1_000_000,
|
||||
"held" if held_ms else "current",
|
||||
"a" * 64,
|
||||
NOW - held_ms * 1_000_000,
|
||||
),
|
||||
"objects": LayerEvidence("objects", "epoch", 2, NOW, "current", "b" * 64, NOW),
|
||||
}
|
||||
for name in ("geometry", "motion", "costmap", "policy"):
|
||||
if name == "geometry" and missing:
|
||||
layers[name] = LayerEvidence(name, "epoch", None, None, "unavailable", None)
|
||||
else:
|
||||
layers[name] = derive_layer(
|
||||
name,
|
||||
epoch_id="epoch",
|
||||
source_sequence=2,
|
||||
source_time_ns=NOW,
|
||||
payload_sha256="c" * 64,
|
||||
inputs=tuple(layers[x] for x in DEPENDENCIES[name]),
|
||||
oldest_required_input_time_ns=NOW,
|
||||
)
|
||||
return SceneFreshness("epoch", "mapped-source", 2, NOW, tuple(layers.values()))
|
||||
|
||||
|
||||
def assess(value, *, elapsed_ms=0, **kwargs):
|
||||
arguments = dict(
|
||||
epoch_id="epoch",
|
||||
clock_domain_id="mapped-source",
|
||||
observed_source_time_ns=NOW + elapsed_ms * 1_000_000,
|
||||
clock_uncertainty_ms=0,
|
||||
maximum_clock_uncertainty_ms=5,
|
||||
maximum_layer_age_ms=250,
|
||||
)
|
||||
arguments.update(kwargs)
|
||||
return value.assess(**arguments)
|
||||
|
||||
|
||||
def test_six_layer_round_trip_is_bounded_and_needs_no_eof():
|
||||
value = scene()
|
||||
wire = json.dumps(value.to_dict())
|
||||
assert len(wire.encode()) < 8192
|
||||
assert SceneFreshness.from_dict(json.loads(wire)) == value
|
||||
checked = assess(value, elapsed_ms=125)
|
||||
assert checked.fresh_complete
|
||||
assert checked.age_upper_bound_ms == (125,) * 6
|
||||
assert checked.to_dict()["actuation_allowed"] is False
|
||||
|
||||
|
||||
def test_json_projection_keeps_int64_exact_above_javascript_safe_integer():
|
||||
value = scene()
|
||||
large = 9_007_199_254_740_993
|
||||
value = replace(
|
||||
value,
|
||||
source_time_ns=large,
|
||||
layers=tuple(
|
||||
replace(x, source_time_ns=large, oldest_required_input_time_ns=large)
|
||||
for x in value.layers
|
||||
),
|
||||
)
|
||||
wire = value.to_dict()
|
||||
assert wire["source_time_ns"] == str(large)
|
||||
assert SceneFreshness.from_dict(json.loads(json.dumps(wire))) == value
|
||||
|
||||
|
||||
@pytest.mark.parametrize("number", [1_000_000_000, "01", "-1", "1.0", str(2**63)])
|
||||
def test_wire_rejects_lossy_or_noncanonical_int64(number):
|
||||
wire = scene().to_dict()
|
||||
wire["source_time_ns"] = number
|
||||
with pytest.raises(RealtimeContractError):
|
||||
SceneFreshness.from_dict(wire)
|
||||
|
||||
|
||||
def test_held_segmenter_keeps_original_identity_through_costmap_and_policy():
|
||||
value = scene(held_ms=150)
|
||||
assert value.layers[0].source_sequence == 1
|
||||
assert value.layers[-1].source_sequence == 2
|
||||
assert value.layers[-1].oldest_required_input_time_ns == NOW - 150_000_000
|
||||
assert assess(value, elapsed_ms=100).fresh_complete
|
||||
expired = assess(value, elapsed_ms=101)
|
||||
assert expired.failures == ("segmentation-stale", "costmap-stale", "policy-stale")
|
||||
assert expired.layers[-1].source_time_ns == NOW # recomputation is not a new input
|
||||
|
||||
|
||||
def test_receipt_latency_cannot_inherit_fresh_publication():
|
||||
value = scene()
|
||||
assert assess(value, elapsed_ms=249).fresh_complete
|
||||
assert not assess(value, elapsed_ms=251).fresh_complete
|
||||
assert all(x.state == "stale" for x in assess(value, elapsed_ms=251).layers)
|
||||
|
||||
|
||||
def test_missing_geometry_propagates_without_fabricating_zero_distance():
|
||||
value = scene(missing=True)
|
||||
checked = assess(value)
|
||||
assert not checked.fresh_complete
|
||||
assert [x.layer for x in checked.layers if x.state == "unavailable"] == list(
|
||||
REQUIRED_LAYERS[2:]
|
||||
)
|
||||
assert all(x.payload_sha256 is None for x in checked.layers[2:])
|
||||
assert checked.age_upper_bound_ms[2:] == (None,) * 4
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"field,value",
|
||||
[
|
||||
("epoch_id", "old-epoch"),
|
||||
("clock_domain_id", "raw-worker-clock"),
|
||||
("observed_source_time_ns", NOW - 1),
|
||||
("clock_uncertainty_ms", None),
|
||||
("clock_uncertainty_ms", float("nan")),
|
||||
("clock_uncertainty_ms", True),
|
||||
],
|
||||
)
|
||||
def test_wrong_or_unmapped_clock_rejected(field, value):
|
||||
with pytest.raises(RealtimeContractError):
|
||||
assess(scene(), **{field: value})
|
||||
|
||||
|
||||
def test_known_clock_uncertainty_counts_against_budget():
|
||||
assert assess(scene(), elapsed_ms=245, clock_uncertainty_ms=5).fresh_complete
|
||||
assert not assess(scene(), elapsed_ms=246, clock_uncertainty_ms=5).fresh_complete
|
||||
assert "clock-uncertainty" in assess(scene(), clock_uncertainty_ms=6).failures
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"change",
|
||||
[
|
||||
{"source_sequence": 3},
|
||||
{"source_time_ns": NOW + 1},
|
||||
{"epoch_id": "old"},
|
||||
{"source_sequence": 1},
|
||||
{"state": "held"},
|
||||
{"oldest_required_input_time_ns": None},
|
||||
],
|
||||
)
|
||||
def test_false_currentness_or_future_lineage_rejected(change):
|
||||
value = scene()
|
||||
layers = (replace(value.layers[0], **change), *value.layers[1:])
|
||||
with pytest.raises(RealtimeContractError):
|
||||
replace(value, layers=layers)
|
||||
|
||||
|
||||
def test_derived_layer_cannot_erase_old_or_missing_input():
|
||||
value = scene(held_ms=150)
|
||||
with pytest.raises(RealtimeContractError, match="refreshed"):
|
||||
replace(
|
||||
value,
|
||||
layers=(
|
||||
*value.layers[:-1],
|
||||
replace(value.layers[-1], oldest_required_input_time_ns=NOW),
|
||||
),
|
||||
)
|
||||
missing = scene(missing=True)
|
||||
with pytest.raises(RealtimeContractError, match="unavailable"):
|
||||
replace(missing, layers=(*missing.layers[:-1], scene().layers[-1]))
|
||||
|
||||
|
||||
def test_stale_input_cannot_be_relabelled_fresh():
|
||||
value = scene()
|
||||
with pytest.raises(RealtimeContractError, match="stale"):
|
||||
replace(value, layers=(replace(value.layers[0], state="stale"), *value.layers[1:]))
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"mutation",
|
||||
[
|
||||
lambda v: v.update(command="drive"),
|
||||
lambda v: v.update(schema_version="unknown"),
|
||||
lambda v: v["layers"].pop(),
|
||||
lambda v: v["layers"].append(v["layers"][0]),
|
||||
lambda v: v["layers"][0].update(age_ms=0),
|
||||
lambda v: v["layers"].__setitem__(1, v["layers"][0]),
|
||||
],
|
||||
)
|
||||
def test_wire_schema_is_closed(mutation):
|
||||
value = scene().to_dict()
|
||||
mutation(value)
|
||||
with pytest.raises(RealtimeContractError):
|
||||
SceneFreshness.from_dict(value)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def pilot(monkeypatch):
|
||||
monkeypatch.syspath_prepend(
|
||||
str(
|
||||
Path(__file__).resolve().parents[1]
|
||||
/ "experiments/perception/worker/streaming_profile_stage1"
|
||||
)
|
||||
)
|
||||
return importlib.import_module("pilot_freshness")
|
||||
|
||||
|
||||
def pilot_input():
|
||||
payload = dict(
|
||||
segmentation_sha256="a" * 64,
|
||||
proposals=[],
|
||||
observations=[],
|
||||
tracks=[],
|
||||
threats=[],
|
||||
surface_state="valid",
|
||||
range_estimator={"detected": "median-camera-z"},
|
||||
costmap_states=[1, 2],
|
||||
costmap_material=[1, 0],
|
||||
policy_actions=[0, 2],
|
||||
policy_counts={"ALLOW_candidate": 1, "HIGH_COST": 0, "NO_GO": 1},
|
||||
tgs_counts={"oldest_permissive_cell_source_ns": NOW - 100_000_000},
|
||||
commands_enabled=False,
|
||||
actuation_allowed=False,
|
||||
)
|
||||
bundle = dict(
|
||||
time_ns=NOW,
|
||||
sequence=2,
|
||||
due_ns=5_000_000_000,
|
||||
available=True,
|
||||
lineage={
|
||||
"pose_host_monotonic_ns": NOW - 10_000_000,
|
||||
"point_increments": [{"host_monotonic_ns": NOW - 20_000_000}],
|
||||
},
|
||||
)
|
||||
ddr = dict(state="current", source_sequence=2, source_host_monotonic_ns=NOW)
|
||||
return payload, bundle, ddr
|
||||
|
||||
|
||||
def test_pilot_receiver_checks_real_payloads_and_ages_after_serialization(pilot):
|
||||
payload, bundle, ddr = pilot_input()
|
||||
pilot.prepare_publication(
|
||||
payload, bundle, ddr, epoch_id="pilot", now_ns=bundle["due_ns"] + 50_000_000
|
||||
)
|
||||
assert payload["freshness_at_publication"]["fresh_complete"]
|
||||
assert payload["policy_actions"] == [0, 2]
|
||||
received = json.loads(json.dumps(payload))
|
||||
freshness = pilot.validate_receipt(received, bundle, epoch_id="pilot")
|
||||
# The costmap's last observed permissive cell ages beyond 250 ms in transit.
|
||||
checked = pilot.assess(freshness, bundle=bundle, now_ns=bundle["due_ns"] + 151_000_000)
|
||||
view = pilot.receipt_view(received, checked)
|
||||
assert checked.failures == ("costmap-stale", "policy-stale")
|
||||
assert view["policy_actions"] == [2, 2]
|
||||
assert received["policy_actions"] == [0, 2] # published evidence remains immutable
|
||||
received["costmap_material"][0] = 2
|
||||
with pytest.raises(ValueError, match="digest"):
|
||||
pilot.validate_receipt(received, bundle, epoch_id="pilot")
|
||||
|
||||
|
||||
def test_empty_detection_is_valid_but_missing_lidar_is_not(pilot):
|
||||
payload, bundle, ddr = pilot_input()
|
||||
bundle["available"] = False
|
||||
pilot.prepare_publication(payload, bundle, ddr, epoch_id="pilot", now_ns=bundle["due_ns"])
|
||||
fresh = pilot.validate_receipt(payload, bundle, epoch_id="pilot")
|
||||
assert fresh.layers[1].state == "current"
|
||||
assert all(x.state == "unavailable" for x in fresh.layers[2:])
|
||||
assert payload["policy_actions"] == [2, 2]
|
||||
assert payload["observations"] == []
|
||||
|
||||
|
||||
def test_failed_online_surface_is_not_fresh_geometry_even_with_sensor_pair(pilot):
|
||||
payload, bundle, ddr = pilot_input()
|
||||
payload["surface_state"] = "fit-failed"
|
||||
pilot.prepare_publication(payload, bundle, ddr, epoch_id="pilot", now_ns=bundle["due_ns"])
|
||||
fresh = pilot.validate_receipt(payload, bundle, epoch_id="pilot")
|
||||
assert all(x.state == "unavailable" for x in fresh.layers[2:])
|
||||
assert payload["policy_actions"] == [2, 2]
|
||||
|
||||
|
||||
def test_pilot_rejects_wrong_run_sequence_and_control_authority(pilot):
|
||||
payload, bundle, ddr = pilot_input()
|
||||
pilot.prepare_publication(payload, bundle, ddr, epoch_id="pilot", now_ns=bundle["due_ns"])
|
||||
with pytest.raises(ValueError, match="identity"):
|
||||
pilot.validate_receipt(payload, bundle, epoch_id="old-pilot")
|
||||
with pytest.raises(ValueError, match="identity"):
|
||||
pilot.validate_receipt(payload, {**bundle, "sequence": 3}, epoch_id="pilot")
|
||||
changed = copy.deepcopy(payload)
|
||||
changed["commands_enabled"] = True
|
||||
with pytest.raises(ValueError, match="authority"):
|
||||
pilot.validate_receipt(changed, bundle, epoch_id="pilot")
|
||||
@@ -0,0 +1,142 @@
|
||||
"""Synthetic readiness checks; passing must never mutate a Worker or grant control."""
|
||||
|
||||
import json
|
||||
from dataclasses import replace
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from k1link.perception.realtime_contract import RealtimeContractError, StreamStart
|
||||
from k1link.perception.worker_operating_envelope import (
|
||||
WorkerOperatingEnvelope,
|
||||
WorkerSnapshot,
|
||||
operating_envelope_failures,
|
||||
)
|
||||
|
||||
|
||||
def start():
|
||||
return StreamStart(
|
||||
"run-1",
|
||||
"source-1",
|
||||
"worker-006",
|
||||
"epoch-1",
|
||||
1,
|
||||
"a" * 64,
|
||||
"b" * 64,
|
||||
"c" * 64,
|
||||
"d" * 64,
|
||||
"source-clock",
|
||||
"recorded-source-paced",
|
||||
)
|
||||
|
||||
|
||||
def envelope():
|
||||
return WorkerOperatingEnvelope(
|
||||
"synthetic-4090-fixed/v1", "RTX 4090", "610.47", 8000, 8192, 2610, 10251
|
||||
)
|
||||
|
||||
|
||||
def snapshot():
|
||||
return WorkerSnapshot(
|
||||
"worker-006",
|
||||
"worker-monotonic",
|
||||
1_000_000_000,
|
||||
"RTX 4090",
|
||||
"610.47",
|
||||
"b" * 64,
|
||||
"c" * 64,
|
||||
8000,
|
||||
8192,
|
||||
2610,
|
||||
10251,
|
||||
"run-1",
|
||||
1,
|
||||
(),
|
||||
True,
|
||||
)
|
||||
|
||||
|
||||
def check(value, **kwargs):
|
||||
options = {"now_monotonic_ns": 1_100_000_000, "clock_domain_id": "worker-monotonic"}
|
||||
options.update(kwargs)
|
||||
return operating_envelope_failures(envelope(), start(), value, **options)
|
||||
|
||||
|
||||
def test_exact_measured_conditions_pass_only_readiness():
|
||||
value = snapshot()
|
||||
assert check(value) == ()
|
||||
assert value == snapshot()
|
||||
assert "qualified" not in value.__dataclass_fields__
|
||||
|
||||
|
||||
def test_candidate_preregisters_conditions_without_installing_host_policy():
|
||||
path = (
|
||||
Path(__file__).resolve().parents[1]
|
||||
/ "config/perception/k1-perception-ddrnet39-rfdetr-tgs-prototype-v1.json"
|
||||
)
|
||||
profile = json.loads(path.read_text())
|
||||
value = profile["operating_envelope"]
|
||||
parsed = WorkerOperatingEnvelope(**value["reference_conditions"])
|
||||
assert parsed.minimum_memory_clock_mhz == 10251
|
||||
assert not value["container_may_set_host_clocks"]
|
||||
assert not value["readiness_is_realtime_qualification"]
|
||||
assert value["failed_envelope_allows_labelled_experiment"]
|
||||
assert profile["packaging"]["image_sha256"] is None
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"field,value,reason",
|
||||
[
|
||||
("worker_id", "worker-007", "worker_id-mismatch"),
|
||||
("gpu_name", "RTX 5090", "gpu_name-outside"),
|
||||
("driver_version", "new-driver", "driver_version-outside"),
|
||||
("image_sha256", "e" * 64, "image_sha256-mismatch"),
|
||||
("effective_config_sha256", "e" * 64, "effective_config_sha256-mismatch"),
|
||||
("sm_clock_mhz", 450, "sm_clock_mhz-below"),
|
||||
("memory_clock_mhz", 405, "memory_clock_mhz-below"),
|
||||
("memory_clock_mhz", None, "memory_clock_mhz-below"),
|
||||
("cpu_limit_millicores", 4000, "cpu_limit_millicores-outside"),
|
||||
("memory_limit_mib", 0, "memory_limit_mib-outside"),
|
||||
("cpu_limit_millicores", None, "cpu_limit_millicores-outside"),
|
||||
("gpu_owner_run_id", None, "exclusive-worker-lease"),
|
||||
("gpu_owner_run_id", "old-run", "exclusive-worker-lease"),
|
||||
("lease_generation", 2, "exclusive-worker-lease"),
|
||||
("competing_gpu_clients", ("other-model",), "competing-gpu-clients"),
|
||||
("competing_gpu_clients", None, "competing-gpu-clients"),
|
||||
("warmup_complete", None, "warmup-not-complete"),
|
||||
("warmup_complete", False, "warmup-not-complete"),
|
||||
],
|
||||
)
|
||||
def test_auto_clocks_wrong_identity_unknown_or_busy_never_pass(field, value, reason):
|
||||
assert any(x.startswith(reason) for x in check(replace(snapshot(), **{field: value})))
|
||||
|
||||
|
||||
def test_low_utilization_does_not_prove_ownership_and_idle_does_not_prove_warmup():
|
||||
failures = check(replace(snapshot(), gpu_owner_run_id=None, warmup_complete=False))
|
||||
assert failures == ("exclusive-worker-lease-unproved", "warmup-not-complete")
|
||||
|
||||
|
||||
def test_snapshot_age_and_clock_domain_cannot_be_ignored():
|
||||
assert check(snapshot(), now_monotonic_ns=2_000_000_000) == ()
|
||||
assert check(snapshot(), now_monotonic_ns=2_000_000_001) == ("worker-snapshot-expired",)
|
||||
with pytest.raises(RealtimeContractError, match="clock domain"):
|
||||
check(snapshot(), clock_domain_id="mac-monotonic")
|
||||
with pytest.raises(RealtimeContractError, match="future"):
|
||||
check(snapshot(), now_monotonic_ns=999_999_999)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"change",
|
||||
[
|
||||
{"sm_clock_mhz": True},
|
||||
{"memory_limit_mib": -1},
|
||||
{"warmup_complete": 1},
|
||||
{"image_sha256": "latest"},
|
||||
{"lease_generation": 0},
|
||||
{"competing_gpu_clients": ["unbounded-list"]},
|
||||
{"competing_gpu_clients": ("client",) * 65},
|
||||
],
|
||||
)
|
||||
def test_invalid_facts_fail_closed(change):
|
||||
with pytest.raises(RealtimeContractError):
|
||||
replace(snapshot(), **change)
|
||||
Reference in New Issue
Block a user