{ "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=}", "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=", "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=}", "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=", "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; 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= 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=", "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= (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" } ] }