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 <noreply@anthropic.com>
This commit is contained in:
Dominik Moritz Roth 2026-03-12 16:42:13 +01:00
parent 90616dcf69
commit 2ec68ff2e5

View File

@ -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])