"""Explicit, time-limited commissioning authority; NEVER enables shadow execution. Only a reviewed internal operator call can arm a trial, and only for a separate allowlist (empty by default). The manager and battery must additionally consent locally. Existing shadow plans/acknowledgements keep their original meaning. """ from copy import deepcopy from datetime import datetime, timedelta, timezone from uuid import UUID import json MAX_SECONDS = 1800 MAX_POWER_W = 5000 AUTHORITY_TTL_SECONDS = 90 def schema(con): con.execute('''CREATE TABLE IF NOT EXISTS planner_controlled_trials( plant TEXT PRIMARY KEY, session_id TEXT NOT NULL UNIQUE, value TEXT NOT NULL, revoked_at TEXT)''') def timestamp(v): if not isinstance(v, str): raise ValueError('Explicit timestamp required') t = datetime.fromisoformat(v.replace('Z', '+00:00')) if t.tzinfo is None: raise ValueError('Timezone required') return t.astimezone(timezone.utc) def uuid(v): if not isinstance(v, str) or str(UUID(v)) != v: raise ValueError('Canonical UUID required') return v def power(v): if type(v) not in (int, float) or not 0 < v <= MAX_POWER_W: raise ValueError('Pilot limit must be explicit and at most 5000 W') return float(v) def accounting(plan): # A human checkbox alone must not relabel aggregate house consumption. quality = plan.get('inputQuality', {}) if quality.get('loadBasis') != 'base_load': raise ValueError('Verified base-load/SDL adapter required before a control trial') evidence = quality.get('accountingEvidenceId') if not isinstance(evidence, str) or not 8 <= len(evidence) <= 160: raise ValueError('Plan lacks traceable base-load accounting evidence') return evidence def eligibility(view, plant): if view.get('installationId') != plant or view.get('liveEnabled') is not False: raise ValueError('Wrong plant or service mode') p = view.get('plan') if view.get('fresh') is not True or not isinstance(p, dict): raise ValueError('Fresh independently validated planning input required') if (p.get('installationId') != plant or p.get('runMode') != 'shadow' or p.get('liveEnabled') is not False or p.get('executable') is not True): raise ValueError('Wrong source plan identity or mode') settings = view.get('settings', {}) if settings.get('family') == 'auto' or p.get('sourceFamily') != settings.get('family'): raise ValueError('First controlled trial requires a fixed model family') if p.get('configRevision') != settings.get('revision'): raise ValueError('Configuration revision changed') if len(p.get('controlContext', {}).get('batteries', {})) != 1: raise ValueError('First controlled trial supports exactly one battery') accounting(p) return p def arm(store, plant, request, view, now, allowed_plants): """Caller is authenticated internal operator; not exposed via device proxy.""" if plant not in allowed_plants: raise ValueError('Controlled trial disabled by server allowlist') fields = {'sessionId', 'expectedPlanId', 'expectedRevision', 'durationSeconds', 'maxChargeW', 'maxDischargeW', 'assetId', 'managerId', 'batteryInstanceId', 'acceptEstimatedPeak', 'actuatorWatchdogEvidenceId', 'confirmation'} if set(request) != fields or request['confirmation'] != 'ARM_BOUNDED_CONTROL_TRIAL': raise ValueError('Explicit reviewed commissioning request required') session = uuid(request['sessionId']); p = eligibility(view, plant) if type(request['expectedRevision']) is not int or request['expectedRevision'] < 0: raise ValueError('Expected revision must be an integer') if request['expectedPlanId'] != p['planId'] or request['expectedRevision'] != p['configRevision']: raise ValueError('Source plan/revision changed; review again') seconds = request['durationSeconds'] if type(seconds) is not int or not 30 <= seconds <= MAX_SECONDS: raise ValueError('Trial duration must be 30..1800 seconds') for k in ('managerId', 'batteryInstanceId'): if type(request[k]) is not int or not 1 <= request[k] <= 99999: raise ValueError('Explicit local instance binding required') if request['managerId'] == request['batteryInstanceId']: raise ValueError('Manager and battery instance must differ') if request['assetId'] not in p['controlContext']['batteries']: raise ValueError('Wrong trial battery') if type(request['acceptEstimatedPeak']) is not bool: raise ValueError('Explicit estimated-peak policy required') if p.get('peakCostIsEstimate') and not request['acceptEstimatedPeak']: raise ValueError('Estimated peak has not been accepted for this trial') evidence = request['actuatorWatchdogEvidenceId'] if not isinstance(evidence, str) or not 8 <= len(evidence) <= 160: raise ValueError('Device-side command-loss watchdog proof required') value = {'kind': 'controlled_trial_grant', 'version': 1, 'sessionId': session, 'installationId': plant, 'issuedAt': now.isoformat(), 'expiresAt': (now + timedelta(seconds=seconds)).isoformat(), 'revision': p['configRevision'], 'family': p['sourceFamily'], 'assetId': request['assetId'], 'managerId': request['managerId'], 'batteryInstanceId': request['batteryInstanceId'], 'maxChargeW': power(request['maxChargeW']), 'maxDischargeW': power(request['maxDischargeW']), 'acceptEstimatedPeak': request['acceptEstimatedPeak'], 'accountingEvidenceId': accounting(p), 'actuatorWatchdogEvidenceId': evidence, 'controlContext': deepcopy(p['controlContext'])} with store.con: existing = store.con.execute('SELECT value,revoked_at FROM planner_controlled_trials WHERE plant=?', (plant,)).fetchone() if existing and existing[1] is None and timestamp(json.loads(existing[0])['expiresAt']) > now: raise ValueError('Existing trial must first end; implicit extension forbidden') # Reusing an expired/revoked session ID must never resurrect it. used = store.con.execute("SELECT 1 FROM planner_audit WHERE plant=? AND kind='trial_arm' AND detail=?", (plant, session)).fetchone() if used: raise ValueError('Session ID already used') store.con.execute('INSERT INTO planner_controlled_trials VALUES(?,?,?,NULL) ON CONFLICT(plant) DO UPDATE SET session_id=excluded.session_id,value=excluded.value,revoked_at=NULL', (plant, session, json.dumps(value, sort_keys=True, allow_nan=False))) store.con.execute("INSERT INTO planner_audit(plant,at,kind,detail) VALUES(?,?,'trial_arm',?)", (plant, now.isoformat(), session)) return value def revoke(store, plant, session, now): uuid(session) with store.con: store.con.execute('UPDATE planner_controlled_trials SET revoked_at=? WHERE plant=? AND session_id=?', (now.isoformat(), plant, session)) return {'status': 'revoked', 'sessionId': session, 'remoteRevocationMaxSeconds': AUTHORITY_TTL_SECONDS} def authority(store, plant, view, now, allowed_plants): """Separate authorization envelope, not a mutation of the shadow plan.""" if plant not in allowed_plants: return None row = store.con.execute('SELECT value,revoked_at FROM planner_controlled_trials WHERE plant=?', (plant,)).fetchone() if not row or row[1] is not None: return None grant = json.loads(row[0]) try: p = eligibility(view, plant) if not timestamp(grant['issuedAt']) <= now < timestamp(grant['expiresAt']): return None if (p['configRevision'] != grant['revision'] or p['sourceFamily'] != grant['family'] or p['controlContext'] != grant['controlContext'] or accounting(p) != grant['accountingEvidenceId'] or p.get('peakCostIsEstimate') and not grant['acceptEstimatedPeak']): return None except (ValueError, KeyError, TypeError): return None until = min(timestamp(grant['expiresAt']), timestamp(p['validUntil']), now + timedelta(seconds=AUTHORITY_TTL_SECONDS)) return {**grant, 'kind': 'controlled_trial_authority', 'sourceShadowPlanId': p['planId'], 'checkedAt': now.isoformat(), 'validUntil': until.isoformat(), 'sourcePlanRemainsShadow': True}