diff --git a/backend/alembic/versions/beca7464b6b4_add_ai_build_session_kind.py b/backend/alembic/versions/beca7464b6b4_add_ai_build_session_kind.py new file mode 100644 index 00000000..ca247718 --- /dev/null +++ b/backend/alembic/versions/beca7464b6b4_add_ai_build_session_kind.py @@ -0,0 +1,48 @@ +"""add ai_build session kind + +Revision ID: beca7464b6b4 +Revises: b3358ba0e48c +Create Date: 2026-05-29 18:41:38.601537 + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision: str = 'beca7464b6b4' +down_revision: Union[str, None] = 'b3358ba0e48c' +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + op.drop_constraint("ck_l1_walk_sessions_session_kind", "l1_walk_sessions", type_="check") + op.create_check_constraint( + "ck_l1_walk_sessions_session_kind", "l1_walk_sessions", + "session_kind IN ('flow', 'proposal', 'adhoc', 'ai_build')", + ) + op.drop_constraint("ck_l1_walk_sessions_target_consistency", "l1_walk_sessions", type_="check") + op.create_check_constraint( + "ck_l1_walk_sessions_target_consistency", "l1_walk_sessions", + "(session_kind = 'flow' AND flow_id IS NOT NULL AND flow_proposal_id IS NULL) " + "OR (session_kind = 'proposal' AND flow_proposal_id IS NOT NULL AND flow_id IS NULL) " + "OR (session_kind IN ('adhoc', 'ai_build') AND flow_id IS NULL AND flow_proposal_id IS NULL)", + ) + + +def downgrade() -> None: + op.drop_constraint("ck_l1_walk_sessions_target_consistency", "l1_walk_sessions", type_="check") + op.create_check_constraint( + "ck_l1_walk_sessions_target_consistency", "l1_walk_sessions", + "(session_kind = 'flow' AND flow_id IS NOT NULL AND flow_proposal_id IS NULL) " + "OR (session_kind = 'proposal' AND flow_proposal_id IS NOT NULL AND flow_id IS NULL) " + "OR (session_kind = 'adhoc' AND flow_id IS NULL AND flow_proposal_id IS NULL)", + ) + op.drop_constraint("ck_l1_walk_sessions_session_kind", "l1_walk_sessions", type_="check") + op.create_check_constraint( + "ck_l1_walk_sessions_session_kind", "l1_walk_sessions", + "session_kind IN ('flow', 'proposal', 'adhoc')", + ) diff --git a/backend/app/models/l1_walk_session.py b/backend/app/models/l1_walk_session.py index 072fd587..2595571e 100644 --- a/backend/app/models/l1_walk_session.py +++ b/backend/app/models/l1_walk_session.py @@ -30,6 +30,7 @@ class L1WalkSession(Base): - flow: Walking a published flow (flow_id required, flow_proposal_id null). - proposal: Walking a draft flow proposal (flow_proposal_id required, flow_id null). - adhoc: Free-form investigation (both flow_id and flow_proposal_id null). + - ai_build: AI-generated decision-tree walk (both flow_id and flow_proposal_id null). status lifecycle: - active: Session is in progress. @@ -45,7 +46,7 @@ class L1WalkSession(Base): name="ck_l1_walk_sessions_ticket_kind", ), CheckConstraint( - "session_kind IN ('flow', 'proposal', 'adhoc')", + "session_kind IN ('flow', 'proposal', 'adhoc', 'ai_build')", name="ck_l1_walk_sessions_session_kind", ), CheckConstraint( @@ -55,7 +56,7 @@ class L1WalkSession(Base): CheckConstraint( "(session_kind = 'flow' AND flow_id IS NOT NULL AND flow_proposal_id IS NULL) " "OR (session_kind = 'proposal' AND flow_proposal_id IS NOT NULL AND flow_id IS NULL) " - "OR (session_kind = 'adhoc' AND flow_id IS NULL AND flow_proposal_id IS NULL)", + "OR (session_kind IN ('adhoc', 'ai_build') AND flow_id IS NULL AND flow_proposal_id IS NULL)", name="ck_l1_walk_sessions_target_consistency", ), ) diff --git a/backend/tests/test_l1_ai_build_model.py b/backend/tests/test_l1_ai_build_model.py new file mode 100644 index 00000000..a23bf0f1 --- /dev/null +++ b/backend/tests/test_l1_ai_build_model.py @@ -0,0 +1,16 @@ +import uuid + +from app.models.l1_walk_session import L1WalkSession + + +def test_ai_build_session_kind_allowed_by_model_constraint(): + """ai_build is a valid session_kind with both target FKs null (like adhoc).""" + s = L1WalkSession( + account_id=uuid.uuid4(), + created_by_user_id=uuid.uuid4(), + ticket_id="t1", + ticket_kind="internal", + session_kind="ai_build", + ) + assert s.session_kind == "ai_build" + assert s.flow_id is None and s.flow_proposal_id is None