"""L1 category allowlist + the always-forbidden hard floor. DEFAULT_L1_CATEGORIES seeds an account's enabled set. HARD_FLOOR_FORBIDDEN is a category-independent safety floor the AI tree builder must never emit and admins cannot enable. See spec §5.1/§5.2. """ from uuid import UUID from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from app.models.account import Account # WARNING: keep in sync with Account.enabled_l1_categories server_default in # app/models/account.py. The migration default (cb9e282267d2) is intentionally # a frozen copy and is NOT updated when this list changes. DEFAULT_L1_CATEGORIES: list[str] = [ "password_reset", "account_lockout", "printer", "email_outlook_client", "wifi_network_basics", "vpn_connect", "teams_zoom_av", "browser_cache_cookies", "peripheral_reconnect", "os_restart_update", ] # Always-forbidden action classes (keys are stable identifiers; the human-readable # phrasing lives in the builder system prompt). Admins cannot enable these. HARD_FLOOR_FORBIDDEN: list[str] = [ "registry_edit", "system_file_or_boot_edit", "data_or_disk_deletion", "credential_or_mfa_change", "security_or_av_or_firewall_change", "elevated_or_admin_script", "domain_dns_dhcp_change", "server_or_production_config", "billing_or_license_change", ] # Substrings that, if present in a generated node's text, indicate a hard-floor # violation. Used by ai_tree_builder per-node validation (defense in depth). HARD_FLOOR_TEXT_PATTERNS: list[str] = [ "regedit", "registry", "format ", "delete partition", "diskpart", "reset password for", "disable firewall", "disable antivirus", "disable defender", "run as administrator", "sudo ", "domain controller", "dns record", "dhcp scope", "uninstall security", "bitlocker", ] def is_category_enabled(category: str, enabled: list[str]) -> bool: """A category is buildable only if explicitly enabled and not hard-floored.""" if category in HARD_FLOOR_FORBIDDEN: return False return category in enabled async def get_enabled_categories(account_id: UUID, db: AsyncSession) -> list[str]: """Return the account's enabled L1 categories (``or []`` guards pre-default rows).""" acct = (await db.execute(select(Account).where(Account.id == account_id))).scalar_one() return list(acct.enabled_l1_categories or []) async def set_enabled_categories( account_id: UUID, categories: list[str], db: AsyncSession ) -> list[str]: """Persist the enabled set, dropping anything unknown or hard-floored. Hard-floored keys (HARD_FLOOR_FORBIDDEN) are by design never present in DEFAULT_L1_CATEGORIES, so the DEFAULT membership filter already excludes them. If you ever add a key to DEFAULT_L1_CATEGORIES, verify it is not also in HARD_FLOOR_FORBIDDEN. dict.fromkeys dedupes while preserving first-seen order. """ cleaned = list(dict.fromkeys(c for c in categories if c in DEFAULT_L1_CATEGORIES)) acct = (await db.execute(select(Account).where(Account.id == account_id))).scalar_one() acct.enabled_l1_categories = cleaned await db.flush() return cleaned