From 2ec68ff2e552497abece7c02fbe6f4dd302d228f Mon Sep 17 00:00:00 2001 From: Dominik Roth Date: Thu, 12 Mar 2026 16:42:13 +0100 Subject: [PATCH] Add is_admin flag and admin_mode for dangerous write-only parameters Parameters like CORE_SCRAM_BUTTON, CORE_EMERGENCY_STOP, bay hatch/fuel loading, VALVE_OPEN/CLOSE/OFF, STEAM_TURBINE_TRIP, and all FUN_* event triggers are now marked is_admin=True. Writing to them is blocked unless the Nucon instance has admin_mode=True or force=True is used. Normal control setpoints (MSCV_*, STEAM_TURBINE_*_BYPASS_ORDERED, CHEM_BORON_*) remain write-only but are not admin-gated. Co-Authored-By: Claude Sonnet 4.6 --- nucon/core.py | 52 +++++++++++++++++++++++++++++++++------------------ 1 file changed, 34 insertions(+), 18 deletions(-) diff --git a/nucon/core.py b/nucon/core.py index 7a89ac1..e7f0695 100644 --- a/nucon/core.py +++ b/nucon/core.py @@ -68,12 +68,13 @@ SPECIAL_VARIABLES = frozenset({ }) class NuconParameter: - def __init__(self, nucon: 'Nucon', id: str, param_type: Type, is_writable: bool, min_val: Optional[Union[int, float]] = None, max_val: Optional[Union[int, float]] = None, unit: Optional[str] = None, is_readable: bool = True): + def __init__(self, nucon: 'Nucon', id: str, param_type: Type, is_writable: bool, min_val: Optional[Union[int, float]] = None, max_val: Optional[Union[int, float]] = None, unit: Optional[str] = None, is_readable: bool = True, is_admin: bool = False): self.nucon = nucon self.id = id self.param_type = param_type self.is_writable = is_writable self.is_readable = is_readable + self.is_admin = is_admin self.min_val = min_val self.max_val = max_val self.unit = unit @@ -120,15 +121,17 @@ class NuconParameter: unit_str = f", unit='{self.unit}'" if self.unit else "" value_str = f", value={self.value}" if self.is_readable else "" rw_str = "write-only" if not self.is_readable else f"is_writable={self.is_writable}" - return f"NuconParameter(id='{self.id}'{value_str}, param_type={self.param_type.__name__}, {rw_str}{unit_str})" + admin_str = ", is_admin=True" if self.is_admin else "" + return f"NuconParameter(id='{self.id}'{value_str}, param_type={self.param_type.__name__}, {rw_str}{admin_str}{unit_str})" def __str__(self): return self.id class Nucon: - def __init__(self, host: str = 'localhost', port: int = 8785): + def __init__(self, host: str = 'localhost', port: int = 8785, admin_mode: bool = False): self.base_url = f'http://{host}:{port}/' self.dummy_mode = False + self.admin_mode = admin_mode self._parameters = self._create_parameters() def _create_parameters(self) -> Dict[str, NuconParameter]: @@ -354,47 +357,53 @@ class Nucon: 'CHEMICAL_CLEANING_PUMP_OVERLOAD_STATUS': (PumpOverloadStatus, False), } + # Write-only params: normal control setpoints (no admin restriction) write_only_values = { - # --- Core actions --- + # --- MSCVs (Main Steam Control Valves) --- + **{f'MSCV_{i}_OPENING_ORDERED': (float, True, 0, 100, '%') for i in range(3)}, + + # --- Steam turbine bypass setpoints --- + **{f'STEAM_TURBINE_{i}_BYPASS_ORDERED': (float, True, 0, 100, '%') for i in range(3)}, + + # --- Chemistry setpoints --- + 'CHEM_BORON_DOSAGE_ORDERED_RATE': (float, True, 0, 100, '%'), + 'CHEM_BORON_FILTER_ORDERED_SPEED': (float, True, 0, 100, '%'), + } + + # Write-only admin params: destructive/irreversible operations, blocked unless admin_mode=True + write_only_admin_values = { + # --- Core safety actions --- 'CORE_SCRAM_BUTTON': (bool, True), 'CORE_EMERGENCY_STOP': (bool, True), 'CORE_END_EMERGENCY_STOP': (bool, True), 'RESET_AO': (bool, True), - # --- Core bay commands (9 bays) --- + # --- Core bay physical operations --- **{f'CORE_BAY_{i}_HATCH': (bool, True) for i in range(1, 10)}, **{f'CORE_BAY_{i}_FUEL_LOADING': (int, True) for i in range(1, 10)}, - # --- Rods group command --- + # --- Bulk rod override --- 'RODS_ALL_POS_ORDERED': (float, True, 0, 100, '%'), - # --- MSCVs (Main Steam Control Valves) --- - **{f'MSCV_{i}_OPENING_ORDERED': (float, True, 0, 100, '%') for i in range(3)}, - - # --- Steam turbine --- + # --- Steam turbine trip --- 'STEAM_TURBINE_TRIP': (bool, True), - **{f'STEAM_TURBINE_{i}_BYPASS_ORDERED': (float, True, 0, 100, '%') for i in range(3)}, # --- Steam ejector valves --- 'STEAM_EJECTOR_CONDENSER_RETURN_VALVE': (bool, True), 'STEAM_EJECTOR_OPERATIONAL_MOTIVE_VALVE': (bool, True), 'STEAM_EJECTOR_STARTUP_MOTIVE_VALVE': (bool, True), - # --- Valve commands (take valve name as value) --- + # --- Generic valve commands (take valve name as value) --- 'VALVE_OPEN': (str, True), 'VALVE_CLOSE': (str, True), 'VALVE_OFF': (str, True), - # --- Condenser / emergency generators --- + # --- Infrastructure start/stop --- 'CONDENSER_VACUUM_PUMP_START_STOP': (bool, True), 'EMERGENCY_GENERATOR_1_START_STOP': (bool, True), 'EMERGENCY_GENERATOR_2_START_STOP': (bool, True), - # --- Chemistry --- - 'CHEM_BORON_DOSAGE_ORDERED_RATE': (float, True, 0, 100, '%'), - 'CHEM_BORON_FILTER_ORDERED_SPEED': (float, True, 0, 100, '%'), - - # --- Fun / event triggers --- + # --- Fun / event triggers (game cheats) --- 'FUN_IS_ENABLED': (bool, True), 'FUN_REQUEST_ENABLE': (bool, True), 'FUN_AO_SABOTAGE_ONCE': (bool, True), @@ -419,6 +428,8 @@ class Nucon: } for name, values in write_only_values.items(): params[name] = NuconParameter(self, name, *values, is_readable=False) + for name, values in write_only_admin_values.items(): + params[name] = NuconParameter(self, name, *values, is_readable=False, is_admin=True) return params def _parse_value(self, parameter: NuconParameter, value: str) -> Union[float, int, bool, str, Enum, None]: @@ -458,6 +469,8 @@ class Nucon: if not force and not parameter.is_writable: raise ValueError(f"Parameter {parameter} is not writable") + if not force and parameter.is_admin and not self.admin_mode: + raise ValueError(f"Parameter {parameter} is an admin parameter. Enable admin_mode on the Nucon instance or use force=True") if not force: parameter.check_in_range(value, raise_on_oob=True) @@ -594,6 +607,9 @@ class Nucon: def set_dummy_mode(self, dummy_mode: bool) -> None: self.dummy_mode = dummy_mode + def set_admin_mode(self, admin_mode: bool) -> None: + self.admin_mode = admin_mode + def __getattr__(self, name): if isinstance(name, int): return self.__getattr__(list(self._parameters.keys())[name])