fix(l1): resolve PR #193 backend review findings (1,4,5,6,7,8,9,10)

Server-assigns a uuid4 id to every AI-generated node (Finding 1 showstopper:
nodes had no id but the advance protocol keys on node_id, so ai_build walks
never advanced past question 1). Replaces the hidden {"node_type":"meta"}
walked_path convention with real category/problem_text/pending_node columns on
l1_walk_sessions (migration 61dda4f615c6) — fixes junk proposals + off-by-one
depth cap (Findings 8,9), and pending_node replays the served node on re-mount
(no duplicate paid LLM call). Intake honors explicit flow_id and adhoc=True
(Findings 4,5); flow_proposals.l1_session_id FK -> CASCADE (Finding 6 time
bomb); L1 category GET is owner+admin like PATCH and require_account_owner_or_admin
delegates to User.can_manage_account (Finding 7); escalate falls back to default
recipients + filters deleted_at + warns when empty (Finding 10). Cleanups: dead
ticket_ref removed, IntakeResponse per-outcome validator, unused acknowledged
dropped, escalations partial index, restored a deleted audit assertion.

Full Phase 2A backend set: 110 passed / 0 failed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-09 15:55:45 -04:00
parent 42a4536c63
commit ac89e7b2fa
17 changed files with 592 additions and 80 deletions

View File

@@ -124,8 +124,9 @@ async def _create_adhoc_session(db: AsyncSession, info: dict, *, problem: str =
async def test_intake_build_creates_ai_build_session(client: AsyncClient, test_db: AsyncSession):
"""POST /l1/intake with a 'build' outcome creates an ai_build session.
Phase 2A: intake dispatches via match_or_build; 'adhoc' is no longer a direct
intake outcome (it is offered from the out_of_scope prompt on the frontend).
Phase 2A: intake dispatches via match_or_build. An explicit adhoc=True (the
out_of_scope prompt's "Walk it ad-hoc") starts an ad-hoc session directly —
see test_l1_api_ai_build.test_intake_adhoc_starts_adhoc_session.
"""
from unittest.mock import AsyncMock, patch
info = await _make_l1_user(client, test_db, email="l1intake@example.com")