- docs/architecture/: god-node map + report (2026-05-06), workflows.json/html + analysis snapshot - docs/plans/2026-05-13-public-landing-routing-refactor.md - docs/tutorials/build-a-page.md - abc-feat-self-serve-signup-phase-2-design-20260507-112020.md (root) Core dumps (core.144926, core.145678, docs/architecture/core.1392564) and agent .remember/ state are intentionally left untracked. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
4094 lines
156 KiB
JSON
4094 lines
156 KiB
JSON
{
|
|
"meta": {
|
|
"project": "ResolutionFlow",
|
|
"generated": "2026-05-13",
|
|
"description": "User-action workflows traced through the codebase at file-level granularity. Each flow is an ordered sequence of steps showing how data passes between files/services.",
|
|
"layers": [
|
|
"page",
|
|
"component",
|
|
"hook",
|
|
"store",
|
|
"api_client",
|
|
"endpoint",
|
|
"service",
|
|
"core",
|
|
"model",
|
|
"db",
|
|
"external"
|
|
],
|
|
"groups": [
|
|
"Integrations",
|
|
"Sessions & FlowPilot",
|
|
"Team & Billing",
|
|
"Flow Authoring",
|
|
"Auth & Access",
|
|
"Tools"
|
|
]
|
|
},
|
|
"nodes": [
|
|
{
|
|
"id": "frontend/src/pages/AcceptInvitePage.tsx",
|
|
"label": "AcceptInvitePage",
|
|
"file": "frontend/src/pages/AcceptInvitePage.tsx",
|
|
"layer": "page",
|
|
"description": ""
|
|
},
|
|
{
|
|
"id": "frontend/src/pages/AccountSettingsPage.tsx",
|
|
"label": "AccountSettingsPage",
|
|
"file": "frontend/src/pages/AccountSettingsPage.tsx",
|
|
"layer": "page",
|
|
"description": ""
|
|
},
|
|
{
|
|
"id": "frontend/src/pages/FlowPilotSessionPage.tsx",
|
|
"label": "FlowPilotSessionPage",
|
|
"file": "frontend/src/pages/FlowPilotSessionPage.tsx",
|
|
"layer": "page",
|
|
"description": "Route /pilot/:id? \u2014 mounts FlowPilotSession; hosts Resolve/Escalate/Abandon modals; useBlocker triggers pause-on-leave"
|
|
},
|
|
{
|
|
"id": "frontend/src/pages/ForgotPasswordPage.tsx",
|
|
"label": "ForgotPasswordPage",
|
|
"file": "frontend/src/pages/ForgotPasswordPage.tsx",
|
|
"layer": "page",
|
|
"description": "Stateless email submission form. Calls authApi.forgotPassword(email). Always shows success regardless of error (anti-enumeration)."
|
|
},
|
|
{
|
|
"id": "frontend/src/pages/LoginPage.tsx",
|
|
"label": "LoginPage",
|
|
"file": "frontend/src/pages/LoginPage.tsx",
|
|
"layer": "page",
|
|
"description": "Password login form. Calls authStore.login(), then navigates to /change-password if must_change_password, otherwise to the originally-requested route."
|
|
},
|
|
{
|
|
"id": "frontend/src/pages/OAuthCallbackPage.tsx",
|
|
"label": "OAuthCallbackPage",
|
|
"file": "frontend/src/pages/OAuthCallbackPage.tsx",
|
|
"layer": "page",
|
|
"description": "Mounted at /auth/google/callback and /auth/microsoft/callback. Validates CSRF state from sessionStorage, calls authApi.googleCallback or authApi.microsoftCallback, persists tokens to localStorage and zustand, calls fetchUser(), then navigates to / or /welcome."
|
|
},
|
|
{
|
|
"id": "frontend/src/pages/PricingPage.tsx",
|
|
"label": "PricingPage",
|
|
"file": "frontend/src/pages/PricingPage.tsx",
|
|
"layer": "page",
|
|
"description": ""
|
|
},
|
|
{
|
|
"id": "frontend/src/pages/ProceduralEditorPage.tsx",
|
|
"label": "/flows/new",
|
|
"file": "frontend/src/pages/ProceduralEditorPage.tsx",
|
|
"layer": "page",
|
|
"description": ""
|
|
},
|
|
{
|
|
"id": "frontend/src/pages/RegisterPage.tsx",
|
|
"label": "RegisterPage",
|
|
"file": "frontend/src/pages/RegisterPage.tsx",
|
|
"layer": "page",
|
|
"description": "Self-serve registration form. Collects name/email/password, optionally invite_code. Also owns the OAuth redirect initiation (buildOAuthAuthorizeUrl + window.location.href) for Google and Microsoft."
|
|
},
|
|
{
|
|
"id": "frontend/src/pages/ResetPasswordPage.tsx",
|
|
"label": "ResetPasswordPage",
|
|
"file": "frontend/src/pages/ResetPasswordPage.tsx",
|
|
"layer": "page",
|
|
"description": "Reads ?token from URL, calls authApi.verifyResetToken() on mount, then on submit calls authApi.resetPassword(token, newPassword), navigates to /login on success."
|
|
},
|
|
{
|
|
"id": "frontend/src/pages/ReviewQueuePage.tsx",
|
|
"label": "/admin/review-queue",
|
|
"file": "frontend/src/pages/ReviewQueuePage.tsx",
|
|
"layer": "page",
|
|
"description": ""
|
|
},
|
|
{
|
|
"id": "frontend/src/pages/ScriptBuilderPage.tsx",
|
|
"label": "ScriptBuilderPage",
|
|
"file": "frontend/src/pages/ScriptBuilderPage.tsx",
|
|
"layer": "page",
|
|
"description": ""
|
|
},
|
|
{
|
|
"id": "frontend/src/pages/TreeEditorPage.tsx",
|
|
"label": "/trees/new",
|
|
"file": "frontend/src/pages/TreeEditorPage.tsx",
|
|
"layer": "page",
|
|
"description": ""
|
|
},
|
|
{
|
|
"id": "frontend/src/pages/VerifyEmailPage.tsx",
|
|
"label": "VerifyEmailPage",
|
|
"file": "frontend/src/pages/VerifyEmailPage.tsx",
|
|
"layer": "page",
|
|
"description": "Reads ?token from URL. Fires POST /auth/email/verify exactly once (useRef guard). On success calls authStore.fetchUser() then navigates to /?verified=1. Error state shows a resend button."
|
|
},
|
|
{
|
|
"id": "frontend/src/pages/account/BillingPage.tsx",
|
|
"label": "BillingPage",
|
|
"file": "frontend/src/pages/account/BillingPage.tsx",
|
|
"layer": "page",
|
|
"description": ""
|
|
},
|
|
{
|
|
"id": "frontend/src/pages/account/IntegrationsPage.tsx",
|
|
"label": "IntegrationsPage",
|
|
"file": "frontend/src/pages/account/IntegrationsPage.tsx",
|
|
"layer": "page",
|
|
"description": ""
|
|
},
|
|
{
|
|
"id": "frontend/src/components/dashboard/NextStepCard.tsx",
|
|
"label": "NextStepCard",
|
|
"file": "frontend/src/components/dashboard/NextStepCard.tsx",
|
|
"layer": "component",
|
|
"description": "Onboarding widget \u2014 'Start a session' CTA fires FOCUS_START_SESSION_EVENT to StartSessionInput"
|
|
},
|
|
{
|
|
"id": "frontend/src/components/dashboard/StartSessionInput.tsx",
|
|
"label": "StartSessionInput",
|
|
"file": "frontend/src/components/dashboard/StartSessionInput.tsx",
|
|
"layer": "component",
|
|
"description": "Dashboard textarea \u2014 collects problem text, optional logs, pending uploads; navigates to /pilot with router state"
|
|
},
|
|
{
|
|
"id": "frontend/src/components/editor-ai/ChatTab.tsx",
|
|
"label": "ChatTab",
|
|
"file": "frontend/src/components/editor-ai/ChatTab.tsx",
|
|
"layer": "component",
|
|
"description": ""
|
|
},
|
|
{
|
|
"id": "frontend/src/components/editor-ai/EditorAIPanel.tsx",
|
|
"label": "EditorAIPanel",
|
|
"file": "frontend/src/components/editor-ai/EditorAIPanel.tsx",
|
|
"layer": "component",
|
|
"description": ""
|
|
},
|
|
{
|
|
"id": "frontend/src/components/flowpilot/EscalateModal.tsx",
|
|
"label": "EscalateModal",
|
|
"file": "frontend/src/components/flowpilot/EscalateModal.tsx",
|
|
"layer": "component",
|
|
"description": "Modal prompting for escalation reason text; calls fp.escalateSession from FlowPilotSessionPage"
|
|
},
|
|
{
|
|
"id": "frontend/src/components/flowpilot/FlowPilotMessageBar.tsx",
|
|
"label": "FlowPilotMessageBar",
|
|
"file": "frontend/src/components/flowpilot/FlowPilotMessageBar.tsx",
|
|
"layer": "component",
|
|
"description": "Chat composer inside active session \u2014 textarea + file attach + paste-image; fires onRespond with {free_text_input} or upload IDs via uploadsApi"
|
|
},
|
|
{
|
|
"id": "frontend/src/components/flowpilot/FlowPilotSession.tsx",
|
|
"label": "FlowPilotSession",
|
|
"file": "frontend/src/components/flowpilot/FlowPilotSession.tsx",
|
|
"layer": "component",
|
|
"description": "Main session view \u2014 renders step cards, sidebar metadata, message bar, paused banner; delegates respond/resume/rate to props from page"
|
|
},
|
|
{
|
|
"id": "frontend/src/components/flowpilot/FlowPilotStepCard.tsx",
|
|
"label": "FlowPilotStepCard",
|
|
"file": "frontend/src/components/flowpilot/FlowPilotStepCard.tsx",
|
|
"layer": "component",
|
|
"description": "Renders a single diagnostic step (question/action/resolution_suggestion/fork); calls onRespond on option select or free-text submit"
|
|
},
|
|
{
|
|
"id": "frontend/src/components/flowpilot/ProposalCard.tsx",
|
|
"label": "ProposalCard",
|
|
"file": "frontend/src/components/flowpilot/ProposalCard.tsx",
|
|
"layer": "component",
|
|
"description": ""
|
|
},
|
|
{
|
|
"id": "frontend/src/components/flowpilot/ProposalDetail.tsx",
|
|
"label": "ProposalDetail",
|
|
"file": "frontend/src/components/flowpilot/ProposalDetail.tsx",
|
|
"layer": "component",
|
|
"description": ""
|
|
},
|
|
{
|
|
"id": "frontend/src/components/subscription/CheckoutButton.tsx",
|
|
"label": "CheckoutButton",
|
|
"file": "frontend/src/components/subscription/CheckoutButton.tsx",
|
|
"layer": "component",
|
|
"description": ""
|
|
},
|
|
{
|
|
"id": "frontend/src/components/tree-editor/TreeEditorLayout.tsx",
|
|
"label": "TreeEditorLayout",
|
|
"file": "frontend/src/components/tree-editor/TreeEditorLayout.tsx",
|
|
"layer": "component",
|
|
"description": ""
|
|
},
|
|
{
|
|
"id": "frontend/src/hooks/useBillingPoll.ts",
|
|
"label": "useBillingPoll",
|
|
"file": "frontend/src/hooks/useBillingPoll.ts",
|
|
"layer": "hook",
|
|
"description": ""
|
|
},
|
|
{
|
|
"id": "frontend/src/hooks/useEditorAI.ts",
|
|
"label": "useEditorAI hook",
|
|
"file": "frontend/src/hooks/useEditorAI.ts",
|
|
"layer": "hook",
|
|
"description": ""
|
|
},
|
|
{
|
|
"id": "frontend/src/hooks/useFlowPilotSession.ts",
|
|
"label": "useFlowPilotSession",
|
|
"file": "frontend/src/hooks/useFlowPilotSession.ts",
|
|
"layer": "hook",
|
|
"description": "Central state hook \u2014 owns session, steps, isLoading, isProcessing; all action methods delegate to aiSessionsApi"
|
|
},
|
|
{
|
|
"id": "frontend/src/hooks/useSubscription.ts",
|
|
"label": "useSubscription",
|
|
"file": "frontend/src/hooks/useSubscription.ts",
|
|
"layer": "hook",
|
|
"description": ""
|
|
},
|
|
{
|
|
"id": "frontend/src/hooks/useTicketContext.ts",
|
|
"label": "useTicketContext",
|
|
"file": "frontend/src/hooks/useTicketContext.ts",
|
|
"layer": "hook",
|
|
"description": ""
|
|
},
|
|
{
|
|
"id": "frontend/src/store/authStore.ts",
|
|
"label": "authStore",
|
|
"file": "frontend/src/store/authStore.ts",
|
|
"layer": "store",
|
|
"description": "Zustand store (persisted). Holds user/token/account/subscription. login() calls authApi.login then fetchUser(). register() calls authApi.register then login(). fetchUser() fans out to GET /auth/me + GET /accounts/me + GET /accounts/me/subscription in parallel."
|
|
},
|
|
{
|
|
"id": "frontend/src/store/billingStore.ts",
|
|
"label": "billingStore",
|
|
"file": "frontend/src/store/billingStore.ts",
|
|
"layer": "store",
|
|
"description": ""
|
|
},
|
|
{
|
|
"id": "frontend/src/store/proceduralEditorStore.ts",
|
|
"label": "proceduralEditorStore (zustand+zundo+immer)",
|
|
"file": "frontend/src/store/proceduralEditorStore.ts",
|
|
"layer": "store",
|
|
"description": ""
|
|
},
|
|
{
|
|
"id": "frontend/src/store/treeEditorStore.ts",
|
|
"label": "treeEditorStore (zustand+zundo+immer)",
|
|
"file": "frontend/src/store/treeEditorStore.ts",
|
|
"layer": "store",
|
|
"description": ""
|
|
},
|
|
{
|
|
"id": "frontend/src/api/accounts.ts",
|
|
"label": "accounts",
|
|
"file": "frontend/src/api/accounts.ts",
|
|
"layer": "api_client",
|
|
"description": ""
|
|
},
|
|
{
|
|
"id": "frontend/src/api/aiSessions.ts",
|
|
"label": "aiSessionsApi",
|
|
"file": "frontend/src/api/aiSessions.ts",
|
|
"layer": "api_client",
|
|
"description": "Frontend API client for all /ai-sessions/* endpoints including create, chat, respond, resolve, escalate, pause, abandon"
|
|
},
|
|
{
|
|
"id": "frontend/src/api/auth.ts",
|
|
"label": "authApi",
|
|
"file": "frontend/src/api/auth.ts",
|
|
"layer": "api_client",
|
|
"description": "Typed wrapper around apiClient for all auth endpoints: register, login (JSON), logout, me, forgotPassword, verifyResetToken, resetPassword, verifyEmail, sendVerificationEmail, googleCallback, microsoftCallback."
|
|
},
|
|
{
|
|
"id": "frontend/src/api/billing.ts",
|
|
"label": "billing",
|
|
"file": "frontend/src/api/billing.ts",
|
|
"layer": "api_client",
|
|
"description": ""
|
|
},
|
|
{
|
|
"id": "frontend/src/api/editorAI.ts",
|
|
"label": "editorAIApi (axios)",
|
|
"file": "frontend/src/api/editorAI.ts",
|
|
"layer": "api_client",
|
|
"description": ""
|
|
},
|
|
{
|
|
"id": "frontend/src/api/flowProposals.ts",
|
|
"label": "flowProposalsApi (axios)",
|
|
"file": "frontend/src/api/flowProposals.ts",
|
|
"layer": "api_client",
|
|
"description": ""
|
|
},
|
|
{
|
|
"id": "frontend/src/api/integrations.ts",
|
|
"label": "integrations",
|
|
"file": "frontend/src/api/integrations.ts",
|
|
"layer": "api_client",
|
|
"description": ""
|
|
},
|
|
{
|
|
"id": "frontend/src/api/invite.ts",
|
|
"label": "invite",
|
|
"file": "frontend/src/api/invite.ts",
|
|
"layer": "api_client",
|
|
"description": ""
|
|
},
|
|
{
|
|
"id": "frontend/src/api/plans.ts",
|
|
"label": "plans",
|
|
"file": "frontend/src/api/plans.ts",
|
|
"layer": "api_client",
|
|
"description": ""
|
|
},
|
|
{
|
|
"id": "frontend/src/api/psaContext.ts",
|
|
"label": "psaContext",
|
|
"file": "frontend/src/api/psaContext.ts",
|
|
"layer": "api_client",
|
|
"description": ""
|
|
},
|
|
{
|
|
"id": "frontend/src/api/scriptBuilder.ts",
|
|
"label": "scriptBuilder",
|
|
"file": "frontend/src/api/scriptBuilder.ts",
|
|
"layer": "api_client",
|
|
"description": ""
|
|
},
|
|
{
|
|
"id": "frontend/src/api/sessionSuggestedFixes.ts",
|
|
"label": "sessionSuggestedFixesApi",
|
|
"file": "frontend/src/api/sessionSuggestedFixes.ts",
|
|
"layer": "api_client",
|
|
"description": "Frontend client for suggested-fix lifecycle: getActive, applyFix, patchOutcome, resolution-note/escalation-package previews and posts"
|
|
},
|
|
{
|
|
"id": "frontend/src/api/trees.ts",
|
|
"label": "treesApi (axios)",
|
|
"file": "frontend/src/api/trees.ts",
|
|
"layer": "api_client",
|
|
"description": ""
|
|
},
|
|
{
|
|
"id": "frontend/src/api/uploads.ts",
|
|
"label": "uploadsApi",
|
|
"file": "frontend/src/api/uploads.ts",
|
|
"layer": "api_client",
|
|
"description": "Frontend API client for multipart /uploads POST; used in StartSessionInput and FlowPilotMessageBar"
|
|
},
|
|
{
|
|
"id": "backend/app/api/endpoints/account_invite_lookup.py",
|
|
"label": "account_invite_lookup",
|
|
"file": "backend/app/api/endpoints/account_invite_lookup.py",
|
|
"layer": "endpoint",
|
|
"description": ""
|
|
},
|
|
{
|
|
"id": "backend/app/api/endpoints/accounts.py",
|
|
"label": "accounts",
|
|
"file": "backend/app/api/endpoints/accounts.py",
|
|
"layer": "endpoint",
|
|
"description": ""
|
|
},
|
|
{
|
|
"id": "backend/app/api/endpoints/ai_chat.py",
|
|
"label": "POST /ai/chat/sessions",
|
|
"file": "backend/app/api/endpoints/ai_chat.py",
|
|
"layer": "endpoint",
|
|
"description": ""
|
|
},
|
|
{
|
|
"id": "backend/app/api/endpoints/ai_sessions.py",
|
|
"label": "ai_sessions endpoint",
|
|
"file": "backend/app/api/endpoints/ai_sessions.py",
|
|
"layer": "endpoint",
|
|
"description": "FastAPI router /ai-sessions \u2014 create, chat, respond, resolve, escalate (via HandoffManager), pause, abandon, pickup, list, get"
|
|
},
|
|
{
|
|
"id": "backend/app/api/endpoints/auth.py",
|
|
"label": "auth endpoint",
|
|
"file": "backend/app/api/endpoints/auth.py",
|
|
"layer": "endpoint",
|
|
"description": "FastAPI router prefix=/auth. Endpoints: POST /register, POST /login, POST /login/json, POST /refresh, GET /me, POST /logout, POST /password/change, POST /password/forgot, POST /password/verify-reset-token, POST /password/reset, POST /email/send-verification, POST /email/verify."
|
|
},
|
|
{
|
|
"id": "backend/app/api/endpoints/billing.py",
|
|
"label": "billing",
|
|
"file": "backend/app/api/endpoints/billing.py",
|
|
"layer": "endpoint",
|
|
"description": ""
|
|
},
|
|
{
|
|
"id": "backend/app/api/endpoints/flow_proposals.py",
|
|
"label": "GET /flow-proposals",
|
|
"file": "backend/app/api/endpoints/flow_proposals.py",
|
|
"layer": "endpoint",
|
|
"description": ""
|
|
},
|
|
{
|
|
"id": "backend/app/api/endpoints/integrations.py",
|
|
"label": "integrations",
|
|
"file": "backend/app/api/endpoints/integrations.py",
|
|
"layer": "endpoint",
|
|
"description": ""
|
|
},
|
|
{
|
|
"id": "backend/app/api/endpoints/invite.py",
|
|
"label": "invite",
|
|
"file": "backend/app/api/endpoints/invite.py",
|
|
"layer": "endpoint",
|
|
"description": ""
|
|
},
|
|
{
|
|
"id": "backend/app/api/endpoints/oauth.py",
|
|
"label": "oauth endpoint",
|
|
"file": "backend/app/api/endpoints/oauth.py",
|
|
"layer": "endpoint",
|
|
"description": "FastAPI router prefix=/auth. POST /google/callback and POST /microsoft/callback. Delegates code exchange to oauth_providers, then calls _sign_in_or_register() to upsert user/account/OAuthIdentity, issues JWT tokens, stores refresh token JTI."
|
|
},
|
|
{
|
|
"id": "backend/app/api/endpoints/plans_public.py",
|
|
"label": "plans_public",
|
|
"file": "backend/app/api/endpoints/plans_public.py",
|
|
"layer": "endpoint",
|
|
"description": ""
|
|
},
|
|
{
|
|
"id": "backend/app/api/endpoints/script_builder.py",
|
|
"label": "script_builder",
|
|
"file": "backend/app/api/endpoints/script_builder.py",
|
|
"layer": "endpoint",
|
|
"description": ""
|
|
},
|
|
{
|
|
"id": "backend/app/api/endpoints/session_suggested_fixes.py",
|
|
"label": "session_suggested_fixes endpoint",
|
|
"file": "backend/app/api/endpoints/session_suggested_fixes.py",
|
|
"layer": "endpoint",
|
|
"description": "Endpoints for suggested-fix lifecycle (active, decision, apply, PATCH /outcome), resolution-note and escalation-package preview/post"
|
|
},
|
|
{
|
|
"id": "backend/app/api/endpoints/sessions.py",
|
|
"label": "sessions",
|
|
"file": "backend/app/api/endpoints/sessions.py",
|
|
"layer": "endpoint",
|
|
"description": ""
|
|
},
|
|
{
|
|
"id": "backend/app/api/endpoints/trees.py",
|
|
"label": "POST /trees",
|
|
"file": "backend/app/api/endpoints/trees.py",
|
|
"layer": "endpoint",
|
|
"description": ""
|
|
},
|
|
{
|
|
"id": "backend/app/api/endpoints/uploads.py",
|
|
"label": "uploads endpoint",
|
|
"file": "backend/app/api/endpoints/uploads.py",
|
|
"layer": "endpoint",
|
|
"description": "POST /uploads \u2014 validates, stores to S3 via storage_service; background task generates AI description; returns presigned URL"
|
|
},
|
|
{
|
|
"id": "backend/app/api/endpoints/webhooks.py",
|
|
"label": "webhooks",
|
|
"file": "backend/app/api/endpoints/webhooks.py",
|
|
"layer": "endpoint",
|
|
"description": ""
|
|
},
|
|
{
|
|
"id": "backend/app/core/ai_chat_service.py",
|
|
"label": "ai_chat_service (start_chat_session / send_message)",
|
|
"file": "backend/app/core/ai_chat_service.py",
|
|
"layer": "service",
|
|
"description": ""
|
|
},
|
|
{
|
|
"id": "backend/app/core/email.py",
|
|
"label": "EmailService",
|
|
"file": "backend/app/core/email.py",
|
|
"layer": "service",
|
|
"description": "Best-effort email dispatch via Resend SDK. Methods: send_password_reset_email, send_email_verification_email, send_invite_email, send_account_invite_email, send_welcome_email. Never raises on failure."
|
|
},
|
|
{
|
|
"id": "backend/app/services/assistant_chat_service.py",
|
|
"label": "assistant_chat_service (_call_ai)",
|
|
"file": "backend/app/services/assistant_chat_service.py",
|
|
"layer": "service",
|
|
"description": "Shared AI calling infrastructure: ASSISTANT_SYSTEM_PROMPT, _call_ai (Anthropic beta with MCP, prompt caching, multimodal), _auto_title"
|
|
},
|
|
{
|
|
"id": "backend/app/services/billing.py",
|
|
"label": "billing",
|
|
"file": "backend/app/services/billing.py",
|
|
"layer": "service",
|
|
"description": ""
|
|
},
|
|
{
|
|
"id": "backend/app/services/escalation_package_generator.py",
|
|
"label": "EscalationPackageGeneratorService",
|
|
"file": "backend/app/services/escalation_package_generator.py",
|
|
"layer": "service",
|
|
"description": "Generates five-section Escalate handoff markdown via Sonnet; cached by (session_id, state_version)"
|
|
},
|
|
{
|
|
"id": "backend/app/services/flowpilot_engine.py",
|
|
"label": "flowpilot_engine",
|
|
"file": "backend/app/services/flowpilot_engine.py",
|
|
"layer": "service",
|
|
"description": "Core guided-session orchestration: classify intake, match flows, call LLM, create AISession + steps, process_response, resolve/pause/abandon"
|
|
},
|
|
{
|
|
"id": "backend/app/services/handoff_manager.py",
|
|
"label": "HandoffManager",
|
|
"file": "backend/app/services/handoff_manager.py",
|
|
"layer": "service",
|
|
"description": "Creates SessionHandoff rows for park/escalate, dual-writes escalation_package, finalizes documentation, dispatches notifications via EscalationBus + email; enrich_escalation_async runs AI assessment in background"
|
|
},
|
|
{
|
|
"id": "backend/app/services/oauth_providers.py",
|
|
"label": "oauth_providers",
|
|
"file": "backend/app/services/oauth_providers.py",
|
|
"layer": "service",
|
|
"description": "HTTP helpers for OAuth code exchange. google_exchange_code: POSTs to https://oauth2.googleapis.com/token, GETs https://openidconnect.googleapis.com/v1/userinfo. microsoft_exchange_code: POSTs to https://login.microsoftonline.com/common/oauth2/v2.0/token, GETs https://graph.microsoft.com/v1.0/me. Returns OAuthProfile(provider_subject, email, name)."
|
|
},
|
|
{
|
|
"id": "backend/app/services/psa/base.py",
|
|
"label": "base",
|
|
"file": "backend/app/services/psa/base.py",
|
|
"layer": "service",
|
|
"description": ""
|
|
},
|
|
{
|
|
"id": "backend/app/services/psa/connectwise/provider.py",
|
|
"label": "provider",
|
|
"file": "backend/app/services/psa/connectwise/provider.py",
|
|
"layer": "service",
|
|
"description": ""
|
|
},
|
|
{
|
|
"id": "backend/app/services/psa/encryption.py",
|
|
"label": "encryption",
|
|
"file": "backend/app/services/psa/encryption.py",
|
|
"layer": "service",
|
|
"description": ""
|
|
},
|
|
{
|
|
"id": "backend/app/services/psa/registry.py",
|
|
"label": "registry",
|
|
"file": "backend/app/services/psa/registry.py",
|
|
"layer": "service",
|
|
"description": ""
|
|
},
|
|
{
|
|
"id": "backend/app/services/psa/ticket_context.py",
|
|
"label": "ticket_context",
|
|
"file": "backend/app/services/psa/ticket_context.py",
|
|
"layer": "service",
|
|
"description": ""
|
|
},
|
|
{
|
|
"id": "backend/app/services/psa_writeback_service.py",
|
|
"label": "psa_writeback_service",
|
|
"file": "backend/app/services/psa_writeback_service.py",
|
|
"layer": "service",
|
|
"description": ""
|
|
},
|
|
{
|
|
"id": "backend/app/services/resolution_note_generator.py",
|
|
"label": "ResolutionNoteGeneratorService",
|
|
"file": "backend/app/services/resolution_note_generator.py",
|
|
"layer": "service",
|
|
"description": "Generates four-section Resolve markdown (Problem/Confirmed/Root cause/Resolution) via Sonnet; cached by (session_id, state_version)"
|
|
},
|
|
{
|
|
"id": "backend/app/services/script_builder_service.py",
|
|
"label": "script_builder_service",
|
|
"file": "backend/app/services/script_builder_service.py",
|
|
"layer": "service",
|
|
"description": ""
|
|
},
|
|
{
|
|
"id": "backend/app/services/storage_service.py",
|
|
"label": "storage_service",
|
|
"file": "backend/app/services/storage_service.py",
|
|
"layer": "service",
|
|
"description": "S3-compatible upload/download/presign via boto3 against Railway Object Storage; resize_image_for_vision resizes to Claude's 1568px max; fetch_upload_images returns base64 dicts for vision calls"
|
|
},
|
|
{
|
|
"id": "backend/app/services/unified_chat_service.py",
|
|
"label": "unified_chat_service",
|
|
"file": "backend/app/services/unified_chat_service.py",
|
|
"layer": "service",
|
|
"description": "Chat session message handling: RAG search, _call_ai, parses [QUESTIONS]/[ACTIONS]/[FORK]/[PROMOTE]/[SUGGEST_FIX]/[FIX_OUTCOME] markers, persists task lane and suggested fixes"
|
|
},
|
|
{
|
|
"id": "backend/app/api/deps.py",
|
|
"label": "deps",
|
|
"file": "backend/app/api/deps.py",
|
|
"layer": "core",
|
|
"description": "FastAPI dependency functions: get_current_user (decodes JWT, fetches User), get_current_active_user (checks is_active + must_change_password), get_refresh_token_payload (validates refresh JWT type), require_verified_email_after_grace (7-day grace window)."
|
|
},
|
|
{
|
|
"id": "backend/app/core/ai_provider.py",
|
|
"label": "ai_provider (get_ai_provider)",
|
|
"file": "backend/app/core/ai_provider.py",
|
|
"layer": "core",
|
|
"description": "Provider abstraction for Anthropic/Gemini; AnthropicProvider and GeminiProvider implement generate_json/stream; handles prompt caching policy"
|
|
},
|
|
{
|
|
"id": "backend/app/core/ai_tree_generator_service.py",
|
|
"label": "ai_tree_generator_service (scaffold/branch_detail/assemble)",
|
|
"file": "backend/app/core/ai_tree_generator_service.py",
|
|
"layer": "core",
|
|
"description": ""
|
|
},
|
|
{
|
|
"id": "backend/app/core/ai_tree_validator.py",
|
|
"label": "ai_tree_validator",
|
|
"file": "backend/app/core/ai_tree_validator.py",
|
|
"layer": "core",
|
|
"description": ""
|
|
},
|
|
{
|
|
"id": "backend/app/core/security.py",
|
|
"label": "security",
|
|
"file": "backend/app/core/security.py",
|
|
"layer": "core",
|
|
"description": "Core crypto helpers: verify_password/get_password_hash (bcrypt), create_access_token (type=access, exp=ACCESS_TOKEN_EXPIRE_MINUTES), create_refresh_token (type=refresh, jti=uuid4), create_password_reset_token (type=password_reset, exp=30m), create_email_verification_token (type=email_verification, exp=24h), decode_token, hash_token (SHA-256 of JTI)."
|
|
},
|
|
{
|
|
"id": "backend/app/core/stripe_handlers.py",
|
|
"label": "stripe_handlers",
|
|
"file": "backend/app/core/stripe_handlers.py",
|
|
"layer": "core",
|
|
"description": ""
|
|
},
|
|
{
|
|
"id": "backend/app/models/account_invite.py",
|
|
"label": "account_invite",
|
|
"file": "backend/app/models/account_invite.py",
|
|
"layer": "model",
|
|
"description": ""
|
|
},
|
|
{
|
|
"id": "backend/app/models/ai_chat_session.py",
|
|
"label": "AIChatSession model / table ai_chat_sessions",
|
|
"file": "backend/app/models/ai_chat_session.py",
|
|
"layer": "model",
|
|
"description": ""
|
|
},
|
|
{
|
|
"id": "backend/app/models/ai_session.py",
|
|
"label": "AISession model",
|
|
"file": "backend/app/models/ai_session.py",
|
|
"layer": "model",
|
|
"description": "SQLAlchemy ORM for ai_sessions table \u2014 status, conversation_messages JSONB, pending_task_lane, escalation_package, state_version"
|
|
},
|
|
{
|
|
"id": "backend/app/models/ai_session_step.py",
|
|
"label": "AISessionStep model",
|
|
"file": "backend/app/models/ai_session_step.py",
|
|
"layer": "model",
|
|
"description": "ORM for ai_session_steps \u2014 step_order, step_type, content JSONB, selected_option, free_text_input, confidence_at_step"
|
|
},
|
|
{
|
|
"id": "backend/app/models/email_verification_token.py",
|
|
"label": "EmailVerificationToken model",
|
|
"file": "backend/app/models/email_verification_token.py",
|
|
"layer": "model",
|
|
"description": "Single-use email verification token record. Fields: token_hash, user_id, expires_at, used_at. is_valid: not used and not expired."
|
|
},
|
|
{
|
|
"id": "backend/app/models/file_upload.py",
|
|
"label": "FileUpload model",
|
|
"file": "backend/app/models/file_upload.py",
|
|
"layer": "model",
|
|
"description": "ORM for file_uploads \u2014 storage_key, content_type, extracted_content, ai_description, session_id FK"
|
|
},
|
|
{
|
|
"id": "backend/app/models/flow_proposal.py",
|
|
"label": "FlowProposal model / table flow_proposals",
|
|
"file": "backend/app/models/flow_proposal.py",
|
|
"layer": "model",
|
|
"description": ""
|
|
},
|
|
{
|
|
"id": "backend/app/models/oauth_identity.py",
|
|
"label": "OAuthIdentity model",
|
|
"file": "backend/app/models/oauth_identity.py",
|
|
"layer": "model",
|
|
"description": "Links a User to a provider identity. Fields: user_id, provider (google|microsoft), provider_subject (opaque ID from provider), provider_email_at_link. Unique on (provider, provider_subject)."
|
|
},
|
|
{
|
|
"id": "backend/app/models/password_reset_token.py",
|
|
"label": "PasswordResetToken model",
|
|
"file": "backend/app/models/password_reset_token.py",
|
|
"layer": "model",
|
|
"description": "Single-use password reset token record. Fields: token_hash (SHA-256 of JTI), user_id, expires_at, used_at. is_valid property: not used and not expired."
|
|
},
|
|
{
|
|
"id": "backend/app/models/psa_connection.py",
|
|
"label": "psa_connection",
|
|
"file": "backend/app/models/psa_connection.py",
|
|
"layer": "model",
|
|
"description": ""
|
|
},
|
|
{
|
|
"id": "backend/app/models/psa_post_log.py",
|
|
"label": "psa_post_log",
|
|
"file": "backend/app/models/psa_post_log.py",
|
|
"layer": "model",
|
|
"description": ""
|
|
},
|
|
{
|
|
"id": "backend/app/models/refresh_token.py",
|
|
"label": "RefreshToken model",
|
|
"file": "backend/app/models/refresh_token.py",
|
|
"layer": "model",
|
|
"description": "Stores SHA-256 hash of refresh token JTI for rotation and revocation. Fields: token_hash, user_id, expires_at, revoked_at."
|
|
},
|
|
{
|
|
"id": "backend/app/models/script_builder_session.py",
|
|
"label": "script_builder_session",
|
|
"file": "backend/app/models/script_builder_session.py",
|
|
"layer": "model",
|
|
"description": ""
|
|
},
|
|
{
|
|
"id": "backend/app/models/session_handoff.py",
|
|
"label": "SessionHandoff model",
|
|
"file": "backend/app/models/session_handoff.py",
|
|
"layer": "model",
|
|
"description": "ORM for session_handoffs \u2014 intent (escalate/park), snapshot JSONB, ai_assessment, ai_assessment_data, claimed_by, handed_off_by"
|
|
},
|
|
{
|
|
"id": "backend/app/models/session_suggested_fix.py",
|
|
"label": "SessionSuggestedFix model",
|
|
"file": "backend/app/models/session_suggested_fix.py",
|
|
"layer": "model",
|
|
"description": "ORM for session_suggested_fixes \u2014 title, confidence_pct, status, applied_at, verified_at, ai_outcome_proposal JSONB, superseded_at"
|
|
},
|
|
{
|
|
"id": "backend/app/models/stripe_event.py",
|
|
"label": "stripe_event",
|
|
"file": "backend/app/models/stripe_event.py",
|
|
"layer": "model",
|
|
"description": ""
|
|
},
|
|
{
|
|
"id": "backend/app/models/subscription.py",
|
|
"label": "subscription",
|
|
"file": "backend/app/models/subscription.py",
|
|
"layer": "model",
|
|
"description": ""
|
|
},
|
|
{
|
|
"id": "backend/app/models/tree.py",
|
|
"label": "Tree model / table trees",
|
|
"file": "backend/app/models/tree.py",
|
|
"layer": "model",
|
|
"description": ""
|
|
},
|
|
{
|
|
"id": "backend/app/models/user.py",
|
|
"label": "User model",
|
|
"file": "backend/app/models/user.py",
|
|
"layer": "model",
|
|
"description": "SQLAlchemy ORM model for the users table. Key auth fields: email, password_hash (nullable for OAuth-only), email_verified_at, must_change_password, is_active, account_id, account_role."
|
|
},
|
|
{
|
|
"id": "external:anthropic_api",
|
|
"label": "Anthropic API",
|
|
"file": "external:anthropic_api",
|
|
"layer": "external",
|
|
"description": "Anthropic messages API (claude-sonnet-4-6); used by _call_ai and AnthropicProvider.generate_json for all LLM calls"
|
|
},
|
|
{
|
|
"id": "external:connectwise",
|
|
"label": "connectwise",
|
|
"file": "external:connectwise",
|
|
"layer": "external",
|
|
"description": ""
|
|
},
|
|
{
|
|
"id": "external:google_oauth",
|
|
"label": "Google OAuth",
|
|
"file": "external:google_oauth",
|
|
"layer": "external",
|
|
"description": "Google authorization server and userinfo endpoint. Authorize URL: accounts.google.com/o/oauth2/v2/auth. Token URL: oauth2.googleapis.com/token. Userinfo URL: openidconnect.googleapis.com/v1/userinfo."
|
|
},
|
|
{
|
|
"id": "external:microsoft_oauth",
|
|
"label": "Microsoft OAuth",
|
|
"file": "external:microsoft_oauth",
|
|
"layer": "external",
|
|
"description": "Microsoft identity platform. Authorize URL: login.microsoftonline.com/common/oauth2/v2.0/authorize. Token URL: login.microsoftonline.com/common/oauth2/v2.0/token. Profile URL: graph.microsoft.com/v1.0/me."
|
|
},
|
|
{
|
|
"id": "external:postgres",
|
|
"label": "PostgreSQL",
|
|
"file": "external:postgres",
|
|
"layer": "external",
|
|
"description": "Primary database. Tables touched in auth flows: users, refresh_tokens, password_reset_tokens, email_verification_tokens, oauth_identities, accounts, subscriptions."
|
|
},
|
|
{
|
|
"id": "external:railway_s3",
|
|
"label": "Railway S3 (Object Storage)",
|
|
"file": "external:railway_s3",
|
|
"layer": "external",
|
|
"description": "S3-compatible bucket on Railway; boto3 client configured via STORAGE_ENDPOINT; used for upload, download, presigned URLs"
|
|
},
|
|
{
|
|
"id": "external:smtp_email",
|
|
"label": "Resend (SMTP/API)",
|
|
"file": "external:smtp_email",
|
|
"layer": "external",
|
|
"description": "External email delivery via Resend SDK. Used for verification emails, password reset emails."
|
|
},
|
|
{
|
|
"id": "external:stripe",
|
|
"label": "stripe",
|
|
"file": "external:stripe",
|
|
"layer": "external",
|
|
"description": ""
|
|
},
|
|
{
|
|
"id": "frontend/src/api/client.ts",
|
|
"label": "apiClient",
|
|
"file": "frontend/src/api/client.ts",
|
|
"layer": "http_client",
|
|
"description": "Axios instance baseURL=/api/v1. Request interceptor attaches Bearer token from localStorage. Response interceptor handles 401 with token rotation (POST /auth/refresh); on refresh failure clears tokens and redirects to /login."
|
|
}
|
|
],
|
|
"flows": [
|
|
{
|
|
"id": "auth.login_oauth",
|
|
"label": "Log in (OAuth \u2014 Google or Microsoft)",
|
|
"description": "User clicks 'Continue with Google/Microsoft' on RegisterPage or LoginPage, browser redirects to provider, provider redirects back to /auth/{provider}/callback, OAuthCallbackPage exchanges code with backend, backend exchanges with provider API, upserts user+OAuthIdentity, issues JWTs.",
|
|
"cluster": "auth",
|
|
"steps": [
|
|
{
|
|
"from": "frontend/src/pages/RegisterPage.tsx",
|
|
"to": "frontend/src/pages/RegisterPage.tsx",
|
|
"via": "function_call",
|
|
"label": "handleOAuth(provider) \u2014 buildOAuthAuthorizeUrl(), store csrf in sessionStorage",
|
|
"passes": "provider = 'google' | 'microsoft'",
|
|
"from_line": 98,
|
|
"to_line": 38,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "frontend/src/pages/RegisterPage.tsx",
|
|
"to": "external:google_oauth",
|
|
"via": "redirect",
|
|
"label": "window.location.href = provider authorize URL",
|
|
"passes": "{client_id, redirect_uri=/auth/google/callback, response_type=code, scope, state=csrf_hex}",
|
|
"from_line": 106,
|
|
"to_line": null,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "external:google_oauth",
|
|
"to": "frontend/src/pages/OAuthCallbackPage.tsx",
|
|
"via": "redirect",
|
|
"label": "Provider redirects to /auth/google/callback?code=...&state=...",
|
|
"passes": "{code, state}",
|
|
"from_line": null,
|
|
"to_line": 28,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "frontend/src/pages/OAuthCallbackPage.tsx",
|
|
"to": "frontend/src/pages/OAuthCallbackPage.tsx",
|
|
"via": "function_call",
|
|
"label": "CSRF check: sessionStorage('rf-oauth-state') vs returned state",
|
|
"passes": "{storedState, returnedState}",
|
|
"from_line": 51,
|
|
"to_line": null,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "frontend/src/pages/OAuthCallbackPage.tsx",
|
|
"to": "frontend/src/api/auth.ts",
|
|
"via": "function_call",
|
|
"label": "authApi.googleCallback(code, inviteOptions?) or authApi.microsoftCallback(code, inviteOptions?)",
|
|
"passes": "{code, account_invite_code?, invited_email?}",
|
|
"from_line": 95,
|
|
"to_line": 82,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "frontend/src/api/auth.ts",
|
|
"to": "frontend/src/api/client.ts",
|
|
"via": "function_call",
|
|
"label": "apiClient.post('/auth/google/callback', payload)",
|
|
"passes": "{code, account_invite_code?, invited_email?}",
|
|
"from_line": 86,
|
|
"to_line": 7,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "frontend/src/api/client.ts",
|
|
"to": "backend/app/api/endpoints/oauth.py",
|
|
"via": "http_post",
|
|
"label": "POST /api/v1/auth/google/callback",
|
|
"passes": "{code, account_invite_code?, invited_email?}",
|
|
"from_line": 7,
|
|
"to_line": 174,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/api/endpoints/oauth.py",
|
|
"to": "backend/app/services/oauth_providers.py",
|
|
"via": "function_call",
|
|
"label": "google_exchange_code(code, redirect_uri)",
|
|
"passes": "{code, redirect_uri, client_id, client_secret}",
|
|
"from_line": 182,
|
|
"to_line": 17,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/services/oauth_providers.py",
|
|
"to": "external:google_oauth",
|
|
"via": "external_api",
|
|
"label": "POST https://oauth2.googleapis.com/token",
|
|
"passes": "{code, client_id, client_secret, redirect_uri, grant_type=authorization_code}",
|
|
"from_line": 18,
|
|
"to_line": null,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/services/oauth_providers.py",
|
|
"to": "external:google_oauth",
|
|
"via": "external_api",
|
|
"label": "GET https://openidconnect.googleapis.com/v1/userinfo",
|
|
"passes": "Bearer access_token",
|
|
"from_line": 32,
|
|
"to_line": null,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/services/oauth_providers.py",
|
|
"to": "backend/app/api/endpoints/oauth.py",
|
|
"via": "function_call",
|
|
"label": "returns OAuthProfile(provider_subject, email, name)",
|
|
"passes": "{sub, email, name}",
|
|
"from_line": 38,
|
|
"to_line": 183,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/api/endpoints/oauth.py",
|
|
"to": "external:postgres",
|
|
"via": "db_read",
|
|
"label": "SELECT oauth_identities WHERE provider=? AND provider_subject=?",
|
|
"passes": "{provider, provider_subject}",
|
|
"from_line": 51,
|
|
"to_line": null,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/api/endpoints/oauth.py",
|
|
"to": "external:postgres",
|
|
"via": "db_write",
|
|
"label": "INSERT Account + User (new user) OR existing user lookup; INSERT oauth_identities; start_trial() for new owners",
|
|
"passes": "{email, name, password_hash=null, email_verified_at=now(), account_id, account_role}",
|
|
"from_line": 139,
|
|
"to_line": null,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/api/endpoints/oauth.py",
|
|
"to": "backend/app/core/security.py",
|
|
"via": "function_call",
|
|
"label": "create_access_token + create_refresh_token",
|
|
"passes": "{sub: user_id}",
|
|
"from_line": 190,
|
|
"to_line": 24,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/api/endpoints/oauth.py",
|
|
"to": "external:postgres",
|
|
"via": "db_write",
|
|
"label": "INSERT refresh_tokens (token_hash, user_id, expires_at)",
|
|
"passes": "{token_hash, user_id, expires_at}",
|
|
"from_line": 195,
|
|
"to_line": null,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/api/endpoints/oauth.py",
|
|
"to": "frontend/src/pages/OAuthCallbackPage.tsx",
|
|
"via": "http_post",
|
|
"label": "200 OK \u2192 {access_token, refresh_token, token_type, is_new_user}",
|
|
"passes": "OAuthCallbackResponse",
|
|
"from_line": 197,
|
|
"to_line": 100,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "frontend/src/pages/OAuthCallbackPage.tsx",
|
|
"to": "frontend/src/store/authStore.ts",
|
|
"via": "function_call",
|
|
"label": "setTokens({access_token, refresh_token, token_type}); localStorage.setItem",
|
|
"passes": "{access_token, refresh_token}",
|
|
"from_line": 102,
|
|
"to_line": 140,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "frontend/src/pages/OAuthCallbackPage.tsx",
|
|
"to": "frontend/src/store/authStore.ts",
|
|
"via": "function_call",
|
|
"label": "fetchUser() \u2014 hydrate user/account/subscription",
|
|
"passes": "Bearer access_token",
|
|
"from_line": 108,
|
|
"to_line": 96,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "frontend/src/pages/OAuthCallbackPage.tsx",
|
|
"to": "frontend/src/pages/OAuthCallbackPage.tsx",
|
|
"via": "redirect",
|
|
"label": "navigate('/welcome') if is_new_user else navigate('/')",
|
|
"passes": null,
|
|
"from_line": 119,
|
|
"to_line": null,
|
|
"unverified": false
|
|
}
|
|
],
|
|
"group": "Auth & Access"
|
|
},
|
|
{
|
|
"id": "auth.login_password",
|
|
"label": "Log in (password)",
|
|
"description": "User submits email+password, backend verifies bcrypt hash, issues access+refresh JWT pair, stores refresh JTI hash in DB. Frontend stores tokens in localStorage and zustand, fetches full user/account/subscription.",
|
|
"cluster": "auth",
|
|
"steps": [
|
|
{
|
|
"from": "frontend/src/pages/LoginPage.tsx",
|
|
"to": "frontend/src/store/authStore.ts",
|
|
"via": "function_call",
|
|
"label": "authStore.login({email, password})",
|
|
"passes": "{email, password}",
|
|
"from_line": 31,
|
|
"to_line": 41,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "frontend/src/store/authStore.ts",
|
|
"to": "frontend/src/api/auth.ts",
|
|
"via": "function_call",
|
|
"label": "authApi.login(credentials)",
|
|
"passes": "{email, password}",
|
|
"from_line": 44,
|
|
"to_line": 17,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "frontend/src/api/auth.ts",
|
|
"to": "frontend/src/api/client.ts",
|
|
"via": "function_call",
|
|
"label": "apiClient.post('/auth/login/json', data)",
|
|
"passes": "{email, password}",
|
|
"from_line": 18,
|
|
"to_line": 7,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "frontend/src/api/client.ts",
|
|
"to": "backend/app/api/endpoints/auth.py",
|
|
"via": "http_post",
|
|
"label": "POST /api/v1/auth/login/json",
|
|
"passes": "{email, password}",
|
|
"from_line": 7,
|
|
"to_line": 342,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/api/endpoints/auth.py",
|
|
"to": "external:postgres",
|
|
"via": "db_read",
|
|
"label": "SELECT users WHERE email=?",
|
|
"passes": "email string",
|
|
"from_line": 351,
|
|
"to_line": null,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/api/endpoints/auth.py",
|
|
"to": "backend/app/core/security.py",
|
|
"via": "function_call",
|
|
"label": "verify_password(plain, hash) \u2014 bcrypt verify",
|
|
"passes": "{plaintext_password, stored_hash}",
|
|
"from_line": 354,
|
|
"to_line": 14,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/api/endpoints/auth.py",
|
|
"to": "backend/app/core/security.py",
|
|
"via": "function_call",
|
|
"label": "create_access_token + create_refresh_token",
|
|
"passes": "{sub: user_id}",
|
|
"from_line": 362,
|
|
"to_line": 24,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/api/endpoints/auth.py",
|
|
"to": "external:postgres",
|
|
"via": "db_write",
|
|
"label": "INSERT refresh_tokens (token_hash, user_id, expires_at); UPDATE users SET last_login=now()",
|
|
"passes": "{token_hash=SHA-256(jti), user_id, expires_at}",
|
|
"from_line": 366,
|
|
"to_line": null,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/api/endpoints/auth.py",
|
|
"to": "frontend/src/store/authStore.ts",
|
|
"via": "http_post",
|
|
"label": "200 OK \u2192 {access_token, refresh_token, token_type, must_change_password}",
|
|
"passes": "Token schema",
|
|
"from_line": 369,
|
|
"to_line": 44,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "frontend/src/store/authStore.ts",
|
|
"to": "frontend/src/store/authStore.ts",
|
|
"via": "state_update",
|
|
"label": "localStorage.setItem access_token + refresh_token; set({token, isAuthenticated:true})",
|
|
"passes": "{access_token, refresh_token}",
|
|
"from_line": 47,
|
|
"to_line": null,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "frontend/src/store/authStore.ts",
|
|
"to": "frontend/src/store/authStore.ts",
|
|
"via": "function_call",
|
|
"label": "fetchUser() \u2014 GET /auth/me + /accounts/me + /accounts/me/subscription (parallel)",
|
|
"passes": "Bearer access_token",
|
|
"from_line": 53,
|
|
"to_line": 96,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "frontend/src/store/authStore.ts",
|
|
"to": "external:postgres",
|
|
"via": "db_read",
|
|
"label": "via GET /auth/me \u2192 SELECT users WHERE id=sub",
|
|
"passes": "user_id from JWT sub claim",
|
|
"from_line": 99,
|
|
"to_line": null,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "frontend/src/pages/LoginPage.tsx",
|
|
"to": "frontend/src/pages/LoginPage.tsx",
|
|
"via": "redirect",
|
|
"label": "navigate('/change-password') if must_change_password else navigate(from)",
|
|
"passes": null,
|
|
"from_line": 33,
|
|
"to_line": null,
|
|
"unverified": false
|
|
}
|
|
],
|
|
"group": "Auth & Access"
|
|
},
|
|
{
|
|
"id": "auth.password_reset",
|
|
"label": "Forgot password / reset password",
|
|
"description": "User requests reset email, backend creates a 30-min JWT token + PasswordResetToken row, emails link. User lands on /reset-password?token=..., page validates token, user submits new password, backend hashes + stores it and revokes all refresh tokens.",
|
|
"cluster": "auth",
|
|
"steps": [
|
|
{
|
|
"from": "frontend/src/pages/ForgotPasswordPage.tsx",
|
|
"to": "frontend/src/api/auth.ts",
|
|
"via": "function_call",
|
|
"label": "authApi.forgotPassword(email)",
|
|
"passes": "{email}",
|
|
"from_line": 19,
|
|
"to_line": 48,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "frontend/src/api/auth.ts",
|
|
"to": "frontend/src/api/client.ts",
|
|
"via": "function_call",
|
|
"label": "apiClient.post('/auth/password/forgot', {email})",
|
|
"passes": "{email}",
|
|
"from_line": 49,
|
|
"to_line": 7,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "frontend/src/api/client.ts",
|
|
"to": "backend/app/api/endpoints/auth.py",
|
|
"via": "http_post",
|
|
"label": "POST /api/v1/auth/password/forgot",
|
|
"passes": "{email}",
|
|
"from_line": 7,
|
|
"to_line": 552,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/api/endpoints/auth.py",
|
|
"to": "external:postgres",
|
|
"via": "db_read",
|
|
"label": "SELECT users WHERE email=? AND password_hash IS NOT NULL",
|
|
"passes": "email",
|
|
"from_line": 560,
|
|
"to_line": null,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/api/endpoints/auth.py",
|
|
"to": "backend/app/core/security.py",
|
|
"via": "function_call",
|
|
"label": "create_password_reset_token(user_id) \u2014 JWT type=password_reset, exp=30m, jti=uuid4",
|
|
"passes": "user_id string",
|
|
"from_line": 565,
|
|
"to_line": 60,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/api/endpoints/auth.py",
|
|
"to": "external:postgres",
|
|
"via": "db_write",
|
|
"label": "INSERT password_reset_tokens (token_hash, user_id, expires_at)",
|
|
"passes": "{SHA-256(jti), user_id, expires_at=now+30m}",
|
|
"from_line": 568,
|
|
"to_line": null,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/api/endpoints/auth.py",
|
|
"to": "backend/app/core/email.py",
|
|
"via": "function_call",
|
|
"label": "EmailService.send_password_reset_email(to_email, reset_url)",
|
|
"passes": "{to_email, reset_url=/reset-password?token=<JWT>}",
|
|
"from_line": 577,
|
|
"to_line": 59,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/core/email.py",
|
|
"to": "external:smtp_email",
|
|
"via": "external_api",
|
|
"label": "resend.Emails.send() \u2014 password reset email",
|
|
"passes": "{from, to, subject='Reset Your Password', html with reset_url}",
|
|
"from_line": 81,
|
|
"to_line": null,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "external:smtp_email",
|
|
"to": "frontend/src/pages/ResetPasswordPage.tsx",
|
|
"via": "redirect",
|
|
"label": "User clicks link \u2192 GET /reset-password?token=<JWT>",
|
|
"passes": "token in query string",
|
|
"from_line": null,
|
|
"to_line": 10,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "frontend/src/pages/ResetPasswordPage.tsx",
|
|
"to": "frontend/src/api/auth.ts",
|
|
"via": "function_call",
|
|
"label": "authApi.verifyResetToken(token) \u2014 on mount",
|
|
"passes": "{token}",
|
|
"from_line": 29,
|
|
"to_line": 52,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "frontend/src/api/auth.ts",
|
|
"to": "backend/app/api/endpoints/auth.py",
|
|
"via": "http_post",
|
|
"label": "POST /api/v1/auth/password/verify-reset-token",
|
|
"passes": "{token}",
|
|
"from_line": 53,
|
|
"to_line": 589,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/api/endpoints/auth.py",
|
|
"to": "external:postgres",
|
|
"via": "db_read",
|
|
"label": "SELECT password_reset_tokens WHERE token_hash=hash(jti); SELECT users.email",
|
|
"passes": "SHA-256(jti)",
|
|
"from_line": 603,
|
|
"to_line": null,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "frontend/src/pages/ResetPasswordPage.tsx",
|
|
"to": "frontend/src/api/auth.ts",
|
|
"via": "function_call",
|
|
"label": "authApi.resetPassword(token, newPassword) \u2014 on form submit",
|
|
"passes": "{token, new_password}",
|
|
"from_line": 60,
|
|
"to_line": 57,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "frontend/src/api/auth.ts",
|
|
"to": "backend/app/api/endpoints/auth.py",
|
|
"via": "http_post",
|
|
"label": "POST /api/v1/auth/password/reset",
|
|
"passes": "{token, new_password}",
|
|
"from_line": 58,
|
|
"to_line": 618,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/api/endpoints/auth.py",
|
|
"to": "external:postgres",
|
|
"via": "db_read",
|
|
"label": "SELECT password_reset_tokens WHERE token_hash=hash(jti) FOR UPDATE",
|
|
"passes": "SHA-256(jti)",
|
|
"from_line": 643,
|
|
"to_line": null,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/api/endpoints/auth.py",
|
|
"to": "backend/app/core/security.py",
|
|
"via": "function_call",
|
|
"label": "get_password_hash(new_password) \u2014 bcrypt",
|
|
"passes": "new plaintext password",
|
|
"from_line": 666,
|
|
"to_line": 19,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/api/endpoints/auth.py",
|
|
"to": "external:postgres",
|
|
"via": "db_write",
|
|
"label": "UPDATE users SET password_hash=?, must_change_password=false; UPDATE password_reset_tokens SET used_at=now(); UPDATE refresh_tokens SET revoked_at=now() for all active tokens",
|
|
"passes": "{new_hash, used_at, revoked_at}",
|
|
"from_line": 666,
|
|
"to_line": null,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "frontend/src/pages/ResetPasswordPage.tsx",
|
|
"to": "frontend/src/pages/ResetPasswordPage.tsx",
|
|
"via": "redirect",
|
|
"label": "navigate('/login') on success",
|
|
"passes": null,
|
|
"from_line": 62,
|
|
"to_line": null,
|
|
"unverified": false
|
|
}
|
|
],
|
|
"group": "Auth & Access"
|
|
},
|
|
{
|
|
"id": "auth.signup",
|
|
"label": "Sign up (self-serve)",
|
|
"description": "User fills the registration form, backend creates User + Account + starts Pro trial, then auto-sends a verification email.",
|
|
"cluster": "auth",
|
|
"steps": [
|
|
{
|
|
"from": "frontend/src/pages/RegisterPage.tsx",
|
|
"to": "frontend/src/store/authStore.ts",
|
|
"via": "function_call",
|
|
"label": "authStore.register(userData)",
|
|
"passes": "{email, password, name} or {email, password, name, invite_code}",
|
|
"from_line": 158,
|
|
"to_line": 64,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "frontend/src/store/authStore.ts",
|
|
"to": "frontend/src/api/auth.ts",
|
|
"via": "function_call",
|
|
"label": "authApi.register(data)",
|
|
"passes": "{email, password, name, invite_code?}",
|
|
"from_line": 67,
|
|
"to_line": 12,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "frontend/src/api/auth.ts",
|
|
"to": "frontend/src/api/client.ts",
|
|
"via": "function_call",
|
|
"label": "apiClient.post('/auth/register', data)",
|
|
"passes": "{email, password, name, invite_code?}",
|
|
"from_line": 13,
|
|
"to_line": 7,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "frontend/src/api/client.ts",
|
|
"to": "backend/app/api/endpoints/auth.py",
|
|
"via": "http_post",
|
|
"label": "POST /api/v1/auth/register",
|
|
"passes": "{email, password, name, invite_code?}",
|
|
"from_line": 7,
|
|
"to_line": 92,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/api/endpoints/auth.py",
|
|
"to": "backend/app/core/security.py",
|
|
"via": "function_call",
|
|
"label": "get_password_hash(password)",
|
|
"passes": "plaintext password",
|
|
"from_line": 228,
|
|
"to_line": 19,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/api/endpoints/auth.py",
|
|
"to": "external:postgres",
|
|
"via": "db_write",
|
|
"label": "INSERT Account, INSERT User, start_trial (INSERT Subscription)",
|
|
"passes": "{email, password_hash, name, account_id, account_role='owner'}",
|
|
"from_line": 217,
|
|
"to_line": null,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/api/endpoints/auth.py",
|
|
"to": "backend/app/core/security.py",
|
|
"via": "function_call",
|
|
"label": "create_email_verification_token(user_id)",
|
|
"passes": "user_id string",
|
|
"from_line": 281,
|
|
"to_line": 73,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/api/endpoints/auth.py",
|
|
"to": "external:postgres",
|
|
"via": "db_write",
|
|
"label": "INSERT email_verification_tokens",
|
|
"passes": "{token_hash, user_id, expires_at (24h)}",
|
|
"from_line": 284,
|
|
"to_line": null,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/api/endpoints/auth.py",
|
|
"to": "backend/app/core/email.py",
|
|
"via": "function_call",
|
|
"label": "EmailService.send_email_verification_email()",
|
|
"passes": "{to_email, verification_url=/verify-email?token=<JWT>}",
|
|
"from_line": 293,
|
|
"to_line": 172,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/core/email.py",
|
|
"to": "external:smtp_email",
|
|
"via": "external_api",
|
|
"label": "resend.Emails.send() \u2014 verify email",
|
|
"passes": "{from, to, subject, html with verification_url}",
|
|
"from_line": 188,
|
|
"to_line": null,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/api/endpoints/auth.py",
|
|
"to": "frontend/src/store/authStore.ts",
|
|
"via": "http_post",
|
|
"label": "201 Created \u2192 UserResponse",
|
|
"passes": "{id, email, name, role, account_id, ...}",
|
|
"from_line": 300,
|
|
"to_line": 67,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "frontend/src/store/authStore.ts",
|
|
"to": "frontend/src/store/authStore.ts",
|
|
"via": "function_call",
|
|
"label": "authStore.login({email, password}) \u2014 auto-login after register",
|
|
"passes": "{email, password}",
|
|
"from_line": 70,
|
|
"to_line": 41,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "frontend/src/pages/RegisterPage.tsx",
|
|
"to": "frontend/src/pages/RegisterPage.tsx",
|
|
"via": "redirect",
|
|
"label": "navigate('/welcome')",
|
|
"passes": null,
|
|
"from_line": 162,
|
|
"to_line": null,
|
|
"unverified": false
|
|
}
|
|
],
|
|
"group": "Auth & Access"
|
|
},
|
|
{
|
|
"id": "auth.verify_email",
|
|
"label": "Verify email",
|
|
"description": "User clicks verification link in email, lands on /verify-email?token=..., page fires POST /auth/email/verify once, sets email_verified_at on success.",
|
|
"cluster": "auth",
|
|
"steps": [
|
|
{
|
|
"from": "external:smtp_email",
|
|
"to": "frontend/src/pages/VerifyEmailPage.tsx",
|
|
"via": "redirect",
|
|
"label": "User clicks link \u2192 GET /verify-email?token=<JWT>",
|
|
"passes": "token (JWT) in query string",
|
|
"from_line": null,
|
|
"to_line": 1,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "frontend/src/pages/VerifyEmailPage.tsx",
|
|
"to": "frontend/src/api/auth.ts",
|
|
"via": "function_call",
|
|
"label": "authApi.verifyEmail(token)",
|
|
"passes": "{token}",
|
|
"from_line": 61,
|
|
"to_line": 78,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "frontend/src/api/auth.ts",
|
|
"to": "frontend/src/api/client.ts",
|
|
"via": "function_call",
|
|
"label": "apiClient.post('/auth/email/verify', {token})",
|
|
"passes": "{token}",
|
|
"from_line": 79,
|
|
"to_line": 7,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "frontend/src/api/client.ts",
|
|
"to": "backend/app/api/endpoints/auth.py",
|
|
"via": "http_post",
|
|
"label": "POST /api/v1/auth/email/verify",
|
|
"passes": "{token}",
|
|
"from_line": 7,
|
|
"to_line": 738,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/api/endpoints/auth.py",
|
|
"to": "backend/app/core/security.py",
|
|
"via": "function_call",
|
|
"label": "decode_token(token) \u2014 verify JWT signature and type=email_verification",
|
|
"passes": "raw JWT string",
|
|
"from_line": 751,
|
|
"to_line": 51,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/api/endpoints/auth.py",
|
|
"to": "external:postgres",
|
|
"via": "db_read",
|
|
"label": "SELECT email_verification_tokens WHERE token_hash=hash(jti) FOR UPDATE",
|
|
"passes": "SHA-256(jti)",
|
|
"from_line": 767,
|
|
"to_line": null,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/api/endpoints/auth.py",
|
|
"to": "external:postgres",
|
|
"via": "db_write",
|
|
"label": "UPDATE email_verification_tokens SET used_at=now(); UPDATE users SET email_verified_at=now()",
|
|
"passes": "{used_at, email_verified_at}",
|
|
"from_line": 781,
|
|
"to_line": null,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "frontend/src/pages/VerifyEmailPage.tsx",
|
|
"to": "frontend/src/store/authStore.ts",
|
|
"via": "function_call",
|
|
"label": "authStore.getState().fetchUser() \u2014 refresh user to get email_verified_at",
|
|
"passes": null,
|
|
"from_line": 65,
|
|
"to_line": 96,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "frontend/src/pages/VerifyEmailPage.tsx",
|
|
"to": "frontend/src/pages/VerifyEmailPage.tsx",
|
|
"via": "redirect",
|
|
"label": "navigate('/?verified=1') after 1200ms",
|
|
"passes": null,
|
|
"from_line": 76,
|
|
"to_line": null,
|
|
"unverified": false
|
|
}
|
|
],
|
|
"group": "Auth & Access"
|
|
},
|
|
{
|
|
"id": "authoring.flow1",
|
|
"label": "Create flow manually",
|
|
"description": "",
|
|
"cluster": "authoring",
|
|
"steps": [
|
|
{
|
|
"from": "frontend/src/pages/TreeEditorPage.tsx",
|
|
"to": "frontend/src/pages/TreeEditorPage.tsx",
|
|
"via": "navigation",
|
|
"label": "User navigates to /trees/new",
|
|
"passes": "URL route (no id param \u2192 isEditMode=false)",
|
|
"from_line": null,
|
|
"to_line": 34,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "frontend/src/pages/TreeEditorPage.tsx",
|
|
"to": "frontend/src/store/treeEditorStore.ts",
|
|
"via": "function_call",
|
|
"label": "initNewTree()",
|
|
"passes": "no args; seeds root decision node {id:'root', type:'decision', options:[], children:[]}",
|
|
"from_line": 221,
|
|
"to_line": 329,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "frontend/src/pages/TreeEditorPage.tsx",
|
|
"to": "frontend/src/components/tree-editor/TreeEditorLayout.tsx",
|
|
"via": "function_call",
|
|
"label": "render TreeEditorLayout (form mode)",
|
|
"passes": "editingNodeId, onNodeSelect, onNodeDelete callbacks",
|
|
"from_line": 814,
|
|
"to_line": null,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "frontend/src/pages/TreeEditorPage.tsx",
|
|
"to": "frontend/src/store/treeEditorStore.ts",
|
|
"via": "function_call",
|
|
"label": "updateNode / addNode mutations",
|
|
"passes": "nodeId + Partial<TreeStructure>; sets isDirty=true, triggers autoSaveDraft()",
|
|
"from_line": 300,
|
|
"to_line": 561,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "frontend/src/store/treeEditorStore.ts",
|
|
"to": "frontend/src/store/treeEditorStore.ts",
|
|
"via": "state_update",
|
|
"label": "autoSaveDraft() \u2192 localStorage",
|
|
"passes": "{treeId, name, description, treeStructure, savedAt} serialised to 'tree-editor-draft'",
|
|
"from_line": 891,
|
|
"to_line": 904,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "frontend/src/pages/TreeEditorPage.tsx",
|
|
"to": "frontend/src/store/treeEditorStore.ts",
|
|
"via": "function_call",
|
|
"label": "handleSaveDraft/handlePublish \u2192 getTreeForSave()",
|
|
"passes": "returns {name, description, category, tags, is_public, tree_structure, status}",
|
|
"from_line": 333,
|
|
"to_line": 918,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "frontend/src/pages/TreeEditorPage.tsx",
|
|
"to": "frontend/src/api/trees.ts",
|
|
"via": "function_call",
|
|
"label": "treesApi.create(treeData) \u2014 new tree",
|
|
"passes": "TreeCreate payload {name, description, tree_structure, status:'draft'|'published', ...}",
|
|
"from_line": 367,
|
|
"to_line": 15,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "frontend/src/api/trees.ts",
|
|
"to": "backend/app/api/endpoints/trees.py",
|
|
"via": "http_post",
|
|
"label": "POST /trees",
|
|
"passes": "TreeCreate JSON body",
|
|
"from_line": 16,
|
|
"to_line": 404,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/api/endpoints/trees.py",
|
|
"to": "backend/app/models/tree.py",
|
|
"via": "db_write",
|
|
"label": "db.add(new_tree) + db.commit()",
|
|
"passes": "Tree ORM object \u2192 INSERT INTO trees",
|
|
"from_line": 489,
|
|
"to_line": null,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/api/endpoints/trees.py",
|
|
"to": "external:postgres",
|
|
"via": "db_write",
|
|
"label": "SQLAlchemy flush + commit",
|
|
"passes": "full Tree row (id, name, tree_structure JSONB, status, author_id, account_id ...)",
|
|
"from_line": 560,
|
|
"to_line": null,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/api/endpoints/trees.py",
|
|
"to": "frontend/src/pages/TreeEditorPage.tsx",
|
|
"via": "http_post",
|
|
"label": "201 TreeResponse",
|
|
"passes": "created Tree with id; page calls navigate(`/trees/${newTree.id}/edit`)",
|
|
"from_line": 580,
|
|
"to_line": 372,
|
|
"unverified": false
|
|
}
|
|
],
|
|
"group": "Flow Authoring"
|
|
},
|
|
{
|
|
"id": "authoring.flow2",
|
|
"label": "Create flow via AI builder (troubleshooting)",
|
|
"description": "",
|
|
"cluster": "authoring",
|
|
"steps": [
|
|
{
|
|
"from": "frontend/src/pages/TreeEditorPage.tsx",
|
|
"to": "frontend/src/hooks/useEditorAI.ts",
|
|
"via": "function_call",
|
|
"label": "editorAI.openPanel()",
|
|
"passes": "no args; sets isOpen=true",
|
|
"from_line": 729,
|
|
"to_line": 56,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "frontend/src/pages/TreeEditorPage.tsx",
|
|
"to": "frontend/src/components/editor-ai/EditorAIPanel.tsx",
|
|
"via": "function_call",
|
|
"label": "render EditorAIPanel isOpen=true",
|
|
"passes": "messages, input, onSend=editorAI.sendMessage, flowType='troubleshooting'",
|
|
"from_line": 907,
|
|
"to_line": 26,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "frontend/src/components/editor-ai/EditorAIPanel.tsx",
|
|
"to": "frontend/src/components/editor-ai/ChatTab.tsx",
|
|
"via": "function_call",
|
|
"label": "render ChatTab",
|
|
"passes": "messages, input, onSend callback",
|
|
"from_line": 108,
|
|
"to_line": null,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "frontend/src/hooks/useEditorAI.ts",
|
|
"to": "frontend/src/api/editorAI.ts",
|
|
"via": "function_call",
|
|
"label": "ensureSession() \u2192 editorAIApi.startSession()",
|
|
"passes": "flowType='troubleshooting', treeId (if editing)",
|
|
"from_line": 38,
|
|
"to_line": 13,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "frontend/src/api/editorAI.ts",
|
|
"to": "backend/app/api/endpoints/ai_chat.py",
|
|
"via": "http_post",
|
|
"label": "POST /ai/chat/sessions",
|
|
"passes": "{flow_type, tree_id}",
|
|
"from_line": 14,
|
|
"to_line": 56,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/api/endpoints/ai_chat.py",
|
|
"to": "backend/app/core/ai_chat_service.py",
|
|
"via": "function_call",
|
|
"label": "start_chat_session()",
|
|
"passes": "flow_type, user_id, account_id, tree_id",
|
|
"from_line": 93,
|
|
"to_line": 469,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/core/ai_chat_service.py",
|
|
"to": "external:anthropic_api",
|
|
"via": "external_api",
|
|
"label": "provider.generate_text() \u2014 opening greeting",
|
|
"passes": "system_prompt (ROLE_PERSONA + SCHEMA_CONTEXT + INTERVIEW_PROTOCOL + RESPONSE_FORMAT), primer message",
|
|
"from_line": 498,
|
|
"to_line": null,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/core/ai_chat_service.py",
|
|
"to": "backend/app/models/ai_chat_session.py",
|
|
"via": "db_write",
|
|
"label": "db.add(session) + db.flush()",
|
|
"passes": "AIChatSession(user_id, account_id, flow_type, conversation_history, working_tree=None)",
|
|
"from_line": 480,
|
|
"to_line": null,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "frontend/src/hooks/useEditorAI.ts",
|
|
"to": "frontend/src/api/editorAI.ts",
|
|
"via": "function_call",
|
|
"label": "editorAIApi.sendMessage() on user submit",
|
|
"passes": "{sessionId, content, actionType='open_chat', focalNodeId, flowContext={name, description, tree_structure}}",
|
|
"from_line": 98,
|
|
"to_line": 21,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "frontend/src/api/editorAI.ts",
|
|
"to": "backend/app/api/endpoints/ai_chat.py",
|
|
"via": "http_post",
|
|
"label": "POST /ai/chat/sessions/{id}/messages",
|
|
"passes": "{content, action_type, focal_node_id, flow_context}",
|
|
"from_line": 22,
|
|
"to_line": 135,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/api/endpoints/ai_chat.py",
|
|
"to": "backend/app/core/ai_chat_service.py",
|
|
"via": "function_call",
|
|
"label": "send_message(session, content, db, action_type, focal_node_id, flow_context)",
|
|
"passes": "AIChatSession ORM object + user message",
|
|
"from_line": 171,
|
|
"to_line": 524,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/core/ai_chat_service.py",
|
|
"to": "external:anthropic_api",
|
|
"via": "external_api",
|
|
"label": "provider.generate_text()",
|
|
"passes": "full system_prompt + live flow_context injected + conversation_history + user_message",
|
|
"from_line": 571,
|
|
"to_line": null,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/core/ai_chat_service.py",
|
|
"to": "backend/app/core/ai_chat_service.py",
|
|
"via": "marker_parse",
|
|
"label": "_parse_ai_response() \u2014 extract [TREE_UPDATE]",
|
|
"passes": "raw_response string \u2192 {content, tree_update, phase, metadata}; [TREE_UPDATE]\u2026[/TREE_UPDATE] block parsed as TreeStructure JSON",
|
|
"from_line": 367,
|
|
"to_line": 367,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/core/ai_chat_service.py",
|
|
"to": "backend/app/models/ai_chat_session.py",
|
|
"via": "state_update",
|
|
"label": "session.working_tree = tree_update",
|
|
"passes": "complete working TreeStructure JSON stored in ai_chat_sessions.working_tree JSONB",
|
|
"from_line": 604,
|
|
"to_line": null,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/api/endpoints/ai_chat.py",
|
|
"to": "frontend/src/hooks/useEditorAI.ts",
|
|
"via": "http_post",
|
|
"label": "200 AIChatMessageResponse",
|
|
"passes": "{content, current_phase, working_tree, tree_metadata}",
|
|
"from_line": 223,
|
|
"to_line": 117,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "frontend/src/hooks/useEditorAI.ts",
|
|
"to": "frontend/src/pages/TreeEditorPage.tsx",
|
|
"via": "function_call",
|
|
"label": "onFlowUpdate(result.working_tree)",
|
|
"passes": "working_tree dict; if .type && .id \u2192 replaceTreeStructure() called",
|
|
"from_line": 117,
|
|
"to_line": 89,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "frontend/src/pages/TreeEditorPage.tsx",
|
|
"to": "frontend/src/store/treeEditorStore.ts",
|
|
"via": "function_call",
|
|
"label": "replaceTreeStructure(workingTree)",
|
|
"passes": "new TreeStructure; sets treeStructure, isDirty=true",
|
|
"from_line": 92,
|
|
"to_line": 1022,
|
|
"unverified": false
|
|
}
|
|
],
|
|
"group": "Flow Authoring"
|
|
},
|
|
{
|
|
"id": "authoring.flow3",
|
|
"label": "Create procedural flow via AI builder",
|
|
"description": "",
|
|
"cluster": "authoring",
|
|
"steps": [
|
|
{
|
|
"from": "frontend/src/pages/ProceduralEditorPage.tsx",
|
|
"to": "frontend/src/hooks/useEditorAI.ts",
|
|
"via": "function_call",
|
|
"label": "editorAI.openPanel() \u2014 user clicks AI Assist",
|
|
"passes": "no args",
|
|
"from_line": 297,
|
|
"to_line": 56,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "frontend/src/hooks/useEditorAI.ts",
|
|
"to": "frontend/src/api/editorAI.ts",
|
|
"via": "function_call",
|
|
"label": "ensureSession() \u2192 startSession('procedural')",
|
|
"passes": "flowType='procedural', treeId",
|
|
"from_line": 38,
|
|
"to_line": 13,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "frontend/src/api/editorAI.ts",
|
|
"to": "backend/app/api/endpoints/ai_chat.py",
|
|
"via": "http_post",
|
|
"label": "POST /ai/chat/sessions",
|
|
"passes": "{flow_type:'procedural', tree_id}",
|
|
"from_line": 14,
|
|
"to_line": 56,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/core/ai_chat_service.py",
|
|
"to": "external:anthropic_api",
|
|
"via": "external_api",
|
|
"label": "opening greeting with PROCEDURAL_SCHEMA_CONTEXT + PROCEDURAL_INTERVIEW_PROTOCOL + PROCEDURAL_RESPONSE_FORMAT",
|
|
"passes": "system prompt assembled by _build_system_prompt('procedural')",
|
|
"from_line": 257,
|
|
"to_line": null,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "frontend/src/hooks/useEditorAI.ts",
|
|
"to": "frontend/src/api/editorAI.ts",
|
|
"via": "function_call",
|
|
"label": "sendMessage() on user submit",
|
|
"passes": "{sessionId, content, actionType, flowContext={name, description, steps, intake_form}}",
|
|
"from_line": 98,
|
|
"to_line": 21,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "frontend/src/api/editorAI.ts",
|
|
"to": "backend/app/api/endpoints/ai_chat.py",
|
|
"via": "http_post",
|
|
"label": "POST /ai/chat/sessions/{id}/messages",
|
|
"passes": "{content, action_type, focal_node_id, flow_context}",
|
|
"from_line": 22,
|
|
"to_line": 135,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/core/ai_chat_service.py",
|
|
"to": "external:anthropic_api",
|
|
"via": "external_api",
|
|
"label": "provider.generate_text()",
|
|
"passes": "full system_prompt with live flow_context (steps + intake_form)",
|
|
"from_line": 571,
|
|
"to_line": null,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/core/ai_chat_service.py",
|
|
"to": "backend/app/core/ai_chat_service.py",
|
|
"via": "marker_parse",
|
|
"label": "_parse_ai_response() \u2014 extract [STEPS_UPDATE]",
|
|
"passes": "raw_response \u2192 tree_update={steps:[...]} dict; also parses [INTAKE_FORM] marker",
|
|
"from_line": 403,
|
|
"to_line": 403,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/core/ai_chat_service.py",
|
|
"to": "backend/app/models/ai_chat_session.py",
|
|
"via": "state_update",
|
|
"label": "session.working_tree = {steps:[...]}, session.tree_metadata.intake_form = [...]",
|
|
"passes": "procedural step list + optional intake form stored in JSONB",
|
|
"from_line": 604,
|
|
"to_line": null,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/api/endpoints/ai_chat.py",
|
|
"to": "frontend/src/hooks/useEditorAI.ts",
|
|
"via": "http_post",
|
|
"label": "200 AIChatMessageResponse",
|
|
"passes": "{content, working_tree={steps:[...]}, tree_metadata={intake_form:[...]}}",
|
|
"from_line": 223,
|
|
"to_line": 117,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "frontend/src/hooks/useEditorAI.ts",
|
|
"to": "frontend/src/pages/ProceduralEditorPage.tsx",
|
|
"via": "function_call",
|
|
"label": "onFlowUpdate(result.working_tree, result.tree_metadata)",
|
|
"passes": "working_tree.steps array + metadata.intake_form array",
|
|
"from_line": 117,
|
|
"to_line": 62,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "frontend/src/pages/ProceduralEditorPage.tsx",
|
|
"to": "frontend/src/store/proceduralEditorStore.ts",
|
|
"via": "function_call",
|
|
"label": "replaceSteps(stepsData, intakeData)",
|
|
"passes": "ProceduralStep[] + IntakeFormField[]; sets isDirty=true",
|
|
"from_line": 67,
|
|
"to_line": 475,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "frontend/src/pages/ProceduralEditorPage.tsx",
|
|
"to": "frontend/src/api/trees.ts",
|
|
"via": "function_call",
|
|
"label": "handleSave() \u2192 treesApi.create/update",
|
|
"passes": "{name, tree_type:'procedural', tree_structure:{steps:[]}, intake_form:[], status}",
|
|
"from_line": 199,
|
|
"to_line": 15,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "frontend/src/api/trees.ts",
|
|
"to": "backend/app/api/endpoints/trees.py",
|
|
"via": "http_post",
|
|
"label": "POST /trees",
|
|
"passes": "TreeCreate with tree_type='procedural', tree_structure={steps:[]}",
|
|
"from_line": 16,
|
|
"to_line": 404,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/api/endpoints/trees.py",
|
|
"to": "external:postgres",
|
|
"via": "db_write",
|
|
"label": "INSERT INTO trees",
|
|
"passes": "Tree row with tree_structure JSONB containing steps array",
|
|
"from_line": 560,
|
|
"to_line": null,
|
|
"unverified": false
|
|
}
|
|
],
|
|
"group": "Flow Authoring"
|
|
},
|
|
{
|
|
"id": "authoring.flow4",
|
|
"label": "Submit flow improvement proposal",
|
|
"description": "FlowProposals are created server-side by the Knowledge Flywheel after AI sessions resolve (flowpilot_engine / session resolution logic). The session UI surfaces existing proposals; it does not POST to /flow-proposals directly. The front-end entry point is ReviewQueuePage which only reads proposals.",
|
|
"cluster": "authoring",
|
|
"steps": [
|
|
{
|
|
"from": "backend/app/services/flowpilot_engine.py",
|
|
"to": "backend/app/models/flow_proposal.py",
|
|
"via": "db_write",
|
|
"label": "Knowledge Flywheel creates FlowProposal after session resolution",
|
|
"passes": "FlowProposal(proposal_type='new_flow'|'enhancement', title, description, proposed_flow_data, source_session_id, account_id)",
|
|
"from_line": null,
|
|
"to_line": null,
|
|
"unverified": true
|
|
},
|
|
{
|
|
"from": "backend/app/models/flow_proposal.py",
|
|
"to": "external:postgres",
|
|
"via": "db_write",
|
|
"label": "INSERT INTO flow_proposals",
|
|
"passes": "full FlowProposal row (id, account_id, status='pending', proposal_type, title, proposed_flow_data JSONB ...)",
|
|
"from_line": null,
|
|
"to_line": null,
|
|
"unverified": false
|
|
}
|
|
],
|
|
"group": "Flow Authoring"
|
|
},
|
|
{
|
|
"id": "authoring.flow5",
|
|
"label": "Approve flow proposal (admin)",
|
|
"description": "",
|
|
"cluster": "authoring",
|
|
"steps": [
|
|
{
|
|
"from": "frontend/src/pages/ReviewQueuePage.tsx",
|
|
"to": "frontend/src/api/flowProposals.ts",
|
|
"via": "function_call",
|
|
"label": "loadProposals() \u2192 flowProposalsApi.list()",
|
|
"passes": "{status:'pending', sort_by, limit:50}",
|
|
"from_line": 33,
|
|
"to_line": 10,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "frontend/src/api/flowProposals.ts",
|
|
"to": "backend/app/api/endpoints/flow_proposals.py",
|
|
"via": "http_get",
|
|
"label": "GET /flow-proposals",
|
|
"passes": "?status=pending&sort_by=newest&limit=50",
|
|
"from_line": 13,
|
|
"to_line": 40,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/api/endpoints/flow_proposals.py",
|
|
"to": "external:postgres",
|
|
"via": "db_read",
|
|
"label": "SELECT flow_proposals WHERE account_id = ... AND status = 'pending'",
|
|
"passes": "returns FlowProposal rows",
|
|
"from_line": 58,
|
|
"to_line": null,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "frontend/src/pages/ReviewQueuePage.tsx",
|
|
"to": "frontend/src/components/flowpilot/ProposalCard.tsx",
|
|
"via": "function_call",
|
|
"label": "render ProposalCard list",
|
|
"passes": "FlowProposalSummary[]",
|
|
"from_line": 167,
|
|
"to_line": null,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "frontend/src/pages/ReviewQueuePage.tsx",
|
|
"to": "frontend/src/api/flowProposals.ts",
|
|
"via": "function_call",
|
|
"label": "loadDetail(id) \u2192 flowProposalsApi.get(id)",
|
|
"passes": "proposal id",
|
|
"from_line": 57,
|
|
"to_line": 27,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "frontend/src/api/flowProposals.ts",
|
|
"to": "backend/app/api/endpoints/flow_proposals.py",
|
|
"via": "http_get",
|
|
"label": "GET /flow-proposals/{id}",
|
|
"passes": "proposal UUID",
|
|
"from_line": 28,
|
|
"to_line": 169,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "frontend/src/pages/ReviewQueuePage.tsx",
|
|
"to": "frontend/src/components/flowpilot/ProposalDetail.tsx",
|
|
"via": "function_call",
|
|
"label": "render ProposalDetail with detail",
|
|
"passes": "FlowProposalDetail + onReview callback",
|
|
"from_line": 186,
|
|
"to_line": null,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "frontend/src/pages/ReviewQueuePage.tsx",
|
|
"to": "frontend/src/api/flowProposals.ts",
|
|
"via": "function_call",
|
|
"label": "handleReview('approve') \u2192 flowProposalsApi.review(id, {action:'approve'})",
|
|
"passes": "{action:'approve', reviewer_notes}",
|
|
"from_line": 69,
|
|
"to_line": 32,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "frontend/src/api/flowProposals.ts",
|
|
"to": "backend/app/api/endpoints/flow_proposals.py",
|
|
"via": "http_post",
|
|
"label": "POST /flow-proposals/{id}/review",
|
|
"passes": "ReviewProposalRequest {action:'approve', reviewer_notes}",
|
|
"from_line": 33,
|
|
"to_line": 194,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/api/endpoints/flow_proposals.py",
|
|
"to": "backend/app/api/endpoints/flow_proposals.py",
|
|
"via": "function_call",
|
|
"label": "_create_tree_from_proposal(proposal, flow_data, user, db)",
|
|
"passes": "proposal.proposed_flow_data \u2192 extracts tree_structure + match_keywords",
|
|
"from_line": 228,
|
|
"to_line": 278,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/api/endpoints/flow_proposals.py",
|
|
"to": "backend/app/models/tree.py",
|
|
"via": "db_write",
|
|
"label": "db.add(new_tree) \u2014 publishes Tree from proposal",
|
|
"passes": "Tree(name=proposal.title, tree_structure, tree_type='troubleshooting', origin='ai_generated', account_id)",
|
|
"from_line": 294,
|
|
"to_line": null,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/api/endpoints/flow_proposals.py",
|
|
"to": "external:postgres",
|
|
"via": "db_write",
|
|
"label": "INSERT INTO trees + UPDATE flow_proposals SET status='approved', published_flow_id",
|
|
"passes": "new Tree row; proposal.status updated to 'approved', proposal.published_flow_id set",
|
|
"from_line": 229,
|
|
"to_line": null,
|
|
"unverified": false
|
|
}
|
|
],
|
|
"group": "Flow Authoring"
|
|
},
|
|
{
|
|
"id": "authoring.flow6",
|
|
"label": "Editor-Embedded Flow Assist (EditorAIPanel)",
|
|
"description": "This is the same AI chat path used by flows 2 and 3 but with action_type != 'open_chat' and a focal_node_id. The key difference is [DELTA] marker handling for node-scoped operations (generate_branch, modify_node, add_steps). DELTA parsing lives in ai_chat_service._parse_delta() and applies partial diffs rather than full [TREE_UPDATE] replacements.",
|
|
"cluster": "authoring",
|
|
"steps": [
|
|
{
|
|
"from": "frontend/src/pages/TreeEditorPage.tsx",
|
|
"to": "frontend/src/hooks/useEditorAI.ts",
|
|
"via": "function_call",
|
|
"label": "editorAI.triggerAction(nodeId, 'generate_branch', prompt)",
|
|
"passes": "nodeId, actionType, prompt text set in input",
|
|
"from_line": 866,
|
|
"to_line": 135,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "frontend/src/hooks/useEditorAI.ts",
|
|
"to": "frontend/src/components/editor-ai/EditorAIPanel.tsx",
|
|
"via": "state_update",
|
|
"label": "setInput(prompt); setIsOpen(true); pendingActionRef.current = 'generate_branch'",
|
|
"passes": "UI state updates \u2014 panel opens pre-filled",
|
|
"from_line": 136,
|
|
"to_line": null,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "frontend/src/hooks/useEditorAI.ts",
|
|
"to": "frontend/src/api/editorAI.ts",
|
|
"via": "function_call",
|
|
"label": "sendMessage() \u2192 editorAIApi.sendMessage()",
|
|
"passes": "{sessionId, content, actionType:'generate_branch', focalNodeId, flowContext}",
|
|
"from_line": 98,
|
|
"to_line": 21,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "frontend/src/api/editorAI.ts",
|
|
"to": "backend/app/api/endpoints/ai_chat.py",
|
|
"via": "http_post",
|
|
"label": "POST /ai/chat/sessions/{id}/messages",
|
|
"passes": "{content, action_type:'generate_branch', focal_node_id, flow_context}",
|
|
"from_line": 22,
|
|
"to_line": 135,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/api/endpoints/ai_chat.py",
|
|
"to": "backend/app/core/ai_chat_service.py",
|
|
"via": "function_call",
|
|
"label": "send_message(..., action_type='generate_branch', focal_node_id)",
|
|
"passes": "action-specific system prompt built by _build_action_prompt() injected into system_prompt",
|
|
"from_line": 171,
|
|
"to_line": 524,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/core/ai_chat_service.py",
|
|
"to": "backend/app/core/ai_chat_service.py",
|
|
"via": "function_call",
|
|
"label": "_build_action_prompt('generate_branch', focal_node_id, tree_structure, flow_type)",
|
|
"passes": "CURRENT FLOW STRUCTURE JSON + FOCAL NODE JSON + action instruction; appended to system_prompt",
|
|
"from_line": 307,
|
|
"to_line": 307,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/core/ai_chat_service.py",
|
|
"to": "external:anthropic_api",
|
|
"via": "external_api",
|
|
"label": "provider.generate_text() with action model",
|
|
"passes": "settings.get_model_for_action(action_type) selects model; full prompt sent",
|
|
"from_line": 569,
|
|
"to_line": null,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/core/ai_chat_service.py",
|
|
"to": "backend/app/core/ai_chat_service.py",
|
|
"via": "marker_parse",
|
|
"label": "_parse_ai_response() \u2014 extracts [TREE_UPDATE] or [DELTA]",
|
|
"passes": "For generate_branch/modify_node: AI may emit [DELTA]{action, target_node_id, nodes}[/DELTA] for partial updates; _parse_delta() called separately",
|
|
"from_line": 281,
|
|
"to_line": 281,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/api/endpoints/ai_chat.py",
|
|
"to": "frontend/src/hooks/useEditorAI.ts",
|
|
"via": "http_post",
|
|
"label": "200 AIChatMessageResponse",
|
|
"passes": "{content, working_tree (full tree if [TREE_UPDATE] present), tree_metadata}",
|
|
"from_line": 223,
|
|
"to_line": 107,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "frontend/src/hooks/useEditorAI.ts",
|
|
"to": "frontend/src/pages/TreeEditorPage.tsx",
|
|
"via": "function_call",
|
|
"label": "onFlowUpdate(result.working_tree, result.tree_metadata)",
|
|
"passes": "working_tree passed to handleFlowUpdate; replaceTreeStructure() if troubleshooting",
|
|
"from_line": 117,
|
|
"to_line": 89,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "frontend/src/pages/TreeEditorPage.tsx",
|
|
"to": "frontend/src/store/treeEditorStore.ts",
|
|
"via": "function_call",
|
|
"label": "replaceTreeStructure(workingTree)",
|
|
"passes": "new TreeStructure applied to store; isDirty=true",
|
|
"from_line": 92,
|
|
"to_line": 1022,
|
|
"unverified": false
|
|
}
|
|
],
|
|
"group": "Flow Authoring"
|
|
},
|
|
{
|
|
"id": "integrations.flow_3_connect_psa",
|
|
"label": "Connect PSA (ConnectWise)",
|
|
"description": "",
|
|
"cluster": "integrations",
|
|
"steps": [
|
|
{
|
|
"from": "frontend/src/pages/account/IntegrationsPage.tsx",
|
|
"to": "frontend/src/api/integrations.ts",
|
|
"via": "function_call",
|
|
"label": "handleCreate calls integrationsApi.createConnection",
|
|
"passes": "{provider:'connectwise', display_name, site_url, company_id, public_key, private_key}",
|
|
"from_line": 102,
|
|
"to_line": 9,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "frontend/src/api/integrations.ts",
|
|
"to": "backend/app/api/endpoints/integrations.py",
|
|
"via": "http_post",
|
|
"label": "POST /integrations/psa/connections",
|
|
"passes": "PsaConnectionCreate body",
|
|
"from_line": 9,
|
|
"to_line": 140,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/api/endpoints/integrations.py",
|
|
"to": "backend/app/services/psa/connectwise/provider.py",
|
|
"via": "function_call",
|
|
"label": "_test_credentials \u2192 ConnectWiseProvider.test_connection",
|
|
"passes": "{site_url, company_id, public_key, private_key, client_id}",
|
|
"from_line": 160,
|
|
"to_line": 37,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/services/psa/connectwise/provider.py",
|
|
"to": "external:connectwise",
|
|
"via": "external_api",
|
|
"label": "GET /system/info \u2014 credential validation",
|
|
"passes": "Basic auth header (company+public_key:private_key)",
|
|
"from_line": 41,
|
|
"to_line": null,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/api/endpoints/integrations.py",
|
|
"to": "backend/app/services/psa/encryption.py",
|
|
"via": "function_call",
|
|
"label": "encrypt_credentials({public_key, private_key})",
|
|
"passes": "plaintext credentials dict",
|
|
"from_line": 186,
|
|
"to_line": 31,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/services/psa/encryption.py",
|
|
"to": "backend/app/api/endpoints/integrations.py",
|
|
"via": "function_call",
|
|
"label": "Returns Fernet-encrypted token (HKDF from SECRET_KEY)",
|
|
"passes": "encrypted credential string",
|
|
"from_line": 35,
|
|
"to_line": 186,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/api/endpoints/integrations.py",
|
|
"to": "external:postgres",
|
|
"via": "db_write",
|
|
"label": "INSERT psa_connections (credentials_encrypted, is_active=true)",
|
|
"passes": "PsaConnection row",
|
|
"from_line": 190,
|
|
"to_line": null,
|
|
"unverified": false
|
|
}
|
|
],
|
|
"group": "Integrations"
|
|
},
|
|
{
|
|
"id": "integrations.flow_4_post_session_docs",
|
|
"label": "Post Session Docs to Ticket",
|
|
"description": "",
|
|
"cluster": "integrations",
|
|
"steps": [
|
|
{
|
|
"from": "frontend/src/api/integrations.ts",
|
|
"to": "backend/app/api/endpoints/sessions.py",
|
|
"via": "http_post",
|
|
"label": "POST /sessions/{id}/psa-post (sessionPsaApi.postToTicket)",
|
|
"passes": "{note_type, content, update_status_id?}",
|
|
"from_line": 54,
|
|
"to_line": 1005,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/api/endpoints/sessions.py",
|
|
"to": "external:postgres",
|
|
"via": "db_read",
|
|
"label": "SELECT sessions + psa_connections + psa_member_mappings",
|
|
"passes": "session_id, account_id",
|
|
"from_line": 1024,
|
|
"to_line": null,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/api/endpoints/sessions.py",
|
|
"to": "backend/app/services/psa/registry.py",
|
|
"via": "function_call",
|
|
"label": "get_provider_for_account \u2192 decrypt credentials \u2192 build ConnectWiseProvider",
|
|
"passes": "account_id, db",
|
|
"from_line": 1068,
|
|
"to_line": 16,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/services/psa/registry.py",
|
|
"to": "backend/app/services/psa/encryption.py",
|
|
"via": "function_call",
|
|
"label": "decrypt_credentials(conn.credentials_encrypted)",
|
|
"passes": "Fernet token",
|
|
"from_line": 64,
|
|
"to_line": 38,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/services/psa/registry.py",
|
|
"to": "backend/app/services/psa/connectwise/provider.py",
|
|
"via": "function_call",
|
|
"label": "Instantiate ConnectWiseProvider(client)",
|
|
"passes": "{site_url, company_id, public_key, private_key, client_id}",
|
|
"from_line": 72,
|
|
"to_line": 34,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/api/endpoints/sessions.py",
|
|
"to": "backend/app/services/psa/connectwise/provider.py",
|
|
"via": "function_call",
|
|
"label": "provider.post_note(ticket_id, content, note_type, member_id?)",
|
|
"passes": "{ticket_id, text, note_type flags, member attribution}",
|
|
"from_line": 1069,
|
|
"to_line": 201,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/services/psa/connectwise/provider.py",
|
|
"to": "external:connectwise",
|
|
"via": "external_api",
|
|
"label": "POST /service/tickets/{id}/notes",
|
|
"passes": "{text, internalAnalysisFlag/resolutionFlag/detailDescriptionFlag, member}",
|
|
"from_line": 255,
|
|
"to_line": null,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/api/endpoints/sessions.py",
|
|
"to": "external:postgres",
|
|
"via": "db_write",
|
|
"label": "INSERT psa_post_log (audit trail)",
|
|
"passes": "{session_id, ticket_id, note_type, content_posted, external_note_id, status}",
|
|
"from_line": 1106,
|
|
"to_line": null,
|
|
"unverified": false
|
|
}
|
|
],
|
|
"group": "Integrations"
|
|
},
|
|
{
|
|
"id": "integrations.flow_5_ticket_context",
|
|
"label": "Get Ticket Context (FlowPilot)",
|
|
"description": "",
|
|
"cluster": "integrations",
|
|
"steps": [
|
|
{
|
|
"from": "frontend/src/hooks/useTicketContext.ts",
|
|
"to": "frontend/src/api/psaContext.ts",
|
|
"via": "function_call",
|
|
"label": "fetchContext calls psaContextApi.getTicketContext(psaTicketId)",
|
|
"passes": "psaTicketId, psaConnectionId (guarded: both must be set)",
|
|
"from_line": 25,
|
|
"to_line": 68,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "frontend/src/api/psaContext.ts",
|
|
"to": "backend/app/api/endpoints/integrations.py",
|
|
"via": "http_get",
|
|
"label": "GET /integrations/psa/tickets/{ticket_id}/context",
|
|
"passes": "ticket_id as path param",
|
|
"from_line": 69,
|
|
"to_line": 692,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/api/endpoints/integrations.py",
|
|
"to": "external:postgres",
|
|
"via": "db_read",
|
|
"label": "SELECT psa_connections WHERE account_id AND is_active",
|
|
"passes": "account_id",
|
|
"from_line": 712,
|
|
"to_line": null,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/api/endpoints/integrations.py",
|
|
"to": "backend/app/services/psa/registry.py",
|
|
"via": "function_call",
|
|
"label": "get_provider_for_account \u2192 ConnectWiseProvider",
|
|
"passes": "account_id, db",
|
|
"from_line": 724,
|
|
"to_line": 16,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/api/endpoints/integrations.py",
|
|
"to": "backend/app/services/psa/connectwise/provider.py",
|
|
"via": "function_call",
|
|
"label": "provider.get_ticket_context(ticket_id, connection_id)",
|
|
"passes": "ticket_id int, connection_id str",
|
|
"from_line": 731,
|
|
"to_line": 352,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/services/psa/connectwise/provider.py",
|
|
"to": "external:connectwise",
|
|
"via": "external_api",
|
|
"label": "Parallel GET /service/tickets/{id} + /configurations + /notes + /company/companies/{id} + related tickets",
|
|
"passes": "ticket_id",
|
|
"from_line": 377,
|
|
"to_line": null,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/services/psa/connectwise/provider.py",
|
|
"to": "backend/app/api/endpoints/integrations.py",
|
|
"via": "function_call",
|
|
"label": "Returns TicketContext (cached 5min per ticket)",
|
|
"passes": "{ticket, company, contact, configurations, notes, related_tickets}",
|
|
"from_line": 371,
|
|
"to_line": 731,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/api/endpoints/integrations.py",
|
|
"to": "frontend/src/hooks/useTicketContext.ts",
|
|
"via": "state_update",
|
|
"label": "TicketContext JSON returned to hook, stored in context state",
|
|
"passes": "TicketContext object",
|
|
"from_line": 743,
|
|
"to_line": 26,
|
|
"unverified": false
|
|
}
|
|
],
|
|
"group": "Integrations"
|
|
},
|
|
{
|
|
"id": "chat.send_message",
|
|
"label": "Send chat message in FlowPilot",
|
|
"description": "Engineer types in FlowPilotMessageBar and submits; onRespond calls useFlowPilotSession.respondToStep which POSTs to /ai-sessions/{id}/respond (guided) or \u2014 for chat sessions \u2014 /ai-sessions/{id}/chat. The chat path flows through unified_chat_service: RAG search, _call_ai (Anthropic), then regex parsing for [QUESTIONS]/[ACTIONS]/[FORK]/[PROMOTE]/[SUGGEST_FIX]/[FIX_OUTCOME] markers, task lane persistence, and response returned.",
|
|
"cluster": "session",
|
|
"steps": [
|
|
{
|
|
"from": "frontend/src/components/flowpilot/FlowPilotSession.tsx",
|
|
"to": "frontend/src/components/flowpilot/FlowPilotMessageBar.tsx",
|
|
"via": "function_call",
|
|
"label": "render FlowPilotMessageBar with onRespond",
|
|
"passes": "onRespond callback, isProcessing",
|
|
"from_line": 375,
|
|
"to_line": 17,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "frontend/src/components/flowpilot/FlowPilotMessageBar.tsx",
|
|
"to": "frontend/src/hooks/useFlowPilotSession.ts",
|
|
"via": "function_call",
|
|
"label": "onRespond({free_text_input})",
|
|
"passes": "StepResponseRequest: {free_text_input: string}",
|
|
"from_line": 46,
|
|
"to_line": 117,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "frontend/src/hooks/useFlowPilotSession.ts",
|
|
"to": "frontend/src/api/aiSessions.ts",
|
|
"via": "function_call",
|
|
"label": "aiSessionsApi.respondToStep(sessionId, data)",
|
|
"passes": "StepResponseRequest",
|
|
"from_line": 122,
|
|
"to_line": 48,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "frontend/src/api/aiSessions.ts",
|
|
"to": "backend/app/api/endpoints/ai_sessions.py",
|
|
"via": "http_post",
|
|
"label": "POST /ai-sessions/{id}/respond",
|
|
"passes": "{free_text_input?, selected_option?, was_skipped?, action_result?}",
|
|
"from_line": 50,
|
|
"to_line": 354,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/api/endpoints/ai_sessions.py",
|
|
"to": "backend/app/services/flowpilot_engine.py",
|
|
"via": "function_call",
|
|
"label": "flowpilot_engine.process_response()",
|
|
"passes": "session_id, StepResponseRequest, user_id, db",
|
|
"from_line": 369,
|
|
"to_line": 365,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/services/flowpilot_engine.py",
|
|
"to": "backend/app/core/ai_provider.py",
|
|
"via": "function_call",
|
|
"label": "provider.generate_json() with full conversation",
|
|
"passes": "system_prompt_snapshot + conversation_messages (all turns)",
|
|
"from_line": 399,
|
|
"to_line": null,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/core/ai_provider.py",
|
|
"to": "external:anthropic_api",
|
|
"via": "external_api",
|
|
"label": "Anthropic messages.create()",
|
|
"passes": "structured JSON response schema",
|
|
"from_line": null,
|
|
"to_line": null,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/services/flowpilot_engine.py",
|
|
"to": "external:postgres",
|
|
"via": "db_write",
|
|
"label": "INSERT new AISessionStep, UPDATE session.step_count/confidence",
|
|
"passes": "parsed {type, content, options, confidence} \u2192 new step row",
|
|
"from_line": 444,
|
|
"to_line": null,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/api/endpoints/ai_sessions.py",
|
|
"to": "frontend/src/hooks/useFlowPilotSession.ts",
|
|
"via": "http_post",
|
|
"label": "200 StepResponseResponse",
|
|
"passes": "{next_step: AISessionStepResponse, status, confidence_tier}",
|
|
"from_line": 408,
|
|
"to_line": 122,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "frontend/src/hooks/useFlowPilotSession.ts",
|
|
"to": "frontend/src/pages/FlowPilotSessionPage.tsx",
|
|
"via": "state_update",
|
|
"label": "setAllSteps / setCurrentStep",
|
|
"passes": "updated steps array + new current step",
|
|
"from_line": 131,
|
|
"to_line": 27,
|
|
"unverified": false
|
|
}
|
|
],
|
|
"group": "Sessions & FlowPilot"
|
|
},
|
|
{
|
|
"id": "chat.send_message_chat_session",
|
|
"label": "Send chat message (chat session type)",
|
|
"description": "For session_type='chat', the FlowPilotMessageBar calls aiSessionsApi.sendChatMessage which posts to /ai-sessions/{id}/chat. The endpoint calls unified_chat_service.send_chat_message which does RAG search, builds conversation history, calls _call_ai (Anthropic beta with prompt caching + optional MCP), parses markers from the response, persists task lane and suggested fixes to Postgres, and returns ChatMessageResponse with parsed actions/questions.",
|
|
"cluster": "session",
|
|
"steps": [
|
|
{
|
|
"from": "frontend/src/api/aiSessions.ts",
|
|
"to": "backend/app/api/endpoints/ai_sessions.py",
|
|
"via": "http_post",
|
|
"label": "POST /ai-sessions/{id}/chat",
|
|
"passes": "{message: string, upload_ids?: string[]}",
|
|
"from_line": 41,
|
|
"to_line": 270,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/api/endpoints/ai_sessions.py",
|
|
"to": "backend/app/services/storage_service.py",
|
|
"via": "function_call",
|
|
"label": "fetch_upload_images(upload_ids, account_id, db)",
|
|
"passes": "upload UUIDs \u2192 FileUpload rows \u2192 S3 download \u2192 base64 dicts",
|
|
"from_line": 292,
|
|
"to_line": 163,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/services/storage_service.py",
|
|
"to": "external:railway_s3",
|
|
"via": "external_api",
|
|
"label": "S3 GetObject for each image upload",
|
|
"passes": "storage_key \u2192 raw bytes",
|
|
"from_line": 199,
|
|
"to_line": null,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/api/endpoints/ai_sessions.py",
|
|
"to": "backend/app/services/unified_chat_service.py",
|
|
"via": "function_call",
|
|
"label": "unified_chat_service.send_chat_message()",
|
|
"passes": "session_id, user_id, account_id, message, db, images",
|
|
"from_line": 304,
|
|
"to_line": 570,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/services/unified_chat_service.py",
|
|
"to": "backend/app/services/assistant_chat_service.py",
|
|
"via": "function_call",
|
|
"label": "_call_ai(system, rag_context, history, new_message, images)",
|
|
"passes": "ASSISTANT_SYSTEM_PROMPT, RAG context, conversation history, images list",
|
|
"from_line": 755,
|
|
"to_line": null,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/services/assistant_chat_service.py",
|
|
"to": "external:anthropic_api",
|
|
"via": "external_api",
|
|
"label": "client.beta.messages.create() with prompt caching",
|
|
"passes": "system blocks (cached), history (last msg cached), new user message + images",
|
|
"from_line": null,
|
|
"to_line": null,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/services/unified_chat_service.py",
|
|
"to": "backend/app/services/unified_chat_service.py",
|
|
"via": "function_call",
|
|
"label": "Parse [QUESTIONS], [ACTIONS], [FORK], [PROMOTE], [SUGGEST_FIX], [FIX_OUTCOME] markers",
|
|
"passes": "raw AI content \u2192 stripped display_content + structured marker data",
|
|
"from_line": 764,
|
|
"to_line": 84,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/services/unified_chat_service.py",
|
|
"to": "external:postgres",
|
|
"via": "db_write",
|
|
"label": "UPDATE conversation_messages, pending_task_lane, upsert SessionSuggestedFix",
|
|
"passes": "display_content appended to JSONB, task lane dict, new fix row (supersedes prior)",
|
|
"from_line": 800,
|
|
"to_line": null,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/api/endpoints/ai_sessions.py",
|
|
"to": "frontend/src/api/aiSessions.ts",
|
|
"via": "http_post",
|
|
"label": "200 ChatMessageResponse",
|
|
"passes": "{content, suggested_flows, fork, actions, questions}",
|
|
"from_line": 343,
|
|
"to_line": 40,
|
|
"unverified": false
|
|
}
|
|
],
|
|
"group": "Sessions & FlowPilot"
|
|
},
|
|
{
|
|
"id": "fix.apply_outcome",
|
|
"label": "Apply suggested fix outcome",
|
|
"description": "After the AI emits [SUGGEST_FIX] in a chat turn, unified_chat_service writes a SessionSuggestedFix row. The frontend polls /suggested-fixes/active to surface VerifyingBanner / PendingBanner components. When engineer records outcome, the component calls sessionSuggestedFixesApi.patchOutcome which PATCHes /ai-sessions/{id}/suggested-fixes/{fix_id}/outcome. Backend validates transitions, stamps verified_at (for success/failed), bumps session.state_version, returns updated fix row.",
|
|
"cluster": "session",
|
|
"steps": [
|
|
{
|
|
"from": "backend/app/services/unified_chat_service.py",
|
|
"to": "external:postgres",
|
|
"via": "db_write",
|
|
"label": "_persist_suggested_fix() \u2014 INSERT SessionSuggestedFix, supersede prior",
|
|
"passes": "{title, description, confidence_pct, ai_drafted_script} + UPDATE superseded_at on old row + bump state_version",
|
|
"from_line": 407,
|
|
"to_line": null,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "frontend/src/api/sessionSuggestedFixes.ts",
|
|
"to": "backend/app/api/endpoints/session_suggested_fixes.py",
|
|
"via": "http_get",
|
|
"label": "GET /ai-sessions/{id}/suggested-fixes/active",
|
|
"passes": "session_id",
|
|
"from_line": 84,
|
|
"to_line": 64,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/api/endpoints/session_suggested_fixes.py",
|
|
"to": "external:postgres",
|
|
"via": "db_read",
|
|
"label": "SELECT SessionSuggestedFix WHERE superseded_at IS NULL",
|
|
"passes": "session_id \u2192 active fix row",
|
|
"from_line": 80,
|
|
"to_line": null,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/api/endpoints/session_suggested_fixes.py",
|
|
"to": "frontend/src/api/sessionSuggestedFixes.ts",
|
|
"via": "http_get",
|
|
"label": "200 SessionSuggestedFixResponse (or 404 \u2192 null)",
|
|
"passes": "{id, title, description, confidence_pct, status, ai_outcome_proposal}",
|
|
"from_line": 94,
|
|
"to_line": 84,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "frontend/src/api/sessionSuggestedFixes.ts",
|
|
"to": "backend/app/api/endpoints/session_suggested_fixes.py",
|
|
"via": "http_patch",
|
|
"label": "PATCH /ai-sessions/{id}/suggested-fixes/{fix_id}/outcome",
|
|
"passes": "{outcome: 'applied_success'|'applied_failed'|'applied_partial'|'applied_pending'|'dismissed', notes?}",
|
|
"from_line": 139,
|
|
"to_line": 285,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/api/endpoints/session_suggested_fixes.py",
|
|
"to": "external:postgres",
|
|
"via": "db_write",
|
|
"label": "UPDATE session_suggested_fixes SET status=outcome, verified_at/failure_reason; UPDATE ai_sessions SET state_version+1",
|
|
"passes": "fix.status transition + timestamp fields + ai_outcome_proposal=null",
|
|
"from_line": 334,
|
|
"to_line": null,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/api/endpoints/session_suggested_fixes.py",
|
|
"to": "frontend/src/api/sessionSuggestedFixes.ts",
|
|
"via": "http_patch",
|
|
"label": "200 updated SessionSuggestedFixResponse",
|
|
"passes": "{id, status, verified_at, applied_at, partial_notes, failure_reason}",
|
|
"from_line": 365,
|
|
"to_line": 139,
|
|
"unverified": false
|
|
}
|
|
],
|
|
"group": "Sessions & FlowPilot"
|
|
},
|
|
{
|
|
"id": "session.escalate",
|
|
"label": "Escalate session",
|
|
"description": "Engineer clicks Escalate in the header, enters reason in EscalateModal, calls fp.escalateSession. POSTs to /ai-sessions/{id}/escalate which is now a thin shim over HandoffManager. HandoffManager.create_handoff writes a SessionHandoff row + dual-writes escalation_package, sets session.status='escalated'. finalize_escalation generates SessionDocumentation + pushes to PSA. dispatch_escalation_notifications fans out via EscalationBus SSE + email. enrich_escalation_async runs Anthropic AI assessment in background.",
|
|
"cluster": "session",
|
|
"steps": [
|
|
{
|
|
"from": "frontend/src/pages/FlowPilotSessionPage.tsx",
|
|
"to": "frontend/src/components/flowpilot/EscalateModal.tsx",
|
|
"via": "state_update",
|
|
"label": "setShowEscalate(true)",
|
|
"passes": "opens modal, passes fp.escalateSession as onEscalate",
|
|
"from_line": 403,
|
|
"to_line": 17,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "frontend/src/components/flowpilot/EscalateModal.tsx",
|
|
"to": "frontend/src/hooks/useFlowPilotSession.ts",
|
|
"via": "function_call",
|
|
"label": "onEscalate({escalation_reason})",
|
|
"passes": "EscalateSessionRequest: {escalation_reason: string}",
|
|
"from_line": 23,
|
|
"to_line": 166,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "frontend/src/hooks/useFlowPilotSession.ts",
|
|
"to": "frontend/src/api/aiSessions.ts",
|
|
"via": "function_call",
|
|
"label": "aiSessionsApi.escalateSession(sessionId, data)",
|
|
"passes": "EscalateSessionRequest",
|
|
"from_line": 170,
|
|
"to_line": 64,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "frontend/src/api/aiSessions.ts",
|
|
"to": "backend/app/api/endpoints/ai_sessions.py",
|
|
"via": "http_post",
|
|
"label": "POST /ai-sessions/{id}/escalate",
|
|
"passes": "{escalation_reason, escalated_to_id?}",
|
|
"from_line": 66,
|
|
"to_line": 463,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/api/endpoints/ai_sessions.py",
|
|
"to": "backend/app/services/handoff_manager.py",
|
|
"via": "function_call",
|
|
"label": "HandoffManager.create_handoff(intent='escalate')",
|
|
"passes": "session_id, engineer_notes, user_id, target_user_id",
|
|
"from_line": 491,
|
|
"to_line": 71,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/services/handoff_manager.py",
|
|
"to": "external:postgres",
|
|
"via": "db_write",
|
|
"label": "INSERT session_handoffs, UPDATE ai_sessions.status='escalated' + escalation_package",
|
|
"passes": "SessionHandoff row, session status fields",
|
|
"from_line": 133,
|
|
"to_line": null,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/api/endpoints/ai_sessions.py",
|
|
"to": "backend/app/services/handoff_manager.py",
|
|
"via": "function_call",
|
|
"label": "HandoffManager.finalize_escalation()",
|
|
"passes": "handoff, session, user_id \u2192 generates documentation + PSA push",
|
|
"from_line": 503,
|
|
"to_line": 174,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/api/endpoints/ai_sessions.py",
|
|
"to": "backend/app/services/handoff_manager.py",
|
|
"via": "function_call",
|
|
"label": "dispatch_escalation_notifications() after commit",
|
|
"passes": "handoff \u2014 publishes to EscalationBus SSE + sends emails",
|
|
"from_line": 509,
|
|
"to_line": 276,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/api/endpoints/ai_sessions.py",
|
|
"to": "backend/app/services/handoff_manager.py",
|
|
"via": "function_call",
|
|
"label": "background_tasks.add_task(enrich_escalation_async) \u2014 AI assessment",
|
|
"passes": "handoff.id, user_id",
|
|
"from_line": 515,
|
|
"to_line": 706,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/services/handoff_manager.py",
|
|
"to": "backend/app/core/ai_provider.py",
|
|
"via": "function_call",
|
|
"label": "_generate_handoff_summary_inner() \u2192 get_ai_provider().generate_json()",
|
|
"passes": "Sonnet prompt with problem, steps, conversation; structured JSON response",
|
|
"from_line": 570,
|
|
"to_line": null,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/core/ai_provider.py",
|
|
"to": "external:anthropic_api",
|
|
"via": "external_api",
|
|
"label": "Anthropic messages.create() for escalation assessment",
|
|
"passes": "escalation summary prompt",
|
|
"from_line": null,
|
|
"to_line": null,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/services/handoff_manager.py",
|
|
"to": "external:postgres",
|
|
"via": "db_write",
|
|
"label": "UPDATE session_handoffs.ai_assessment + ai_assessment_data",
|
|
"passes": "summary_prose + structured {what_we_know, likely_cause, suggested_steps, confidence}",
|
|
"from_line": 762,
|
|
"to_line": null,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/api/endpoints/ai_sessions.py",
|
|
"to": "frontend/src/hooks/useFlowPilotSession.ts",
|
|
"via": "http_post",
|
|
"label": "200 SessionCloseResponse",
|
|
"passes": "{session_id, status:'escalated', documentation, psa_push_status}",
|
|
"from_line": 519,
|
|
"to_line": 166,
|
|
"unverified": false
|
|
}
|
|
],
|
|
"group": "Sessions & FlowPilot"
|
|
},
|
|
{
|
|
"id": "session.pause_leave",
|
|
"label": "Pause & leave session",
|
|
"description": "Two paths: (1) Engineer clicks Pause in overflow menu \u2014 calls fp.pauseSession directly. (2) Engineer navigates away while session is active \u2014 useBlocker fires, modal offers 'Pause & Leave', which calls fp.pauseSession() then blocker.proceed(). Both paths POST /ai-sessions/{id}/pause via flowpilot_engine.pause_session which sets status='paused' in Postgres.",
|
|
"cluster": "session",
|
|
"steps": [
|
|
{
|
|
"from": "frontend/src/pages/FlowPilotSessionPage.tsx",
|
|
"to": "frontend/src/pages/FlowPilotSessionPage.tsx",
|
|
"via": "state_update",
|
|
"label": "useBlocker fires on navigation away from active session",
|
|
"passes": "blocker.state='blocked' renders modal",
|
|
"from_line": 42,
|
|
"to_line": 331,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "frontend/src/pages/FlowPilotSessionPage.tsx",
|
|
"to": "frontend/src/hooks/useFlowPilotSession.ts",
|
|
"via": "function_call",
|
|
"label": "fp.pauseSession() (from modal 'Pause & Leave' or overflow menu)",
|
|
"passes": "no arguments \u2014 uses session.id from hook state",
|
|
"from_line": 353,
|
|
"to_line": 188,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "frontend/src/hooks/useFlowPilotSession.ts",
|
|
"to": "frontend/src/api/aiSessions.ts",
|
|
"via": "function_call",
|
|
"label": "aiSessionsApi.pauseSession(session.id)",
|
|
"passes": "session UUID",
|
|
"from_line": 191,
|
|
"to_line": 187,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "frontend/src/api/aiSessions.ts",
|
|
"to": "backend/app/api/endpoints/ai_sessions.py",
|
|
"via": "http_post",
|
|
"label": "POST /ai-sessions/{id}/pause",
|
|
"passes": "(no body)",
|
|
"from_line": 188,
|
|
"to_line": 529,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/api/endpoints/ai_sessions.py",
|
|
"to": "backend/app/services/flowpilot_engine.py",
|
|
"via": "function_call",
|
|
"label": "flowpilot_engine.pause_session(session_id, user_id, db)",
|
|
"passes": "session_id, user_id",
|
|
"from_line": 540,
|
|
"to_line": null,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/services/flowpilot_engine.py",
|
|
"to": "external:postgres",
|
|
"via": "db_write",
|
|
"label": "UPDATE ai_sessions SET status='paused'",
|
|
"passes": "session.id \u2192 status field",
|
|
"from_line": null,
|
|
"to_line": null,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/api/endpoints/ai_sessions.py",
|
|
"to": "frontend/src/hooks/useFlowPilotSession.ts",
|
|
"via": "http_post",
|
|
"label": "204 No Content",
|
|
"passes": "(empty response)",
|
|
"from_line": 548,
|
|
"to_line": 188,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "frontend/src/hooks/useFlowPilotSession.ts",
|
|
"to": "frontend/src/pages/FlowPilotSessionPage.tsx",
|
|
"via": "state_update",
|
|
"label": "setSession({status:'paused'})",
|
|
"passes": "updated session state",
|
|
"from_line": 193,
|
|
"to_line": 27,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "frontend/src/pages/FlowPilotSessionPage.tsx",
|
|
"to": "frontend/src/pages/FlowPilotSessionPage.tsx",
|
|
"via": "navigation",
|
|
"label": "blocker.proceed() \u2014 navigation continues",
|
|
"passes": "router proceeds to original destination",
|
|
"from_line": 355,
|
|
"to_line": null,
|
|
"unverified": false
|
|
}
|
|
],
|
|
"group": "Sessions & FlowPilot"
|
|
},
|
|
{
|
|
"id": "session.resolve",
|
|
"label": "Resolve session",
|
|
"description": "Engineer clicks Resolve in FlowPilotSessionPage header, enters summary in modal, calls fp.resolveSession which POSTs to /ai-sessions/{id}/resolve. The endpoint calls flowpilot_engine.resolve_session (writes resolution fields, sets status='resolved'), commits, then fire-and-forgets ResolutionOutputGenerator. FlowPilotSessionPage also uses the resolution-note preview path (POST /resolution-note/preview \u2192 ResolutionNoteGeneratorService \u2192 Anthropic) before posting.",
|
|
"cluster": "session",
|
|
"steps": [
|
|
{
|
|
"from": "frontend/src/pages/FlowPilotSessionPage.tsx",
|
|
"to": "frontend/src/pages/FlowPilotSessionPage.tsx",
|
|
"via": "state_update",
|
|
"label": "setShowResolve(true) on Resolve button click",
|
|
"passes": "opens inline resolve modal",
|
|
"from_line": 396,
|
|
"to_line": 572,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "frontend/src/pages/FlowPilotSessionPage.tsx",
|
|
"to": "frontend/src/hooks/useFlowPilotSession.ts",
|
|
"via": "function_call",
|
|
"label": "fp.resolveSession({resolution_summary})",
|
|
"passes": "ResolveSessionRequest: {resolution_summary: string}",
|
|
"from_line": 597,
|
|
"to_line": 144,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "frontend/src/hooks/useFlowPilotSession.ts",
|
|
"to": "frontend/src/api/aiSessions.ts",
|
|
"via": "function_call",
|
|
"label": "aiSessionsApi.resolveSession(sessionId, data)",
|
|
"passes": "ResolveSessionRequest",
|
|
"from_line": 148,
|
|
"to_line": 56,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "frontend/src/api/aiSessions.ts",
|
|
"to": "backend/app/api/endpoints/ai_sessions.py",
|
|
"via": "http_post",
|
|
"label": "POST /ai-sessions/{id}/resolve",
|
|
"passes": "{resolution_summary: string}",
|
|
"from_line": 58,
|
|
"to_line": 413,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/api/endpoints/ai_sessions.py",
|
|
"to": "backend/app/services/flowpilot_engine.py",
|
|
"via": "function_call",
|
|
"label": "flowpilot_engine.resolve_session()",
|
|
"passes": "session_id, ResolveSessionRequest, user_id, db",
|
|
"from_line": 425,
|
|
"to_line": null,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/services/flowpilot_engine.py",
|
|
"to": "external:postgres",
|
|
"via": "db_write",
|
|
"label": "UPDATE ai_sessions SET status='resolved', resolution_summary, resolved_at",
|
|
"passes": "session status + summary fields",
|
|
"from_line": null,
|
|
"to_line": null,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/api/endpoints/ai_sessions.py",
|
|
"to": "backend/app/services/resolution_note_generator.py",
|
|
"via": "function_call",
|
|
"label": "ResolutionOutputGenerator.generate_all() (fire-and-forget asyncio.create_task)",
|
|
"passes": "session_id",
|
|
"from_line": 445,
|
|
"to_line": null,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/services/resolution_note_generator.py",
|
|
"to": "backend/app/core/ai_provider.py",
|
|
"via": "function_call",
|
|
"label": "get_ai_provider().generate() \u2014 draft resolution note",
|
|
"passes": "session facts + suggested fix bundle + Sonnet model",
|
|
"from_line": null,
|
|
"to_line": null,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/core/ai_provider.py",
|
|
"to": "external:anthropic_api",
|
|
"via": "external_api",
|
|
"label": "Anthropic messages.create() for resolution note",
|
|
"passes": "_RESOLUTION_NOTE_SYSTEM_PROMPT + session bundle",
|
|
"from_line": null,
|
|
"to_line": null,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/api/endpoints/ai_sessions.py",
|
|
"to": "frontend/src/hooks/useFlowPilotSession.ts",
|
|
"via": "http_post",
|
|
"label": "200 SessionCloseResponse",
|
|
"passes": "{session_id, status:'resolved', documentation, psa_push_status}",
|
|
"from_line": 451,
|
|
"to_line": 144,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "frontend/src/hooks/useFlowPilotSession.ts",
|
|
"to": "frontend/src/pages/FlowPilotSessionPage.tsx",
|
|
"via": "state_update",
|
|
"label": "setSession(status='resolved'), setDocumentation()",
|
|
"passes": "updated session + SessionDocumentation",
|
|
"from_line": 149,
|
|
"to_line": 27,
|
|
"unverified": false
|
|
}
|
|
],
|
|
"group": "Sessions & FlowPilot"
|
|
},
|
|
{
|
|
"id": "session.start",
|
|
"label": "Start a session from dashboard",
|
|
"description": "Engineer types a problem in StartSessionInput (or clicks a suggestion chip), submits, browser navigates to /pilot with router state; FlowPilotSessionPage auto-fires fp.startSession which POSTs to /ai-sessions; flowpilot_engine classifies, matches flows, calls Anthropic, writes AISession + first AISessionStep row.",
|
|
"cluster": "session",
|
|
"steps": [
|
|
{
|
|
"from": "frontend/src/components/dashboard/NextStepCard.tsx",
|
|
"to": "frontend/src/components/dashboard/StartSessionInput.tsx",
|
|
"via": "state_update",
|
|
"label": "Dispatch FOCUS_START_SESSION_EVENT",
|
|
"passes": "CustomEvent rf:focus-start-session",
|
|
"from_line": 82,
|
|
"to_line": 43,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "frontend/src/components/dashboard/StartSessionInput.tsx",
|
|
"to": "frontend/src/pages/FlowPilotSessionPage.tsx",
|
|
"via": "navigation",
|
|
"label": "navigate('/pilot', {state})",
|
|
"passes": "{prefill: string, logs?: string, uploadIds?: string[]}",
|
|
"from_line": 74,
|
|
"to_line": 22,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "frontend/src/pages/FlowPilotSessionPage.tsx",
|
|
"to": "frontend/src/hooks/useFlowPilotSession.ts",
|
|
"via": "function_call",
|
|
"label": "fp.startSession(intake)",
|
|
"passes": "{intake_type:'free_text', intake_content:{text}}",
|
|
"from_line": 51,
|
|
"to_line": 63,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "frontend/src/hooks/useFlowPilotSession.ts",
|
|
"to": "frontend/src/api/aiSessions.ts",
|
|
"via": "function_call",
|
|
"label": "aiSessionsApi.createSession(intake)",
|
|
"passes": "AISessionCreateRequest",
|
|
"from_line": 67,
|
|
"to_line": 27,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "frontend/src/api/aiSessions.ts",
|
|
"to": "backend/app/api/endpoints/ai_sessions.py",
|
|
"via": "http_post",
|
|
"label": "POST /ai-sessions",
|
|
"passes": "{intake_type, intake_content, psa_ticket_id?}",
|
|
"from_line": 28,
|
|
"to_line": 192,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/api/endpoints/ai_sessions.py",
|
|
"to": "backend/app/services/flowpilot_engine.py",
|
|
"via": "function_call",
|
|
"label": "flowpilot_engine.start_session()",
|
|
"passes": "AISessionCreateRequest, user_id, account_id, team_id, db",
|
|
"from_line": 229,
|
|
"to_line": 174,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/services/flowpilot_engine.py",
|
|
"to": "backend/app/core/ai_provider.py",
|
|
"via": "function_call",
|
|
"label": "provider.generate_json() \u2014 classify intake",
|
|
"passes": "INTAKE_CLASSIFICATION_PROMPT + intake text",
|
|
"from_line": 258,
|
|
"to_line": null,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/core/ai_provider.py",
|
|
"to": "external:anthropic_api",
|
|
"via": "external_api",
|
|
"label": "Anthropic messages.create()",
|
|
"passes": "system prompt + messages, max_tokens=2048",
|
|
"from_line": null,
|
|
"to_line": null,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/services/flowpilot_engine.py",
|
|
"to": "backend/app/core/ai_provider.py",
|
|
"via": "function_call",
|
|
"label": "provider.generate_json() \u2014 first diagnostic step",
|
|
"passes": "FLOWPILOT_SYSTEM_PROMPT + user_message",
|
|
"from_line": 259,
|
|
"to_line": null,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/services/flowpilot_engine.py",
|
|
"to": "external:postgres",
|
|
"via": "db_write",
|
|
"label": "INSERT AISession + AISessionStep",
|
|
"passes": "session row (status=active) + step_order=0 row",
|
|
"from_line": 318,
|
|
"to_line": null,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/api/endpoints/ai_sessions.py",
|
|
"to": "frontend/src/hooks/useFlowPilotSession.ts",
|
|
"via": "http_post",
|
|
"label": "201 AISessionCreateResponse",
|
|
"passes": "{session_id, status, first_step, problem_summary, confidence_tier}",
|
|
"from_line": 265,
|
|
"to_line": 67,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "frontend/src/hooks/useFlowPilotSession.ts",
|
|
"to": "frontend/src/pages/FlowPilotSessionPage.tsx",
|
|
"via": "state_update",
|
|
"label": "setSession / setCurrentStep",
|
|
"passes": "AISessionDetail (synthetic from response), AISessionStepResponse",
|
|
"from_line": 70,
|
|
"to_line": 27,
|
|
"unverified": false
|
|
}
|
|
],
|
|
"group": "Sessions & FlowPilot"
|
|
},
|
|
{
|
|
"id": "upload.image_to_chat",
|
|
"label": "Upload image to chat",
|
|
"description": "Engineer pastes an image into FlowPilotMessageBar (or StartSessionInput). handlePaste extracts image File objects and calls processFiles which calls uploadsApi.upload(file). Frontend POSTs multipart to /uploads, backend validates, stores to Railway S3 via storage_service.upload_file (boto3), persists FileUpload row, returns presigned URL. In the background _generate_ai_description calls _call_ai with vision to produce ai_description. On message send, the upload IDs are passed in ChatMessageRequest.upload_ids; the /chat endpoint calls fetch_upload_images which downloads from S3, resizes via Pillow, base64-encodes for Anthropic vision.",
|
|
"cluster": "session",
|
|
"steps": [
|
|
{
|
|
"from": "frontend/src/components/flowpilot/FlowPilotMessageBar.tsx",
|
|
"to": "frontend/src/components/flowpilot/FlowPilotMessageBar.tsx",
|
|
"via": "state_update",
|
|
"label": "handlePaste / handleDrop / handleFileSelect \u2192 processFiles(imageFiles)",
|
|
"passes": "File[] extracted from clipboard or drop event",
|
|
"from_line": 96,
|
|
"to_line": 64,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "frontend/src/components/flowpilot/FlowPilotMessageBar.tsx",
|
|
"to": "frontend/src/api/uploads.ts",
|
|
"via": "function_call",
|
|
"label": "uploadsApi.upload(file)",
|
|
"passes": "File object",
|
|
"from_line": 77,
|
|
"to_line": 5,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "frontend/src/api/uploads.ts",
|
|
"to": "backend/app/api/endpoints/uploads.py",
|
|
"via": "http_post",
|
|
"label": "POST /uploads (multipart/form-data)",
|
|
"passes": "file binary + optional session_id",
|
|
"from_line": 9,
|
|
"to_line": 148,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/api/endpoints/uploads.py",
|
|
"to": "backend/app/services/storage_service.py",
|
|
"via": "function_call",
|
|
"label": "storage_service.upload_file(file_data, filename, content_type, account_id)",
|
|
"passes": "raw bytes \u2192 key = uploads/{account_id}/{uuid}.{ext}",
|
|
"from_line": 205,
|
|
"to_line": 61,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/services/storage_service.py",
|
|
"to": "external:railway_s3",
|
|
"via": "external_api",
|
|
"label": "boto3 S3 PutObject",
|
|
"passes": "file bytes \u2192 bucket/key with ContentType",
|
|
"from_line": 72,
|
|
"to_line": null,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/api/endpoints/uploads.py",
|
|
"to": "external:postgres",
|
|
"via": "db_write",
|
|
"label": "INSERT file_uploads",
|
|
"passes": "{account_id, uploaded_by, filename, content_type, size_bytes, storage_key}",
|
|
"from_line": 213,
|
|
"to_line": null,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/api/endpoints/uploads.py",
|
|
"to": "backend/app/services/storage_service.py",
|
|
"via": "function_call",
|
|
"label": "get_presigned_url(storage_key) \u2014 returned to frontend",
|
|
"passes": "storage_key \u2192 time-limited URL (1hr)",
|
|
"from_line": 231,
|
|
"to_line": 89,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/services/storage_service.py",
|
|
"to": "external:railway_s3",
|
|
"via": "external_api",
|
|
"label": "boto3 generate_presigned_url('get_object')",
|
|
"passes": "bucket + key \u2192 HTTPS presigned URL",
|
|
"from_line": 92,
|
|
"to_line": null,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/api/endpoints/uploads.py",
|
|
"to": "backend/app/services/assistant_chat_service.py",
|
|
"via": "function_call",
|
|
"label": "_generate_ai_description() \u2014 fire-and-forget asyncio.create_task",
|
|
"passes": "upload.id, file_data, content_type \u2192 vision or text extraction \u2192 writes ai_description",
|
|
"from_line": 227,
|
|
"to_line": 61,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/api/endpoints/uploads.py",
|
|
"to": "frontend/src/api/uploads.ts",
|
|
"via": "http_post",
|
|
"label": "201 FileUploadResponse",
|
|
"passes": "{id, filename, content_type, size_bytes, url (presigned)}",
|
|
"from_line": 233,
|
|
"to_line": 5,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "frontend/src/components/flowpilot/FlowPilotMessageBar.tsx",
|
|
"to": "frontend/src/components/flowpilot/FlowPilotMessageBar.tsx",
|
|
"via": "state_update",
|
|
"label": "setPendingUploads(status='done', result) \u2014 thumbnail shown",
|
|
"passes": "FileUploadResponse stored in pendingUploads state",
|
|
"from_line": 80,
|
|
"to_line": null,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "frontend/src/components/flowpilot/FlowPilotMessageBar.tsx",
|
|
"to": "backend/app/api/endpoints/ai_sessions.py",
|
|
"via": "http_post",
|
|
"label": "On send: POST /ai-sessions/{id}/chat with upload_ids",
|
|
"passes": "{message, upload_ids: [FileUploadResponse.id, ...]}",
|
|
"from_line": null,
|
|
"to_line": 270,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/api/endpoints/ai_sessions.py",
|
|
"to": "backend/app/services/storage_service.py",
|
|
"via": "function_call",
|
|
"label": "fetch_upload_images(upload_ids, account_id, db)",
|
|
"passes": "UUIDs \u2192 FileUpload rows \u2192 S3 GetObject \u2192 resize_image_for_vision \u2192 base64 list",
|
|
"from_line": 292,
|
|
"to_line": 163,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/services/storage_service.py",
|
|
"to": "backend/app/services/storage_service.py",
|
|
"via": "function_call",
|
|
"label": "resize_image_for_vision(file_data, content_type) \u2014 Pillow resize to 1568px max",
|
|
"passes": "raw bytes \u2192 resized JPEG/PNG bytes + updated media_type",
|
|
"from_line": 117,
|
|
"to_line": null,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/api/endpoints/ai_sessions.py",
|
|
"to": "backend/app/services/unified_chat_service.py",
|
|
"via": "function_call",
|
|
"label": "unified_chat_service.send_chat_message(images=[{media_type, data}])",
|
|
"passes": "base64-encoded image dicts included in AI call",
|
|
"from_line": 304,
|
|
"to_line": 570,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/services/unified_chat_service.py",
|
|
"to": "external:anthropic_api",
|
|
"via": "external_api",
|
|
"label": "_call_ai() with images in multimodal content block",
|
|
"passes": "images as [{type:'image', source:{type:'base64', data:...}}] before text block",
|
|
"from_line": 755,
|
|
"to_line": null,
|
|
"unverified": false
|
|
}
|
|
],
|
|
"group": "Sessions & FlowPilot"
|
|
},
|
|
{
|
|
"id": "integrations.flow_1_invite_teammate",
|
|
"label": "Invite Teammate",
|
|
"description": "",
|
|
"cluster": "integrations",
|
|
"steps": [
|
|
{
|
|
"from": "frontend/src/pages/AccountSettingsPage.tsx",
|
|
"to": "frontend/src/api/accounts.ts",
|
|
"via": "function_call",
|
|
"label": "handleInvite calls accountsApi.createInvite",
|
|
"passes": "{email, role}",
|
|
"from_line": 232,
|
|
"to_line": 53,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "frontend/src/api/accounts.ts",
|
|
"to": "backend/app/api/endpoints/accounts.py",
|
|
"via": "http_post",
|
|
"label": "POST /accounts/me/invites",
|
|
"passes": "{email, role}",
|
|
"from_line": 53,
|
|
"to_line": 257,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/api/endpoints/accounts.py",
|
|
"to": "external:postgres",
|
|
"via": "db_write",
|
|
"label": "INSERT account_invites row",
|
|
"passes": "{account_id, invited_by_id, email, code, role, expires_at}",
|
|
"from_line": 278,
|
|
"to_line": null,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/api/endpoints/accounts.py",
|
|
"to": "backend/app/core/email.py",
|
|
"via": "function_call",
|
|
"label": "EmailService.send_account_invite_email",
|
|
"passes": "{to_email, code, account_name, role}",
|
|
"from_line": 289,
|
|
"to_line": 130,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/core/email.py",
|
|
"to": "external:smtp_email",
|
|
"via": "external_api",
|
|
"label": "resend.Emails.send \u2014 account invite email",
|
|
"passes": "HTML email with /accept-invite?code=<token> link",
|
|
"from_line": 43,
|
|
"to_line": null,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/api/endpoints/accounts.py",
|
|
"to": "external:postgres",
|
|
"via": "db_write",
|
|
"label": "UPDATE invite.email_sent_at, COMMIT",
|
|
"passes": "email_sent_at timestamp",
|
|
"from_line": 295,
|
|
"to_line": null,
|
|
"unverified": false
|
|
}
|
|
],
|
|
"group": "Team & Billing"
|
|
},
|
|
{
|
|
"id": "integrations.flow_2_accept_invitation",
|
|
"label": "Accept Invitation",
|
|
"description": "",
|
|
"cluster": "integrations",
|
|
"steps": [
|
|
{
|
|
"from": "external:smtp_email",
|
|
"to": "frontend/src/pages/AcceptInvitePage.tsx",
|
|
"via": "redirect",
|
|
"label": "User clicks email link \u2192 /accept-invite?code=<token>",
|
|
"passes": "invite code in URL query param",
|
|
"from_line": null,
|
|
"to_line": 29,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "frontend/src/pages/AcceptInvitePage.tsx",
|
|
"to": "frontend/src/api/invite.ts",
|
|
"via": "function_call",
|
|
"label": "useEffect calls inviteApi.lookupAccountInvite(code)",
|
|
"passes": "invite code string",
|
|
"from_line": 58,
|
|
"to_line": 22,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "frontend/src/api/invite.ts",
|
|
"to": "backend/app/api/endpoints/account_invite_lookup.py",
|
|
"via": "http_get",
|
|
"label": "GET /accounts/invites/{code}/lookup",
|
|
"passes": "invite code (URL param, no auth)",
|
|
"from_line": 23,
|
|
"to_line": 24,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/api/endpoints/account_invite_lookup.py",
|
|
"to": "external:postgres",
|
|
"via": "db_read",
|
|
"label": "SELECT account_invites WHERE code=? (joinedload account + invited_by)",
|
|
"passes": "invite code",
|
|
"from_line": 33,
|
|
"to_line": null,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/api/endpoints/account_invite_lookup.py",
|
|
"to": "frontend/src/pages/AcceptInvitePage.tsx",
|
|
"via": "state_update",
|
|
"label": "Returns {account_name, inviter_name, invited_email, role} \u2192 lookup state 'ok'",
|
|
"passes": "InviteLookupResponse",
|
|
"from_line": 49,
|
|
"to_line": 60,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "frontend/src/pages/AcceptInvitePage.tsx",
|
|
"to": "frontend/src/store/authStore.ts",
|
|
"via": "function_call",
|
|
"label": "handleSubmit calls authStore.register with account_invite_code",
|
|
"passes": "{email, password, name, account_invite_code}",
|
|
"from_line": 114,
|
|
"to_line": null,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "frontend/src/store/authStore.ts",
|
|
"to": "backend/app/api/endpoints/auth.py",
|
|
"via": "http_post",
|
|
"label": "POST /auth/register",
|
|
"passes": "{email, password, name, account_invite_code}",
|
|
"from_line": null,
|
|
"to_line": null,
|
|
"unverified": true
|
|
},
|
|
{
|
|
"from": "backend/app/api/endpoints/auth.py",
|
|
"to": "external:postgres",
|
|
"via": "db_write",
|
|
"label": "INSERT user row with account_id from invite, mark invite used_at",
|
|
"passes": "User row + invite.used_at",
|
|
"from_line": null,
|
|
"to_line": null,
|
|
"unverified": true
|
|
},
|
|
{
|
|
"from": "frontend/src/pages/AcceptInvitePage.tsx",
|
|
"to": "frontend/src/pages/AcceptInvitePage.tsx",
|
|
"via": "navigation",
|
|
"label": "navigate('/?welcome=teammate')",
|
|
"passes": "welcome param",
|
|
"from_line": 124,
|
|
"to_line": null,
|
|
"unverified": false
|
|
}
|
|
],
|
|
"group": "Team & Billing"
|
|
},
|
|
{
|
|
"id": "integrations.flow_6a_stripe_checkout_user",
|
|
"label": "Subscribe to Plan \u2014 User-Initiated Half",
|
|
"description": "",
|
|
"cluster": "integrations",
|
|
"steps": [
|
|
{
|
|
"from": "frontend/src/pages/PricingPage.tsx",
|
|
"to": "frontend/src/pages/PricingPage.tsx",
|
|
"via": "navigation",
|
|
"label": "CTA link navigates to /register?plan=<plan> (PricingPage does NOT call billingApi directly)",
|
|
"passes": "plan name in query param",
|
|
"from_line": 281,
|
|
"to_line": null,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "frontend/src/pages/account/BillingPage.tsx",
|
|
"to": "frontend/src/api/billing.ts",
|
|
"via": "function_call",
|
|
"label": "billingApi.createCheckoutSession({plan, seats, billing_interval})",
|
|
"passes": "CheckoutSessionRequest",
|
|
"from_line": null,
|
|
"to_line": 68,
|
|
"unverified": true
|
|
},
|
|
{
|
|
"from": "frontend/src/api/billing.ts",
|
|
"to": "backend/app/api/endpoints/billing.py",
|
|
"via": "http_post",
|
|
"label": "POST /billing/checkout-session",
|
|
"passes": "{plan, seats, billing_interval}",
|
|
"from_line": 71,
|
|
"to_line": 23,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/api/endpoints/billing.py",
|
|
"to": "backend/app/services/billing.py",
|
|
"via": "function_call",
|
|
"label": "BillingService.create_checkout_session(db, account, plan, seats, ...)",
|
|
"passes": "{account, plan, seats, billing_interval, success_url, cancel_url}",
|
|
"from_line": 32,
|
|
"to_line": 71,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/services/billing.py",
|
|
"to": "external:postgres",
|
|
"via": "db_read",
|
|
"label": "SELECT plan_billing WHERE plan=? (get stripe price_id)",
|
|
"passes": "plan name",
|
|
"from_line": 88,
|
|
"to_line": null,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/services/billing.py",
|
|
"to": "external:stripe",
|
|
"via": "external_api",
|
|
"label": "stripe.Customer.create (if no stripe_customer_id yet)",
|
|
"passes": "{email: null, metadata: {account_id}}",
|
|
"from_line": 103,
|
|
"to_line": null,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/services/billing.py",
|
|
"to": "external:stripe",
|
|
"via": "external_api",
|
|
"label": "stripe.checkout.Session.create",
|
|
"passes": "{customer, line_items: [{price_id, quantity:seats}], mode:'subscription', trial_end?, success_url, cancel_url}",
|
|
"from_line": 122,
|
|
"to_line": null,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/services/billing.py",
|
|
"to": "frontend/src/api/billing.ts",
|
|
"via": "function_call",
|
|
"label": "Returns Stripe Checkout URL",
|
|
"passes": "session.url string",
|
|
"from_line": 131,
|
|
"to_line": 76,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "frontend/src/api/billing.ts",
|
|
"to": "external:stripe",
|
|
"via": "redirect",
|
|
"label": "window.location.href = url (browser redirects to Stripe hosted page)",
|
|
"passes": "Stripe Checkout URL",
|
|
"from_line": 76,
|
|
"to_line": null,
|
|
"unverified": false
|
|
}
|
|
],
|
|
"group": "Team & Billing"
|
|
},
|
|
{
|
|
"id": "integrations.flow_6b_stripe_webhook",
|
|
"label": "Subscribe to Plan \u2014 Async Webhook Half",
|
|
"description": "",
|
|
"cluster": "integrations",
|
|
"steps": [
|
|
{
|
|
"from": "external:stripe",
|
|
"to": "backend/app/api/endpoints/webhooks.py",
|
|
"via": "webhook",
|
|
"label": "POST /webhooks/stripe (checkout.session.completed or subscription events)",
|
|
"passes": "Stripe event JSON + stripe-signature header",
|
|
"from_line": null,
|
|
"to_line": 14,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/api/endpoints/webhooks.py",
|
|
"to": "backend/app/services/billing.py",
|
|
"via": "function_call",
|
|
"label": "BillingService.apply_subscription_event(db, event_id, event_type, payload)",
|
|
"passes": "{event_id, event_type, payload}",
|
|
"from_line": 43,
|
|
"to_line": 208,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/services/billing.py",
|
|
"to": "external:postgres",
|
|
"via": "db_write",
|
|
"label": "INSERT stripe_events (idempotency guard \u2014 IntegrityError on dupe)",
|
|
"passes": "event_id, event_type, payload_excerpt",
|
|
"from_line": 222,
|
|
"to_line": null,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/services/billing.py",
|
|
"to": "external:stripe",
|
|
"via": "external_api",
|
|
"label": "stripe.Subscription.retrieve(subscription_id) \u2014 only on checkout.session.completed",
|
|
"passes": "subscription_id",
|
|
"from_line": 286,
|
|
"to_line": null,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/services/billing.py",
|
|
"to": "external:postgres",
|
|
"via": "db_write",
|
|
"label": "UPDATE subscriptions SET plan, status, stripe_subscription_id, period dates, seat_limit \u2014 COMMIT",
|
|
"passes": "Subscription row fields derived from Stripe event",
|
|
"from_line": 287,
|
|
"to_line": null,
|
|
"unverified": false
|
|
}
|
|
],
|
|
"group": "Team & Billing"
|
|
},
|
|
{
|
|
"id": "integrations.flow_7_cancel_portal",
|
|
"label": "Cancel Subscription / Open Customer Portal",
|
|
"description": "",
|
|
"cluster": "integrations",
|
|
"steps": [
|
|
{
|
|
"from": "frontend/src/pages/account/BillingPage.tsx",
|
|
"to": "frontend/src/api/billing.ts",
|
|
"via": "function_call",
|
|
"label": "handleOpenPortal calls billingApi.getPortalSession()",
|
|
"passes": "no body (auth via cookie/header)",
|
|
"from_line": 74,
|
|
"to_line": 45,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "frontend/src/api/billing.ts",
|
|
"to": "backend/app/api/endpoints/billing.py",
|
|
"via": "http_get",
|
|
"label": "GET /billing/portal-session",
|
|
"passes": "authenticated user context",
|
|
"from_line": 47,
|
|
"to_line": 56,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/api/endpoints/billing.py",
|
|
"to": "backend/app/services/billing.py",
|
|
"via": "function_call",
|
|
"label": "BillingService.open_customer_portal(account)",
|
|
"passes": "Account ORM row",
|
|
"from_line": 73,
|
|
"to_line": 134,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/services/billing.py",
|
|
"to": "external:stripe",
|
|
"via": "external_api",
|
|
"label": "stripe.billing_portal.Session.create({customer, return_url})",
|
|
"passes": "{customer: account.stripe_customer_id, return_url: /account/billing}",
|
|
"from_line": 148,
|
|
"to_line": null,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/services/billing.py",
|
|
"to": "frontend/src/api/billing.ts",
|
|
"via": "function_call",
|
|
"label": "Returns Stripe portal URL",
|
|
"passes": "session.url string",
|
|
"from_line": 149,
|
|
"to_line": 54,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "frontend/src/pages/account/BillingPage.tsx",
|
|
"to": "external:stripe",
|
|
"via": "redirect",
|
|
"label": "window.location.href = url (browser redirects to Stripe portal)",
|
|
"passes": "Stripe Customer Portal URL",
|
|
"from_line": 75,
|
|
"to_line": null,
|
|
"unverified": false
|
|
}
|
|
],
|
|
"group": "Team & Billing"
|
|
},
|
|
{
|
|
"id": "integrations.flow_8_script_builder",
|
|
"label": "Build Script (Script Builder)",
|
|
"description": "",
|
|
"cluster": "integrations",
|
|
"steps": [
|
|
{
|
|
"from": "frontend/src/pages/ScriptBuilderPage.tsx",
|
|
"to": "frontend/src/api/scriptBuilder.ts",
|
|
"via": "function_call",
|
|
"label": "handleSend \u2192 scriptBuilderApi.createSession(language) if no session yet",
|
|
"passes": "{language}",
|
|
"from_line": 91,
|
|
"to_line": 16,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "frontend/src/api/scriptBuilder.ts",
|
|
"to": "backend/app/api/endpoints/script_builder.py",
|
|
"via": "http_post",
|
|
"label": "POST /scripts/builder/sessions",
|
|
"passes": "{language, origin:'standalone'}",
|
|
"from_line": 20,
|
|
"to_line": 66,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/api/endpoints/script_builder.py",
|
|
"to": "backend/app/services/script_builder_service.py",
|
|
"via": "function_call",
|
|
"label": "script_builder_service.create_session(db, user_id, account_id, ...)",
|
|
"passes": "{user_id, account_id, team_id, language, origin}",
|
|
"from_line": 157,
|
|
"to_line": 144,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/services/script_builder_service.py",
|
|
"to": "external:postgres",
|
|
"via": "db_write",
|
|
"label": "INSERT script_builder_sessions",
|
|
"passes": "ScriptBuilderSession row",
|
|
"from_line": 155,
|
|
"to_line": null,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "frontend/src/pages/ScriptBuilderPage.tsx",
|
|
"to": "frontend/src/api/scriptBuilder.ts",
|
|
"via": "function_call",
|
|
"label": "scriptBuilderApi.sendMessage(session.id, content)",
|
|
"passes": "{session_id, content}",
|
|
"from_line": 99,
|
|
"to_line": 40,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "frontend/src/api/scriptBuilder.ts",
|
|
"to": "backend/app/api/endpoints/script_builder.py",
|
|
"via": "http_post",
|
|
"label": "POST /scripts/builder/sessions/{id}/messages",
|
|
"passes": "{content: user message}",
|
|
"from_line": 41,
|
|
"to_line": 199,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/api/endpoints/script_builder.py",
|
|
"to": "backend/app/services/script_builder_service.py",
|
|
"via": "function_call",
|
|
"label": "script_builder_service.send_message(db, session, content)",
|
|
"passes": "{session ORM, user_content}",
|
|
"from_line": 217,
|
|
"to_line": 173,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/services/script_builder_service.py",
|
|
"to": "external:postgres",
|
|
"via": "db_write",
|
|
"label": "INSERT script_builder_messages (user role)",
|
|
"passes": "ScriptBuilderMessage row",
|
|
"from_line": 200,
|
|
"to_line": null,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/services/script_builder_service.py",
|
|
"to": "external:anthropic_api",
|
|
"via": "external_api",
|
|
"label": "get_ai_provider \u2192 provider.generate_text (model: script_build alias)",
|
|
"passes": "{system_prompt (language-specific), messages: last 20, max_tokens:8192}",
|
|
"from_line": 224,
|
|
"to_line": null,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "external:anthropic_api",
|
|
"to": "backend/app/services/script_builder_service.py",
|
|
"via": "external_api",
|
|
"label": "Returns ai_text, input_tokens, output_tokens",
|
|
"passes": "assistant message text (may contain fenced code block)",
|
|
"from_line": null,
|
|
"to_line": 240,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/services/script_builder_service.py",
|
|
"to": "external:postgres",
|
|
"via": "db_write",
|
|
"label": "INSERT script_builder_messages (assistant role) + UPDATE session.latest_script / title",
|
|
"passes": "{content, script, script_filename, line_count, token counts}",
|
|
"from_line": 245,
|
|
"to_line": null,
|
|
"unverified": false
|
|
},
|
|
{
|
|
"from": "backend/app/services/script_builder_service.py",
|
|
"to": "frontend/src/pages/ScriptBuilderPage.tsx",
|
|
"via": "state_update",
|
|
"label": "ScriptBuilderMessageResponse returned \u2192 assistantMessage appended to messages[]",
|
|
"passes": "{role, content, script, script_filename, line_count, timestamp}",
|
|
"from_line": 270,
|
|
"to_line": 101,
|
|
"unverified": false
|
|
}
|
|
],
|
|
"group": "Tools"
|
|
}
|
|
]
|
|
} |