feat(l1): category service (defaults + hard floor) and AI action keys
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
69
backend/app/services/l1_category_service.py
Normal file
69
backend/app/services/l1_category_service.py
Normal file
@@ -0,0 +1,69 @@
|
||||
"""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
|
||||
Reference in New Issue
Block a user