From 9a5cbc35ae5b37141184a6fa99cef20282948511 Mon Sep 17 00:00:00 2001 From: Michael Chihlas Date: Fri, 29 May 2026 14:49:14 -0400 Subject: [PATCH] feat(l1): add accounts.enabled_l1_categories with default allowlist Co-Authored-By: Claude Opus 4.7 --- ...2_add_enabled_l1_categories_to_accounts.py | 35 +++++++++++++++++++ backend/app/models/account.py | 13 ++++++- .../test_account_l1_categories_column.py | 7 ++++ 3 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 backend/alembic/versions/cb9e282267d2_add_enabled_l1_categories_to_accounts.py create mode 100644 backend/tests/test_account_l1_categories_column.py diff --git a/backend/alembic/versions/cb9e282267d2_add_enabled_l1_categories_to_accounts.py b/backend/alembic/versions/cb9e282267d2_add_enabled_l1_categories_to_accounts.py new file mode 100644 index 00000000..acea2e28 --- /dev/null +++ b/backend/alembic/versions/cb9e282267d2_add_enabled_l1_categories_to_accounts.py @@ -0,0 +1,35 @@ +"""add enabled_l1_categories to accounts + +Revision ID: cb9e282267d2 +Revises: beca7464b6b4 +Create Date: 2026-05-29 18:48:27.155183 + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import postgresql + + +# revision identifiers, used by Alembic. +revision: str = 'cb9e282267d2' +down_revision: Union[str, None] = 'beca7464b6b4' +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +_DEFAULT = ('["password_reset","account_lockout","printer","email_outlook_client",' + '"wifi_network_basics","vpn_connect","teams_zoom_av","browser_cache_cookies",' + '"peripheral_reconnect","os_restart_update"]') + + +def upgrade() -> None: + op.add_column("accounts", sa.Column( + "enabled_l1_categories", postgresql.JSONB(), nullable=False, + server_default=sa.text(f"'{_DEFAULT}'::jsonb"), + )) + + +def downgrade() -> None: + op.drop_column("accounts", "enabled_l1_categories") diff --git a/backend/app/models/account.py b/backend/app/models/account.py index 4162e844..6a081215 100644 --- a/backend/app/models/account.py +++ b/backend/app/models/account.py @@ -1,7 +1,7 @@ import uuid from datetime import datetime, timezone from typing import Optional, TYPE_CHECKING -from sqlalchemy import String, DateTime, ForeignKey, Boolean, Integer +from sqlalchemy import String, DateTime, ForeignKey, Boolean, Integer, text as sa_text from sqlalchemy.orm import Mapped, mapped_column, relationship from sqlalchemy.dialects.postgresql import UUID, JSONB from app.core.database import Base @@ -67,6 +67,17 @@ class Account(Base): sso_provider: Mapped[Optional[str]] = mapped_column(String(20), nullable=True) # "saml" | "oidc" sso_config: Mapped[Optional[dict]] = mapped_column(JSONB, nullable=True) + # L1 AI tree builder — per-account allowlist of problem categories + enabled_l1_categories: Mapped[list[str]] = mapped_column( + JSONB(), nullable=False, + server_default=sa_text( + "'[\"password_reset\",\"account_lockout\",\"printer\"," + "\"email_outlook_client\",\"wifi_network_basics\",\"vpn_connect\"," + "\"teams_zoom_av\",\"browser_cache_cookies\",\"peripheral_reconnect\"," + "\"os_restart_update\"]'::jsonb" + ), + ) + # Relationships owner: Mapped["User"] = relationship("User", foreign_keys=[owner_id], back_populates="owned_account") users: Mapped[list["User"]] = relationship("User", foreign_keys="[User.account_id]", back_populates="account") diff --git a/backend/tests/test_account_l1_categories_column.py b/backend/tests/test_account_l1_categories_column.py new file mode 100644 index 00000000..1407ca1a --- /dev/null +++ b/backend/tests/test_account_l1_categories_column.py @@ -0,0 +1,7 @@ +from app.models.account import Account + + +def test_account_has_enabled_l1_categories_default(): + a = Account(name="Acme", display_code="ABC12345") + # Column default is applied at flush; attribute may be None pre-flush. + assert hasattr(a, "enabled_l1_categories")