Unkerien/PLL-Sensor-Cyber-Guard
0
1"""2PLL-Sensor-Cyber-Guard-v1 -- Pydantic Models3=============================================4OpenEnv 2026 Sync Protocol schemas for a PLL sensor cyber-attack5detection environment.6 7Observation: continuous PLL signals + 10-step buffer.8Action: classification (attack_detected, attack_type, confidence).9Sync: player_id + session_id for turn validation.10"""11from __future__ import annotations12 13from typing import List, Optional14 15from pydantic import BaseModel, Field16 17 18# ---------------------------------------------------------------------------19# Observation Model20# ---------------------------------------------------------------------------21 22class PLLObservation(BaseModel):23 """Observation returned after reset() or step()."""24 25 task_id: str = Field(..., description="Current task identifier.")26 step_count: int = Field(0, description="Current simulation step.")27 phase_error: float = Field(28 ..., description="Instantaneous phase error (radians)."29 )30 freq_deviation: float = Field(31 ..., description="Frequency deviation from nominal (Hz)."32 )33 vco_voltage: float = Field(34 ..., description="Voltage-Controlled Oscillator control voltage (V)."35 )36 buffer: List[float] = Field(37 default_factory=list,38 description="Rolling buffer of the last 10 phase-error readings.",39 )40 done: bool = Field(False, description="Episode finished flag.")41 score: Optional[float] = Field(42 None, ge=0.0, le=1.0, description="Grader score (0-1)."43 )44 reward: Optional[float] = Field(None, description="Reward signal.")45 feedback: Optional[str] = Field(None, description="Grader feedback.")46 47 48# ---------------------------------------------------------------------------49# Action Models50# ---------------------------------------------------------------------------51 52class PLLAction(BaseModel):53 """Classification payload from the agent."""54 55 attack_detected: bool = Field(56 ..., description="Whether the agent believes an attack is occurring."57 )58 attack_type: str = Field(59 ...,60 description=(61 "Predicted attack type: 'none', 'step_attack', "62 "'freq_ramp', or 'stealthy_fdi'."63 ),64 )65 confidence: float = Field(66 ..., ge=0.0, le=1.0, description="Agent confidence in prediction."67 )68 69 70class ActionPayload(BaseModel):71 """Wrapped-int action envelope (2026 Sync Protocol)."""72 73 action_id: int = Field(74 ..., description="Protocol-required integer. 1=classify, 0=observe."75 )76 classification: PLLAction = Field(77 ..., description="Classification result from the agent."78 )79 80 81# ---------------------------------------------------------------------------82# State Model83# ---------------------------------------------------------------------------84 85class PLLState(BaseModel):86 """Full environment state snapshot for /state endpoint."""87 88 current_task_id: Optional[str] = None89 player_id: Optional[str] = None90 session_id: Optional[str] = None91 step_count: int = 092 max_steps: int = 10093 done: bool = False94 last_score: Optional[float] = None95 last_reward: Optional[float] = None96 phase_error: float = 0.097 freq_deviation: float = 0.098 vco_voltage: float = 0.099 buffer: List[float] = Field(default_factory=list)100 attack_active: bool = False101 102 103# ---------------------------------------------------------------------------104# Request Models (2026 Sync Protocol)105# ---------------------------------------------------------------------------106 107class ResetRequest(BaseModel):108 """POST /reset body. Registers the player and starts a session."""109 110 player_id: str = Field(111 ..., description="Unique agent identifier for turn validation."112 )113 session_id: str = Field(114 ..., description="Session token for request deduplication."115 )116 task_id: Optional[str] = Field(117 None,118 description="Task to load (task_1|task_2|task_3). Random if omitted.",119 )120 121 122class StepRequest(BaseModel):123 """POST /step body. Wrapped action + sync metadata."""124 125 player_id: str = Field(126 ..., description="Must match the player_id from /reset."127 )128 session_id: str = Field(129 ..., description="Must match the session_id from /reset."130 )131 action: ActionPayload = Field(132 ..., description="Wrapped action with action_id and classification."133 )134 135 