feat(perception): add scoped stream control and bounded clock observations

This commit is contained in:
DCCONSTRUCTIONS
2026-09-02 19:41:43 +03:00
parent 0cc6b96306
commit d7b8989e43
6 changed files with 1160 additions and 2 deletions
+134
View File
@@ -0,0 +1,134 @@
"""Conditional clock intervals, never a symmetric-network offset assertion."""
from dataclasses import replace
import pytest
from k1link.perception.streaming_clock import ClockMappingError, ClockProbe, ClockWindow
BASE = 10**16 # Deliberately above IEEE754 exact-integer range.
def probe(number=1, *, outbound=1_000_000, inbound=2_000_000, offset=-(10**12)):
sent = BASE + number * 100_000_000
return ClockProbe(
"mac",
"worker",
str(number),
sent,
sent + offset + outbound,
sent + offset + outbound + 100_000,
sent + outbound + inbound + 100_000,
)
def window(**kwargs):
return ClockWindow("mac", "worker", rate_ppm=500, timestamp_error_ns=50_000, **kwargs)
@pytest.mark.parametrize("offset", [-(10**12), 0, 10**12])
@pytest.mark.parametrize("outbound,inbound", [(1, 9_000_000), (9_000_000, 1), (1, 1)])
def test_contains_true_offset_without_symmetric_path_assumption(offset, outbound, inbound):
sample = probe(offset=offset, outbound=outbound, inbound=inbound)
mapping = window().add(sample)
now = sample.local_receive_ns
assert mapping.offset_at(now)[0] <= offset <= mapping.offset_at(now)[1]
assert mapping.uncertainty_ns(now + 1_000_000) > mapping.uncertainty_ns(now)
value = mapping.to_dict()
assert value["measured_at_ns"] == str(now)
assert not value["symmetric_network_assumed"]
assert value["timestamp_error_budget_ns"] == "50000"
def test_intersection_requires_evidence_and_drift_is_not_averaged_away():
mapping = window()
first = mapping.add(probe(outbound=1_000_000, inbound=20_000_000))
with pytest.raises(ClockMappingError, match="budget"):
first.require(first.measured_at_ns)
second = mapping.add(probe(2, outbound=20_000_000, inbound=1_000_000))
second.require(second.measured_at_ns)
assert second.uncertainty_ns(second.measured_at_ns) < 2_000_000
assert (
second.offset_at(second.measured_at_ns)[0]
< -(10**12)
< second.offset_at(second.measured_at_ns)[1]
)
def test_expired_mapping_waits_and_new_observation_can_restore_it():
mapping = window()
first = mapping.add(probe())
with pytest.raises(ClockMappingError, match="expired"):
first.require(first.expires_at_ns)
with pytest.raises(ClockMappingError, match="expired"):
mapping.current(first.expires_at_ns)
resumed = mapping.add(probe(30))
assert resumed.samples == 1
resumed.require(resumed.measured_at_ns)
def test_contradictory_samples_quarantine_not_cherry_pick():
mapping = window()
mapping.add(probe())
with pytest.raises(ClockMappingError, match="envelope"):
mapping.add(probe(2, offset=0))
with pytest.raises(ClockMappingError, match="quarantined"):
mapping.add(probe(3))
with pytest.raises(ClockMappingError):
mapping.current(BASE + 3_000_000_000)
@pytest.mark.parametrize("fault", ["local", "remote", "nonce", "overlap"])
def test_foreign_or_replayed_observation_cannot_refresh_mapping(fault):
mapping = window()
original = probe()
mapping.add(original)
second = probe(2)
changes = {
"local": {"local_clock_id": "other"},
"remote": {"remote_clock_id": "rebooted"},
"nonce": {"nonce": original.nonce},
"overlap": {"local_send_ns": original.local_receive_ns},
}
with pytest.raises(ClockMappingError):
mapping.add(replace(second, **changes[fault]))
assert mapping.last_receive_ns == original.local_receive_ns
def test_history_is_bounded_and_exact_values_survive():
mapping = window(maximum_age_ns=5_000_000_000)
for number in range(1, 35):
bounds = mapping.add(probe(number))
assert len(mapping.samples) == bounds.samples == 16
assert bounds.to_dict()["measured_at_ns"] == str(probe(34).local_receive_ns)
@pytest.mark.parametrize("value", [-1, 2**63, True, 1.0])
def test_invalid_monotonic_timestamp_rejected(value):
with pytest.raises(ClockMappingError):
replace(probe(), local_send_ns=value)
def test_deadline_remote_order_and_backward_reads():
sample = probe()
for changes in (
{"local_receive_ns": sample.local_send_ns - 1},
{"local_receive_ns": sample.local_send_ns + 2_000_000_001},
{"remote_send_ns": sample.remote_receive_ns - 1},
):
with pytest.raises(ClockMappingError):
replace(sample, **changes)
mapping = window().add(sample)
with pytest.raises(ClockMappingError):
mapping.offset_at(mapping.measured_at_ns - 1)
with pytest.raises(ClockMappingError):
replace(mapping, offset_lower_ns=mapping.offset_upper_ns + 1)
@pytest.mark.parametrize(
"rate,error,age",
[(0, 1, 1), (1001, 1, 1), (True, 1, 1), (1, 0, 1), (1, 1_000_001, 1), (1, 1, 0)],
)
def test_controller_must_supply_bounded_envelope(rate, error, age):
with pytest.raises(ClockMappingError):
ClockWindow("mac", "worker", rate_ppm=rate, timestamp_error_ns=error, maximum_age_ns=age)
@@ -0,0 +1,206 @@
"""Small TLS clock/grant checks; no model workload or lease authority in Poll."""
# ruff: noqa: E402, F811 -- imported shared pytest TLS fixture.
import asyncio
import threading
import time
from contextlib import asynccontextmanager
from dataclasses import replace
from hashlib import sha256
import pytest
grpc = pytest.importorskip("grpc")
from test_perception_streaming_grpc import event, eventually, identity, tls # noqa: F401
from k1link.perception import streaming_wire as wire
from k1link.perception.streaming_clock import ClockWindow
from k1link.perception.streaming_continuity import ResumeEvidence
from k1link.perception.streaming_control_grpc import (
MAX_CONTROL,
ControlTicket,
StreamControlClient,
StreamControlEndpoint,
_offer,
)
from k1link.perception.streaming_grpc import GrpcStreamClient, GrpcStreamEndpoint, StreamAccess
from k1link.perception.streaming_lifecycle import StreamingLifecycle
from k1link.perception.streaming_queue import StreamMailbox
@asynccontextmanager
async def controlled(tmp_path, tls):
run = StreamingLifecycle(
identity(),
tmp_path,
StreamMailbox(),
threading.Event(),
recover_input=True,
source_clock_ns=lambda: 1_000_000_000,
)
run.ready()
endpoint = GrpcStreamEndpoint(
run,
lambda value: endpoint.publish(
run.continuity.epoch, value.ingress_sequence, sha256(value.payload).digest()
),
lambda _: None,
)
pending = [endpoint.issue(identity(), "capture", 1)]
ticket = ControlTicket(identity(), "b" * 64)
control = StreamControlEndpoint(ticket, lambda: pending[0], clock_id="worker")
server, port = await endpoint.serve(
"localhost:0",
certificate=tls[0],
private_key=tls[1],
control_handlers=(control.handler(),),
)
client = StreamControlClient(f"localhost:{port}", tls[0], ticket, clock_id="mac")
try:
yield run, endpoint, control, client, pending, port
finally:
await client.close()
await server.stop(0)
await eventually(lambda: endpoint.active is None)
assert run.close()
def test_control_delivery_coexists_with_stream_without_lease_or_model_authority(tmp_path, tls):
async def check():
async with controlled(tmp_path, tls) as (run, endpoint, control, client, pending, port):
probe, access = await client.poll()
assert access == pending[0]
mapping = ClockWindow("mac", "worker", rate_ppm=500, timestamp_error_ns=50_000)
assert mapping.add(probe).uncertainty_ns(probe.local_receive_ns) > 0
first = access
for phase in range(2):
stream = GrpcStreamClient(f"localhost:{port}", tls[0], access)
try:
await stream.open()
pending[0] = None
await stream.send(event(phase + 1))
assert await stream.receive() == (phase + 1, sha256(event().payload).digest())
await asyncio.sleep(0.03)
_, absent = await client.poll()
assert absent is None and endpoint.active is not None
finally:
await stream.close()
await eventually(lambda: endpoint.active is None)
assert run.continuity.phase == "waiting"
assert run.lease.start == identity()
if phase == 0:
run.renew(identity()) # Only independent trusted owner renews lease.
epoch = run.begin_input(identity())
run.resume_input(
epoch, ResumeEvidence(*([1_000_000_000] * 4), True), lambda: None
)
pending[0] = endpoint.issue(epoch, "capture", 1)
await asyncio.sleep(0.03)
_, access = await client.poll()
assert access.epoch != first.epoch and access.token != first.token
assert control.accepted == 4 and run.lease.start.lease_generation == 1
asyncio.run(check())
@pytest.mark.parametrize("fault", ["secret", "binding", "duplicate", "oversize", "int64", "extra"])
def test_bad_control_cannot_claim_data_or_change_owner(tmp_path, tls, fault):
async def check():
async with controlled(tmp_path, tls) as (run, endpoint, control, client, pending, _):
metadata = client.ticket.metadata()
request = {"nonce": "n", "local_clock_id": "mac", "local_send_ns": "1"}
if fault == "secret":
metadata = ControlTicket(identity(), "0" * 64).metadata()
elif fault == "binding":
metadata = ControlTicket(
replace(identity(), source_id="other"), "b" * 64
).metadata()
elif fault == "duplicate":
metadata = (*metadata, *metadata)
elif fault == "int64":
request["local_send_ns"] = str(2**63)
elif fault == "extra":
request["renew_lease"] = True
raw = b"x" * (MAX_CONTROL + 1) if fault == "oversize" else wire.canonical(request)
with pytest.raises(grpc.aio.AioRpcError):
await client.call(raw, metadata=metadata, timeout=1)
assert endpoint.active is None and endpoint.grant is not None
assert run.lease.start == identity() and run.mailbox.bytes == 0
assert control.accepted == 0
asyncio.run(check())
def test_control_rate_and_single_outstanding_are_bounded(tmp_path, tls):
async def check():
async with controlled(tmp_path, tls) as (_, _, control, client, _, _):
control.clock_ns = lambda: 10**16
await client.poll()
with pytest.raises(grpc.aio.AioRpcError) as exc:
await client.poll()
assert exc.value.code() == grpc.StatusCode.RESOURCE_EXHAUSTED
client.polling = True
with pytest.raises(ValueError, match="outstanding"):
await client.poll()
assert control.accepted == control.rejected == 1
asyncio.run(check())
@pytest.mark.parametrize(
"changes",
[
{"source_id": "other"},
{"clock_domain_id": "other"},
{"input_mode": "recorded-source-paced"},
{"lease_generation": 2},
],
)
def test_offer_is_bound_to_every_activation_field(changes):
access = StreamAccess(replace(identity(), **changes), "capture", 1, "a" * 64)
with pytest.raises(ValueError):
_offer(access, identity())
@pytest.mark.parametrize("value", [None, 1, "x" * 64, "a" * 63])
def test_invalid_capabilities_fail_closed_without_leaking_in_repr(value):
with pytest.raises(ValueError):
ControlTicket(identity(), value)
assert "b" * 64 not in repr(ControlTicket(identity(), "b" * 64))
def test_malicious_response_nonce_or_offer_rejected(tls):
async def check():
ticket = ControlTicket(identity(), "a" * 64)
client = StreamControlClient("localhost:1", tls[0], ticket, clock_id="mac")
for mode in ("nonce", "offer"):
async def fake(raw, mode=mode, **kwargs):
request = wire.parse_header(bytearray(raw))
return wire.canonical(
{
"schema_version": "missioncore.stream-control-poll/v1",
"activation_binding": wire.binding(identity()),
**request,
"nonce": "wrong" if mode == "nonce" else request["nonce"],
"remote_clock_id": "worker",
"remote_receive_ns": str(time.monotonic_ns()),
"remote_send_ns": str(time.monotonic_ns()),
"grant": {
"epoch": replace(identity(), source_id="foreign").to_dict(),
"session_id": "capture",
"session_generation": "1",
"token": "a" * 64,
},
}
)
client.call = fake
with pytest.raises(ValueError):
await client.poll()
assert not client.polling
await client.close()
asyncio.run(check())