from dataclasses import dataclass,asdict from pathlib import Path import threading @dataclass(frozen=True) class Observation: observation_id:str stamp_ns:int frame_id:str image_path:str station_id:str registry_version:int shelf_id:str calibration_id:str='' geometry_epoch:int=0 def validate(self,goal,now_ns,max_age_ns): if type(self.stamp_ns) is not int or not goal['capture_after']<=self.stamp_ns<=now_ns or now_ns-self.stamp_ns>max_age_ns:raise ValueError('stale/future observation') if not self.observation_id or not self.frame_id or not self.image_path:raise ValueError('observation identity incomplete') if goal.get('observation_station_id',self.station_id)!=self.station_id or goal.get('station_registry_version',self.registry_version)!=self.registry_version:raise ValueError('station/version mismatch') if goal.get('source_region_ref',self.shelf_id)!=self.shelf_id:raise ValueError('wrong shelf') if goal.get('expected_geometry_epoch',self.geometry_epoch)!=self.geometry_epoch:raise ValueError('geometry changed') return asdict(self) class ObservationCache: def __init__(self,media_root):self.root=Path(media_root).resolve(strict=True);self.lock=threading.Lock();self.observation=None def put(self,observation): path=Path(observation.image_path).resolve(strict=True) if not path.is_relative_to(self.root) or not path.is_file() or path.stat().st_size>32*1024*1024:raise ValueError('image must be a bounded local trusted media file') with self.lock: # Accept a new clock epoch only after consumer freshness check; never # attach a current timestamp to old image bytes. self.observation=observation def get(self): with self.lock: if self.observation is None:raise ValueError('no observation') return self.observation